Android学习系列(19)--App离线下载
宜未雨而绸缪,毋临渴而掘井。----朱用纯《治家格言》
离线下载,在有网络的情况下下载服务器数据,以便无网络时也能阅读,就是离线阅读。
离线下载的功能点如下:
1.下载管理(开始、取消下载)。
2.网络判断(Wi-Fi,3G)。
3.独立进程。
4.定时和手机催醒。
5.自启动。
1.下载管理
这里不便关注下载的细节方法,网络下载的方法很多,大概如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | /** * 下载文件 * @param url 下载地址 * @param dest 下载存放的本地文件 * @param append 断点续传 * @return * @throws Exception */ public long download(String url, File dest, boolean append) throws Exception{ //初始化变量 //准备工作 // ... ... try { // ... ... while ((readSize = is.read(buffer)) > 0 ){ //网络判断 os.write(buffer, 0 , readSize); os.flush(); //如果需要停止下载,如取消,跳出当前下载 } } } finally { // ... ... } // ... ... } |
这里要注意几点:
(1).在下载的时候,我们希望能及时检测到网络状况,比如由Wi-Fi切换到3G网络下,我们应该能及时停止下载。
(2).当用户选择取消下载的时候,我们也能停止当前下载。
2.网络判断
获取当前网络状态,主要分为Wi-Fi和Mobile(包括3G,GPRS)两种,我们写一个工具类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class NetworkUtils { public final static int NONE = 0 ; //无网络 public final static int WIFI = 1 ; //Wi-Fi public final static int MOBILE = 2 ; //3G,GPRS /** * 获取当前网络状态 * @param context * @return */ public static int getNetworkState(Context context){ ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); //手机网络判断 State state = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState(); if (state == State.CONNECTED||state == State.CONNECTING){ return MOBILE; } //Wifi网络判断 state = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState(); if (state == State.CONNECTED||state == State.CONNECTING){ return WIFI; } return NONE; } } |
根据网络状态,我们能够控制下载方式:
(1).下载量很大的情况下,我们不大可能在3G情况下进行下载,容易引起用户的反感和担忧。
(2).当客户十分确认可以在3G情况下进行下载,那么也是允许的。
所以,这里提出一个需求,我们要为下载方式设置一个灵活的等级,结合离线下载的特点,我们给出3中方案由用户选择:
(1).移动数据情况下自动下载
(2).只允许Wi-Fi情况下自动下载
(3).关闭下载
这里只列出了自动下载,是因为如果不是自动下载,手动下载用户可以随意控制,无需设置,当然设计到丢流量情况下,如3G下手动下载,提示用户会消耗较大的数据流量,慎用即可。
1 2 3 4 5 6 7 8 9 10 11 12 | public class Constant { //离线下载网络设置 public final static int OFFLINE_MOBILE = 0 ; public final static int OFFLINE_WIFI = 1 ; public final static int OFFLINE_OFF = 3 ; } public class Global { //设置默认关闭状态, //为了应用程序下次启动能够记住用户选择,在第一次启动应用的时候,这个值最终应该存放到数据库中, public static int OfflineNetworkSetting = Constant.OFFLINE_OFF; } |
现在可以根据规则比较当前网络和离线网络设置,判定离线下载服务的开启。
3.独立进程
离线下载,无论何时何地,只要适宜进行,则当进行,目前主流的做法是建立后台服务。
1 2 3 | 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.
1 2 3 4 5 6 7 | < manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.cnblogs.download"> < application android:icon="@drawable/icon" android:label="@string/app_name"> < service android:name="cn.cnblogs.download.OfflineService" android:process="cn.cnblogs.download.offline"/> </ application > </ manifest > |
4.定时下载和手机催醒
根据用户设置,在wifi的情况下自动下载,但是自动下载的方案有很多种,频繁的更新下载,定点下载(早上8点,下午4点),间隔下载(每隔6小时)。
这里,我们选择每隔6个小时下载。
(1).这里介绍一种错误的方案。一看到每隔6小时,很容易想到开启一个子线程计时,累计到6个小时,子线程通知下载服务开始新一轮下载。这个方案的思路是没有错的,但是却忽略了手机处于休眠状态,这个子线程其实是停止执行的,那么所谓的6个小时的效果就又可能永远达不到,而且必然不正确或者不准确。
(2).所以,需要使用到一种不休眠的办法:定时器和广播接收器。每隔6小时我们发送一个广播,广播接收器通知开始离线下载。(可参考newsrob源码和书籍《Pro Android 3》):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class OfflineSerivice extends Service { //上次成功下载的时间 private long lastDownloadTime; // 省略代码... ... public static void startAlarm(Context context){ AlarmManager alarmManager = (AlarmManager) context.getSystemService( "alarm" ); //每隔6个小时发送广播到OfflineAlarmReceiver //也可以设置为10分钟检测一下下载条件,而在OfflineAlarmRecrive中判断开始下载,避免6小时下载失败需再等待6小时过长时间的问题 Intent intent = new Intent(context,OfflineAlarmReceiver. class ); PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0 , intent, 0 ); alarmManager.cancel(pendingIntent); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(), 3600000 * 6 , pendingIntent); } } |
OfflineAlarmRecriver中处理开始下载条件,并通知开始下载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class OfflineAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent arg1) { // 省略代码...,初始化变量,准备工作... if (System.currentTimeMillis()-OfflineService.lastDownloadTime> 3600000 * 60 &&其他条件){ //打开离线下载服务 Intent alarmIntent = new Intent(context, OfflineService. class ); context.startService(alarmIntent); } } } |
前面我们提到了线程休眠的问题,需要在下载的时候能够唤醒手机,下载完成后能回到休眠状态,下面是两个工具方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public static PowerManager.WakeLock wakeLock; /** * 唤醒服务 */ public static void acquireWakeLock(Context context){ if (wakeLock!= null ){ return ; } PowerManager powerManager = (PowerManager)(context.getSystemService(Context.POWER_SERVICE)); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "com.cnblogs.download.OfflineService" ); wakeLock.acquire(); } /** * 释放唤醒服务,返回休眠状态 */ public static void releaseWakeLock(){ if (wakeLock!= null &&wakeLock.isHeld()){ wakeLock.release(); wakeLock = null ; } } |
其中PowerManager.PARTIAL_WAKE_LOCK意思是仅唤醒CPU方式,此时能自动主动检测网络状态,从而保证网络正常。
需要在Mainifest.xml中设置权限:
1 | < uses-permission android:name="android.permission.WAKE_LOCK" /> |
然后在下载服务的onStartConmmand()激活催醒状态,然后在下载完成后释放催醒状态:
1 2 3 4 5 6 | @Override public int onStartCommand(Intent intent, int flags, int startId) { acquireWakeLock(OfflineService. this ); //省略代码... ... return super .onStartCommand(intent, flags, startId); } |
5.自启动
为了代码清晰,我们再定义一个自启动的receiver:
1 2 3 4 5 6 7 8 9 10 11 | /** * 自启动离线下载服务 * @author user */ public class OfflineReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent arg1) { //启动定时器 OfflineService.startAlarm(context); } } |
在AndroidManifest.xml注册此接收器,如下:
1 2 3 4 5 6 7 | < receiver android:name="cn.cnblogs.download.OfflineReceiver"> < intent-filter > <!--自启动--> < action android:name="android.intent.action.BOOT_COMPLETED" /> < category android:name="android.intent.category.HOME" /> </ intent-filter > </ receiver > |
这样,在启动的时候,能够接受启动广播,并执行启动定时器操作。
6.小结
为了简洁明晰,开门见山,本文仅针对离线下载的最重要的关联点列举说明,而对于清理策略,手动和自动模式,界面跳转,UI设计和业务要求没有过多的涉及,但是往往这些东西才是花费你大量的时间,需要大量细节的积累和耐心的调试,我们唯一要做的事情就是不断的完善!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构