前段时间做了一个离线下载的模块,需求如下:
1、后台独立进程运行,可以脱离主程序运行
2、可以暂停、继续下载
3、可以判断网络状况和SD卡
4、显示下载进度
4、多个个任务在一个队列中
5、定时下载
一、选择离线下载的核心方法
后台独立运行,我们很容易想到服务(Service),但是有以下几种问题
(1)如果服务的进程和应用一致,那么在应用退出后,服务会重启一次
(2)如果服务的进程和应用不一致,进程间的通信就会麻烦一点
(3)如果服务的进程和应用一致,选择IntentService,可以避免重启问题
而且我们不需要多个任务同时下载,用IntentService完全可以,而且IntentService还有其他优势
参考这篇文章:Android中IntentService与Service的比较
二、下载管理
由于用IntentService,一个任务就是一个intent,服务处理一个Intent就是下载一个任务
(1)当前任务的暂停,可以设置一个下载状态的变量
1 public static final int CANCEL_STATE = 1 ; 2 3 private state int state ; 4 5 ... 6 7 //download 8 9 10 11 while ( ... ) { 12 13 ... 14 15 if ( state == CANCEL_STATE ) { 16 17 break ; 18 19 } 20 21 }
暂停就相当于取消了当前任务,服务去处理下一个intent。
如果要继续这个任务,就要重新发送一个Intent,为了可以从刚刚下载的地方接着下载,我们要坐下处理:
1 File file = new File ( path ) ; 2 3 file.getParentFile().mkdirs () ; 4 5 RandomAccessFile rdFile = new RandomAccessFile ( file, "rwd" ) ;
//获得刚刚下载的文件的大小
1 int mCurrentSize = ( int ) rdFile.length () ; 2 3 HttpURLConnection conn = ( HttpURLConnection ) new URL ( url ).openConnection () ; 4 5 conn.setConnectTimeout ( 5000 ) ; 6 7 conn.setRequestMethod ( "get" ) ;
//从mCurrentSize后开始下载
conn.setRequestProperty ( "Range", "bytes=" + mCurrentSize + "-" ) ;
rdFile.seek ( mCurrentSize ) ;
(2)取消队列中等待下载的任务
发现IntentService不可以取消还未处理的Intent,看IntentService的源码,发现只需略作修改就可以了
//下面自定义一个可以删除intent的服务,只显示新增的代码和覆盖的部分,其他和IntentService一样
1 public abstract class BaseIntentService extends Service { 2 3 private final int MESSAGE_TYPE = 12 ; //消息类型 4 5 6 7 @Override 8 9 public void onStart ( Intent intent, int startId ) { 10 11 Message msg = mServiceHandler.obtainMessage () ; 12 13 msg.arg1 = startId ; 14 15 msg.obj = intent ; 16 17 msg.what = MESSAGE_TYPE ; 18 19 mServiceHandler.sendMessage ( msg ) ; 20 21 } 22 23 24 25 protected boolean hasIntent ( Intent intent ) { 26 27 return mServiceHandler.hasMessages ( MESSAGE_TYPE, intent ) ; 28 29 } 30 31 32 33 protected void removeIntent ( Intent intent ) { 34 35 if ( mServiceHandler.hasMessages ( MESSAGE_TYPE, intent ) ) { 36 37 mServiceHandler.removeMessages ( MESSAGE_TYPE, intent ) ; 38 39 } 40 41 } 42 43 44 45 protected void removeAllIntent () { 46 47 mServiceHandler.removeMessages ( MESSAGE_TYPE ) ; 48 49 } 50 51 }
这边还有另外一个需要注意的地方:在测试中发现删除不了,后来才知道是个低级错误,默认Intent的equals方法是判断两个引用是否指向一个对象,所以我们要重载Intent的 hashCode 和 equals 方法。
三、判断网络状态和SD卡
获得当前的网络状态,如果不是wifi就停止下载,如果SD卡没有挂载,也停止下载
1 public class NetworkUtils { 2 3 public static boolean existWifi ( Context context ) { 4 5 ConnectivityManager connManager = ( ConnectivityManager ) context.getSystemService ( Context.CONNECTIVITY_SERVICE ) ; 6 7 NetworkInfo info = connManager.getActiveNetworkInfo () ; 8 9 return ( null != info && ConnectivityManager.TYPE_WIFI == info.getType () ) ; 10 11 } 12 13 }
1 public class SDUtils { 2 3 public static boolean existSDCard () { 4 5 return ( android.os.Environment.getExternalStorageStage(). 6 7 equals( android.os.Environment.MEDIA_MOUNTED ) ) ; 8 9 } 10 11 }
四、显示下载进度
(1)应用程序中显示下载进度,利用ResultReceiver
1 public class MainActivity extends Activity { 2 3 ... 4 5 6 7 public void startDownload () { 8 9 Intent intent = new ... 10 11 intent.putExtra ( "receiver", new DownloadReceiver() ) ; 12 13 ... 14 15 startService ( intent ) ; 16 17 } 18 19 20 21 public class DownloadReceiver extends ResultReceiver { 22 23 ... 24 25 } 26 27 28 29 }
//在IntentService中的onHandleIntent中,获得ResultReceiver
ResultReceiver receiver = intent.getParcelableExtra ( "receiver" ) ;
//在循环下载处理中,发送下载进度
1 Bundle resultData = new Bundle () ; 2 3 resultData.putString ( "progress", percent ) ; 4 5 receiver.send ( UPDATE_PROGRESS, resultData ) ;
(2)在通知栏中,更新下载进度,利用Notifications
五、定时下载
这个和闹钟的原理相类似,定义一个定时器和广播接收器,利用AlarmManager
1 public static void startAlarm ( Context context, long time ) { 2 3 AlarmManager am = (AlarmManager) context.getSystemService ( Context.ALARM_SERVICE ) ; 4 5 Intent intent = new Intent ( context, AlarmReceiver.class ) ; 6 7 PendingIntent pIntent = PendingIntent.getBroadcast ( context, 0, intent, 0 ) ; 8 9 am.cancel ( pIntent ) ; 10 11 am.setRepeating ( AlarmManager.RTC_WAKEUP, time, AlarmManager.INTERVAL_DAY, pIntent ) ; 12 13 } 14 15 16 17 public class AlarmReceiver extends BroadcastReceiver { 18 19 @Override 20 21 public void onReceive ( Context context, Intent intent ) { 22 23 //启动下载服务 24 25 。。。 26 27 } 28 29 }
我们在下载的时候可以唤醒手机,下载后可以回到休眠状态,利用PowerManager
1 public class WakeLockUtils { 2 3 public static WakeLock wl ; 4 5 public static String tag ; // 服务的包名 6 7 8 9 public static void acquirePratialWakeLock ( Context context ) { 10 11 if ( null != wl ) return ; 12 13 PowerManager pm = ( PoweerManager ) ( context.getSystemService ( Context.POWER_SERVICE ) ) ; 14 15 wl = pm.newWakeLock ( PowerManager.PARTIAL_WAKE_LOCK, tag ) ; 16 17 wl.acquire () ; 18 19 } 20 21 22 23 public static void releaseWakeLock () { 24 25 if ( null != wl && wl.isHeld () ) { 26 27 wl.release () ; 28 29 wl = null ; 30 31 } 32 33 } 34 35 } 36 37
六、自启动
为了让我们的闹钟可以在开机后自动startAlarm
1 public AutoRunReceiver extends BroadcastReceiver { 2 3 public void onReceive ( Context context, Intent intent ) { 4 5 //启动定时器 6 7 8 9 。。。startAlram () ; 10 11 } 12 13 }
在AndroidManifest.xml注册此接收器:
1 <receiver android:name="包名"> 2 3 <intent-filter> 4 5 <action android:name="android.intent.action.BOOT_COMPLETED"/> 6 7 <category android:name="android.intent.category.HOME"/> 8 9 </intent-filter> 10 11 </receiver>