編輯:Android開發實例
宜未雨而綢缪,毋臨渴而掘井。----朱用純《治家格言》
離線下載,在有網絡的情況下下載服務器數據,以便無網絡時也能閱讀,就是離線閱讀。
離線下載的功能點如下:
1.下載管理(開始、取消下載)。
2.網絡判斷(Wi-Fi,3G)。
3.獨立進程。
4.定時和手機催醒。
5.自啟動。
1.下載管理
這裡不便關注下載的細節方法,網絡下載的方法很多,大概如下:
/** * 下載文件 * @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)兩種,我們寫一個工具類如下:
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下手動下載,提示用戶會消耗較大的數據流量,慎用即可。
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.獨立進程
離線下載,無論何時何地,只要適宜進行,則當進行,目前主流的做法是建立後台服務。
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設計和業務要求沒有過多的涉及,但是往往這些東西才是花費你大量的時間,需要大量細節的積累和耐心的調試,我們唯一要做的事情就是不斷的完善!
前面walfred已經介紹了使用apktool對apk進行逆向編譯,通過apktool我們的確可以反編譯已經序列化後的AndroidManifest.xml和資源
眾所周知,一般情況下我們使用android中的monkeyrunner進行自動化測試時,使用的是python語言來寫測試腳本。不過,最近發現可以用java調用mo
有一種需要,我們在菜單項中點擊退出應用程序,應用程序就退出,不需要回到MainActivity設計:有兩個應用界面MainActivity和BActivity,以
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個