当前位置: 首页 > news >正文

Android学习系列(19)--App离线下载

宜未雨而绸缪,毋临渴而掘井。----朱用纯《治家格言》
      离线下载,在有网络的情况下下载服务器数据,以便无网络时也能阅读,就是离线阅读。
      离线下载的功能点如下:
      1.下载管理(开始、取消下载)。
      2.网络判断(Wi-Fi,3G)。
      3.独立进程。
      4.定时和手机催醒。
      5.自启动。
1.下载管理
       这里不便关注下载的细节方法,网络下载的方法很多,大概如下:
  1.     /**
  2. * 下载文件
  3. * @param url 下载地址
  4. * @param dest 下载存放的本地文件
  5. * @param append 断点续传
  6. * @return
  7. * @throws Exception 
  8. */
  9. public long download(String url, File dest, boolean append) throws Exception{
  10.        //初始化变量
  11.        //准备工作
  12.        // ... ...
  13.        try {
  14.                // ... ...
  15.                while((readSize = is.read(buffer)) > 0){
  16.                    //网络判断
  17.                    
  18.                    os.write(buffer, 0, readSize);
  19.                    os.flush();
  20.                    
  21.                    //如果需要停止下载,如取消,跳出当前下载
  22.                }
  23.            }
  24.        } finally {
  25.            // ... ...
  26.        }
  27.            // ... ...
  28. }
复制代码
这里要注意几点:
      (1).在下载的时候,我们希望能及时检测到网络状况,比如由Wi-Fi切换到3G网络下,我们应该能及时停止下载。
      (2).当用户选择取消下载的时候,我们也能停止当前下载。
2.网络判断
      获取当前网络状态,主要分为Wi-Fi和Mobile(包括3G,GPRS)两种,我们写一个工具类如下:
  1. public class NetworkUtils {
  2.     public final static int NONE = 0;//无网络
  3.     public final static int WIFI = 1;//Wi-Fi
  4.     public final static int MOBILE = 2;//3G,GPRS
  5.     
  6.     /**
  7.      * 获取当前网络状态
  8.      * @param context
  9.      * @return
  10.      */
  11.         public static int getNetworkState(Context context){
  12.         ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  13.         
  14.         //手机网络判断
  15.         State state = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
  16.         if(state == State.CONNECTED||state == State.CONNECTING){
  17.             return MOBILE;
  18.         }
  19.         //Wifi网络判断
  20.         state = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
  21.         if(state == State.CONNECTED||state == State.CONNECTING){
  22.             return WIFI;
  23.         }
  24.         return NONE;
  25.     }
  26. }
复制代码

根据网络状态,我们能够控制下载方式:
      (1).下载量很大的情况下,我们不大可能在3G情况下进行下载,容易引起用户的反感和担忧。
      (2).当客户十分确认可以在3G情况下进行下载,那么也是允许的。
      所以,这里提出一个需求,我们要为下载方式设置一个灵活的等级,结合离线下载的特点,我们给出3中方案由用户选择:
      (1).移动数据情况下自动下载
      (2).只允许Wi-Fi情况下自动下载
      (3).关闭下载
      这里只列出了自动下载,是因为如果不是自动下载,手动下载用户可以随意控制,无需设置,当然设计到丢流量情况下,如3G下手动下载,提示用户会消耗较大的数据流量,慎用即可。

  1. public class Constant {
  2.         //离线下载网络设置
  3.         public final static int OFFLINE_MOBILE = 0;
  4.         public final static int OFFLINE_WIFI = 1;
  5.         public final static int OFFLINE_OFF = 3;
  6. }
  7. public class Global {
  8.         //设置默认关闭状态,
  9.         //为了应用程序下次启动能够记住用户选择,在第一次启动应用的时候,这个值最终应该存放到数据库中,
  10.         public static int OfflineNetworkSetting = Constant.OFFLINE_OFF;
  11. }
复制代码
现在可以根据规则比较当前网络和离线网络设置,判定离线下载服务的开启。
3.独立进程
      离线下载,无论何时何地,只要适宜进行,则当进行,目前主流的做法是建立后台服务。
  1. public class OfflineSerivice extends Service {
  2.       // ... ...
  3. }
复制代码

   (1).OfflineService的进程如果默认和应用程序一致,则在应用进程kill的时候,会重启一次(网易新闻在离线下载的时候,退出应用,下载会停顿一小会儿就是这个原因),如果影响不大,这个方案也是可选的。
     (2).OfflineService的进程和应用程序分开,如应用程序进程为"cn.cnblogs.tianxia.download",则离线下载服务的进程设置为"cn.cnblogs.tianxia.download.offline",撇清和应用程序的进程的关系。当然,这个会带来一个新的问题,进程间通信,当然因为离线下载和应用程序间的模块比较独立,这个问题还算比较好规避。
     (3).OfflineService的进程如果默认和应用程序一致,但是OfflineService继承IntentService,可避免重启的问题,这个是《Pro Android 3》书中提到的方法,非常的好用,但是非常遗憾,本人最近才看到,暂时没有亲手测验,不敢在工作中试用。
     按理说,方案3是最佳方案, 但是个人原因,选择了方案2.

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2.       package="com.cnblogs.download">
  3.     <application android:icon="@drawable/icon" android:label="@string/app_name">        
  4.         <!--省略其他-->
  5.         <service android:name="cn.cnblogs.download.OfflineService" android:process="cn.cnblogs.download.offline"/>
  6.     </application>
  7. </manifest>
复制代码

4.定时下载和手机催醒
     根据用户设置,在wifi的情况下自动下载,但是自动下载的方案有很多种,频繁的更新下载,定点下载(早上8点,下午4点),间隔下载(每隔6小时)。
     这里,我们选择每隔6个小时下载。
     (1).这里介绍一种错误的方案。一看到每隔6小时,很容易想到开启一个子线程计时,累计到6个小时,子线程通知下载服务开始新一轮下载。这个方案的思路是没有错的,但是却忽略了手机处于休眠状态,这个子线程其实是停止执行的,那么所谓的6个小时的效果就又可能永远达不到,而且必然不正确或者不准确。
     (2).所以,需要使用到一种不休眠的办法:定时器和广播接收器。每隔6小时我们发送一个广播,广播接收器通知开始离线下载。(可参考newsrob源码和书籍《Pro Android 3》):

  1. public class OfflineSerivice extends Service {
  2.     
  3.     //上次成功下载的时间
  4.     private long lastDownloadTime;
  5.     // 省略代码... ...
  6.     public static void startAlarm(Context context){
  7.         AlarmManager alarmManager = (AlarmManager) context.getSystemService("alarm");
  8.         
  9.         //每隔6个小时发送广播到OfflineAlarmReceiver
  10.         //也可以设置为10分钟检测一下下载条件,而在OfflineAlarmRecrive中判断开始下载,避免6小时下载失败需再等待6小时过长时间的问题
  11.         Intent intent = new Intent(context,OfflineAlarmReceiver.class);  
  12.         PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, intent, 0);
  13.         alarmManager.cancel(pendingIntent);
  14.         alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(), 3600000*6, pendingIntent);
  15.     }
  16. }
复制代码

OfflineAlarmRecriver中处理开始下载条件,并通知开始下载:

  1. public class OfflineAlarmReceiver extends BroadcastReceiver {
  2.     @Override
  3.     public void onReceive(Context context, Intent arg1) {
  4.         
  5.         // 省略代码...,初始化变量,准备工作...
  6.         
  7.         if(System.currentTimeMillis()-OfflineService.lastDownloadTime>3600000*60&&其他条件){
  8.             //打开离线下载服务
  9.             Intent alarmIntent = new Intent(context, OfflineService.class);
  10.             context.startService(alarmIntent);
  11.         }
  12.     }
  13. }
复制代码

前面我们提到了线程休眠的问题,需要在下载的时候能够唤醒手机,下载完成后能回到休眠状态,下面是两个工具方法:

  1. public static PowerManager.WakeLock wakeLock;
  2. /**
  3. * 唤醒服务
  4. */
  5. public static void acquireWakeLock(Context context){
  6.     
  7.         if(wakeLock!=null){
  8.         return;
  9.     }
  10.     PowerManager powerManager = (PowerManager)(context.getSystemService(Context.POWER_SERVICE));
  11.     wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "com.cnblogs.download.OfflineService");
  12.     wakeLock.acquire();
  13. }
  14. /**
  15. * 释放唤醒服务,返回休眠状态
  16. */
  17. public static void releaseWakeLock(){
  18.     if(wakeLock!=null&&wakeLock.isHeld()){
  19.         wakeLock.release();
  20.         wakeLock = null;
  21.     }
  22. }
复制代码

其中PowerManager.PARTIAL_WAKE_LOCK意思是仅唤醒CPU方式,此时能自动主动检测网络状态,从而保证网络正常。
       需要在Mainifest.xml中设置权限:

  1. <uses-permission android:name="android.permission.WAKE_LOCK" />
复制代码

然后在下载服务的onStartConmmand()激活催醒状态,然后在下载完成后释放催醒状态:

  1. @Override
  2. public int onStartCommand(Intent intent, int flags, int startId) {
  3.     acquireWakeLock(OfflineService.this);
  4.     //省略代码... ...
  5.         return super.onStartCommand(intent, flags, startId);
  6. }
复制代码

5.自启动
      为了代码清晰,我们再定义一个自启动的receiver:

  1. /**
  2. * 自启动离线下载服务
  3. * @author user
  4. */
  5. public class OfflineReceiver extends BroadcastReceiver {
  6.     @Override
  7.     public void onReceive(Context context, Intent arg1) {
  8.         //启动定时器
  9.         OfflineService.startAlarm(context);
  10.     }
  11. }
复制代码

在AndroidManifest.xml注册此接收器,如下:

  1. <receiver android:name="cn.cnblogs.download.OfflineReceiver">
  2.             <intent-filter>
  3.                 <!--自启动-->
  4.                 <action android:name="android.intent.action.BOOT_COMPLETED" /> 
  5.                 <category android:name="android.intent.category.HOME" /> 
  6.             </intent-filter>
  7. </receiver>
复制代码
这样,在启动的时候,能够接受启动广播,并执行启动定时器操作。
6.小结
      为了简洁明晰,开门见山,本文仅针对离线下载的最重要的关联点列举说明,而对于清理策略,手动和自动模式,界面跳转,UI设计和业务要求没有过多的涉及,但是往往这些东西才是花费你大量的时间,需要大量细节的积累和耐心的调试,我们唯一要做的事情就是不断的完善!

 

转载于:https://www.cnblogs.com/xiaochao1234/p/3625226.html

相关文章:

  • oracle12c管理作业资源的一种方式
  • uva 11121(-2进制)
  • IDEA编码编译不通过
  • ❲很有料❳系统负载能力浅析
  • mysql更新一个表中的某个字段值等于另一个表的某个字段值
  • a++与 ++a
  • ios 自定义cell cellForRowAtIndexPath中的写法
  • Angular学习(8)- 路由
  • PHP核心技术与最佳实践 读书笔记 第三章 正则表达式基础与应用
  • Ubantu 16.04升级内核版本和还原到升级之前的内核版本的方法
  • Android五种数据传递方法汇总
  • 10.1 使用w查看系统负载 10.2 vmstat命令 10.3 top命令 10.4 sar命令 10.5 nload命令
  • Python爬虫实战之爬取链家广州房价_02把小爬虫变大
  • 对时间的二分 奔跑的xiaodao double + 精度
  • shiro 静态页面资源不显示 解决方案(转)
  • es6
  • javascript面向对象之创建对象
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Promise面试题,控制异步流程
  • SQLServer之创建数据库快照
  • SwizzleMethod 黑魔法
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 前端之React实战:创建跨平台的项目架构
  • 悄悄地说一个bug
  • 小李飞刀:SQL题目刷起来!
  • 【云吞铺子】性能抖动剖析(二)
  • !!java web学习笔记(一到五)
  • #pragma once与条件编译
  • #stm32整理(一)flash读写
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (学习日记)2024.01.19
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)JAVA中的堆栈
  • (转)平衡树
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .NET CORE Aws S3 使用
  • .net Signalr 使用笔记
  • .Net 路由处理厉害了
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .NET中使用Redis (二)
  • ::什么意思
  • [ C++ ] STL---string类的使用指南
  • [ASP.NET MVC]Ajax与CustomErrors的尴尬
  • [echarts] y轴不显示0
  • [Firefly-Linux] RK3568修改控制台DEBUG为普通串口UART
  • [Flex][问题笔记]TextArea滚动条问题
  • [Flexbox] Using order to rearrange flexbox children
  • [hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 将mysq数据导入HBASE
  • [HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [LeetCode] Contains Duplicate
  • [linux] git lfs install 安装lfs
  • [Luogu 2816]宋荣子搭积木