1. 程式人生 > >android-新聞客戶端-離線下載的簡單實現(圖片部分)

android-新聞客戶端-離線下載的簡單實現(圖片部分)

轉載請註明:來自Xuye_(http://blog.csdn.net/x1876631/article/details/44202471)的專欄

1、寫在前面:

做android應用開發也有段時間了,也做了幾個專案。之前一直在看各個前輩大牛的部落格,心裡總是有些感動。正是因為有許許多多這樣樂於分享知識的人,大家才能共同進步。我也受益於此從一個小菜鳥在自學中茁壯成長起來。現在我也有了些經驗,希望自己的一些拙見能幫助到需要它的人。

2、成文原因:

最近一直在做android的新聞類客戶端,其中需要完成個【離線下載】的功能。這種功能算是新聞客戶端的標配,在網上找了很久也沒有特別切合的實現demo。

    現在自己實現了,就把【圖片離線下載,顯示下載進度,無網路時也可檢視】這個功能寫成demo分享給大家。

市面上幾個主流的新聞客戶端的離線下載基本都是這個demo實現的形式,具體實現可以看這個評測:

3、實際效果展示(直接上圖)

     效果簡單總結:

下載圖片儲存到本地,實時顯示下載內容和進度,結束後無網路時能檢視圖片。下載過程中可以點選按鈕取消下載,退出應用時會自動取消下載。

3.1、先斷開網路,開啟應用listview圖片列表,可以看到載入都失敗了:

3.2、在離線下載頁點選【離線下載按鈕】,出現下載提示notification,同時按鈕UI文字變為"取消圖片離線下載":


3.3、下拉通知欄,顯示下載進度通知notification:


3.4、下載完畢結束service,通知消失。此時斷網去檢視圖片列表頁,發現圖片在無網的情況下也都顯示出來了(極少部分超時或者過大圖片下載失敗了):



3.5、以上基本展示了圖片離線下載的過程和效果。容錯處理:如果沒有網路時會直接結束下載,並提示錯誤:


4、程式碼相關:

4.1、圖片離線下載主要用到的幾個類:

UILApplication.java:應用全域性類,在這裡設定及初始化了Universal-image-loader載入框架、含有啟動和停止下載的函式

ImageListActivity.java:圖片列表顯示類,這裡呼叫了UIL框架載入圖片

OfflineDownloadActivity.java:圖片離線下載頁,點選按鈕啟動離線下載service,再點選則取消下載

OfflineDownloadService.java:圖片離線下載後臺服務service,這個service裡含有下載和停止下載的邏輯

具體如圖:


ImageListActivity.java和OfflineDownloadActivity.java沒什麼好說的,一個listview,一個只有個按鈕。
UILApplication.java裡的universal-image-loader載入框架的配置和使用請參考一下csdn的博文,寫的很清楚詳細了:

4.2、圖片離線下載流程:

在主頁面(HomeActivity.java)點選離線下載按鈕
——>跳轉到離線下載頁(OfflineDownloadActivity.java)
——>在離線下載頁點選下載按鈕,呼叫application類的startOfflineDownloadService()方法
——>啟動離線下載service(OfflineDownloadService.java)
——>執行service的onStartCommand()方法,其又執行下載執行緒imageDownload()方法
——>下載函式中迴圈執行圖片下載方法imageLoader.loadImage()
——>執行loadImage()中圖片下載成功或者失敗的回撥函式onLoadingFailed()或onLoadingComplete()
——>在這2個方法裡執行imagedownloadResult()方法,判斷進度(有以下1、2、3可能)
1——>如果可以更新進度,則執行sendMsg(更新)去更新進度(執行handler的update項)
2——>如果下載完成,則執行sendMsg(完成)去結束下載service(執行handler的finish項)
3——>如果下載錯誤,則執行sendMsg(錯誤)去結束下載service(執行handler的error項)
補充:

1、service傳遞資訊給activity(用廣播),參考:

2、關於通知notification的學習和注意事項,參考:

Notification分析——你可能遇到的各種問題
3、關於檔案後臺service下載的demo和講解,參考:

5、注意事項: 5.1、下載activity/後臺service/結束通知廣播broadcastReceiver的註冊:
<!-- 離線圖片下載頁 -->
<activity android:name="main.OfflineDownloadActivity" />
         <!-- 離線圖片下載service-->        
        <service android:name="main.OfflineDownloadService">                                                     <intent-filter>
		<!-- 為該Service元件的intent-filter配置action -->
   <action android:name="com.nostra13.example.universalimageloader.action.OFFLINE_DOWNLOAD_SERVICE" />
	   </intent-filter>		
        </service>
	<!-- 離線下載完成廣播 -->
        <receiver android:name="main.OfflineDownload$OfflineDownloadReceiver">                                    <intent-filter>
 <action android:name="com.nostra13.example.universalimageloader.action.OFFLINE_DOWNLOAD_BROADCAST" />
	    </intent-filter>		
         </receiver>
這裡切記service和broadcastReceiver的啟動匹配符action要唯一,加上包名是比較好的辦法。 否則如果多個應用(A/B/C)使用的是統一action啟動符,A/B/C又同時在手機上,此時A啟動service時可能會啟動B/C的service。 不要問我為什麼知道快哭了,犯懶copy就是這麼悲劇。 5.2、連線網路、網路狀態的判斷、sd卡讀寫的許可權:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
5.3、取消下載,停止service中的執行緒的方法:
/**
	 * 停止執行緒執行標識 
	 * 取消下載時,設定執行緒暫停執行標識變為true
	 * 除了stopServiceFlag,其他的設定是由於離線下載按鈕頁取消呼叫此方法
	 */
	public static void setStop() {
		stopServiceFlag = true;
		count=0;
		oldImageCount = 0;
		manager.cancel(flag);
		
		imageLoader.stop();//取消下載時停止圖片的下載
	}
呼叫service裡的setStop(),將其中的stopServiceFlag欄位設為true,代表執行緒已被人為停止。在每次下載前都會對stopServiceFlag進行檢查, 如果為true,則直接跳出迴圈,取消下載,結束service。 5.4、限制更新進度通知notification的次數:
/**
	 * 圖片下載結果處理,計算進度,更新notification
	 * 
	 * @param notification	下載進度通知notification	
	 * @param imageNumber	正在下載的圖片下標號
	 */
	private void imagedownloadResult(Notification notification,int imageNumber){
		//下載成功或者失敗都增加進度
		count += 1.0 / downloadimages.length;
		isImageDownloadFinish[imageNumber] = true;
		//判斷進度差值,更新notification
		if(count-oldImageCount>IMAGE_DIFFERENCE_COUNT_VALUE){
			oldImageCount = count;
			sendMsg(UPDATE_NOTIFICATION, count, notification,flag, "url:"+downloadimages[imageNumber]);
		}		
		Log.e(TAG, "count--->" + count+" , imageNumber--->"+imageNumber+" is download finish!");
		if(count>=1||isDownloadFinish()){
			//如果進度到達100%或者所有圖片都下載完了,結束下載
			sendMsg(DOWNLOAD_FINISH, 1, notification, flag,"下載完成");
		}
	}
imagedownloadResult()是下載圖片回撥函式中的結果處理邏輯。 無論下載成功還是失敗都讓進度增加(失敗可能是連線超時或者圖片過大導致的), 然後去更新進度。但是這裡更新進度的次數要做限制,如果更新過快會使介面操作時非常卡!(我的都宕機了...)  這裡使用新老進度差值是否大於臨界值(count-oldImageCount>IMAGE_DIFFERENCE_COUNT_VALUE)來限制更新。 5.5、下載完成的判斷 還是上面那個函式,每次下載圖片的回撥都會使進度count增加,本來只對進度進行判斷就可以了, 但是在實際操作中發現進度在結束時會稍大於或稍小於100%,所以增加了圖片下載完成標識組isImageDownloadFinish[], 每次下載完一個圖片(無論成功或失敗)只要有結果就設定下載完成標識為true。但所有標識都完成了以後就結束下載。 之前想用下載到最後一個,對i判斷作為完成標識,總是不對。後來列印了一下下載情況,就知道怎麼回事了,如圖:
可以看到UIL自己使用了多執行緒非同步下載,後面的圖片可能會先下完,所以還是要判斷count。 5.6、離線下載按鈕UI文字的顯示和改變 正常來說,要實現的效果是:點選下載,再點選取消;下載完成或者錯誤,文字從取消變為下載。 我們一般不會在下載時一直停在下載頁,所以每次都要對下載狀態做判斷,顯示文字。 辦法是使用shareRreference儲存一個是否正在下載的本地標誌。這裡使用的標誌名為:isOfflineDownload。 每次開始下載時設定為true。取消、結束、出錯時設定為false。如程式碼所示:
/**
	 * 開始離線圖片下載
	 */
	public static final  void startOfflineDownloadService(){
		Intent intent = new Intent(Constants.OfflineDownloadServiceAction);
		getApp().startService(intent);
		SP.edit().putBoolean("isOfflineDownload", true).commit();
		Log.e(TAG, "startOfflineDownloadService--->ok");
	}
	
	/**
	 * 取消離線離線下載
	 */
	public static final  void stopOfflineDownloadService(){
		Intent intent = new Intent(Constants.OfflineDownloadServiceAction);
		OfflineDownloadService.setStop();
		getApp().stopService(intent);
		//設定下載完成標識
		SP.edit().putBoolean("isOfflineDownload", false).commit();
		Log.e(TAG, "stopOfflineDownloadService--->ok");
	}
	
而每次進入下載也是根據此標識來設定按鈕顯示文字的,如程式碼所示:
/**
	 * 初始化控制元件
	 */
	private void initView(){
		downloadText = (TextView) findViewById(R.id.downLoadText);
		downlload = (LinearLayout) findViewById(R.id.downLoad);
		downlload.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				//如果開始了離線下載,點選後取消離線下載
				if(SP.getBoolean("isOfflineDownload", false)){
					Log.e(tag, "offline_download--->cancel");
					//取消離線下載
					UILApplication.stopOfflineDownloadService();
					downloadText.setText(getResources().getString(R.string.download));
					SP.edit().putBoolean("isOfflineDownload", false).commit();				
				}else {
					Log.e(tag, "offline_download--->begin");
					//如果沒有開始,啟動下載service
					UILApplication.startOfflineDownloadService();
					//記錄已經開始離線下載
					SP.edit().putBoolean("isOfflineDownload", true).commit();
					//顯示可以取消離線下載
					downloadText.setText(getResources().getString(R.string.download_cancel));
				}
			}
		});
	}
下載結束時service會發送廣播通知下載頁更新UI,這裡在service裡要發廣播,在下載頁要註冊廣播接受者類。如下:
/**
	 * 離線下載完成後傳送廣播,家頁離線下載欄文字由"取消離線下載"變為"離線下載"
	 * @author Xuye
	 *
	 */
	public static class OfflineDownloadReceiver extends BroadcastReceiver{
		@Override
		public void onReceive(Context context, Intent intent) {
			// TODO Auto-generated method stub
			downloadText.setText("離線圖片下載");
		}	
	}
/** 離線下載完成,傳送廣播,更改離線下載按鈕的UI文字 */
	private void sendFinishBroadcastReceiver() {
		Intent intent = new Intent();
		intent.setAction(Constants.OfflineDownloadBroadcastReceiverAction);
		sendBroadcast(intent);
	}
切記傳送廣播時使用的action字串要和manifest.xml裡設定的一樣才有效,且內部類形式的廣播在manifest.xml的設定要用: 外部類名$內部類名,廣播才會生效。如下:
<!-- 離線下載完成廣播 -->
<receiver android:name="main.OfflineDownload$OfflineDownloadReceiver">
	<intent-filter>
<action android:name="com.nostra13.example.universalimageloader.action.OFFLINE_DOWNLOAD_BROADCAST" />
	</intent-filter>		
</receiver>
6、寫在最後:
以上就是離線下載圖片功能簡單實現的講解,其實想寫一個完整的離線下載,但是文字部分會暴露公司介面,所以就沒有demo了。 另外demo還有很多不完善。比如單執行緒下載速度慢,連線超時無處理等問題。 這次的文章希望能起到一個拋磚引玉的作用,以後會繼續完善,歡迎拍磚。 當然,必須得有demo,下載地址: