Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 後台Service下載 (一)

Android 後台Service下載 (一)

編輯:關於Android編程

一、前言           原理其實大家都懂,只不過沒動手實際好好的寫過,項目中也沒有涉及到用這塊內容,所以....所以被人問及細節時,就說不清個123了,為了一改我的慵懶,因此,我寫這篇文章,至少下次再被問起時,不會尴尬。           本篇文章會涉及到以下知識點:           1. Service (兩種啟動方法,對應的不同生命周期不同);           2. Binder;           3. Activity如何與Service交互;           4. Service如何更新帶進度條的狀態欄;   二、Service & Binder           2.1 Service           Service有兩個方法來啟動:startService 和 bindService,采用不同的方法,service的生命周期也不同(本篇只講同進程,不講跨進程):           1. startService啟動,其生命周期不會因啟動它的組件Destroy而消亡,而是依賴於mainThread(即應用主線程),一但主線程退出,即代表整個應用退出,因為Service就會Destroy。           2. bindService啟動,其生命周期依賴啟動它的組件,組件Destroy時,Service也隨之一起Destroy。           2.2 Binder           Binder是Android系統中一個重要的“設備”,之所以加引號,實際上它是虛擬出來的,類似於Linux中的塊設備,因此,它也是基於IO的。           Binder在Android中,是被用做進程間通信使用的,而且,Binder是Parcelable的,通過Transaction,與它的代理端,即Binder Server端交互,本章只是簡單的使用Binder來做同一進程中的線程間通信。   三、Activity與Service交互           Question:如何將Service用做後台下載,其生命周期不依賴啟動它的組件,且能夠與它的組件相互通信?           分析問題:           該問題,表述了三點信息:           1. 後台下載;           2. 生命周期不依賴其它組件;           3. 數據交互;           3.1 後台下載           通常,我們使用Service,會有這麼幾點需求:           1. 若是前台Service,一般是用來做類似於音樂播放器的;           2. 若是後台Service,則通常是用來和服務器進行交互(數據下載),或是其它不需要用戶參與的操作;           同一進程中,啟動Service,若直接與服務器交互,則很容易引起ANR,因為,Service是由mainThread創建出來,因此,此時Service是運行在UI主線程的,如果需要聯網下載,則需要開啟一個Thread,然後在子線程中來運行。在Service中創建/使用線程,與在Activity中一樣,無區別。           3.2 生命周期不依賴其它組件           這點,我前面說過了,使用startService來啟動該service就行;           3.3 數據交互           組件通常是Activity,可以通過bindService,當成功綁定時,可以獲取Service中定義後的一個IBinder接口,我們可以通過這個接口,返回該Service對象,從而,可以直接訪問該Service中的公有方法;           當Service想要把數據傳遞給某個組件時,最簡單最好的辦法就是通過Broadcast,在Intent中帶上數據,廣播給組件即可(記住,BroadcastReceiver中,onReceive也不能運行太久,否則也會ANR,只有10秒哦)。   四、Service刷新帶有進度條的狀態欄           通常,我們會發一些Notification到系統狀態欄上,以提醒用戶做一些事情,但是,如果大家仔細看了Notification的參數,就會發現裡面有一個RemoteViews類型的成員,是不是有點像在哪見過?對的,如果你做個Widget應用,那麼RemoteViews你應該很熟悉:           RemoteViews可以讓我們自定義一個View,裡面放一些小的控件,系統有定義的,不是所有的控件都能放!那麼,我們就可能自定義一個帶有ProgressBar的layout,然後綁定到Notification對象上,並通過NotificationManager來通知更新即可。           注:網上有提醒說,建議不要更新太頻繁,否則會使系統很卡!   五、用例子說話           本節,就將寫一個Demo,帶大家一起了解如何活用以上這些概念,能夠讓大家應用到將來自己的項目中。文件不多,三個類,一個Service,一個Activity,和一個任務類(因為我在Service中,創建了一個線程隊列,使用單線程來模擬)。           5.1 DownloadManagerActivity           對應的layout:   [html]   <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"       xmlns:tools="http://schemas.android.com/tools"       android:layout_width="match_parent"       android:layout_height="match_parent"       tools:context=".DownloadManagerActivity" >          <TextView           android:layout_width="wrap_content"           android:layout_height="wrap_content"           android:layout_centerHorizontal="true"           android:layout_centerVertical="true"           android:text="@string/hello_world" />              <Button            android:id="@+id/add_task"           android:layout_width="wrap_content"           android:layout_height="wrap_content"           android:text="@string/addTask"/>              <Button            android:id="@+id/cancel_task"           android:layout_toRightOf="@id/add_task"           android:layout_width="wrap_content"           android:layout_height="wrap_content"           android:text="@string/cancelTask"/>      </RelativeLayout>            裡面主要有兩個Button,一個告訴Service添加任務,一個告訴Service取消指定的任務。   [java]   public final static String TAG = "DownloadService";   private DownloadService mService = null;   private static int task_count = 0;   private final static String ACTION_UPDATE = "com.chris.download.service.UPDATE";   private final static String ACTION_FINISHED = "com.chris.download.service.FINISHED";            幾個對象,mService就是當bindService成功時,通過IBinder返回Service對象,ACTION_XXX用來接收Service發送的廣播,在Activity中動態注冊廣播。   [java]   @Override   protected void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       setContentView(R.layout.activity_download_manager);              IntentFilter filter = new IntentFilter();       filter.addAction(ACTION_UPDATE);       filter.addAction(ACTION_FINISHED);       registerReceiver(myReceiver, filter);              Intent it = new Intent(this, DownloadService.class);       startService(it);              Button add_task = (Button) findViewById(R.id.add_task);       add_task.setOnClickListener(new OnClickListener(){           @Override           public void onClick(View arg0) {               TaskInfo ti = new TaskInfo();               ti.setTaskId(task_count++);               ti.setTaskName(TAG + ti.getTaskId());               ti.setProgress(0);               ti.setStatus(TaskInfo.WAITING);               mService.addTaskInQueue(ti);           }       });              Button cancel_task = (Button) findViewById(R.id.cancel_task);       cancel_task.setOnClickListener(new OnClickListener(){           @Override           public void onClick(View arg0) {               int index = (int) (Math.random() * task_count);               mService.cancelTaskById(index);           }       });   }           一開始,動態注冊一下BroadcastReceiver,指定接收兩個ACTION;然後,startService啟動一個Service。自定義BroadcastReceiver:   [java]  private BroadcastReceiver myReceiver = new BroadcastReceiver(){       @Override       public void onReceive(Context context, Intent intent) {           if(intent.getAction().equals(ACTION_UPDATE)){               int progress = intent.getIntExtra("progress", 0);               Log.d(TAG, "myReceiver - progress = " + progress);           }else if(intent.getAction().equals(ACTION_FINISHED)){               boolean isSuccess = intent.getBooleanExtra("success", false);               Log.d(TAG, "myReceiver - success = " + isSuccess);           }       }   };           在onResume時,去bindService:   [java]   @Override   protected void onResume() {       super.onResume();       Log.d(TAG, "Activity onResume");              Intent it = new Intent(this, DownloadService.class);       bindService(it, mServiceConn, BIND_AUTO_CREATE);   }           並在onDestroy時,unbindService,以及unregisterReceiver:   [java]  @Override   protected void onDestroy() {       super.onDestroy();       unbindService(mServiceConn);       //stopService(new Intent(this, DownloadService.class));       unregisterReceiver(myReceiver);   }           ServiceConnection代碼:   [java]   public ServiceConnection mServiceConn = new ServiceConnection(){       @Override       public void onServiceConnected(ComponentName name, IBinder service) {           mService = ((DownloadService.ServiceBinder)service).getService();           Log.d(TAG, "onServiceConnected: mService = " + mService);                      if(mService != null){               mService.notifyToActivity(false, true);           }       }          @Override       public void onServiceDisconnected(ComponentName name) {           mService = null;       }   };           如果成功了,就通過IBinder接口,獲得Service對象。           5.2 DownloadService           繼承Service類,override一些方法:   [java]   @Override   public IBinder onBind(Intent intent) {       Log.d(TAG, "onBind");       return mBinder;   }      @Override    public int onStartCommand(Intent intent, int flags, int startId) {            Log.d(TAG, "onStartCommand");       return START_STICKY;   }      @Override   public void onCreate() {       super.onCreate();       Log.d(TAG, "onCreate");       mBinder = new ServiceBinder();       mDownloadQueue = new ArrayList<TaskInfo>();       mNotificationManager = (NotificationManager) getSystemService(               android.content.Context.NOTIFICATION_SERVICE);       mNotification = new Notification();       mRemoteView = new RemoteViews(this.getPackageName(), R.layout.remote_view_layout);   }      @Override   public void onDestroy() {       super.onDestroy();       mBinder = null;       mDownloadQueue = null;       mNotificationManager = null;       mNotification = null;       mRemoteView = null;       Log.d(TAG, "onDestroy");   }           我們通過startService來啟動,因此,啟動流程為:onCreate -> onStartCommand(注:onStart在API5以後,就不在用了,取而代之的是onStartCommand)。           然後,我們bindService,此時service已經啟動,所以,只會調用onBind。           通常,我們應該在onCreate中,去完成一些初始化,而在onDestroy中,去釋放這些內存,因為一但Service運行起來,再去掉startService或bindService,系統就不會再去調用onCreate了,但是onStartCommand或onBind仍舊會被調用。           內部類ServiceBinder,只有一個公有方法,用來返回當前的Service對象:   [java]   public class ServiceBinder extends Binder{       public DownloadService getService(){           return DownloadService.this;       }   }           提供給外部組件的公有方法:   [java]   public void notifyToActivity(boolean update, boolean finished){       bNotifyWhenUpdate = update;       bNotifyWhenFinished = finished;   }      public void addTaskInQueue(TaskInfo ti){       if(mDownloadQueue != null){           mDownloadQueue.add(ti);           Log.d(TAG, "addTaskInQueue id = " + ti.getTaskId());       }              if(isRunning == false && mDownloadQueue.size() > 0){           startDownload();       }   }      public void cancelTaskById(int id){       Log.d(TAG, "cancelTaskById id = " + id);       for(int i = 0; i < mDownloadQueue.size(); i ++){           TaskInfo ti = mDownloadQueue.get(i);           if(ti.getTaskId() == id){               if(ti.getStatus() == TaskInfo.RUNNING){                   ti.setStatus(TaskInfo.CANCELED);               }else{                   mDownloadQueue.remove(i);               }               break;           }       }   }           三個方法:添加任務,取消任務,是否需要通知給已經綁定的組件。           接下來,就是我們的線程了,這裡的線程是單線程,使用私有的線程隊列   [java]  private void startDownload(){       if(isRunning){           return;       }              new Thread(new Runnable(){           @Override           public void run() {               while(mDownloadQueue != null && mDownloadQueue.size() > 0){                   isRunning = true;                                      TaskInfo ti = mDownloadQueue.get(0);                   while(ti.getProgress() < 100 && ti.getStatus() != TaskInfo.CANCELED){                       Message msg = mHandler.obtainMessage(DOWNLOAD_STATUS_UPDATE, ti);                       mHandler.sendMessage(msg);                       try {                           Thread.sleep(1000);                       } catch (InterruptedException e) {                           e.printStackTrace();                       }                       ti.setProgress(ti.getProgress()+10);                   }                                      if(ti.getProgress() == 100 && mDownloadQueue.size() == 1){                       Log.d(TAG, ti.getTaskName() + " is finished!");                       Message msg = mHandler.obtainMessage(DOWNLOAD_STATUS_SUCCESS, ti);                       mHandler.sendMessage(msg);                   }else if(ti.getStatus() == TaskInfo.CANCELED){                       Log.d(TAG, ti.getTaskName() + " is canceled!");                   }                   if(mDownloadQueue != null){                       mDownloadQueue.remove(ti);                   }               }               isRunning = false;           }       }).start();   }           通過Thread.sleep(1000)來模擬網絡,並使用Thread / Handler的模式,來更新Notification的RemoteViews。          Handler的實現:   [java]   private Handler mHandler = new Handler(){       @Override       public void handleMessage(Message msg) {           switch(msg.what){           case DOWNLOAD_STATUS_UPDATE:           {               mNotification.icon = R.drawable.ic_launcher;               mNotification.when = System.currentTimeMillis();               mNotification.tickerText = "開始下載...";               // 放置在"正在運行"欄目中                  mNotification.flags = Notification.FLAG_ONGOING_EVENT;                              TaskInfo ti = (TaskInfo) msg.obj;               Log.d(TAG, "update : progress = " + ti.getProgress());               mRemoteView.setImageViewResource(R.id.ivIcon, R.drawable.ic_launcher);               mRemoteView.setTextViewText(R.id.tvName, ti.getTaskName());               mRemoteView.setProgressBar(R.id.pbProgress, 100, ti.getProgress(), false);               mRemoteView.setTextViewText(R.id.tvProgress, ti.getProgress() + "%");               mNotification.contentView = mRemoteView;               mNotificationManager.notify(NOTIFY_ID, mNotification);                              notifyUpdate(ti);               break;           }                      case DOWNLOAD_STATUS_SUCCESS:           {               mNotification.flags = Notification.FLAG_AUTO_CANCEL;               mNotification.contentView = null;               Intent it = new Intent(DownloadService.this, DownloadManagerActivity.class);               PendingIntent pi = PendingIntent.getActivity(DownloadService.this, 0, it, PendingIntent.FLAG_UPDATE_CURRENT);               mNotification.setLatestEventInfo(DownloadService.this, "下載完成", "文件已下載完畢", pi);               mNotificationManager.notify(NOTIFY_ID, mNotification);                              notifyFinished(true);               break;           }                      case DOWNLOAD_STATUS_FAILED:           {               mNotification.flags = Notification.FLAG_AUTO_CANCEL;               mNotification.contentView = null;               Intent it = new Intent(DownloadService.this, DownloadManagerActivity.class);               PendingIntent pi = PendingIntent.getActivity(DownloadService.this, 0, it, PendingIntent.FLAG_UPDATE_CURRENT);               mNotification.setLatestEventInfo(DownloadService.this, "下載失敗", "", pi);               mNotificationManager.notify(NOTIFY_ID, mNotification);                              notifyFinished(false);               break;           }                      default:               break;           }       }   };           通知組件新的情況:   [java]   private void notifyUpdate(TaskInfo ti){       if(bNotifyWhenUpdate){           Intent it = new Intent(ACTION_UPDATE);           it.putExtra("progress", ti.getProgress());           DownloadService.this.sendBroadcast(it);       }   }      private void notifyFinished(boolean isSuccess){       if(bNotifyWhenFinished){           Intent it = new Intent(ACTION_FINISHED);           it.putExtra("success", isSuccess);           DownloadService.this.sendBroadcast(it);       }   }           5.3 TaskInfo類   [java]   package com.chris.download.service.Bean;      import java.io.Serializable;      public class TaskInfo implements Serializable {          private static final long serialVersionUID = -2810508248527772902L;          public static final int WAITING = 0;       public static final int RUNNING = 1;       public static final int CANCELED = 2;              private int taskId;       private String taskName;       private int progress;       private int status;              public int getTaskId() {           return taskId;       }       public void setTaskId(int taskId) {           this.taskId = taskId;       }       public String getTaskName() {           return taskName;       }       public void setTaskName(String taskName) {           this.taskName = taskName;       }       public int getProgress() {           return progress;       }       public void setProgress(int progress) {           this.progress = progress;       }       public int getStatus() {           return status;       }       public void setStatus(int status) {           this.status = status;       }   }       六、總結           本篇只是帶大家入門,仍有許多可以改進的地方,如:使用多線程以及如何同步線程隊列,多線程對應在狀態欄上的多個RemoteViews更新,Activity中顯示下載任務隊列及其各任務的狀態等。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved