离线下载的需求:

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

       1.下载管理
       这里不便关注下载的细节方法,网络下载的方法很多,大概如下:

java代码:

    1.  
    2.  /** * 下载文件 
    3.  * @param url 下载地址 
    4.  * @param dest 下载存放的本地文件 
    5.  * @param append 断点续传 
    6.  * @return 
    7.  * @throws Exception 
    8.  */
    9.  
    10.  public long download(String url, File dest, boolean append) throws Exception{ 
    11.  //初始化变量 
    12.  //准备工作 
    13.  // ... ... 
    14.  try { 
    15.  // ... ... 
    16.  while((readSize = is.read(buffer)) > 0){ 
    17.  //网络判断 
    18.  os.write(buffer, 0, readSize); 
    19.  os.flush(); 
    20.  //如果需要停止下载,如取消,跳出当前下载 
    21.  } 
    22.  } 
    23.  } finally { 
    24.  // ... ... 
    25.  } 
    26.  // ... ... 
    27.  } 
    28.


    复制代码


            这里要注意几点:


            (1).在下载的时候,我们希望能及时检测到网络状况,比如由Wi-Fi切换到3G网络下,我们应该能及时停止下载。


            (2).当用户选择取消下载的时候,我们也能停止当前下载。 



           

    2.网络判断


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



    java代码:

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


    复制代码

    根据网络状态,我们能够控制下载方式:


           (1).下载量很大的情况下,我们不大可能在3G情况下进行下载,容易引起用户的反感和担忧。


           (2).当客户十分确认可以在3G情况下进行下载,那么也是允许的。


           所以,这里提出一个需求,我们要为下载方式设置一个灵活的等级,结合离线下载的特点,我们给出3中方案由用户选择:



           (1).移动数据情况下自动下载


           (2).只允许Wi-Fi情况下自动下载


           (3).关闭下载


           这里只列出了自动下载,是因为如果不是自动下载,手动下载用户可以随意控制,无需设置,当然设计到丢流量情况下,如3G下手动下载,提示用户会消耗较大的数据流量,慎用即可。



    java代码:

      1.  
      2.  public class Constant { 
      3.  //离线下载网络设置 
      4.  public final static int Offline_Mobile = 0; 
      5.  public final static int Offline_Wifi = 1; 
      6.  public final static int Offline_Off = 3; 
      7.  } 
      8.  public class Global { 
      9.  //设置默认关闭状态, 
      10.  //为了应用程序下次启动能够记住用户选择,在第一次启动应用的时候,这个值最终应该存放到数据库中, 
      11.  public static int OfflineNetworkSetting = Constant.Offline_Off; 
      12.  } 
      13.


      复制代码


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



            

       3.独立进程


             离线下载,无论何时何地,只要适宜进行,则当进行,目前主流的做法是建立后台服务。



      java代码:



      1.  public class OfflineSerivice extends Service { // ... ... }


      复制代码


             

      (1).OfflineService 的进程如果默认和应用程序一致,则在应用进程kill的时候,会重启一次(网易新闻在离线下载的时候,退出应用,下载会停顿一小会儿就是这个原因),如果影响不大,这个方案也是可选的。



           

        (2).OfflineService 的进程和应用程序分开,如应用程序进程为"cn.cnblogs.tianxia.download",则离线下载服务的进程设置为"cn.cnblogs.tianxia.download.offline",撇清和应用程序的进程的关系。当然,这个会带来一个新的问题,进程间通信,当然因为离线下载和应用程序间的模块比较独立,这个问题还算比较好规避。



             

      (3).OfflineService  的进程如果默认和应用程序一致,但是OfflineService集成IntentService,可避免重启的问题,这个是《Pro Android 3》书中提到的方法,非常的好用,但是非常遗憾,本人最近才看到,暂时没有亲手测验,不敢在工作中试用。



              按理说,方案3是最佳方案, 但是个人原因,选择了方案2.



      java代码:



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


      复制代码


             4.定时下载和手机催醒


             根据用户设置,在wifi的情况下自动下载,但是自动下载的方案有很多种,频繁的更新下载,定点下载(早上8点,下午4点),间隔下载(每隔6小时)。这里,我们选择每隔6个小时下载。



             (1).这里介绍一种错误的方案。一看到每隔6小时,很容易想到开启一个子线程计时,累计到6个小时,子线程通知下载服务开始新一轮下载。这个方案的思路是没有错的,但是却忽略了手机处于休眠状态,这个子线程其实是停止执行的,那么所谓的6个小时的效果就又可能永远达不到,而且必然不正确或者不准确。


             (2).所以,需要使用到一种不休眠的办法:定时器和广播接收器。每隔6小时我们发送一个广播,广播接收器通知开始离线下载。

      java代码:



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


      复制代码


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



      java代码:



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


      复制代码


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



      java代码:


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


      复制代码


      其中PowerManager.PARTIAL_WAKE_LOCK意思是仅唤醒CPU方式,此时能自动主动检测网络状态,从而保证网络正常。



             需要在Mainifest.xml中设置权限:



      java代码:


      1.  <uses-permission android:name="android.permission.WAKE_LOCK" />


      复制代码


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



      java代码:



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


      复制代码


             

      5.自启动


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



      java代码:



      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注册此接收器,如下:



      java代码:



      1.  
      2.  <receiver android:name="cn.cnblogs.download.OfflineReceiver"> 
      3.  <intent-filter> 
      4.  <!--自启动--> 
      5.  <action android:name="android.intent.action.BOOT_COMPLETED" /> 
      6.  <category android:name="android.intent.category.HOME" /> 
      7.  </intent-filter> 
      8.  </receiver>


      复制代码