转载请注明:来自Xuye_(http://blog.youkuaiyun.com/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的博文,写的很清楚详细了:
1、Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用
2、Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
在主页面(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(用广播),参考:
Service 通知Activity更新界面的方法研究|Service通过Broadcast更新UI
2、关于通知notification的学习和注意事项,参考:
Notification分析——你可能遇到的各种问题
3、关于文件后台service下载的demo和讲解,参考:
后台Service下载 (一)
实现service后台下载notification进度条(有代码)
<!-- 离线图片下载页 -->
<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要唯一,加上包名是比较好的办法。

<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进行检查,
/**
* 图片下载结果处理,计算进度,更新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()是下载图片回调函数中的结果处理逻辑。
/**
* 开始离线图片下载
*/
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>