[转载]android应用安全——(数据抓包)跟踪监控android数据包 - 张兴业 - 博客园

mikel阅读(892)

[转载]android应用安全——(数据抓包)跟踪监控android数据包 – 张兴业 – 博客园.

       web开发中Chrome、IE、firefox等浏览器都自带提供了插件帮助开发者跟踪http数据,在手机客户端怎么实现http数据抓包呢?Fiddler可以实现真机调试抓包。Fiddler支持Any Browser,Any System,Any Platform。

 


 

 

 

        今天,向大家介绍 Fiddler 如何抓取 Android 应用上的数据包。

 

 

 

 

 

        Fiddler 是通过代理来实现数据捕获的。对 Android 手机来说,也是通过将网络连接的代理指向 PC 机的 Fiddler 端口,来实现数据包的拦截。

 

       下面,我以我的一次实践为例,向大家介绍如何操作。

 

        环境:Windows7、G11(android 4.0.3)

 

       1、首先,确保安装 Fiddler 的电脑和手机在同一局域网内,因为要将手机的代理指向 PC 机,不能互相访问是不行的。

 

       2、Fiddler 开启远程连接。Fiddler 主菜单 Tools -> Fiddler Options…-> Connections页签,选中Allow remote computers to connect。

 

       

 

 

 

       3、重启Fidler(这一步很重要,必须做)。

 

       4、获取PC的IP地址:

 

       获得安装 Fiddler 这台电脑在局域网内的 IP,在手机上设置代理服务器的时候会用到,开始菜单打开运行窗口(快捷键 Win + R),输入 cmd 回车,进入 Windows 命令行工具,输入 ipconfig 回车,获取PC的ip地址:

 

  

 

      5、设置手机代理:

 

        需要手机和安装 Fiddler 电脑在同一WIFI下,手机连接到WIFI。打开手机的WIFI设置界面,选中连接的WIFI,长按,弹出如下界面,选择“修改网络”。

 


 

       将使用代理服务器打勾,并填上刚才在 PC 机上 ipconfig 获得的 IP 地址 192.168.1.95,端口号填 Fiddler 默认的 8888

 


 

       点击保存,这样就完成了代理的设置。可以通过Fiddler抓去http数据包了。

 

         

 

      

 

 

 

 

 

/**
* @author 张兴业

 

*  iOS入门群:83702688
*  android开发进阶群:241395671
*  我的新浪微博:@张兴业TBOW
*/

 

 

 

 

 

 

 

 

 

参考:http://fiddler2.com/documentation/Configure-Fiddler/Tasks/ConfigureForAndroid

Fildder官网:http://fiddler2.com/home

[转载]Android应用中使用及实现系统“分享”接口 - 张兴业 - 博客园

mikel阅读(1085)

[转载]Android应用中使用及实现系统“分享”接口 – 张兴业 – 博客园.

为了应用的推广、传播,很多的应用中都有“分享”功能,一个按 钮,点击后会出现短信、微博等等一切实现了分享功能的应用列表。这一篇文章主要介绍怎么调用分享功能和怎么实现分享接口让自己应用出现分享列表中。 Android应用中能很方便的完成这些功能,这也正是Android的伟大之处,他能很简单的完成应用之间的沟通以相互整合。

 

调用分享功能

1、分享文本

分享功能使用的隐式启动Activity的方法,这里的Action使用的是 ACTION_SEND 

 

  1. Intent sendIntent = new Intent();  
  2. sendIntent.setAction(Intent.ACTION_SEND);  
  3. sendIntent.putExtra(Intent.EXTRA_TEXT, “This is my text to send.”);  
  4. sendIntent.setType(“text/plain”);  
  5. startActivity(sendIntent);  

 

 

效果如下图的图一。

2、改变分享列表标题

    使用上面的分享方式分享列表标题为“使用一下内容完成操作”,Android中提供了Intent.createChooser() ,这样能一直显示分享选择列表,并且修改了分享列表标题内容。

 

  1. Intent sendIntent = new Intent();  
  2. sendIntent.setAction(Intent.ACTION_SEND);  
  3. sendIntent.putExtra(Intent.EXTRA_TEXT, “This is my text to send.”);  
  4. sendIntent.setType(“text/plain”);  
  5. startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));  

使用Intent.createChooser()的好处:

 

 

If you callIntent.createChooser() for the intent, Android will always display the chooser. This has some advantages:

  • Even if the user has previously selected a default action for this intent, the chooser will still be displayed.
  • If no applications match, Android displays a system message.
  • You can specify a title for the chooser dialog.

 

                    

 

    分享功能不只是Intent.EXTRA_TEXT,还可以 EXTRA_EMAILEXTRA_CCEXTRA_BCC,EXTRA_SUBJECT. 只需要接受方完成响应数据接受。

 

3、分享图片

    分享功能还支持二进制内容(Binary Content),但是多数是处理的图片,因为shareIntent.setType(“image/jpeg”)这一项设置了内容类型。可也以是其他类型,需要接受方支持。

 

  1. Intent shareIntent = new Intent();  
  2. shareIntent.setAction(Intent.ACTION_SEND);  
  3. shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);  
  4. shareIntent.setType(“image/jpeg”);  
  5. startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));  

 

 

4、分享图片列表

    分享功能不仅支持单张图片,还支持图片列表,这里还是说的范围太窄了,应该声明不仅仅是图片。

 

  1. ArrayList<Uri> imageUris = new ArrayList<Uri>();  
  2. imageUris.add(imageUri1); // Add your image URIs here  
  3. imageUris.add(imageUri2);  
  4.   
  5. Intent shareIntent = new Intent();  
  6. shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);  
  7. shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);  
  8. shareIntent.setType(“image/*”);  
  9. startActivity(Intent.createChooser(shareIntent, “Share images to..”));  

 

实现分享功能

    上面说的都是怎么调用分享功能,以下就开始写怎么实现分享功能,让我们的应用也出现在分享列表中。前面也说了分享功能是使用隐式调用Activtiy实现的,Activity需要声明 <intent-filter> 。

 

声明intent-filter

 

  1. <activity  
  2.            android:name=“com.example.sharedemo.ShareActivity”  
  3.            android:label=“@string/app_name” >  
  4.            <intent-filter>  
  5.                <action android:name=“android.intent.action.SEND” />  
  6.   
  7.                <category android:name=“android.intent.category.DEFAULT” />  
  8.   
  9.                <data android:mimeType=“image/*” />  
  10.            </intent-filter>  
  11.            <intent-filter>  
  12.                <action android:name=“android.intent.action.SEND” />  
  13.   
  14.                <category android:name=“android.intent.category.DEFAULT” />  
  15.   
  16.                <data android:mimeType=“text/plain” />  
  17.            </intent-filter>  
  18.            <intent-filter>  
  19.                <action android:name=“android.intent.action.SEND_MULTIPLE” />  
  20.   
  21.                <category android:name=“android.intent.category.DEFAULT” />  
  22.   
  23.                <data android:mimeType=“image/*” />  
  24.            </intent-filter>  
  25.        </activity>  

 

 

上面声明了三种intent-filter,当然可以更多,这里只是举个例子,

 

处理接收数据

声明了intent-filter,响应的Activity就要处理响应的数据,示例如下:

 

  1. public class ShareActivity extends Activity{  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         // TODO Auto-generated method stub  
  6.         super.onCreate(savedInstanceState);  
  7.           
  8.         // Get intent, action and MIME type  
  9.         Intent intent = getIntent();  
  10.         String action = intent.getAction();  
  11.         String type = intent.getType();  
  12.   
  13.         if (Intent.ACTION_SEND.equals(action) && type != null) {  
  14.             if (“text/plain”.equals(type)) {  
  15.                 handleSendText(intent); // Handle text being sent  
  16.             } else if (type.startsWith(“image/”)) {  
  17.                 handleSendImage(intent); // Handle single image being sent  
  18.             }  
  19.         } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {  
  20.             if (type.startsWith(“image/”)) {  
  21.                 handleSendMultipleImages(intent); // Handle multiple images being sent  
  22.             }  
  23.         } else {  
  24.             // Handle other intents, such as being started from the home screen  
  25.         }  
  26.     }  
  27.   
  28.     void handleSendText(Intent intent) {  
  29.         String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);  
  30.         String sharedTitle = intent.getStringExtra(Intent.EXTRA_TITLE);  
  31.         if (sharedText != null) {  
  32.             // Update UI to reflect text being shared  
  33.         }  
  34.     }  
  35.   
  36.     void handleSendImage(Intent intent) {  
  37.         Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);  
  38.         if (imageUri != null) {  
  39.             // Update UI to reflect image being shared  
  40.         }  
  41.     }  
  42.   
  43.     void handleSendMultipleImages(Intent intent) {  
  44.         ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);  
  45.         if (imageUris != null) {  
  46.             // Update UI to reflect multiple images being shared  
  47.         }  
  48.     }  
  49. }  

 

 

通过声明intent-filter,处理接受到的数据就能完成分享的接收功能。

 

更多

    上面只做了分享功能简单的说明,伴随着Android api的升级,也出现了一些新的完成“分享”功能的方法,比如 ShareActionProvider ,更多请参考。

 

 

示例下载

 

/**
* @author 张兴业
*  iOS入门群:83702688
*  android开发进阶群:241395671
*  我的新浪微博:@张兴业TBOW
*/

 

 

参考:

http://developer.android.com/training/sharing/index.html

[转载]Asynchronous HTTP Requests in Android Using Volley - 张兴业 - 博客园

mikel阅读(820)

[转载]Asynchronous HTTP Requests in Android Using Volley – 张兴业 – 博客园.

        Volley是Android开发者新的瑞士军刀,它提供了优美的框架,使得Android应用程序网络访问更容易和更快。Volley抽象实现了底层的 HTTP Client库,让你不关注HTTP Client细节,专注于写出更加漂亮、干净的RESTful HTTP请求。另外,Volley请求会异步执行,不阻挡主线程。

 

Volley提供的功能

简单的讲,提供了如下主要的功能:

1、封装了的异步的RESTful 请求API;

2、一个优雅和稳健的请求队列;

3、一个可扩展的架构,它使开发人员能够实现自定义的请求和响应处理机制;

4、能够使用外部HTTP Client库;

5、缓存策略;

6、自定义的网络图像加载视图(NetworkImageView,ImageLoader等);

 

为什么使用异步HTTP请求?

       Android中要求HTTP请求异步执行,如果在主线程执行HTTP请求,可能会抛出android.os.NetworkOnMainThreadException  异常。阻塞主线程有一些严重的后果,它阻碍UI渲染,用户体验不流畅,它可能会导致可怕的ANR(Application Not Responding)。要避免这些陷阱,作为一个开发者,应该始终确保HTTP请求是在一个不同的线程。

 

怎样使用Volley

         这篇博客将会详细的介绍在应用程程中怎么使用volley,它将包括一下几方面:

1、安装和使用Volley库

2、使用请求队列

3、异步的JSON、String请求

4、取消请求

5、重试失败的请求,自定义请求超时

6、设置请求头(HTTP headers)

7、使用Cookies

8、错误处理

 

安装和使用Volley库

引入Volley非常简单,首先,从git库先克隆一个下来:

 

git clone https://android.googlesource.com/platform/frameworks/volley

然后编译为jar包,再把jar包放到自己的工程的libs目录。

 

 

使用请求队列

 

Volley的所有请求都放在一个队列,然后进行处理,这里是你如何将创建一个请求队列:

 

RequestQueue mRequestQueue = Volley.newRequestQueue(this); // 'this' is Context

理想的情况是把请求队列集中放到一个地方,最好是初始化应用程序类中初始化请求队列,下面类做到了这一点:

public class ApplicationController extends Application {

/**
* Log or request TAG
*/
public static final String TAG = "VolleyPatterns";

/**
* Global request queue for Volley
*/
private RequestQueue mRequestQueue;

/**
* A singleton instance of the application class for easy access in other places
*/
private static ApplicationController sInstance;

@Override
public void onCreate() {
super.onCreate();

// initialize the singleton
sInstance = this;
}

/**
* @return ApplicationController singleton instance
*/
public static synchronized ApplicationController getInstance() {
return sInstance;
}

/**
* @return The Volley Request queue, the queue will be created if it is null
*/
public RequestQueue getRequestQueue() {
// lazy initialize the request queue, the queue instance will be
// created when it is accessed for the first time
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(getApplicationContext());
}

return mRequestQueue;
}

/**
* Adds the specified request to the global queue, if tag is specified
* then it is used else Default TAG is used.
*
* @param req
* @param tag
*/
public void addToRequestQueue(Request req, String tag) {
// set the default tag if tag is empty
req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);

VolleyLog.d("Adding request to queue: %s", req.getUrl());

getRequestQueue().add(req);
}

/**
* Adds the specified request to the global queue using the Default TAG.
*
* @param req
* @param tag
*/
public void addToRequestQueue(Request req) {
// set the default tag if tag is empty
req.setTag(TAG);

getRequestQueue().add(req);
}

/**
* Cancels all pending requests by the specified TAG, it is important
* to specify a TAG so that the pending/ongoing requests can be cancelled.
*
* @param tag
*/
public void cancelPendingRequests(Object tag) {
if (mRequestQueue != null) {
mRequestQueue.cancelAll(tag);
}
}
}

异步的JSON、String请求

Volley提供了以下的实用工具类进行异步HTTP请求:

 

  • JsonObjectRequest — To send and receive JSON Object from the Server
  • JsonArrayRequest — To receive JSON Array from the Server
  • StringRequest — To retrieve response body as String (ideally if you intend to parse the response by yourself)

JsonObjectRequest

        这个类可以用来发送和接收JSON对象。这个类的一个重载构造函数允许设置适当的请求方法(DELETE,GET,POST和PUT)。如果您正在使用一个RESTful服务端,可以使用这个类。下面的示例显示如何使GET和POST请求。

 

 
GET请求:

final String URL = "/volley/resource/12";
// pass second argument as "null" for GET requests
JsonObjectRequest req = new JsonObjectRequest(URL, null,
new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
try {
VolleyLog.v("Response:%n %s", response.toString(4));
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.e("Error: ", error.getMessage());
}
});

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

POST请求:

final String URL = "/volley/resource/12";
// Post params to be sent to the server
HashMap&lt;String, String&gt; params = new HashMap&lt;String, String&gt;();
params.put("token", "AbCdEfGh123456");

JsonObjectRequest req = new JsonObjectRequest(URL, new JSONObject(params),
new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
try {
VolleyLog.v("Response:%n %s", response.toString(4));
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.e("Error: ", error.getMessage());
}
});

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

JsonArrayRequest

   这个类可以用来接受 JSON Arrary,不支持JSON Object。这个类现在只支持 HTTP GET。由于支持GET,你可以在URL的后面加上请求参数。类的构造函数不支持请求参数。

final String URL = "/volley/resource/all?count=20";
JsonArrayRequest req = new JsonArrayRequest(URL, new Response.Listener () {
@Override
public void onResponse(JSONArray response) {
try {
VolleyLog.v("Response:%n %s", response.toString(4));
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.e("Error: ", error.getMessage());
}
});

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

StringRequest

  这个类可以用来从服务器获取String,如果想自己解析请求响应可以使用这个类,例如返回xml数据。它还可以使用重载的构造函数定制请求。

final String URL = "/volley/resource/recent.xml";
StringRequest req = new StringRequest(URL, new Response.Listener() {
@Override
public void onResponse(String response) {
VolleyLog.v("Response:%n %s", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.e("Error: ", error.getMessage());
}
});

// add the request object to the queue to be executed
ApplicationController.getInstance().addToRequestQueue(req);

取消请求

       Volley提供了强大的API取消未处理或正在处理的请求。取消请求最简单的方法是调用请求队列cancelAll(tag)的方法,前提是你在添加请求时设置了标记。这样就能使标签标记的请求挂起。
给请求设置标签:

request.setTag("My Tag");

使用ApplicationController添加使用了标签的请求到队列中:

 

 

ApplicationController.getInstance().addToRequestQueue(request, "My Tag");

取消所有指定标记的请求:

 

 

mRequestQueue.cancelAll("My Tag");

 

 

 

重试失败的请求,自定义请求超时

          Volley中没有指定的方法来设置请求超时时间,可以设置RetryPolicy 来变通实现。DefaultRetryPolicy类有个initialTimeout参数,可以设置超时时间。要确保最大重试次数为1,以保证超时后不重新请求。

 

 

Setting Request Timeout

1
request.setRetryPolicy(new DefaultRetryPolicy(20 * 1000, 1, 1.0f));
 

        如果你想失败后重新请求(因超时),您可以指定使用上面的代码,增加重试次数。注意最后一个参数,它允许你指定一个退避乘数可以用来实现“指数退避”来从RESTful服务器请求数据。

 

 

设置请求头(HTTP headers)

      有时候需要给HTTP请求添加额外的头信息,一个常用的例子是添加 “Authorization”到HTTP 请求的头信息。Volley请求类提供了一个 getHeaers()的方法,重载这个方法可以自定义HTTP 的头信息。

 

添加头信息:

JsonObjectRequest req = new JsonObjectRequest(URL, new JSONObject(params),
new Response.Listener() {
@Override
public void onResponse(JSONObject response) {
// handle response
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// handle error
}
}) {

@Override
public Map&lt;String, String&gt; getHeaders() throws AuthFailureError {
HashMap&lt;String, String&gt; headers = new HashMap&lt;String, String&gt;();
headers.put("CUSTOM_HEADER", "Yahoo");
headers.put("ANOTHER_CUSTOM_HEADER", "Google");
return headers;
}
};

使用Cookies

        Volley中没有直接的API来设置cookies,Volley的设计理念就是提供干净、简洁的API来实现RESTful HTTP请求,不提供设置cookies是合理的。

 

        下面是修改后的ApplicationController类,这个类修改了getRequestQueue()方法,包含了 设置cookie方法,这些修改还是有些粗糙。

// http client instance
private DefaultHttpClient mHttpClient;
public RequestQueue getRequestQueue() {
// lazy initialize the request queue, the queue instance will be
// created when it is accessed for the first time
if (mRequestQueue == null) {
// Create an instance of the Http client.
// We need this in order to access the cookie store
mHttpClient = new DefaultHttpClient();
// create the request queue
mRequestQueue = Volley.newRequestQueue(this, new HttpClientStack(mHttpClient));
}
return mRequestQueue;
}

/**
* Method to set a cookie
*/
public void setCookie() {
CookieStore cs = mHttpClient.getCookieStore();
// create a cookie
cs.addCookie(new BasicClientCookie2("cookie", "spooky"));
}

// add the cookie before adding the request to the queue
setCookie();

// add the request to the queue
mRequestQueue.add(request);

错误处理

       正如前面代码看到的,在创建一个请求时,需要添加一个错误监听onErrorResponse。如果请求发生异常,会返回一个VolleyError实例。

以下是Volley的异常列表:

AuthFailureError:如果在做一个HTTP的身份验证,可能会发生这个错误。

NetworkError:Socket关闭,服务器宕机,DNS错误都会产生这个错误。

NoConnectionError:和NetworkError类似,这个是客户端没有网络连接。

ParseError:在使用JsonObjectRequest或JsonArrayRequest时,如果接收到的JSON是畸形,会产生异常。

SERVERERROR:服务器的响应的一个错误,最有可能的4xx或5xx HTTP状态代码。

TimeoutError:Socket超时,服务器太忙或网络延迟会产生这个异常。默认情况下,Volley的超时时间为2.5秒。如果得到这个错误可以使用RetryPolicy。

 

可以使用一个简单的Help类根据这些异常提示相应的信息:

public class VolleyErrorHelper {
/**
* Returns appropriate message which is to be displayed to the user
* against the specified error object.
*
* @param error
* @param context
* @return
*/
public static String getMessage(Object error, Context context) {
if (error instanceof TimeoutError) {
return context.getResources().getString(R.string.generic_server_down);
}
else if (isServerProblem(error)) {
return handleServerError(error, context);
}
else if (isNetworkProblem(error)) {
return context.getResources().getString(R.string.no_internet);
}
return context.getResources().getString(R.string.generic_error);
}

/**
* Determines whether the error is related to network
* @param error
* @return
*/
private static boolean isNetworkProblem(Object error) {
return (error instanceof NetworkError) || (error instanceof NoConnectionError);
}
/**
* Determines whether the error is related to server
* @param error
* @return
*/
private static boolean isServerProblem(Object error) {
return (error instanceof ServerError) || (error instanceof AuthFailureError);
}
/**
* Handles the server error, tries to determine whether to show a stock message or to
* show a message retrieved from the server.
*
* @param err
* @param context
* @return
*/
private static String handleServerError(Object err, Context context) {
VolleyError error = (VolleyError) err;

NetworkResponse response = error.networkResponse;

if (response != null) {
switch (response.statusCode) {
case 404:
case 422:
case 401:
try {
// server might return error like this { "error": "Some error occured" }
// Use "Gson" to parse the result
HashMap&lt;String, String&gt; result = new Gson().fromJson(new String(response.data),
new TypeToken&lt;Map&lt;String, String&gt;&gt;() {
}.getType());

if (result != null &amp;&amp; result.containsKey("error")) {
return result.get("error");
}

} catch (Exception e) {
e.printStackTrace();
}
// invalid request
return error.getMessage();

default:
return context.getResources().getString(R.string.generic_server_down);
}
}
return context.getResources().getString(R.string.generic_error);
}
}

总结:

        Volley是一个非常好的库,你可以尝试使用一下,它会帮助你简化网络请求,带来更多的益处。

我也希望更加全面的介绍Volley,以后可能会介绍使用volley加载图像的内容,欢迎关注。

        谢谢你的阅读,希望你能喜欢。

 

参考:

 

 

 

/**
* @author 张兴业
*  iOS入门群:83702688
*  android开发进阶群:241395671
*  我的新浪微博:@张兴业TBOW
*/

 

 

原文:

http://arnab.ch/blog/2013/08/asynchronous-http-requests-in-android-using-volley/

[转载]图解Android - Android GUI 系统 (5) - Android的Event Input System - 漫天尘沙 - 博客园

mikel阅读(1015)

[转载]图解Android – Android GUI 系统 (5) – Android的Event Input System – 漫天尘沙 – 博客园.

Android的用户输入系统获取用户按键(或模拟按键)输入,分发给特定的模块(Framework或应用程序)进行处理,它涉及到以下一些模块:

  • Input Reader: 负责从硬件获取输入,转换成事件(Event), 并分发给Input Dispatcher.
  • Input Dispatcher: 将Input Reader传送过来的Events 分发给合适的窗口,并监控ANR。
  • Input Manager Service: 负责Input Reader 和 Input Dispatchor的创建,并提供Policy 用于Events的预处理。
  • Window Manager Service:管理Input Manager 与 View(Window) 以及 ActivityManager 之间的通信。
  • View and Activity:接收按键并处理。
  • ActivityManager Service:ANR 处理。

它们之间的关系如下图所示(黑色箭头代表控制信号传递方向,而红色箭头代表用户输入数据的传递方向)。

这块代码很多,但相对来说不难理解,按照惯例,我们先用一张大图(点击看大图)鸟瞰一下全貌先。

四种不同颜色代表了四个不同的线程, InputReader Thread,InputDispatch Thread 和 Server Thread 存在于SystemServer进程里。UI Thread则存在于Activity所在进程。颜色较深部分是比较重要,需要重点分析的模块。

初始化

整个输入系统的初始化可以划分为Java 和 Native两个部分,可以用两张时序图分别描述,首先看Java端,

  1. 在SystemServer的初始化过程中,InputManagerService 被创建出来,它做的第一件事情就是初始化Native层,包括EventHub, InputReader 和 InputDispatcher,这一部分我们将在后面详细介绍。
  2. 当InputManager Service 以及其他的System Service 初始化完成之后,应用程序就开始启动。如果一个应用程序有Activity(只有Activit能够接受用户输入),它要将自己的 Window(ViewRoot)通过setView()注册到Window Manager Service 中。(详见图解Android – Android GUI 系统 (2) – 窗口管理 (View, Canvas, Window Manager))。
  3. 用户输入的捕捉和处理发生在不同的进程里(生产者:Input Reader 和 Input Dispatcher 在System Server 进程里,而消耗者,应用程序运行在自己的进程里),因此用户输入事件(Event)的传递需要跨进程。在这里,Android使用了Socket 而不是 Binder来完成。OpenInputChannelPair 生成了两个Socket的FD, 代表一个双向通道的两端,向一端写入数据,另外一端便可以读出,反之依然,如果一端没有写入数据,另外一端去读,则陷入阻塞等待。 OpenInputChannelPair() 发生在WindowManager Service 内部。为什么不用binder? 个人的分析是,Socket可以实现异步的通知,且只需要两个线程参与(Pipe两端各一个),假设系统有N个应用程序,跟输入处理相关的线程数目是 n+1 (1是发送(Input Dispatcher)线程)。然而,如果用Binder实现的话,为了实现异步接收,每个应用程序需要两个线程,一个Binder线程,一个后台处理线 程,(不能在Binder线程里处理输入,因为这样太耗时,将会堵塞住发送端的调用线程)。在发送端,同样需要两个线程,一个发送线程,一个接收线程来接 收应用的完成通知,所以,N个应用程序需要 2(N+1)个线程。相比之下,Socket还是高效多了。
  4. 通过RegisterInputChannel, Window Manager Service 将刚刚创建的一个Socket FD,封装在InputWindowHandle(代表一个WindowState) 里传给InputManagerService。
  5. InputManagerService 通过JNI(NativeInputManager)最终调用到了InputDispatchor 的 RegisterInputChannel()方法,这里,一个Connection 对象被创建出来,代表与远端某个窗口(InputWindowHandle)的一条用户输入数据通道。一个Dispatcher可能有多个 Connection(多个Window)同时存在。为了监听来自于Window的消息,InputDispator 通过AddFd 将这些个FD 加入到Looper中,这样,只要某个Window在Socket的另一端写入数据,Looper就会马上从睡眠中醒来,进行处理。
  6. 到这里,ViewRootImpl 的 AddWindow 返回,WMS 将SocketPair的另外一个FD 放在返回参数 OutputChannel 里。
  7. 接着ViewRootImpl 创建了WindowInputEventReceiver 用于接受InputDispatchor 传过来的事件,后者同样通过AddFd() 将读端的Socket FD 加入到Looper中,这样一旦InputDispatchor发送Event,Looper就会立即醒来处理。

接下来看刚才没有讲完的NativeInit。

  1. NativeInit 是 NativeInputManager类的一个方法,在InputManagerService的构造函数中被调用。代码在 frameworks/base/services/jni/com_Android_server_input_inputManagerService.cpp.
  2. 首先创建一个EventHub, 用来监听所有的event输入。
  3. 创建一个InputDispatchor对象。
  4. 创建一个InputReader对象,他的输入是EventHub, 输出是InputDispatchor。
  5. 然后分别为InputReader 和 InputDispatchor 创建各自的线程。注意,当前运行在System Server 的 WMThread线程里。
  6. 接着,InputManagerService 调用NativeStart 通知InputReader 和 InputDispatchor 开始工作。
  7. InputDispatchor是InputReader的消费者,它的线程首先启动,进入Looper等待状态。
  8. 接着 InputReader 线程启动,等待用户输入的发生。

至此,一切准备工作就绪,万事具备,之欠用户一击了。

Eventhub 和 Input Reader

Android设备可以同时连接多个输入设备,比如说触摸屏,键盘,鼠标等等。用户在任何一个设备上的输入就会产生一个中断,经由Linux内核的 中断处理以及设备驱动转换成一个Event,并传递给用户空间的应用程序进行处理。每个输入设备都有自己的驱动程序,数据接口也不尽相同,如何在一个线程 里(上面说过只有一个InputReader Thread)把所有的用户输入都给捕捉到? 这首先要归功于Linux 内核的输入子系统(Input Subsystem), 它在各种各样的设备驱动程序上加了一个抽象层,只要底层的设备驱动程序按照这层抽象接口来实现,上层应用就可以通过统一的接口来访问所有的输入设备。这个 抽象层有三个重要的概念,input handler, input handle 和 input_dev,它们的关系如下图所示:

 

  • input_dev 代表底层的设备,比如图中的“USB keyboard” 或 “Power Button” (PC的电源键),所有设备的input_dev 对象保存在一个全局的input_dev 队列里。
  • input_handler 代表某类输入设备的处理方法,比如说 evdev就是专门处理输入设备产成的Event(事件),而“sysrq” 是专门处理键盘上“sysrq”与其他按键组合产生的系统请求,比如“ALT+SysRq+p”(先Ctrl+ALT+F1切换到虚拟终端)可以打印当前 CPU的寄存器值。所有的input_handler 存放在 input_handler队列里。
  • 一个input_dev 可以有多个input_handler, 比如下图中“USB Mouse” 设备可以由”evdev” 和 “mousedev” 来分别处理它产生的输入。
  • 同样,一个input_handler 可以用于多种输入设备,比如“USB Keyboard”, “Power Button” 都可以产成Event,所以,这些Event都可以交由evdev进行处理。
  • Input handle 用来关联某个input_dev 和 某个 input_handler, 它对应于下图中的紫色的原点。每个input handle 都会生成一个文件节点,比如图中4个 evdev的handle就对应与 /dev/input/下的四个文件”event0~3″. 通过input handle, 可以找到对应的input_handler 和 input_dev.

简单说来,input_dev对应于底层驱动,而input_handler是个上层驱动,而input_handle 提供给应用程序标准的文件访问接口来打通这条上下通道。通过Linux input system获取用户输入的流程简单如下:

  1. 设备通过input_register_dev 将自己的驱动注册到Input 系统。
  2. 各种Handler 通过 input_register_handler将自己注册到Input系统中。
  3. 每一个注册进来的input_dev 或 Input_handler 都会通过input_connect() 寻找对方,生成对应的 input_handle,并在/dev/input/下产成一个设备节点文件.
  4. 应用程序通过打开(Open)Input_handle对应的文件节点,打开其对应的input_dev 和 input_handler的驱动。这样,当用户按键时,底层驱动就能捕捉到,并交给对应的上次驱动(handler)进行处理,然后返回给应用程序,流 程如下图中红色箭头所示。

上图中的深色点就是 Input Handle, 左边垂直方向是Input Handler, 而水平方向是Input Dev。 下面是更为详细的一个流程图,感兴趣的同学可以点击大图看看。

 

所以,只要打开 /dev/input/ 下的所有 event* 设备文件,我们就可以有办法获取所有输入设备的输入事件,不管它是触摸屏,还是一个USB 设备,还是一个红外遥控器。Android中完成这个工作的就是EventHub。

EventHub实现在 framework/base/services/input/EventHub.cpp, 它和InputReader 的工作流程如下图所示:

  1.  NativeInputManager的构造函数里第一件事情就是创建一个EventHub对象,它的构造函数里主要生成并初始化几个控制的FD:
    1. mINotifyFd: 用来监控””/dev/input”目录下是否有文件生成,有的话说明有新的输入设备接入,EventHub将从epool_wait中唤醒,来打开新加入的设备。
    2. mWakeReaderFD, mWakeWriterFD: 一个Pipe的两端,当往mWakeWriteFD 写入数据的时候,等待在mWakeReaderFD的线程被唤醒,这里用来给上层应用提供唤醒等待线程,比如说,当上层应用改变输入属性需要 EventHub进行相应更新时。
    3. mEpollFD,用于epoll_wait()的阻塞等待,这里通过epoll_ctrl(EPOLL_ADD_FD, fd) 可以等待多个fd的事件,包括上面提到的mINotifyFD, mWakeReaderFD, 以及输入设备的FD。
  2. 紧接着,InputManagerService启动InputReader 线程,进入无限的循环,每次循环调用loopOnce(). 第一次循环,会主动扫描 “/dev/input/” 目录,并打开下面的所有文件,通过ioctl()从底层驱动获取设备信息,并判断它的设备类型。这里处理的设备类型 有:INPUT_DEVICE_CLASS_KEYBOARD, INPUT_DEVICE_CLASS_TOUCH, INPUT_DEVICE_CLASS_DPAD,INPUT_DEVICE_CLASS_JOYSTICK 等。
  3. 找到每个设备对应的键值映射文件,读取并生产一个KeyMap 对象。一般来说,设备对应的键值映射文件是 “/system/usr/keylayout/Vendor_%04x_Product_%04x”.
  4. 将刚才扫描到的/dev/input 下所有文件的FD 加到epool等待队列中,调用epool_wait() 开始等待事件的发生。
  5. 某个时间发生,可能是用户按键输入,也可能是某个设备插入,亦或用户调整了设备属性,epoll_wait() 返回,将发生的Event 存放在mPendingEventItems 里。如果这是一个用户输入,系统调用Read() 从驱动读到这个按键的信息,存放在rawEvents里。
  6. getEvents() 返回,进入InputReader的processEventLocked函数。
  7. 通过rawEvent 找到产生时间的Device,再找到这个Device对应的InputMapper对象,最终生成一个NotifyArgs对象,将其放到NotifyArgs的队列中。
  8. 第一次循环,或者后面发生设备变化的时候(比如说设备拔插),调用 NativeInputManager 提供的回调,通过JNI通知Java 层的Input Manager Service 做设备变化的相应处理,比如弹出一个提示框提示新设备插入。这部分细节会在后面介绍。
  9. 调用NotifyArgs里面的Notify()方法,最终调用到InputDispatchor 对应的Notify接口(比如NotifyKey) 将接下来的处理交给InputDispatchor,EventHub 和 InputReader 工作结束,但马上又开始新的一轮等待,重复6~9的循环。

Input Dispatcher

接下来看看目前为止最长一张时序图,通过下面18个步骤,事件将发送到应用程序进行处理。

  1. 接上节的最后一步,NotifyKey() 的实现在Input Dispatcher 内部,他首先做简单的校验,对于按键事件,只有Action 是 AKEY_EVENT_ACTION_DOWN 和 AKEY_EVENT_ACTION_UP,即按下和弹起这两个Event别接受。
  2. Input Reader 传给Input Dispather的数据类型是 NotifyKeyArgs, 后者在这里将其转换为 KeyEvent, 然后交由 Policy 来进行第一步的解析和过滤,interceptKeyBeforeQueuing, 对于手机产品,这个工作是在PhoneWindowManager 里完成,(不同类型的产品可以定义不同的WindowManager, 比如GoogleTV 里用到的是TVWindowManager)。KeyEvent 在这里将会被分为三类:
    1. System Key: 比如说 音量键,Power键,电话键,以及一些特殊的组合键,如用于截屏的音量+Power,等等。部分System Key 会在这里立即处理,比如说电话键,但有一些会放到后面去做处理,比如说音量键,但不管怎样,这些键不会传给应用程序,所以称为系统键。
    2. Global Key:最终产品中可能会有一些特殊的按键,它不属于某个特定的应用,在所有应用中的行为都是一样,但也不包含在Andrioid的系统键中,比如说 GoogleTV 里会有一个“TV” 按键,按它会直接呼起“TV”应用然后收看电视直播,这类按键在Android定义为Global Key.
    3. User Key:除此之外的按键就是User Key, 它最终会传递到当前的应用窗口。
  3. phoneWindowManager的 interceptKeyBeforeQueuing() 最后返回了wmActiions,里面包含若干个flags,NativeInputManager在 handleInterceptActions(), 假如用户按了Power键,这里会通知Android睡眠或唤醒。最后,返回一个 policyFlags,结束第一次的intercept 过程。
  4. 接下来,按键马上进入第二轮处理。如果用户在Setting->Accessibility 中选择打开某些功能,比如说手势识别,Android的AccessbilityManagerService(辅助功能服务) 会创建一个 InputFilter 对象,它会检查输入的事件,根据需要可能会转换成新的Event,比如说两根手指头捏动的手势最终会变成ZOOM的event. 目前,InputManagerService 只支持一个InputFilter, 新注册的InputFilter会把老的覆盖。InputFilter 运行在SystemServer 的 ServerThread 线程里(除了绘制,窗口管理和Binder调用外,大部分的System Service 都运行在这个线程里)。而filterInput() 的调用是发生在Input Reader线程里,通过InputManagerService 里的 InputFilterHost 对象通知另外一个线程里的InputFilter 开始真正的解析工作。所以,InputReader 线程从这里结束一轮的工作,重新进入epoll_wait() 等待新的用户输入。InputFilter 的工作也分为两个步骤,首先由InputEventConsistencyVerifier 对象(InputEventConsistencyVerifier.java)对输入事件的完整性做一个检查,检查事件的ACTION_DOWN 和 ACTION_UP 是否一一配对。很多同学可能在Android Logcat 里看到过以下一些类似的打印:”ACTION_UP but key was not down.” 就出自此处。接下来,进入到AccessibilityInputFilter 的 onInputEvent(),这里将把输入事件(主要是MotionEvent)进行处理,根据需要变成另外一个Event,然后通过 sendInputEvent()将事件发回给InputDispatcher。最终调用到injectInputEvent() 将这个事件送入 mInBoundQueue.
  5. 这个时候,InputDispather 还在Looper中睡眠等待,injectInputEvent()通过wake() 将其唤醒。这是进入Input Dispatcher 线程。
  6. InputDispatcher 大部分的工作在 dispatcherOnce 里完成。首先从mInBoundQueue 中读出队列头部的事件 mPendingEvent, 然后调用 pokeUserActivity(). poke的英文意思是”搓一下, 捅一下“, 这个函数的目的也就是”捅一下“PowerManagerService 提醒它”别睡眠啊,我还活着呢“,最终调用到PowerManagerService 的 updatePowerStateLocked(),防止手机进入休眠状态。需要注意的是,上述动作不会马上执行,而是存储在命令队 列,mCommandQueue里,这里面的命令会在后面依次被执行。
  7. 接下来是dispatchKeyLocked(), 第一次进去这个函数的时候,先检查Event是否已经过处理(interceptBeforeDispatching), 如果没有,则生成一个命令,同样放入mCommandQueue里。
  8. runCommandsLockedInterruptible() 依次执行mCommandQueue 里的命令,前面说过,pokeUserActivity 会调用PowerManagerService 的 updatePowerStateLocked(), 而 interceptKeyBeforeDispatching() 则最终调用到PhoneWindowManager的同名函数。我们在interceptBeforeQueuing 里面提到的一些系统按键在这个被执行,比如 HOME/MENU/SEARCH 等。
  9. 接下来,处理前面提过GlobalKey,GlobalKeyManager 通过broadcast将这些全局的Event发送给感兴趣的应用。最终,interceptKeyBeforeDispatching 将返回一个Int值,-1 代表Skip,这个Event将不会发送给应用程序。0 代表 Continue, 将进入下一步的处理。1 则表明还需要后续的Event才能做出决定。
  10. 命令运行完之后,退出 dispatchOnce, 然后调用pollOnce 进入下一轮等待。但这里不会被阻塞,因为timeout值被设成了0.
  11. 第二次进入dispatchKeyLocked(), 这是Event的状态已经设为”已处理“,这时候才真正进入了发射阶段。
  12. 接下来调用 findFocusedWindowTargetLocked() 获取当前的焦点窗口,这里面会做一件非常重要的事情,就是检测目标应用是否有ANR发生,如果下诉条件满足,则说明可能发生了ANR:
    1. 目标应用不会空,而目标窗口为空。说明应用程序在启动过程中出现了问题。
    2. 目标 Activity 的状态是Pause,即不再是Focused的应用。
    3. 目标窗口还在处理上一个事件。这个我们下面会说到。
  13. 如果目标窗口处于正常状态,调用dispatchEventLocked() 进入真正的发送程序。
  14. 这里,事件又换了一件马甲,从EventEntry 变成 DispatchEntry, 并送人mOutBoundQueue。然后调用startDispatchCycle() 开始发送。
  15. 最终的发送发生在InputPublish的sendMessage()。这里就用到了我们前面提到的SocketPair, 一旦sendMessage() 执行,目标窗口所在进程的Looper线程就会被唤醒,然后读取键值并进行处理,这个过程我们下面马上就会谈到。
  16. 乖乖,还没走完啊?是的,工作还差最后一步,Input Dispatcher给这个窗口发送下一个命令之前,必须等待该窗口的回复,如果超过5s没有收到,就会通过Input Manager Service 向Activity Manager 汇报,后者会弹出我们熟知的 “Application No Response” 窗口。所以,事件会放入mWaitQueue进行暂存。如果窗口一切正常,完成按键处理后它会调用InputConsumer的 sendFinishedSignal() 往SocketPair 里写入完成信号,Input Dispatcher 从 Loop中醒来,并从Socket中读取该信号,然后从mWaitQueue 里清除该事件标志其处理完毕。
  17. 并非所有的事件应用程序都会处理,如果没有处理,窗口程序返回的完成消息里 的 msg.body.finished.handled 会等于false,InputDispatcher 会调用dispatchKeyUnhandled() 将其交给PhoneWindowManager。Android 在这里提供了一个Fallback机制,如果在 /system/usr/keychars/ 下面的kcm文件里定义了 fallback关键字,Android就识别它为一个Fallback Keycode。当它的Parent Keycode没有被应用程序处理,InputDispatcher 会把 Fallback Keycode 当成一个新的Event,重新发给应用程序。下面是一个定义Fallback Key 的例子。如果按了小键盘的0且应用程序不受理它,InputDispatcher 会再发送一个’INSERT’ event 给应用程序。
    复制代码
    #/system/usr/keychars/generic.kcm
    ...
    key NUMPAD_0 {
        label: '0'             //打印字符
        base: fallback INSERT  //behavior
        numlock: '0'        //在一个textView里输出的字符
    }
    复制代码
  18. 经历了重重关卡,一个按键发送的流程终于完成了,不管有没有Fallback Key存在,调用startDispatcherCycle() 开始下一轮征程。。。

史上最长的流程图终于介绍完了,有点迷糊了?好吧,再看看下面这张图总结一下:

  • InputDispatcher 是一个异步系统,里面用到3个Queue(队列)来保存中间任务和事件,分别是 mInBoundQueue, mOutBoundQueue,mWaitQueue不同队列的进出划分了按键的不同处理阶段。
  • InputReader 采集的输入实现首先经过InterceptBeforeQueuing处理,Android 系统会将这些按键分类(System/Global/User), 这个过程是在InputReader线程里完成。
  • 如果是Motion Event, filterEvent()可能会将其转换成其他的Event。然后通过InjectKeyEvent 将这个按键发给InputDispatcher。这个过程是在System Process的ServerThread里完成。
  • 在进入mOutBoundQueue 之前,首先要经过 interceptBeforeDispatching() 的处理,System 和 Global 事件会在这个处理,而不会发送给用户程序。
  • 通过之前生成的Socket Pair, InputPublish 将 Event发送给当前焦点窗口,然后InputDispatcher将Event放入mWaitQueue 等待窗口的回复。
  • 如果窗口回复,该对象被移出mWaitQueue, 一轮事件处理结束。如果窗口没有处理该事件,从kcm文件里搜寻Fallback 按键,如果有,则重新发送一个新的事件给用户。
  • 如果超过5s没有收到用户回复,则说明用户窗口出现阻塞,InputDispather 会通过Input Manager Service发送ANR给ActivityManager。

 

Key processing

前面我们说过,NativeInputEventReceiver() 通过addFd() 将SocketPair的一个FD 加入到UI线程的loop里,这样,当Input Dispatcher在Socket的另外一端写入Event数据,应用程序的UI线程就会从睡眠中醒来,开始事件的处理流程。时序图如下所示:

 

  1.  收到的时间首先会送到队列中,ViewRootImpl 通过 deliverInputEvent() 向InputStage传递消息。
  2. InputStage 是 Android 4.3 新推出的实现,它将输入事件的处理分成若干个阶段(Stage), 如果当前有输入法窗口,则事件处理从 NativePreIme 开始,否则的话,从EarlyPostIme 开始。事件会依次经过每个Stage,如果该事件没有被标识为 “Finished”, 该Stage就会处理它,然后返回处理结果,Forward 或 Finish, Forward 运行下一Stage继续处理,而Finished事件将会简单的Forward到下一级,直到最后一级 Synthetic InputStage。流程图和每个阶段完成的事情如下图所示。

  3. 最后 通过finishInputEvent() 回复InputDispatcher。

 

[转载]分享: 利用Readability解决网页正文提取问题 - Ivan Zou - 博客园

mikel阅读(917)

[转载]分享: 利用Readability解决网页正文提取问题 – Ivan Zou – 博客园.

做数据抓取和分析的各位亲们, 有没有遇到下面的难题呢?

– 如何从各式各样的网页中提取正文!?

虽然可以用SS为各种网站写脚本做解析, 但是互联网各类网站何止千万种, 纵使累死我们也是做不完的. 这里我给大家热情推荐使用Readability来彻底解决这个难题 (呵呵, 不是做广告, 真心热爱这个好东东)

Raedability网站(www.readability.com)最引以为傲的就是其强大的解析引擎, 号称世界上最强大的文本解析神器. Safari中的”阅读器”功能就是用它来实现的! 他们还提供了API可以调用解析器的功能, 而我做了一个C#的代理类来方便大家使用.

开始之前请大家自行注册readability并申请appkey, 免费的.

代理类代码:

public static class ReadabilityProxy
{
public static Article Parse(string url, string token) //token就是各位的appkey
{
WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
var encUrl = HttpUtility.UrlEncode(url);
Uri u = new Uri(string.Format("https://readability.com/api/content/v1/parser?url={0}&amp;token={1}", encUrl, token));
var json = wc.DownloadString(u);
JavaScriptSerializer se = new JavaScriptSerializer();
return se.Deserialize(json, typeof(Article)) as Article;
}
}

public class Article
{
public string Domain;
public string Next_Page_Id;
public string Url;
public string Content;
public string Short_Url;
public string Excerpt;
public string Direction;
public int Word_Count;
public int Total_Pages;
public string Date_Published;
public string Dek;
public string Lead_Image_Url;
public string Title;
public int Rendered_Pages;

public virtual void Decode()
{
this.Excerpt = HttpUtility.HtmlDecode(this.Excerpt);
this.Content = HttpUtility.HtmlDecode(this.Content);
}
}

由于readability返回的Content, Excerpt都是编码过的, 因此我提供了Article.Decode方法来解码.

在ConsoleApp中测试效果:

class Program
{
static void Main(string[] args)
{
var article = ReadabilityProxy.Parse("http://www.mot.gov.cn/st2010/shanghai/sh_zhaobiaoxx/201203/t20120330_1219097.html", "***此处省略n个字***");
article.Decode();
Console.WriteLine(article.Title);
Console.WriteLine(article.Excerpt);
Console.WriteLine(article.Content);
Console.ReadLine();
}
}

怎么样? 效果不错吧, 赶快试试吧!

[转载]Android开发系列(四) TabView 选项卡控件的使用 - Carlos.V - 博客园

mikel阅读(964)

[转载]Android开发系列(四) TabView 选项卡控件的使用 – Carlos.V – 博客园.

最近学习Android开发的时候,发现网上对于设计选项卡的教程很乱,因此结合Mars老师的视频,在这里做一下总结以备参考。

这里创建三个Activity,一个是TabActivity ,另外两个分别是两个选项卡对应的Activity。

第一步  创建三个Activity并在AndroidManifest文件中进行注册;






&nbsp;

然后设置MainActivity 的布局:

<!--?xml version="1.0" encoding="utf-8"?-->

<!-- 最外层是一个TabHost控件,对应了MianActivity,注意这里的id需要用系统自带的id -->

&nbsp;

<!--不要忘记设置方向  -->

&nbsp;

&nbsp;

然后是TabOne和TabTwo的布局:二者是一样的,所以只贴了一个

<!--?xml version="1.0" encoding="utf-8"?-->

&nbsp;

第二步 在MainActivity中创建TabHost和TabHost.TabSpec对象,然后调用setIndicator() 和setContent() 方法,最后再调用TabHost的addTab()方法,将选项卡添加到选项卡控件中,程序如下:

package com.example.android_tabname;
import android.app.TabActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.*;

public class MainActivity extends TabActivity{

TabHost tabHost=null; //选项卡控制器
TabHost.TabSpec tabSpecA,tabSpecB=null; //选项卡,这里选项卡最好不用混用,有几个选项卡就设置几个对象

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//获得TabHost实例;
tabHost=getTabHost();
//获得TabHost.TabSpec对象实例;
tabSpecA=tabHost.newTabSpec("One");
//为TabSpec对象设置指示器
tabSpecA.setIndicator("TabA",getResources().getDrawable(android.R.drawable.ic_media_play));
//为选项卡设置内容,这里需要创建一个intent对象
Intent intentA=new Intent();
intentA.setClass(this, TabOne.class);
tabSpecA.setContent(intentA);

//然后创建第二个选项卡:
tabSpecB=tabHost.newTabSpec("Two");
tabSpecB.setIndicator("TabB",getResources().getDrawable(android.R.drawable.ic_media_next));
Intent intentB=new Intent();
intentB.setClass(this, TabTwo.class);
tabSpecB.setContent(intentB);

//最后一步,把两个选项卡TabSpec添加到选项卡控件TabHost中
tabHost.addTab(tabSpecA);
tabHost.addTab(tabSpecB);

}

}

另外两个Activity只是设置了一下布局文件,后续可以根据不同需要进行扩展。代码如下:

package com.example.android_tabname;
import android.app.Activity;
import android.os.Bundle;

public class TabOne extends Activity{

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_tabone);
}

}
package com.example.android_tabname;

import android.app.Activity;
import android.os.Bundle;

public class TabTwo extends Activity{

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_tabtwo);
}

}

以上步骤就可以设置一个简单的选项卡:最终结果如下:

[转载] Android之ListView原理学习与优化总结

mikel阅读(996)

在整理前几篇文章的时候有朋友提出写一下ListView的性能优化方面的东西,这个问题也是小马在面试过程中被别人问到的…..今天小马就借此机会来整理下,网上类似的资料蛮多的,倒不如自己写一篇,记录在这个地方,供自己以后使用,不用再翻来翻去的找了,用自己写的…呵呵,不多讲其它了,说起优化我想大家第一反应跟小马一样吧?想到利用ViewHolder来优化ListView数据加载,仅仅就此一条吗?其实不是的,首先,想要优化ListView就得先了解ListView加载数据原理,这是前提,但是小马在这个地方先做一些简单的补充,大家一定仔细看下,保证会有收获的:
列表的显示需要三个元素:

ListVeiw: 用来展示列表的View。

适配器 : 用来把数据映射到ListView上

数据: 具体的将被映射的字符串,图片,或者基本组件。

根据列表的适配器类型,列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter,这三种适配器的使用大家可学习下官网上面的使用或者自行百度谷歌,一堆DEMO!!!其中以ArrayAdapter最为简单,只能展示一行字。SimpleAdapter有最好的扩充性,可以自定义出各种效果。SimpleCursorAdapter可以认为是SimpleAdapter对数据库的简单结合,可以方便的把数据库的内容以列表的形式展示出来。

系统要绘制ListView了,他首先用getCount()函数得到要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(这个看实际情况,如果是一个简单的显示则是View,如果是一个自定义的里面包含很多控件的时候它其实是一个ViewGroup),然后再实例化并设置各个组件及其数据内容并显示它。好了,绘制完这一行了。那 再绘制下一行,直到绘完为止,前面这些东西做下铺垫,继续…….

现在我们再来了解ListView加载数据的原理,有了这方面的了解后再说优化才行,下面先跟大家一起来看下ListView加载数据的基本原理小马就直接写了:
ListView的工作原理如下:

ListView 针对每个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后根据这个长度,调用getView()一行一行的绘制ListView的每一项。如果你的getCount()返回值是0的话,列表一行都不会显示,如果返回1,就只显示一行。返回几则显示几行。如果我们有几千几万甚至更多的item要显示怎么办?为每个Item创建一个新的View?不可能!!!实际上Android早已经缓存了这些视图,大家可以看下下面这个截图来理解下,这个图是解释ListView工作原理的最经典的图了大家可以收藏下,不懂的时候拿来看看,加深理解,其实Android中有个叫做Recycler的构件,顺带列举下与Recycler相关的已经由Google做过N多优化过的东东比如:AbsListView.RecyclerListener、ViewDebug.RecyclerTraceType等等,要了解的朋友自己查下,不难理解,下图是ListView加载数据的工作原理(原理图看不清楚的点击后看大图):

102950574

下面简单说下上图的原理:

如果你有几千几万甚至更多的选项(item)时,其中只有可见的项目存在内存(内存内存哦,说的优化就是说在内存中的优化!!!)中,其他的在Recycler中
ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的
当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图

下面来看下小马从网上找来的示例代码,网址搞丢了,只有一个word文档,只能 copy过来,不然直接贴网址,结合上面的原理图一起加深理解,如下:

public class MultipleItemsList extends ListActivity {

private MyCustomAdapter mAdapter;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new MyCustomAdapter();
for (int i = 0; i &lt; 50; i++) {
mAdapter.addItem("item " + i);
}
setListAdapter(mAdapter);
}

private class MyCustomAdapter extends BaseAdapter {

private ArrayList mData = new ArrayList();
private LayoutInflater mInflater;

public MyCustomAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public void addItem(final String item) {
mData.add(item);
notifyDataSetChanged();
}

@Override
public int getCount() {
return mData.size();
}

@Override
public String getItem(int position) {
return mData.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
System.out.println("getView " + position + " " + convertView);
ViewHolder holder = null;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item1, null);
holder = new ViewHolder();
holder.textView = (TextView)convertView.findViewById(R.id.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.textView.setText(mData.get(position));
return convertView;
}

}

public static class ViewHolder {
public TextView textView;
}
}

执行程序,查看日志:

103104354

getView 被调用 9 次 ,convertView 对于所有的可见项目是空值(如下):

103133723

然后稍微向下滚动List,直到item10出现:

103211137

convertView不是空值了!item1离开屏幕到Recycler中去了,然后item11被创建,再滚动下:

convertView仍然是空值,因为recycler中没有视图(item1的边缘仍然可见,在顶端)再滚动列表,继续滚动:

103243344

convertView不是空值了!item1离开屏幕到Recycler中去了,然后item11被创建,再滚动下:

103322896

此时的convertView非空了,在item11离开屏幕之后,它的视图(…0f8)作为convertView容纳item12了,好啦,结合以上原理,下面来看看今天最主要的话题,主角ListView的优化:

首先,这个地方先记两个ListView优化的一个小点:

1. ExpandableListView 与 ListActivity 由官方提供的,里面要使用到的ListView是已经经过优化的ListView,如果大家的需求可以用Google自带的ListView满足的的话尽量用官方的,绝对没错!

2.其次,像小马前面讲的,说ListView优化,其实并不是指其它的优化,就是内存是的优化,提到内存…(想到OOM,折腾了我不少时间),很多很多,先来写下,如果我们的ListView中的选项仅仅是一些简单的TextView的话,就好办啦,消耗不了多少的,但如果你的Item是自定义的Item的话,例如你的自定义Item布局ViewGroup中包含:按钮、图片、flash、CheckBox、RadioButton等一系列你能想到的控件的话, 你要在getView中单单使用文章开头提到的ViewHolder是远远不够的,如果数据过多,加载的图片过多过大,你BitmapFactory.decode的猛多的话,OOM搞死你,这个地方再警告下大家,是警告……….也提醒下自己:

小马碰到的问题大家应该也都碰到过的,自定义的ListView项乱序问题,我很天真的在getView()中强制清除了下ListView的缓存数据convertView,也就是convertView = null了,虽然当时是解决了这个问题让其它每次重绘,但是犯了大错了,如果数据太多的话,出现最最恶心的错,手机卡死或强制关机,关机啊哥哥们……O_O,客户杀了我都有可能,但大家以后别犯这样的错了,单单使用清除缓存convertView是解决不了实际问题的,继续……

下面是小记:图片用完了正确的释放…

if(!bmp.isRecycle() ){
bmp.recycle() //回收图片所占的内存
system.gc() //提醒系统及时回收
}

下面来列举下真正意义上的优化吧:

ViewHolder Tag 必不可少,这个不多说!
如果自定义Item中有涉及到图片等等的,一定要狠狠的处理图片,图片占的内存是ListView项中最恶心的,处理图片的方法大致有以下几种:
2.1:不要直接拿个路径就去循环decodeFile();这是找死….用Option保存图片大小、不要加载图片到内存去;
2.2: 拿到的图片一定要经过边界压缩
2.3:在ListView中取图片时也不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference代替强引用。比如可以使 用WeakReference mContextRef)、SoftReference、WeakHashMap等的来存储图片信息,是图片信息不是图片哦!
2.4:在getView中做图片转换时,产生的中间变量一定及时释放,用以下形式:
尽量避免在BaseAdapter中使用static 来定义全局静态变量,我以为这个没影响 ,这个影响很大,static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(比如Context的情况最多),这时就要尽量避免使用了..
如果为了满足需求下必须使用Context的话:Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题
尽量避免在ListView适配器中使用线程,因为线程产生内存泄露的主要原因在于线程生命周期的不可控制
记下小马自己的错误:
之前使用的自定义ListView中适配数据时使用AsyncTask自行开启线程的,这个比用Thread更危险,因为Thread只有在run函数不 结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了线程执行池(ThreadPoolExcutor,要想了解这个类的话大家加下我们的Android开发群五号,因为其它群的存储空间快满了,所以只上传到五群里了,看下小马上传的Gallery源码,你会对线程执行池、软、弱、强引用有个更深入的认识),这个类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。这个问题的解决办法小马当时网上查到了记在txt里了,如下:
6.1:将线程的内部类,改为静态内部类。
6.2:在线程内部采用弱引用保存Context引用
示例代码如下:

public abstract class WeakAsyncTask&lt;Params, Progress, Result, WeakTarget&gt; extends
AsyncTask&lt;Params, Progress, Result&gt; {
protected WeakReference mTarget;

public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference(target);
}

/** {@inheritDoc} */
@Override
protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}

/** {@inheritDoc} */
@Override
protected final Result doInBackground=\'#\'" /span&gt;
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground=\'#\'" /span&gt;
} else {
return null;
}
}

/** {@inheritDoc} */
@Override
protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}

protected void onPreExecute(WeakTarget target) {
// No default action
}

protected abstract Result doInBackground(WeakTarget target, Params... params);

protected void onPostExecute(WeakTarget target, Result result) {
// No default action
}

}

好啦,ListVIew的优化问题,小马就暂时先理解记录这么多了,如果朋友们有什么更好的优化建议什么的,留言指点下小马,一定会及时添加到进来的,先谢谢啦,其实在ListView适配器的getView()方法中可以做很多的优化,我记得还有可以优化findViewById()这个方法来寻址资源信息效率的方法,资料太多了,小马发现了会及时更新的哦,天太晚了,先休息了,吼吼,大家加油,一起努力学习!!!O_O

本文出自 “酷_莫名简单、KNothing” 博客,请务必保留此出处http://mzh3344258.blog.51cto.com/1823534

[转载]Android_HttpClient_get请求post表单提交上传 - 子墨的Android开发之旅 - 博客频道 - CSDN.NET

mikel阅读(739)

[转载]Android_HttpClient_get请求post表单提交上传 – 子墨的Android开发之旅 – 博客频道 – CSDN.NET.

关于HttpUrlConnection用法

1.HttpRequestBase(HttpGet..)

HttpClient 支持多种访问网络的方式,包括GET, HEAD, POST, PUT, DELETE, TRACE and OPTIONS. 其对应子类为HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace, and HttpOptions.但在使用时多为HttpGet, HttpPost两种方式。

/**
* 一个简单的get请求
*/
private static void get() {
// 1.得到HttpClient对象
HttpClient httpClient = new DefaultHttpClient();
// 2.实例化一个HttpGet对象
HttpGet httpGet = new HttpGet("http://www.baidu.com");
try {
// 3.httpClient执行httpGet请求
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) { // 如果有数据表示请求成功
System.out.println(EntityUtils.toString(entity, "utf-8"));
} else {
System.out.println("连接失败!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4.释放资源(Shuts down this connection manager and releases allocated
// resources)
httpClient.getConnectionManager().shutdown();
}
}

2.HttpResponse

通过得到HttpResponse对象,可以得到相关访问网络的信息,比如getEntity() getStatusLine()等等

System.out.println(response.getProtocolVersion());
System.out.println(response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getReasonPhrase());
System.out.println(response.getStatusLine().toString());

输出结果为

/*
HTTP/1.1
200
OK
HTTP/1.1 200 OK
*/

//得到请求的响应数据
//方式一.
HttpEntity entity = response.getEntity();
if (entity != null) { // 如果有数据表示请求成功
System.out.println(EntityUtils.toString(entity, “utf-8”));
} else {
System.out.println(“连接失败!”);
}
//方式二.
if(response.getStatusLine().getStatusCode() == 200){
HttpEntity myEntity = response.getEntity();
BufferedInputStream in = new BufferedInputStream(myEntity.getContent());
byte[] bytes = read(in);//通过自定义的read方法,取得该输入流对应的数据
}
[/java]

3.ResponseHandler

/**
* 通过ResponseHandler处理请求
*/
private static void test() {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("http://www.baidu.com");

//自定义ResponseHandler对象
ResponseHandler myHandler = new ResponseHandler() {
@Override
public String handleResponse(HttpResponse response)
throws ClientProtocolException, IOException {
HttpEntity entity = response.getEntity();
String result = "连接失败!";
if (entity != null) { // 如果有数据表示请求成功
result = EntityUtils.toString(entity, "utf-8");
}
return result;
}
};
try {
String result = httpClient.execute(httpGet, myHandler);//接收该myHandler对象
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源(Shuts down this connection manager and releases allocated resources)
httpClient.getConnectionManager().shutdown();
}
}

5.GZIP压缩

对于文本数据,特别是json数据或者html网页数据,最好使用gzip进行压缩,理论上文本数据可以压缩为原来的1/3,效果很明显,压缩之后应该使用gzip流进行解压缩!
get.addHeader("Accept-Encoding", "gzip");//使用addHeader,否则没有效果

HttpResponse response = client.execute(get);
int code = response.getStatusLine().getStatusCode();
Header header = response.getFirstHeader("Content-Encoding");
InputStream is = new BufferedInputStream(response.getEntity().getContent());
if (header != null &amp;&amp; header.getValue().contains("gzip")) {//首先判断服务器是否支持gzip压缩
is = new GZIPInputStream(is);
}

6.Post完成Form表单的提交

/**
* 完成form表单的提交
*/
private static void post() {
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost("http://127.0.0.1:8080/My/upload");

try {
// 为httpPost设置HttpEntity对象
List parameters = new ArrayList();
parameters.add(new BasicNameValuePair("username", "zhangsan"));
parameters.add(new BasicNameValuePair("password", "123321"));
HttpEntity entity = new UrlEncodedFormEntity(parameters);
httpPost.setEntity(entity);
// httpClient执行httpPost表单提交
HttpResponse response = httpClient.execute(httpPost);
// 得到服务器响应实体对象
HttpEntity responseEntity = response.getEntity();
if (responseEntity != null) {
System.out.println(EntityUtils
.toString(responseEntity, "utf-8"));
System.out.println("表单上传成功!");
} else {
System.out.println("服务器无响应!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
httpClient.getConnectionManager().shutdown();
}
}

7.Post完成文件的长传

/**
 * 通过post完成文件的上传
 */
private static void postFile() {
	HttpClient httpClient = new DefaultHttpClient();
	HttpPost httpPost = new HttpPost("http://127.0.0.1:8080/My/upload");
	try {
		// 需要上传的文件
		String root = "D:/api/";
		String fileName = "JDK6.0 中文文档.CHM";
		File uploadFile = new File(root+fileName);
		//定义FileEntity对象
		HttpEntity entity = new FileEntity(uploadFile);
		//为httpPost设置头信息
		httpPost.setHeader("filename", URLEncoder.encode(fileName,"utf-8"));//服务器可以读取到该文件名
		httpPost.setHeader("Content-Length", String.valueOf(entity.getContentLength()));//设置传输长度
		httpPost.setEntity(entity);	//设置实体对象
		
		// httpClient执行httpPost提交
		HttpResponse response = httpClient.execute(httpPost);
		// 得到服务器响应实体对象
		HttpEntity responseEntity = response.getEntity();
		if (responseEntity != null) {
			System.out.println(EntityUtils.toString(responseEntity, "utf-8"));
			System.out.println("文件 "+fileName+"上传成功!");
		} else {
			System.out.println("服务器无响应!");
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		// 释放资源
		httpClient.getConnectionManager().shutdown();
	}
}

[转载]Android杂谈--内存泄露(1)--contentView缓存使用与ListView优化 - hwoarang - 博客园

mikel阅读(903)

[转载]Android杂谈–内存泄露(1)–contentView缓存使用与ListView优化 – hwoarang – 博客园.

引起Android内存泄露有很多种原因,下面罗列了一些问题,以后会一一解决

1、构造Adapter时没有使用缓存convertView(衍生出ListView优化问题)

2、查询数据库游标没有关闭

3、Activity中生命周期对象大于Activity生命周期(关于Application Context与Activity Context)

4、Bitmap对象不使用时没有recycle掉(这里还有其他解决方案)

 

今天说的是第一种:如何使用缓存来优化ListView

因为如果不使用缓存convertView的话,调用getView时每次都会重新创建View,这样之前的View可能还没有销毁,加之不断的新建View势必会造成内存泄露。

使用getView时有3方案:(1)没有使用convertView,(2)使用convertView,(3)使用convertView+静态类ViewHolder

 

我做了一个测试,代码在下面,创建2000个View,从0拉到最后,计算总共耗,同时显示GC释放内存的大小,三种测试的结果如下:

注:这里先说下 GC_EXTERNAL_ALLOC freed 7K, 18% free 11153K/13511K, external 1632K/1672K, paused 89ms 的意思

在Dalvik中,为一个程序分配的内存要根据机型的不同而不同,一般为32M,而虚拟机会把这些内存分别分配给,JAVA使用的堆内存 (heap)和Nativie使用的内存(external)(即虚拟机中通过JNI调用本地Nativie的类中malloc分配的内存,如 Bitmap,java.nio.ByteBuffers)。不过两者不同共享,也就是说Native的内存不够用了,而JAVA内存够用时是不能向 JAVA申请的,必须向虚拟机申请才行,当虚拟机无法分配的时候就会报OOM的错误

freed 7k:表示GC已经释放了7K的内存

18% free 11153K/13511K:表示JAVA使用的堆内存(对象存在于此),18% free表示当前剩余18%的堆内存(heap memory),11153K表示当前已用的堆内存,13511K表示堆内存总共大小(网上有些文章这部分弄错了,很多转载都是同一个)

external 1632K/1672K:1632K表示已用external memory,总共1672K external memory(注意:这个可能只存在于Android 3.0之前)

paused 89ms:这里其实包括了两部分,一个是在调用GC之前暂停的时间,一个是调用GC后基本完成时暂停的时间

详细可参考:http://stackoverflow.com/questions/4550757/android-logs-gc-external-alloc-gc-for-malloc

 

(1)没有使用convertView

  没有任何处理,不建议这样写。如果数据量少可以,但是如果列表项数据量很大的时候,会每次都重新创建View,设置资源,严重影响性能,所以从一开始就不要用这种方式

@Override
public View getView(int position, View convertView, ViewGroup parent) {
//Get a View that displays the data at the specified position in the data set.
//开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
long startTime = System.nanoTime();
View item = mInflater.inflate(R.layout.list_item, null);
ImageView img = (ImageView)item.findViewById(R.id.img);
TextView title = (TextView)item.findViewById(R.id.title);
TextView info = (TextView)item.findViewById(R.id.info);
img.setImageResource(R.drawable.ic_launcher);
title.setText("loulijun");
info.setText("www.cnblogs.com/loulijun");

//停止计时
long endTime = System.nanoTime();
//耗时
long spendTime = (endTime - startTime);

sumTime += spendTime;
Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
return item;
}

测试结果:

目前VM只为他们分配了5767K+518k的内存,而内存峰值是32M

刚开始时,而且heap memory只申请了5767K,已用内存3353K,注意数据大小的变化:耗时:167633055ns = 0.167633055秒

当拉到1000的时候,堆内存总计已经申请了9607K,已用内存7245K,明显已经比刚开始时要大了 ,耗时:3435241667ns=3.435241667秒

当拉到2000的时候,堆内存总计13511K,已用内存11153K,耗时:6660369835ns = 6.660369835秒

—————————我又创建了10000个ListView,测试后直到内存泄露,证明峰值却是是32M,而 不使用convertView导致的内存泄露,当内存泄露时手机会提示force close,并将错误写入/data/anr/traces.txt中,你可以adb pull下来查看具体信息

(2)使用convertView后的测试数据(优化后)

  通过缓存convertView,convertView可以缓存可视范围内的convertView,当再次向下滑动时又开始更 新View,这种利用缓存convertView的方式可以判断如果缓存中不存在View才创建View,如果已经存在可以利用缓存中的View,这样会 减少很多View的创建,提升了性能

@Override
public View getView(int position, View convertView, ViewGroup parent) {
//Get a View that displays the data at the specified position in the data set.
if(convertView == null)
{
convertView = mInflater.inflate(R.layout.list_item, null);
}
//开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
long startTime = System.nanoTime();

ImageView img = (ImageView)convertView.findViewById(R.id.img);
TextView title = (TextView)convertView.findViewById(R.id.title);
TextView info = (TextView)convertView.findViewById(R.id.info);
img.setImageResource(R.drawable.ic_launcher);
title.setText("loulijun");
info.setText("www.cnblogs.com/loulijun");

//停止计时
long endTime = System.nanoTime();
//耗时
long spendTime = (endTime - startTime);

sumTime += spendTime;
Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
return convertView;
}

测试数据我还是用2000吧,10000太大了(一万年太久,只争朝夕)

测试结果:

这次一直拉到最后明显比刚才流畅多了,而且GC释放内存的次数也明显少了很多,最后用的时间和当前使用的内存也小很多,优化后的确好多了

当position为1000的时候,附近没怎么调用GC,用时:213653551ns=0.213653551秒,额,差距有点大,上面到达1000时用时达到3.43秒之多。

当position为2000的时候,已用内存只有3068K,堆总共内存6215K,而且external memory是0K,用时:378326396ns = 0.378326396秒,性能差距如此之大,都有点不敢相信。也不知道这种方式对不对,如有不妥的地方,还希望大牛能给出正确回答

(3)使用contentView+静态类ViewHolder类

通过convertView+ViewHolder来实现,ViewHolder就是一个静态类,使用 ViewHolder 的关键好处是缓存了显示数据的视图(View),加快了 UI 的响应速度。

当我们判断 convertView == null  的时候,如果为空,就会根据设计好的List的Item布局 (XML),来为convertView赋值,并生成一个viewHolder来绑定converView里面的各个View控件(XML布局里面的那些 控件)。再用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。(看下 面代码中)

如果convertView不为空的时候,就会直接用convertView的getTag(),来获得一个ViewHolder。

静态类ViewHolder

//定义静态类ViewHolder
static class ViewHolder
{
public ImageView img;
public TextView title;
public TextView info;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//Get a View that displays the data at the specified position in the data set.

//开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
long startTime = System.nanoTime();
ViewHolder holder;

if(convertView == null)
{
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
holder.img = (ImageView)convertView.findViewById(R.id.img);
holder.title = (TextView)convertView.findViewById(R.id.title);
holder.info = (TextView)convertView.findViewById(R.id.info);
convertView.setTag(holder);
}else
{
holder = (ViewHolder)convertView.getTag();
holder.img.setImageResource(R.drawable.ic_launcher);
holder.title.setText("loulijun");
holder.info.setText("www.cnblogs.com/loulijun");
}

//停止计时
long endTime = System.nanoTime();
//耗时
long spendTime = (endTime - startTime);

sumTime += spendTime;
Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
return convertView;
}

到这里,可能会有人问ViewHolder静态类结合缓存convertView与直接使用convertView有什么区别吗,是否重复了

在这里,官方给出了解释

提升Adapter的两种方法

To work efficiently the adapter implemented here uses two techniques:
-It reuses the convertView passed to getView() to avoid inflating View when it is not necessary

(译:重用缓存convertView传递给getView()方法来避免填充不必要的视图)
-It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary

(译:使用ViewHolder模式来避免没有必要的调用findViewById():因为太多的findViewById也会影响性能)
ViewHolder类的作用
-The ViewHolder pattern consists in storing a data structure in the tag of the view
returned by getView().This data structures contains references to the views we want to bind data to,
thus avoiding calling to findViewById() every time getView() is invoked

(译:ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们

要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById())

 

测试数据:(跟直接使用convertView数据相差不多)

当position为1000时,用时:199188216ns = 0.199188216秒,堆内存的时候也没比没有使用convertView理想的多

当position为2000时,用时:336669887ns = 0.336669887秒,比直接使用convertView的方式稍微好一点点,不过性能相差不多

[转载]Android安卓性能调优:内存使用分析和方法调用优化 - liaohuqiu - 博客园

mikel阅读(900)

[转载]安卓性能调优:内存使用分析和方法调用优化 – liaohuqiu – 博客园.

Android 应用的性能分析,优化,需要检查分析内存使用情况和方法调用情况。本文给出进行这两方面分析的工具和方法。

内存使用分析


1. 分析内存使用

虽然Android系统的Dalvik虚拟机有垃圾回收机制,但因手机内存使用存在不同于普通PC的更大的限制,内存使用方面的问题,我们更应多加注意。

  1. 一些内存使用问题会非常明显,比如内存耗尽(不足)时触发的OutOfMemoryError可能会使App直接崩溃。
  2. 另有一些内存问题则表现得不那么明显,但他们会让你的App以及系统变得越来越慢。

当有以上两种情况之一时,就得看看内存的使用情况了,是否存在:

  • 过大的对象,占用内存
  • 有些对象一直被创建,从未被释放。
2. 工具

Android的ADT中,提供了两种工具可以用来分析内存使用

  • 对象分配相关:DDMS中的Allocation Tracker。借助这个工具可以查看对象的生成和分配情况, 可了解到对象在何时被创建,但无法了解整个App的对象分配情况。
  • Heap使用情况相关:
    1. DDMS中的Heap工具。
    2. hprof导出工具,在DDMS中导出hprof文件,在Memory Analyzer中查看。

hprof文件是Java 虚拟机的Heap快照

3. 查看Heap实时情况

  1. 打开DDMS,选中应用,点击Update Heap按钮
  2. 右侧Heap标签页,显示了Heap使用情况
  3. 操作应用,看哪些操作将导致内存用量增大
4. Memeory Analyzer分析内存使用情况

根据实时的Heap使用情情况,我们可以大致判断哪些操作,哪些页面可能存在内存是一共问题,但是具体的问题的需要更进一步的数据。

Allocation Tracker提供了对象分配和被引用的详细的信息

另外,还提供了一个报告,为我们分析提供参考

请在此处下载:Memeory Analyzer

我们可以通过DDMS导出hprof文件,在Memeory Analyzer中分析, 如下:

  1. 打开DDMS, 选中应用,点击Dump HPROF file, 等待一段时间, 10几秒甚至更长,保存hprof文件。
  2. 导出的文件为Dalvik虚拟机格式的,需要转成J2SE虚拟机格式的,否则Memeory Analyzer无法打开

    在windows中,cmd:

    cd /d D:\android\adt\adk\tools
    hprof-conv.exe D:\tmp\com.srain.cube.sample.hprof D:\tmp\com.srain.cube.sample-conv.hprof
    
  3. 在Memeory Analyzer 中打开文件 打开文件分析的过程中,会提示是否生成分析报告,分析报告会指出哪些对象是可疑的占用内存的对象。

界面展示大致如下: 

点击Histogram:

各对象在列表中,可排序:

  • Shallow Heap: 占用的真正的内存大小
  • Retained Heap: 对象自身的大小 + 所维护的引用的大小

选中某个对象,List Objects -> with incoming reference / with outcoming reference 可查看引用和被应用的情况。 根据这些,加上搜索,可判断未释放的或者过大的有问题的对象的位置。

Memeory Analyzer功能强大,更多用法,点击这里


方法调用分析

App不流畅卡顿,和方法执行速度有更直接的关系。 主UI线程上的耗时操作,超过5s,系统就会提示用户,是否终止程序。 在ListView中的getView()方法,一个耗时10ms的操作就足够把你的列表卡顿得惨不忍睹。

Android框架Debug类提供了方法,记录方法调用的执行数据到一个trace文件,在代码中:

// 开始 trace文件位置: /sdcard/cube.trace
Debug.startMethodTracing("cube");

// ...
// 其他的代码

// 停止
Debug.stopMethodTracing();

在模拟器或者没SDK的真机上调试时,直接使用 /sdcard下的路径可能会有Permission deny错误,改用机身内部存储试试。

生成的trace文件,通过adb pull存到本地。

adb pull /sdcard/cube.trace D:\tmp\cube.trace

直接在ADT的eclipse中打开:

上图中:

  1. 上部区域(Timeline Panel)为各线程的时间线上的概况
    • 在区域1,鼠标为左右箭头状,在放大之后,可拖动缩小尺寸
    • 鼠标放在各线程时间轴区域,比如主线程的2区域,鼠标成十字状,左右拖动可以选择关注区域,选择合适的关注区域,松开鼠标,区域将放大。
  2. 下部区域(Profile Panel)为方法调用情况, 几个参数介绍如下:
    • cpu time,方法执行的真正的时间
    • real time, cpu time + 其他时间(IO wait, Thread wait)
    • Inc xxx Time, Inc 为 inclusive 缩写,本方法调用时间以及本方法内部所调用的方法(子方法)的总和
    • Excl xxx Time, Excl 为exclusive 的缩写,指的除去子方法,该方法本身执行时间
    • Calls + RecurCalls/Total, 显示父子方法调用次数占比
  3. 选中一个方法,在时间线图中会有突出显示。放大时间线图,可直观看出方法执行时间长度;看调用图,可找出该方法被调用的层次关系。
  4. 查看Excl Time 和Inc Time,分析调用关系,可找出真正耗时的方法,找出性能瓶颈。

上图中,Excl Time 排名第二的方法 bytesToHexString 很可能是有性能问题的。