android-新闻客户端-离线下载的简单实现(图片部分)

来源:互联网 发布:机顶盒看电影的软件 编辑:程序博客网 时间:2024/04/27 20:48

转载请注明:来自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的博文,写的很清楚详细了:
1、Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用
2、Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)


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(用广播),参考:

Service 通知Activity更新界面的方法研究|Service通过Broadcast更新UI

2、关于通知notification的学习和注意事项,参考:

Notification分析——你可能遇到的各种问题
3、关于文件后台service下载的demo和讲解,参考:

后台Service下载 (一)
实现service后台下载notification进度条(有代码)


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;//判断进度差值,更新notificationif(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");//如果没有开始,启动下载serviceUILApplication.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{@Overridepublic void onReceive(Context context, Intent intent) {// TODO Auto-generated method stubdownloadText.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,下载地址:
1、免费版 (没钱的捧个人场) 
2、收费版(2csdn币) 有钱的捧个钱场



2 0