To develop a responsive Android Application, a good practice is to ensure that as few code as possible is executed in the main UI thread. If any code that may take a long time to execute in the main UI thread, it will cause the program to suspend and fail to respond to user operations. Therefore, it should be executed in a separate thread. A typical example is the operations related to network communication. We cannot predict the speed of sending and receiving information through the network. It is possible that the operation can be completed by "Biu, it may also take a long time. If the user is in a good mood, he may tolerate a little delay, and the premise is that you give the necessary tips, but a program that does not seem to look like a fart ...... Just like a web page before the emergence of Ajax technology, users can get used to loading in a short time, however, a browser window that has been loaded for half a day is often confusing and mad at us in the dial-up era .)
In this article, we will create a simple image download program to demonstrate the multi-threaded mode. We will download a batch of images from the Internet and use these images to generate a thumbnail list. Creating an asynchronous task to download images in the background will make our program look faster. Here I add "looks", because I think the so-called multithreading makes the program faster, and more importantly, "improves the response to user operations ". This includes the title of this article, the so-called "High Performance", mainly refers to the prevention of the hard straight UI (fighting game terminology, please google), suspended. After all, multithreading cannot avoid the main resource overhead inherent in the Code .)
An image downloader
It is easy to download images from the web. You can use the http-related classes provided by the SDK. The following is a simple implementation.
The androidhttpclient and other classes used below are provided from Version 2.2, that is, API Level 8. Please refer to the code below 2.1 to understand the spirit. Httpclient can be used directly .)
Static bitmap downloadbitmap (string URL ){
Final androidhttpclient client = androidhttpclient. newinstance ("android ");
Final httpget getrequest = new httpget (URL );
Try {
Httpresponse response = client.exe cute (getrequest );
Final int statuscode = response. getstatusline (). getstatuscode ();
If (statuscode! = Httpstatus. SC _ OK ){
Log. W ("imagedownloader", "error" + statuscode + "while retrieving bitmap from" + URL );
Return NULL;
}
Final httpentity entity = response. getentity ();
If (entity! = NULL ){
Inputstream = NULL;
Try {
Inputstream = entity. getcontent ();
Final Bitmap bitmap = bitmapfactory. decodestream (inputstream );
Return bitmap;
} Finally {
If (inputstream! = NULL ){
Inputstream. Close ();
}
Entity. consumecontent ();
}
}
} Catch (exception e ){
// Cocould provide a more explicit error message for ioexception or illegalstateexception
Getrequest. Abort ();
Log. W ("imagedownloader", "error while retrieving bitmap from" + URL, E. tostring ());
} Finally {
If (client! = NULL ){
Client. Close ();
}
}
Return NULL;
}
First, we create an HTTP client and an HTTP request. If the request succeeds, the image content in the response is decoded into a bitmap format and returned for future use. To allow the program to access the network, the Internet must be declared in the manifest file of the program.
Note: The old version of bitmapfactory. decodestream has a bug, which may make it unable to work normally when the network is slow. You can use flushedinputstream (inputstream) instead of the original inputstream to solve this problem. The following is the implementation of this helper class:
Static class flushedinputstream extends filterinputstream {
Public flushedinputstream (inputstream ){
Super (inputstream );
}
@ Override
Public long SKIP (long n) throws ioexception {
Long totalbytesskipped = 0l;
While (totalbytesskipped <n ){
Long bytesskipped = in. Skip (n-totalbytesskipped );
If (bytesskipped = 0l ){
Int byte = read ();
If (byte <0 ){
Break; // we reached EOF
} Else {
Bytesskipped = 1; // we read one byte
}
}
Totalbytesskipped + = bytesskipped;
}
Return totalbytesskipped;
}
}
This class can ensure that skip () does Skip the number of bytes provided by the parameter until the end of the stream file.
If you directly use the downloadbitmap method above in the getview method of listadapter, the result can be imagined. As we scroll the screen, it must be a bad meal. Each time a new view is displayed, you must wait for an image to be downloaded. This will inevitably affect the smoothness of the scrolling screen.
This is precisely because of the poor experience that we all wanted. androidhttpclient cannot be started in the main thread! The above code will prompt "this thread cannot carry out HTTP requests" in the main thread ". If you don't see the coffin and don't cry, you can use defaulthttpclient to replace androidhttpclient and give yourself an explanation.
Asynchronous task
The asynctask class provides a method for generating new tasks from the main thread. Let's create an imagedownloader class to generate tasks. This class provides a download method to download images from a specified URL and display them in imageview.
Public class imagedownloader {
Public void download (string URL, imageview ){
Bitmapdownloadertask task = new bitmapdownloadertask (imageview );
Task.exe cute (URL );
}
}
/* Class bitmapdownloadertask, see below */
}
Bitmapdownloadertask is inherited from asynctask. It is used to download images. The task is started using the execute method, which returns immediately, so that the main thread code that calls it can be quickly executed. This is exactly what we mean by using asynctask. The implementation of bitmapdownloadertask is as follows:
Class bitmapdownloadertask extends asynctask {
Private string URL;
Private Final weakreference imageviewreference;
Public bitmapdownloadertask (imageview ){
Imageviewreference = new weakreference (imageview );
}
@ Override
// Actual download method, run in the task thread
Protected bitmap doinbackground (string... Params ){
// Params comes from the execute () call: Params [0] is the URL.
Return downloadbitmap (Params [0]);
}
@ Override
// Once the image is downloaded, associates it to the imageview
Protected void onpostexecute (Bitmap bitmap ){
If (iscancelled ()){
Bitmap = NULL;
}
If (imageviewreference! = NULL ){
Imageview = imageviewreference. Get ();
If (imageview! = NULL ){
Imageview. setimagebitmap (Bitmap );
}
}
}
}
The doinbackground method is the code that truly executes an asynchronous task in a separate process. It calls the downloadbitmap method described earlier to complete the download and obtain the bitmap.
Onpostexecute is called by the main thread after the task ends. It obtains the downloaded bitmap through the input parameters and sets it to imageview display (this imageview is passed in when bitmapdownloadertask is instantiated ). It should be noted that the reference to imageview is saved in the bitmapdownloadertask instance in the form of weakreference. Therefore, if the activity is disabled during the download process, the imageview in the activity cannot be recycled. Therefore, we must check whether the imageviewreference and imageview are empty before use.
This simple example demonstrates how to use asynctask. If you do this experiment yourself, you will find that these few lines of code have significantly improved the scrolling experience of listview. We recommend that you read painless threading, an article by developer.android.com, to learn more about asynctasks.
However, this listview-based example exposes a problem. For the sake of memory utilization efficiency, listview will reuse the view cyclically during screen scrolling. If the user quickly rolls the screen, an imageview object will be used multiple times. Each time it is displayed, a download task is triggered to change the display content of this imageview. So where is the problem? Like most parallel programs, the key issue lies in order. In this example, we have not taken any measures to ensure that all download tasks are completed in order. In other words, the tasks started first cannot be completed, and those started later are completed. In this way, the image displayed in the list may come from the previous task. This task ends because it takes a longer time and leads to unexpected results. If the images you want to download are bound to an imageview at a time, there is no problem, but we should proceed from the overall situation and fix it for general purposes.
Concurrent processing
To solve the problem mentioned above, we need to know and save the download task order to ensure that the last started task ends and the imageview is updated. To achieve this goal, let every imageview remember its last download task. We use a dedicated drawable class to add this information to imageview. This drawable class will be temporarily bound to imageview during download. The following is the code of the downloadeddrawable class:
Static class downloadeddrawable extends colordrawable {
Private Final weakreference bitmapdownloadertaskreference;
Public downloadeddrawable (bitmapdownloadertask ){
Super (color. Black );
Bitmapdownloadertaskreference =
New weakreference (bitmapdownloadertask );
}
Public bitmapdownloadertask getbitmapdownloadertask (){
Return bitmapdownloadertaskreference. Get ();
}
}
This implementation introduces a colordrawable, which causes the imageview to display a black background during the download process. If needed, you can use a "download ..." And so on, in exchange for a more friendly user interface. Again, pay attention to using weakreference to reduce coupling with the object instance.
Let's modify the previous code to make this class take effect. First, the download method creates an instance of this class and binds it to the imageview:
Public void download (string URL, imageview ){
If (cancelpotentialdownload (URL, imageview )){
Bitmapdownloadertask task = new bitmapdownloadertask (imageview );
Downloadeddrawable = new downloadeddrawable (task );
Imageview. setimagedrawable (downloadeddrawable );
Task.exe cute (URL, cookie );
}
}
The cancelpotentialdownload method cancels the ongoing download task before a new download starts. Note: This is not enough to ensure that the image obtained from the new download task will be displayed, because the previous task may have been completed and is waiting for the onpostexecute method to be executed, the onpostexecute method may be executed after the onpostexecute method of the new task.
Private Static Boolean cancelpotentialdownload (string URL, imageview ){
Bitmapdownloadertask = getbitmapdownloadertask (imageview );
If (bitmapdownloadertask! = NULL ){
String bitmapurl = bitmapdownloadertask. url;
If (bitmapurl = NULL) | (! Bitmapurl. Equals (URL ))){
Bitmapdownloadertask. Cancel (true );
} Else {
// The same URL is already being downloaded.
Return false;
}
}
Return true;
}
Cancelpotentialdownload calls the cancel method of the asynctask class to stop the ongoing download task. In most cases, it returns true, so you can call its download method to start a new download. The only exception is that if the ongoing download task and the new task request are the same URL, we will not cancel the old task and let it continue downloading. Note: In our implementation method, if the imageview is recycled, the associated download will not stop (you can implement it using recyclerlistener ).
This method also calls a helper function getbitmapdownloadertask. The code is intuitive and will not be repeated:
Private Static bitmapdownloadertask getbitmapdownloadertask (imageview ){
If (imageview! = NULL ){
Drawable = imageview. getdrawable ();
If (drawable instanceof downloadeddrawable ){
Downloadeddrawable = (downloadeddrawable) drawable;
Return downloadeddrawable. getbitmapdownloadertask ();
}
}
Return NULL;
}
Finally, you must modify the onpostexecute method to ensure that the bitmap is bound to the imageview only when the imageview is associated with the download process:
If (imageviewreference! = NULL ){
Imageview = imageviewreference. Get ();
Bitmapdownloadertask = getbitmapdownloadertask (imageview );
// Change bitmap only if this process is still associated with it
If (this = bitmapdownloadertask ){
Imageview. setimagebitmap (Bitmap );
}
}
After these modifications, our imagedownloader class can provide the expected services. You can use the code or its asynchronous ideas in your project to improve user experience.
Demo
The source code of this article can be obtained from Google Code. You can switch and compare the three implementation methods mentioned in this article (non-asynchronous, non-concurrent processing, and final version. Note that the cache size has been limited to 10 images to better demonstrate possible problems.
Further work
The code in this article has been simplified to focus on parallel issues, so it lacks many functions. First, the imagedownloader class should use the cache, especially when used with listview. Because the listview displays the same image multiple times when the user goes up or down the screen, the cache can greatly reduce the overhead. You can easily implement this by using an LRU cache based on linkedhashmap (which provides URL ing from URL to bitmap softreference. More complex caching mechanisms can also rely on local storage. You can also add features such as creating thumbnails and scaling images.
The code in this article has taken into account the download error and timeout situations. In these cases, a vacant space graph is returned. You can also display an image with a prompt.
The HTTP request in this example is simple. Depending on the actual situation (mostly on the server), you can add various parameters or cookies to the HTTP request.
The asynctask class used in this article is a simple and convenient way to separate tasks from the main thread. You may use the handler class to better control the task flow, such as controlling the number of parallel download threads.
From: http://hi.baidu.com/zhuawatianhou/blog/item/b012921723513109972b4398.html