Android 非同步載入解決方案
阿新 • • 發佈:2019-01-16
from http://www.open-open.com/lib/view/open1345017746897.html
Android的Lazy Load主要體現在網路資料(圖片)非同步載入、資料庫查詢、複雜業務邏輯處理以及費時任務操作導致的非同步處理等方面。在介紹Android開發過程中,非同步處理這個常見的技術問題之前,我們簡單回顧下Android開發過程中需要注意的幾個地方。
Android應用開發過程中必須遵循單執行緒模型(Single Thread Model)的原則。因為Android的UI操作並不是執行緒安全的,所以涉及UI的操作必須在UI執行緒中完成。但是並非所有的操作都能在主執行緒中進行,Google工程師在設計上約定,Android
本文以自定義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"
> |
05 |
06 |
<uses-sdk |
07 |
android:minSdkVersion= "8" |
08 |
android:targetSdkVersion= "15"
/> |
09 |
10 |
<uses-permission android:name= "android.permission.INTERNET"
/> |
11 |
12 |
<application |
13 |
android:icon= "@drawable/ic_launcher" |
14 |
android:label= "@string/app_name" |
15 |
android:theme= "@style/AppTheme"
> |
16 |
<activity |
17 |
android:name= "com.doodle.asynctasksample.ThreadHandlerPostActivity"
> |
18 |
</activity> |
19 |
<activity android:name= "com.doodle.asynctasksample.AsyncTastActivity"
> |
20 |
</activity> |
21 |
<activity android:name= "com.doodle.asynctasksample.ThreadHandlerActivity"
> |
22 |
</activity> |
23 |
<activity |
24 |
android:name= "com.doodle.asynctasksample.BootActivity" |
25 |
android:label= "@string/title_activity_boot"
> |
26 |
<intent-filter> |
27 |
<action android:name= "android.intent.action.MAIN"
/> |
28 |
<category android:name= "android.intent.category.LAUNCHER"
/> |
29 |
</intent-filter> |
30 |
</activity> |
31 |
</application> |
32 |
33 |
</manifest> |
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"
> |
05 |
06 |
< LinearLayout |
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"
> |
12 |
13 |
< ImageView |
14 |
android:id = "@+id/imageView" |
15 |
android:layout_width = "match_parent" |
16 |
android:layout_height = "match_parent" |
17 |
android:src = "@android :drawable/alert_dark_frame"
/> |
18 |
19 |
</ LinearLayout > |
20 |
21 |
</ RelativeLayout > |
01 |
/** |
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 |
06 |
* ListView. |
07 |
*
|
08 |
* @author Jie.Geng Aug 01, 2012. |
09 |
* |
10 |
*/ |
11 |
public class
ImageAdapter extends
BaseAdapter { |
12 |
private
Context context; |
13 |
private
List<HashMap<String, Object>> listItems; |
14 |
private
LayoutInflater listContainer; |
15 |
16 |
public
ImageView imageView; |
17 |
18 |
public
ImageAdapter(Context context, List<HashMap<String, Object>> listItems) { |
19 |
super (); |
20 |
this .context = context; |
21 |
this .listContainer = LayoutInflater.from(context); |
22 |
this .listItems = listItems; |
23 |
} |
24 |
25 |
@Override |
26 |
public
int getCount() { |
27 |
return
listItems.size(); |
28 |
} |
29 |
30 |
@Override |
31 |
public
Object getItem( int
position) { |
32 |
return
null ; |
33 |
} |
34 |
35 |
@Override |
36 |
public
long getItemId( int
position) { |
37 |
return
0 ; |
38 |
} |
39 |
40 |
@Override |
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); |
46 |
}
else { |
47 |
imageView = (ImageView) convertView.getTag(); |
48 |
} |
49 |
imageView.setImageDrawable((Drawable) listItems.get(position).get( "ItemImage" )); |
50 |
return
convertView; |
51 |
} |
一、採用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 { |
02 |
03 |
private
List<String> urlList; |
04 |
private
ImageAdapter listItemAdapter; |
05 |
private
ArrayList<HashMap<String, Object>> listItem; |
06 |
07 |
@Override |
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" ); |
15 |
16 |
listItem =
new ArrayList<HashMap<String, Object>>(); |
17 |
18 |
listItemAdapter =
new ImageAdapter( this , listItem); |
19 |
ListView listView = (ListView)
this .findViewById(R.id.listView1); |
20 |
listView.setAdapter(listItemAdapter); |
21 |
22 |
AsyncTask<List<String>, Integer, Hashtable<String, SoftReference<Drawable>>> task =
new AsyncTask<List<String>, Integer, Hashtable<String, SoftReference<Drawable>>>() { |
23 |
24 |
@Override |
25 |
protected
void onPreExecute() { |
26 |
super .onPreExecute(); |
27 |
} |
28 |
29 |
@Override |
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) { |
35 |
try
{ |
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) { |
41 |
e.printStackTrace(); |
42 |
} |
43 |
} |
44 |
return
table; |
45 |
} |
46 |
47 |
@Override |
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()); |
55 |
listItem.add(map); |
56 |
} |
57 |
listItemAdapter.notifyDataSetChanged(); |
58 |
59 |
} |
60 |
}; |
61 |
62 |
task.execute(urlList); |
63 |
} |
64 |
65 |
@Override |
66 |
public
boolean onCreateOptionsMenu(Menu menu) { |
67 |
getMenuInflater().inflate(R.menu.activity_main, menu); |
68 |
return
true ; |
69 |
} |
70 |
} |
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 { |
02 |
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 ); |
08 |
09 |
@Override |
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" ); |
17 |
18 |
listItem =
new LinkedList<HashMap<String, Object>>(); |
19 |
20 |
listItemAdapter =
new ImageAdapter( this , listItem); |
21 |
ListView listView = (ListView)
this .findViewById(R.id.listView1); |
22 |
listView.setAdapter(listItemAdapter); |
23 |
24 |
handler =
new Handler(){ |
25 |
@Override |
26 |
public
void handleMessage(Message msg) { |
27 |
HashMap<String, Object> map = (HashMap<String, Object>) msg.obj; |
28 |
listItem.add(map); |
29 |
listItemAdapter.notifyDataSetChanged(); |
30 |
} |
31 |
}; |
32 |
for
( final
String urlStr : urlList) { |
33 |
executorService.submit( new
Runnable() { |
34 |
@Override |
35 |
public
void run() { |
36 |
try
{ |
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(); |
43 |
msg.obj = table; |
44 |
msg.setTarget(handler); |
45 |
handler.sendMessage(msg); |
46 |
}
catch (Exception e) { |
47 |
e.printStackTrace(); |
48 |
} |
49 |
} |
50 |
}); |
51 |
} |
52 |
} |
53 |
54 |
@Override |
55 |
public
boolean onCreateOptionsMenu(Menu menu) { |
56 |
getMenuInflater().inflate(R.menu.activity_main, menu); |
57 |
return
true ; |
58 |
} |
59 |
} |
使用post方法將Runnable物件放到Handler的執行緒佇列中,該Runnable的執行其實並未單獨開啟執行緒,而是仍然在當前Activity的UI執行緒中執行,Handler只是呼叫了Runnable物件的run方法。ThreadHandlerPostActivity.java
01 |
public class
ThreadHandlerPostActivity extends
Activity { |
02 |
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 ); |
08 |
09 |
@Override |
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" ); |
17 |
18 |
listItem =
new LinkedList<HashMap<String, Object>>(); |
19 |
20 |
listItemAdapter =
new ImageAdapter( this , listItem); |
21 |
ListView listView = (ListView)
this .findViewById(R.id.listView1); |
22 |
listView.setAdapter(listItemAdapter); |
23 |
24 |
for
( final
String urlStr : urlList) { |
25 |
executorService.submit( new
Runnable() { |
26 |
@Override |
27 |
public
void run() { |
28 |
try
{ |
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(){ |
35 |
36 |
@Override |
37 |
public
void run() { |
38 |
listItem.add(table); |
39 |
listItemAdapter.notifyDataSetChanged(); |
40 |
|