原文:http://www.cnblogs.com/qianxudetianxia/archive/2011/07/20/2108965.html

宜未雨而绸缪,毋临渴而掘井。----朱用纯《治家格言》

离线下载,在有网络的情况下下载服务器数据,以便无网络时也能阅读,就是离线阅读。

离线下载的功能点如下:
      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.

<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》):

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中处理开始下载条件,并通知开始下载:

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);
}
} }

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

    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中设置权限:

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

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

    @Override
public int onStartCommand(Intent intent, int flags, int startId) {
acquireWakeLock(OfflineService.this);
//省略代码... ...
return super.onStartCommand(intent, flags, startId);
}

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

/**
* 自启动离线下载服务
* @author user
*/
public class OfflineReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent arg1) {
//启动定时器
OfflineService.startAlarm(context);
}
}

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

<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设计和业务要求没有过多的涉及,但是往往这些东西才是花费你大量的时间,需要大量细节的积累和耐心的调试,我们唯一要做的事情就是不断的完善!

【转】Android学习系列–App离线下载功能实现的更多相关文章

  1. Android学习系列--App列表之拖拽ListView(下)

    接着上篇Android学习系列(10)--App列表之拖拽ListView(上)我们继续实现ListView的拖拽效果. 7.重写onTouchEvent()方法.     在这个方法中我们主要是处理 ...

  2. Android学习系列--App列表之拖拽ListView(上)

    研究了很久的拖拽ListView的实现,受益良多,特此与尔共飨.      鉴于这部分内容网上的资料少而简陋,而具体的实现过程或许对大家才有帮助,为了详尽而不失真,我们一步一步分析,分成两篇文章. 一 ...

  3. Android学习之APP点击功能闪退问题的处理一

    java代码如下: activity_main.xml代码如下: APP测试问题:安装好APP后,点击“点击事件”,APP会闪退 检查方向: 1.查看java代码中的成员变量textview是否初始化 ...

  4. Android学习系列(18)--App工程结构搭建

     本文算是一篇漫谈,谈一谈关于Android开发中工程初始化的时候如何在初期我们就能搭建一个好的架构.      关于android架构,因为手机的限制,目前我觉得也确实没什么大谈特谈的,但是从开发的 ...

  5. Android学习系列(3)--App自动更新之自定义进度视图和内部存储

    友好的视觉感知和稳定的不出错表现,来自于我们追求美感和考虑的全面性,博客园从技术的角度,一直我都很欣赏.这篇文章是android开发人员的必备知识,是我特别为大家整理和总结的,不求完美,但是有用. 这 ...

  6. Android学习系列(37)--App调试内存泄露之Context篇(下)

    接着<Android学习系列(36)--App调试内存泄露之Context篇(上)>继续分析. 5. AsyncTask对象 我N年前去盛大面过一次试,当时面试官极力推荐我使用AsyncT ...

  7. Android学习系列(7)--App轮询服务器消息

    这篇文章是android开发人员的必备知识. 1.轮询服务器     一般的应用,定时通知消息可以采用轮询的方法从服务器拿取消息,当然实时消息通知的话,建议采用推送服务.    其中需要注意轮询的频率 ...

  8. Android学习系列(15)--App列表之游标ListView(索引ListView)

    游标ListView,提供索引标签,使用户能够快速定位列表项.      也可以叫索引ListView,有的人称也为Tweaked ListView,可能更形象些吧.      一看图啥都懂了: 1. ...

  9. Android学习系列(23)--App主界面实现

    在上篇文章<Android学习系列(22)--App主界面比较>中我们浅略的分析了几个主界面布局,选了一个最大众化的经典布局.今天我们就这个经典布局,用代码具体的实现它. 1.预览图先看下 ...

随机推荐

  1. 开展.net mvc3遇到怪事+解

    发展到今天.net mvc3遇到怪事. 使用Firefox浏览器.打开index页,求index该控制器是很多次,代码查询数据库的多个运行.server减速. 而且没有刷新页面,随着时间的推移有十二请 ...

  2. ASP.NET MVC 3: Razor中的@:和语法

    原文 ASP.NET MVC 3: Razor中的@:和语法 [原文发表地址] ASP.NET MVC 3: Razor’s @: and <text> syntax[原文发表时间] De ...

  3. 详细说明C++笔试题,调查超载、盖、多态

    C++可见版本,他说,这本书是采访的主题,调查超载.盖.多态性等概念,比较有代表性的.今天上午,远程辅导 Yan Wang 学生们学习 Qt 时还觉得这个话题,假设你能正确地理解这一主题,注意对于 C ...

  4. android浏览器开发小技巧集锦(转)

    本人和朋友们做了一段时间浏览器,将一些小技巧分享出来,先写一部分,慢慢写,同时也为我们的浏览器打打广告 我们的浏览器将要上线,名叫沙发浏览 1.网页内的右键菜单 public boolean onLo ...

  5. unity3d 学习笔记_____Native2d 刚体、冲击、联合使用

    Mass Mass of the rigidbody. Linear Drag Drag coefficient affecting positional movement. Angular Drag ...

  6. TFS 2010 使安装更容易,让VSS历史

    一转眼VS 2010 RC(Release Candidate)版本号已经公布一月多了,RTM(Release To Manufacturer)版本号也快妥了,已经进入了最后的倒计时,仅仅等4月12号 ...

  7. .NET中Redis安装部署及使用方法简介

    一Redis服务端以服务方式运行 修改端口压缩文件中配置的是6488 修改密码 修改库的数量 工具配置安装后如下图 二Redis服务端以控制台方式运行 第一步配置本地服务 第二部安装和配置客户端 三C ...

  8. 你可以不知道原因,但是,我们不能停止努力。httplook抓取路由配置界面信息

    因为实验室的项目要求,需要动态控制路由的配置信息,我们知道.路由选择需要购买的访问后,:http://192.168.1.1 路由配置页面.配置,不须要人手工操作.甚至定时任务配置的时候,就须要进一步 ...

  9. mysql只导出表结构或数据

    唯一的非导电结构指南数据 mysqldump -t 数据库名称 -uroot -p > xxx.sql 指南结构不仅指导数据 mysqldump    --opt -d  数据库名 -u -p ...

  10. RQNOJ PID2 / 开心的金明

    题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N 元钱就行”.今 ...