zoukankan      html  css  js  c++  java
  • android离线下载的相关知识

    离线下载的功能点如下:
          1.下载管理(开始、取消下载)。
          2.网络判断(Wi-Fi,3G)。
          3.独立进程。
          4.定时和手机催醒。
          5.自启动。 

    选择离线下载的核心方法
     
    后台独立运行,我们很容易想到服务(Service),但是有以下几种问题
    (1)如果服务的进程和应用一致,那么在应用退出后,服务会重启一次
    (2)如果服务的进程和应用不一致,进程间的通信就会麻烦一点
    (3)如果服务的进程和应用一致,选择IntentService,可以避免重启问题
    而且我们不需要多个任务同时下载,用IntentService完全可以,而且IntentService还有其他优势

    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  
    14     try {
    15             // ... ...
    16             while((readSize = is.read(buffer)) > 0){
    17                 //网络判断
    18                  
    19                 os.write(buffer, 0, readSize);
    20                 os.flush();
    21                  
    22                 //如果需要停止下载,如取消,跳出当前下载
    23             }
    24         }
    25     } finally {
    26         // ... ...
    27     }
    28         // ... ...
    29 }

     这里要注意几点:
          (1).在下载的时候,我们希望能及时检测到网络状况,比如由Wi-Fi切换到3G网络下,我们应该能及时停止下载。
          (2).当用户选择取消下载的时候,我们也能停止当前下载。 

    2.网络判断
          获取当前网络状态,主要分为Wi-Fi和Mobile(包括3G,GPRS)两种,我们写一个工具类如下:

     1 public class NetworkUtils {
     2  
     3     public final static int NONE = 0;//无网络
     4     public final static int WIFI = 1;//Wi-Fi
     5     public final static int MOBILE = 2;//3G,GPRS
     6      
     7     /**
     8      * 获取当前网络状态
     9      * @param context
    10      * @return
    11      */
    12     public static int getNetworkState(Context context){
    13         ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    14          
    15         //手机网络判断
    16         State state = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
    17         if(state == State.CONNECTED||state == State.CONNECTING){
    18             return MOBILE;
    19         }
    20  
    21         //Wifi网络判断
    22         state = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
    23         if(state == State.CONNECTED||state == State.CONNECTING){
    24             return WIFI;
    25         }
    26         return NONE;
    27     }
    28 }

    根据网络状态,我们能够控制下载方式:
          (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  
     8 public class Global {
     9     //设置默认关闭状态,
    10     //为了应用程序下次启动能够记住用户选择,在第一次启动应用的时候,这个值最终应该存放到数据库中,
    11     public static int OfflineNetworkSetting = Constant.OFFLINE_OFF;
    12 }

    现在可以根据规则比较当前网络和离线网络设置,判定离线下载服务的开启。

    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 
     7     public static void startAlarm(Context context){
     8         AlarmManager alarmManager = (AlarmManager) context.getSystemService("alarm");
     9         
    10         //每隔6个小时发送广播到OfflineAlarmReceiver
    11         //也可以设置为10分钟检测一下下载条件,而在OfflineAlarmRecrive中判断开始下载,避免6小时下载失败需再等待6小时过长时间的问题
    12         Intent intent = new Intent(context,OfflineAlarmReceiver.class);  
    13 
    14         PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, intent, 0);
    15         alarmManager.cancel(pendingIntent);
    16         alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(), 3600000*6, pendingIntent);
    17     }
    18 }

    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 
    14 }

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

     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      */
    18     public static void releaseWakeLock(){
    19         if(wakeLock!=null&&wakeLock.isHeld()){
    20             wakeLock.release();
    21             wakeLock = null;
    22         }
    23     }

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

        <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 }
    12        在AndroidManifest.xml注册此接收器,如下:
    13 
    14 <receiver android:name="cn.cnblogs.download.OfflineReceiver">
    15             <intent-filter>
    16                 <!--自启动-->
    17                 <action android:name="android.intent.action.BOOT_COMPLETED" /> 
    18                 <category android:name="android.intent.category.HOME" /> 
    19             </intent-filter>
    20 </receiver>

    这样,在启动的时候,能够接受启动广播,并执行启动定时器操作。

    6.小结
          为了简洁明晰,开门见山,本文仅针对离线下载的最重要的关联点列举说明,而对于清理策略,手动和自动模式,界面跳转,UI设计和业务要求没有过多的涉及,但是往往这些东西才是花费你大量的时间,需要大量细节的积累和耐心的调试,我们唯一要做的事情就是不断的完善! 

  • 相关阅读:
    emoji表情,直接存入数据库报错,,出现java.sql.SQLException: Incorrect string value: 'xF0x9Fx98x8ExF0。。。。。。
    Springmvc的服务端数据验证-----Hibernate Validator
    HashMap
    Treeset的两种排序方法(自然排序和比较器排序)
    Java设计模式之适配器模式
    Java中的三种工厂模式
    构造方法私有化_骰子
    Java中equals的覆盖
    HttpClient请求
    JAVA的单例模式与延时加载
  • 原文地址:https://www.cnblogs.com/xuanyuanzhuo-blog/p/4005120.html
Copyright © 2011-2022 走看看