Android的Lazy Load主要體現在網絡數據(圖片)異步加載、數據庫查詢、複雜業務邏輯處理以及費時任務操作導致的異步處理等方面。在介紹Android開發過程中,異步處理這個常見的技術問題之前,我們簡單回顧下Android開發過程中需要注意的幾個地方。
Android應用開發過程中必須遵循單線程模型(Single Thread Model)的原則。因爲Android的UI操作並不是線程安全的,所以涉及UI的操作必須在UI線程中完成。但是並非所有的操作都能在主線程中進行,Google工程師在設計上約定,Android應用在5s內無響應的話會導致ANR(Application
Not Response),這就要求開發者必須遵循兩條法則:1、不能阻塞UI線程,2、確保只在UI線程中訪問Android
UI工具包。於是,開啓子線程進行異步處理的技術方案應運而生。
本文以自定義ListView,異步加載網絡圖片示例,總結了Android開發過程中,常用的三種異步加載的技術方案。
相關資源:
AndroidManifest.xml
01 |
<manifest
xmlns:android= "http://schemas.android.com/apk/res/android" |
02 |
package = "com.doodle.asycntasksample" |
03 |
android:versionCode= "1" |
04 |
android:versionName= "1.0" > |
07 |
android:minSdkVersion= "8" |
08 |
android:targetSdkVersion= "15" /> |
10 |
<uses-permission
android:name= "android.permission.INTERNET" /> |
13 |
android:icon= "@drawable/ic_launcher" |
14 |
android:label= "@string/app_name" |
15 |
android:theme= "@style/AppTheme" > |
17 |
android:name= "com.doodle.asynctasksample.ThreadHandlerPostActivity" > |
19 |
<activity
android:name= "com.doodle.asynctasksample.AsyncTastActivity" > |
21 |
<activity
android:name= "com.doodle.asynctasksample.ThreadHandlerActivity" > |
24 |
android:name= "com.doodle.asynctasksample.BootActivity" |
25 |
android:label= "@string/title_activity_boot" > |
27 |
<action
android:name= "android.intent.action.MAIN" /> |
28 |
<category
android:name= "android.intent.category.LAUNCHER" /> |
list_item.xml
01 |
< RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
02 |
xmlns:tools = "http://schemas.android.com/tools" |
03 |
android:layout_width = "match_parent" |
04 |
android:layout_height = "match_parent" > |
07 |
android:layout_width = "match_parent" |
08 |
android:layout_height = "150dp" |
09 |
android:layout_alignParentLeft = "true" |
10 |
android:layout_alignParentRight = "true" |
11 |
android:layout_alignParentTop = "true" > |
14 |
android:id = "@+id/imageView" |
15 |
android:layout_width = "match_parent" |
16 |
android:layout_height = "match_parent" |
17 |
android:src = "<a
href=" http://my.oschina.net/asia" target = "_blank" rel = "nofollow" >@android</ a >
:drawable/alert_dark_frame" /> |
ImageAdapter.java
02 |
*
Create a customized data structure for each item of ListView. |
03 |
*
An ImageAdapter inherited from BaseAdapter must overwrites |
04 |
*
getView method to show every image in specified style.In this |
05 |
*
instance only a ImageView will put and fill in each item of |
08 |
*
@author Jie.Geng Aug 01, 2012. |
11 |
public class ImageAdapter extends BaseAdapter
{ |
12 |
private Context
context; |
13 |
private List<HashMap<String,
Object>> listItems; |
14 |
private LayoutInflater
listContainer; |
16 |
public ImageView
imageView; |
18 |
public ImageAdapter(Context
context, List<HashMap<String, Object>> listItems) { |
20 |
this .context
= context; |
21 |
this .listContainer
= LayoutInflater.from(context); |
22 |
this .listItems
= listItems; |
26 |
public int getCount()
{ |
27 |
return listItems.size(); |
31 |
public Object
getItem( int position)
{ |
36 |
public long getItemId( int position)
{ |
41 |
public View
getView( int position,
View convertView, ViewGroup parent) { |
42 |
if (convertView
== null )
{ |
43 |
convertView
= listContainer.inflate(R.layout.list_item, null ); |
44 |
imageView
= (ImageView) convertView.findViewById(R.id.imageView); |
45 |
convertView.setTag(imageView); |
47 |
imageView
= (ImageView) convertView.getTag(); |
49 |
imageView.setImageDrawable((Drawable)
listItems.get(position).get( "ItemImage" )); |
一、採用AsyncTask
AsyncTask簡介 AsyncTask的特點是任務在主線程之外運行,而回調方法是在主線程中執行,這就有效地避免了使用Handler帶來的麻煩。閱讀 AsyncTask的源碼可知,AsyncTask是使用java.util.concurrent 框架來管理線程以及任務的執行的,concurrent框架是一個非常 成熟,高效的框架,經過了嚴格的測試。這說明AsyncTask的設計很好的解決了匿名線程存在的問題。 AsyncTask是抽象類,其結構圖如下圖所示: AsyncTask定義了三種泛型類型 Params,Progress和Result。
Params 啓動任務執行的輸入參數,比如HTTP請求的URL。 Progress 後臺任務執行的百分比。 Result 後臺執行任務最終返回的結果,比如String。 子類必須實現抽象方法doInBackground(Params… p) ,在此方法中實現任務的執行工作,比如連接網絡獲取數據等。通常還應 該實現onPostExecute(Result r)方法,因爲應用程序關心的結果在此方法中返回。需要注意的是AsyncTask一定要在主線程中創 建實例。 AsyncTask的執行分爲四個步驟,每一步都對應一個回調方法,需要注意的是這些方法不應該由應用程序調用,開發者需要做的
就是實現這些方法。在任務的執行過程中,這些方法被自動調用,運行過程,如下圖所示: onPreExecute() 當任務執行之前開始調用此方法,可以在這裏顯示進度對話框。 doInBackground(Params…) 此方法在後臺線程執行,完成任務的主要工作,通常需要較長的時間。在執行過程中可以調用 publicProgress(Progress…)來更新任務的進度。 onProgressUpdate(Progress…) 此方法在主線程執行,用於顯示任務執行的進度。 onPostExecute(Result)
此方法在主線程執行,任務執行的結果作爲此方法的參數返回
There are a few threading rules that must be followed for this class to work properly: The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN. The task instance must be created on the UI thread. execute(Params...) must
be invoked on the UI thread. Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) manually. The task can be executed only once (an exception will be thrown if a second execution is attempted.)
AsyncTastActivity.java
01 |
public class AsyncTastActivity extends Activity
{ |
03 |
private List<String>
urlList; |
04 |
private ImageAdapter
listItemAdapter; |
05 |
private ArrayList<HashMap<String,
Object>> listItem; |
08 |
public void onCreate(Bundle
savedInstanceState) { |
09 |
super .onCreate(savedInstanceState); |
10 |
setContentView(R.layout.activity_main); |
11 |
urlList
= new ArrayList<String>(); |
12 |
urlList.add( "http://www.baidu.com/img/baidu_sylogo1.gif" ); |
13 |
urlList.add( "http://y2.ifengimg.com/2012/06/24/23063562.gif" ); |
14 |
urlList.add( "http://himg2.huanqiu.com/statics/images/index/logo.png" ); |
16 |
listItem
= new ArrayList<HashMap<String,
Object>>(); |
18 |
listItemAdapter
= new ImageAdapter( this ,
listItem); |
19 |
ListView
listView = (ListView) this .findViewById(R.id.listView1); |
20 |
listView.setAdapter(listItemAdapter); |
22 |
AsyncTask<List<String>,
Integer, Hashtable<String, SoftReference<Drawable>>> task = new AsyncTask<List<String>,
Integer, Hashtable<String, SoftReference<Drawable>>>() { |
25 |
protected void onPreExecute()
{ |
30 |
protected Hashtable<String,
SoftReference<Drawable>> doInBackground( |
31 |
List<String>...
params) { |
32 |
Hashtable<String,
SoftReference<Drawable>> table = new Hashtable<String,
SoftReference<Drawable>>(); |
33 |
List<String>
imageUriList = params[ 0 ]; |
34 |
for (String
urlStr : imageUriList) { |
36 |
URL
url = new URL(urlStr); |
37 |
Drawable
drawable = Drawable.createFromStream( |
38 |
url.openStream(), "src" ); |
39 |
table.put(urlStr, new SoftReference<Drawable>(drawable)); |
40 |
} catch (Exception
e) { |
48 |
protected void onPostExecute( |
49 |
Hashtable<String,
SoftReference<Drawable>> result) { |
50 |
super .onPostExecute(result); |
51 |
Collection<SoftReference<Drawable>>
col = result.values(); |
52 |
for (SoftReference<Drawable>
ref : col) { |
53 |
HashMap<String,
Object> map = new HashMap<String,
Object>(); |
54 |
map.put( "ItemImage" ,
ref.get()); |
57 |
listItemAdapter.notifyDataSetChanged(); |
62 |
task.execute(urlList); |
66 |
public boolean onCreateOptionsMenu(Menu
menu) { |
67 |
getMenuInflater().inflate(R.menu.activity_main,
menu); |
二、採用Thread + Handler + Message
Handler簡介 Handler爲Android提供了一種異步消息處理機制,它包含兩個隊列,一個是線程列隊,另一個是消息列隊。使用post方法將線 程對象添加到線程隊列中,使用sendMessage(Message message)將消息放入消息隊列中。當向消息隊列中發送消息後就立 即返回,而從消息隊列中讀取消息對象時會阻塞,繼而回調Handler中public void handleMessage(Message msg)方法。因此 在創建Handler時應該使用匿名內部類重寫該方法。如果想要這個流程一直執行的話,可以再run方法內部執行postDelay或者
post方法,再將該線程對象添加到消息隊列中重複執行。想要停止線程,調用Handler對象的removeCallbacks(Runnable r)從 線程隊列中移除線程對象,使線程停止執行。
ThreadHandlerActivity.java
01 |
public class ThreadHandlerActivity extends Activity
{ |
03 |
private List<String>
urlList; |
04 |
private ImageAdapter
listItemAdapter; |
05 |
private LinkedList<HashMap<String,
Object>> listItem; |
06 |
private Handler
handler; |
07 |
private ExecutorService
executorService = Executors.newFixedThreadPool( 10 ); |
10 |
public void onCreate(Bundle
savedInstanceState) { |
11 |
super .onCreate(savedInstanceState); |
12 |
setContentView(R.layout.activity_main); |
13 |
urlList
= new ArrayList<String>(); |
14 |
urlList.add( "http://www.baidu.com/img/baidu_sylogo1.gif" ); |
15 |
urlList.add( "http://y2.ifengimg.com/2012/06/24/23063562.gif" ); |
16 |
urlList.add( "http://himg2.huanqiu.com/statics/images/index/logo.png" ); |
18 |
listItem
= new LinkedList<HashMap<String,
Object>>(); |
20 |
listItemAdapter
= new ImageAdapter( this ,
listItem); |
21 |
ListView
listView = (ListView) this .findViewById(R.id.listView1); |
22 |
listView.setAdapter(listItemAdapter); |
24 |
handler
= new Handler(){ |
26 |
public void handleMessage(Message
msg) { |
27 |
HashMap<String,
Object> map = (HashMap<String, Object>) msg.obj; |
29 |
listItemAdapter.notifyDataSetChanged(); |
32 |
for ( final String
urlStr : urlList) { |
33 |
executorService.submit( new Runnable()
{ |
37 |
URL
url = new URL(urlStr); |
38 |
Drawable
drawable = Drawable.createFromStream( |
39 |
url.openStream(), "src" ); |
40 |
HashMap<String,
Object> table = new HashMap<String,
Object>(); |
41 |
table.put( "ItemImage" ,
drawable); |
42 |
Message
msg = new Message(); |
44 |
msg.setTarget(handler); |
45 |
handler.sendMessage(msg); |
46 |
} catch (Exception
e) { |
55 |
public boolean onCreateOptionsMenu(Menu
menu) { |
56 |
getMenuInflater().inflate(R.menu.activity_main,
menu); |
三、採用Thread
+ Handler + post方法
使用post方法將Runnable對象放到Handler的線程隊列中,該Runnable的執行其實並未單獨開啓線程,而是仍然在當前Activity的UI線程中執行,Handler只是調用了Runnable對象的run方法。
ThreadHandlerPostActivity.java
01 |
public class ThreadHandlerPostActivity extends Activity
{ |
03 |
private List<String>
urlList; |
04 |
private ImageAdapter
listItemAdapter; |
05 |
private LinkedList<HashMap<String,
Object>> listItem; |
06 |
private Handler
handler = new Handler(); |
07 |
private ExecutorService
executorService = Executors.newFixedThreadPool( 10 ); |
10 |
public void onCreate(Bundle
savedInstanceState) { |
11 |
super .onCreate(savedInstanceState); |
12 |
setContentView(R.layout.activity_main); |
13 |
urlList
= new ArrayList<String>(); |
14 |
urlList.add( "http://www.baidu.com/img/baidu_sylogo1.gif" ); |
15 |
urlList.add( "http://y2.ifengimg.com/2012/06/24/23063562.gif" ); |
16 |
urlList.add( "http://himg2.huanqiu.com/statics/images/index/logo.png" ); |
18 |
listItem
= new LinkedList<HashMap<String,
Object>>(); |
20 |
listItemAdapter
= new ImageAdapter( this ,
listItem); |
21 |
ListView
listView = (ListView) this .findViewById(R.id.listView1); |
22 |
listView.setAdapter(listItemAdapter); |
24 |
for ( final String
urlStr : urlList) { |
25 |
executorService.submit( new Runnable()
{ |
29 |
URL
url = new URL(urlStr); |
30 |
Drawable
drawable = Drawable.createFromStream( |
31 |
url.openStream(), "src" ); |
32 |
final HashMap<String,
Object> table = new HashMap<String,
Object>(); |
33 |
table.put( "ItemImage" ,
drawable); |
34 |
handler.post( new Runnable(){ |
39 |
listItemAdapter.notifyDataSetChanged(); |
43 |
} catch (Exception
e) { |
52 |
public boolean onCreateOptionsMenu(Menu
menu) { |
53 |
getMenuInflater().inflate(R.menu.activity_main,
menu); |
綜上所述,我們可以看出,Android API中AsyncTask對於異步處理不是萬能的,對於需要循環、多次的任務處理,我們任然需要採用傳統的Thread線程機制。我們可以根據需要,靈活取捨。