Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> Android學習系列(19)--App離線下載

Android學習系列(19)--App離線下載

編輯: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設計和業務要求沒有過多的涉及,但是往往這些東西才是花費你大量的時間,需要大量細節的積累和耐心的調試,我們唯一要做的事情就是不斷的完善! 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved