《Android開發藝術探索》之理解四大組件的工作過程、服務上(十)

                                                                        第九章  四大組件的工作過程
          四大組件分別是Activity,Service,BroadcastReceiver,ContentProvider。如何使用是最基礎的,但我們在本章想談更多:(1)對四大組件的運行狀態和工作方式做一個概括性的描述;(2)對四大組件的運行過程進行分析。
(一)四大組件的運行狀態
        除了BroadcastReceiver之外,其他三個組件都必須在AndroidManifest.xml中註冊;BroadcastReceiver有動態註冊(在代碼中定義)和靜態註冊(XML兩種)。除了ContentProvider不需要藉助Intent,其他三者都需要Intent。
1.1.Activity的運行狀態
       Activity是一種展示型組件,主要作用是展示一個界面並和用戶交互,扮演的是前臺界面的角色。用戶可感知,Activity由Intent觸發,可以用顯示Intent明確指定某個Activity或者多個Activity組件,也可以用隱式Intent去指定某個Activity或者多個Activity組件,一個Activity也存在特定的啓動模式(Standard、SingleTop、SingleTask、SingleInstance)。
1.2.Service的運行狀態
       Service是一種計算型組件,用於在後臺執行一系列計算任務。Service工作於後臺,無法直接感知。Activity有一種工作狀態,而Service有兩種狀態:啓動狀態和綁定狀態。
       Service在啓動狀態時,可以做一些後臺計算並且不需要與外界直接交互,但是運行在主線程中,所以儘量不要耗時,或者開闢線程去處理耗時任務。但當Service處於綁定狀態的時候,內部同樣可以做後臺計算,並且還可以與外界靈活交互,Service組件停止的話就需要先stopService並且unBindService。
1.3.BroadcastReceiver的運行狀態
        BroadcastReceiver是一種消息型組件,用於在不同的組件乃至不同的應用之間傳遞消息,同樣無法被用戶感知。被稱爲廣播。廣播有兩種註冊方式,靜態和動態註冊。靜態註冊需要在XML中註冊,並在應用安裝時被系統解析,無需應用啓動就可收到廣播;動態註冊廣播需要Context.registerReceiver來註冊,並且在不需要的時候通過Context.unregisterReceiver來解除廣播。必須啓動才能註冊解除廣播,通過sendBroadCast進行傳播。標準廣播(異步執行的廣播,無先後順序,無法截斷),有序廣播(同步執行的廣播,又先後順序,可被截斷)。OnReceiver方法。
1.4.ContentProvider的運行狀態
      ContentProvider是一種數據共享型組件,用於向其他組件乃至其他應用共享數據,無法被用戶直接感知。它的內部需要實現增刪查改四個操作,在他的內部維持着一份數據集合,這個數據集合既可以通過數據庫來實現,也可以採用其他類型來實現,ContentProvider內部的inset、update、delete和query方法需要處理好線程同步,因爲這幾個方法都是在線程池中被調用的。ContentResolver方法。
(二)Activity的工作過程
       步驟一:顯示調用啓動,startActivity開始啓動,調用了startActivityForResult方法。

Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);

    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
}

       步驟二:startActivityForResult中調用了execStartActivity方法。然後再調用execStartActivity的 ActivityManager.getDefault().startActivity方法,該通過Binder實現ActivityManagerService啓動

  public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
//ActivityThread的ApplicationThread在啓動過程中發揮着很重要的作用,接下來我們看mInstrumentation.execStartActivity這個方法
          .....
}
public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, android.app.Activity target,
            Intent intent, int requestCode, Bundle options) {
        ...
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
//ActivityManager.getDefault()實際上是AMS,啓動過程轉移至AMS中,
            int result = ActivityManager.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
//假設XML中沒有定義該Activity,在這裏進行檢查報錯。
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
}

      步驟三:emmm..經過了複雜的過程,Activity的啓動過程最終還是回到了ActivityThread中,performLaunchActivity方法最終完成了對象的創建和啓動。最終執行了onResume方法。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);
           ...
        }
       ....
    }

        需要說明的是:performLaunchActivity方法主要完成如下幾個事件:1.從ActivityClientRecord中獲取待啓動的Activity組件信息;2.通過instrumentation的newActivity方法使用類加載器創建Activity對象;(activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);)3.通過LoadedApk的makeApplicatiton方法來創建Application對象(public Application makeApplication(...) {...});4.創建ContextImpl對象並通過Activity的attach方法來完成一些重要數據的初始化。5.調用Activity的onCreate方法。
(三)後臺默默的勞動者-服務
      Android從一開始就支持後臺功能,使得應用程序即使在關閉的情況下仍然可以在後臺運行。譬如:一邊打電話,一邊後臺掛着QQ。下面主要聚焦在Android多線程編程(解析異步消息處理機制<Handler、Message的處理機制>、異步通信任務AsynacTask)、服務的基本用法、生命週期以及高階技巧(前臺服務與IntentService),
3.1.服務是什麼?
       Android實現程序後臺運行的解決方案,適合去執行不需要和用戶交互還要求長期運行的任務。服務運行不依賴於任何用戶界面,當程序被切換至後臺或者打開另一個App,服務依然可以正常運行。
       服務不是獨立進程,而依賴於創建服務所在App進程,當App進程被殺掉時,所有依賴於該進程的服務也會停止。服務並不會自動開啓線程,所有線程都是默認運行在主線程的,所以存在被阻塞住的情況。
3.2.Android多線程編程
       耗時任務譬如發送網絡請求,需要放在子線程中運行,如果放在主線程運行,則會有阻塞住的風險。
3.2.1.線程的基本用法
       與Java類似,使用相同語法。
       定義並啓動一個線程。
       方法一:新建類繼承自Thread並重寫父類的run()方法,在其中執行耗時邏輯。如何調用?new出一個示例並調用它的start方法。

 //啓動剛纔定義的線程
    new MyThread().run();

    //定義一個線程,繼承自Thread類並實現其run方法
    class MyThread extends Thread{
        @Override
        public void run() {
            //處理具體耗時邏輯。
        }
}

       方法二:繼承的方式耦合性高,可以使用實現Runnable接口的方式來定義一個線程。

    //啓動剛纔定義的實現Runnable接口的線程。
    MyThread01 myThread01 = new MyThread01();
    //Thread構造函數接受一個Runnable參數並傳至Thread構造函數中。
    new Thread(myThread01).start();

    //實現Runnable接口會導致較低的耦合性,
    class MyThread01 implements Runnable{
        @Override
        public void run() {
            //處理具體邏輯
        }
}

       方法三:匿名類的方式進行實現運行一個線程。

       new Thread(new Runnable() {
            @Override
            public void run() {
                //處理耗時具體邏輯
            }
        }).start();

3.2.2.在子線程中更新UI
    Andreoid UI線程是不安全的。如果需要更新App的UI元素,必須在主線程中進行,否則就會出現異常。子線程更新UI會報錯:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

      場景:點擊按鈕在子線程完成耗時任務,在主線程更新UI。點擊Button在子線程中通過異步消息處理機制改變UI的內容。
解決方案:
     步驟一:在子線程中創建一個Message對象,並在這裏對具體的Message進行處理,執行一些耗時操作;然後將handler.sendMessage將Message對象發送出去;
     步驟二:新建Handler對象,並重寫它的handleMessage方法,在這裏對剛纔傳遞過來的具體的Message進行處理。handleMessage方法中的代碼是在主線程中運行的,在這裏進行UI操作。
     代碼示例:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn_click;
    private TextView tv_content;
    //整型常量UPDATE_TEXT用來更新TextView這個操作
    public static final int UPDATE_TEXT = 1;
    //新建Handler對象,並重寫它的handleMessage方法,在這裏對具體的Message進行處理。
    private Handler handler = new Handler(){
        //handleMessage方法中的代碼是在主線程中運行的,在這裏進行UI操作。
        @Override
        public void handleMessage(Message msg) {
            //如果發現Message的what字段等於UPDATE_TEXT,修改TextView的內容
            switch (msg.what){
                case UPDATE_TEXT:
                    //在這裏可以進行UI操作
                    tv_content.setText("Nice to meet u");
                    break;
                default:
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_click = (Button) findViewById(R.id.btn_click);
        tv_content = (TextView) findViewById(R.id.text);
        btn_click.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_click:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //創建一個Message對象,並在這裏對具體的Message進行處理
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);//將Message對象傳遞出去。
                    }
                }).start();
        }
    }
}

3.2.3.解析異步消息處理機制
      Android異步消息處理主要有四個部分:Message、Handler、MessageQueue、Looper。上一小節講了Message和Handler。
3.2.3.1.概念介紹:
      Message:線程之間傳遞的消息。內部可攜帶少量的信息,可用於不同線程之間交互。譬如:Message的what字段,此外還有arg1、arg2字段攜帶整形數據;obj字段攜帶Object對象。
      Handler:處理者。用於發送和處理消息。發送消息一般是Handler的sendMessage方法,經過一系列處理後,最終會傳遞到Handler的handleMessage方法中。
      MessageQueue:消息隊列。存放所有通過Handler發送的消息,這部分消息存儲於消息隊列中,等待被處理。每個線程只會有一個MessageQueue對象。
     Looper:每個線程中MessageQueue的管家,調用Looper的loop方法後,就會進入到一個無線循環中,每當發現MessageQueue存在一條消息,就會將它去除,並傳遞到Handler的handleMessage方法中,每個線程同樣也只有一個Looper。
3.2.3.2.工作流程:
      步驟一:主線程中創建Handler對象,重寫handleMessage方法,當子線程需要更新UI,構建Message對象,通過Handle發送出去;
      步驟二:該消息被添加進MessageQueue隊列中等待被處理,Looper方法一直嘗試從MessageQueue中讀取待處理消息,並通過dispatchMessage方法分發給handleMessage方法;
      步驟三:handleMessage方法在主線程操作,可以在這裏操作UI。

                                           
3.2.4.AsyncTask的用法
       爲了更方便在子線程中更新UI,Android提供了AsyncTask,更簡單的從子線程到主線程。背後的處理機制也是基於異步消息處理機制的。
        AsyncTask是一個抽象類,並制定三個泛型參數,這三個參數的用途如下:1.Params:執行AsyncTask需要傳入的參數,可用於在後臺任務中使用;2.Progress:後臺任務執行時,如果需要界面上顯示進度,此爲進度單位;3.Result:任務執行結束後,需要對結果返回。
3.2.4.1.幾個關鍵API方法:
       1.onPreExecute在後臺任務開始執行之前進行調用,用於界面的初始化操作,譬如顯示一個進度條對話框等。
       2.doInBackground(處理具體耗時任務):會在子線程中運行,在這裏處理所有耗時任務,在這裏是不可以更新UI元素,如果需要反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。
       3.onProgressUpdate(進行UI操作):後臺調用了publishProgress(Progress...)方法後,onProgressUpdate將會被很快調用,其方法中攜帶的參數是從後臺任務中傳遞過來的,在這裏對UI進行更新,利用參數中的數值可以對界面元素進行相應的更新。
       4.onPostExecute(任務收尾工作):當後臺任務執行完畢後並使用return語句返回後,該方法很快被調用。返回數據會作爲參數傳遞到此方法中。可利用返回數據來進行UI操作。譬如:提醒任務執行結果,以及關閉進度條對話框等。
3.2.4.2.代碼示例:

//第一個參數是Void,執行AsyncTask無需傳入參數給後臺任務;第二個泛型參數是Integer,使用整形數據作爲進度顯示單位;
    //第三個參數是使用布爾類型用來反饋執行結果。
public class DownLoadTask extends AsyncTask<Void,Integer,Boolean>{
    //1.onPreExecute在後臺任務開始執行之前進行調用,用於界面的初始化操作,譬如顯示一個進度條對話框等。
    @Override
    protected void onPreExecute() {
        super.onPreExecute();//顯示進度條
    }
    //2.該方法中的所有代碼都會在子線程中運行,在這裏處理所有耗時任務,在這裏是不可以更新UI元素,如果需要反饋當前任務的執行進度,
    // 可以調用publishProgress(Progress...)方法來完成。
    @Override
    protected Boolean doInBackground(Void... params) {
        return null;//執行後臺耗時任務
    }
    //3.後臺調用了publishProgress(Progress...)方法後,onProgressUpdate將會被很快調用,其方法中攜帶的參數是從後臺任務中傳遞過來的,
    // 在這裏對UI進行更新,利用參數中的數值可以對界面元素進行相應的更新。
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);//在這裏顯示下載的進度
    }
    //4.當後臺任務執行完畢後並使用return語句返回後,該方法很快被調用。返回數據會作爲參數傳遞到此方法中。可利用返回數據來進行UI操作。
    //譬如:提醒任務執行結果,以及關閉進度條對話框等。
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);//關閉進度對話框並提示下載結果
    }
}

3.2.4.3.啓動這個任務

  new DowloadTask().execute();

        使用publishProgress方法完成子線程到主線程的切換。
3.3.服務的基本用法:
3.3.1.定義一個服務

        右擊包名->New->Service->Service。Exported表示是允許除了當前App之外的其他程序訪問;Enabled屬性表示是否啓用該服務。
        MyService繼承自Service,是一個服務。每一個服務都需要註冊在XML中的。複寫以下的方法。onCreate:在服務創建的時候調用;onStartCommand:每次服務啓動的時候調用,希望服務一旦啓動就立刻執行某個動作,則將其寫在onStartCommand中;onDestroy:服務銷燬的時候調用,回收不再使用的資源;onBind:唯一的抽一的抽象方法,需要在子類中實現,暫時忽略。
3.3.2.啓動和停止服務
啓動服務:                

//構建Intent對象
    Intent startIntent = new Intent(this,MyService.class);
    //調用startService來啓動MyService這個任務。定義於Context類中的,所以可以直接調用startService方法。
    startService(startIntent);//啓動服務

結束服務:

Intent stopIntent = new Intent(this,MyService.class);
//除了這種調用方式外,還有那些停止方法?MyService內部的任何一處位置調用StopSelf即可讓該服務停止。
stopService(stopIntent);//停止服務

      執行的操作:startService->stopService->startService->startService,截圖如下所示,理解其生命週期。

                                            
3.3.3.活動與服務之間通信
       上一節類似於活動通知服務:“可以幹了!”但幹了什麼,什麼時候搞的咋樣活動完全不知道。那麼如何讓服務活動和服務之間更緊密一些?onBind方法瞭解一下。
       場景:在MyService模擬一個簡單的下載功能。
       步驟一:創建一個專門的Binder對象來對下載功能進行管理。新建DowbLoadBinder繼承自Binder,提供了開始下載和查看下載的方法,並提供打印日誌。onBind方法中返回這個實例。

public class MyService extends Service {
    private DowloadBinder mBinder = new DowloadBinder();
    private final static String TAG = "MyService" ;
    public MyService() {
    }
     ....
    //唯一的抽一的抽象方法,需要在子類中實現,暫時忽略。
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
    class DowloadBinder extends Binder{
        public void startDownload(){
            Log.d(TAG, "startDownload exec: ");
        }
        public int getProgress(){
            Log.d(TAG, "getProgress exec: ");
            return 0;
        }
    }
}

       步驟二:創建ServieConnection內部匿名類,在裏面重寫了onServiceConnected和onServiceDisconnected兩個方法,會在活動綁定與服務綁定以及解除綁定的時候調用。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MyService.DowloadBinder dowloadBinder;
    private Button btn_start, btn_stop,btn_bind_service,btn_unbind_service;
   private ServiceConnection connection = new ServiceConnection() {
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           //向下轉型得到DownloadBinder實例,有了這個實例,活動和服務之間建立了密切的關係。
           dowloadBinder = (MyService.DowloadBinder) service;
           //可根據不同場景具體調用dowloadBinder中的不同方法,指揮服務去幹什麼
           dowloadBinder.startDownload();
           dowloadBinder.getProgress();
       }

       @Override
       public void onServiceDisconnected(ComponentName name) {
       }
   };
    @Override
    protected void onCreate(Bundle savedInstanceState) { 
        .....
        btn_bind_service = (Button) findViewById(R.id.btn_bind_service);
        btn_unbind_service = (Button) findViewById(R.id.btn_unbind_service);
        ....
        btn_bind_service.setOnClickListener(this);
        btn_unbind_service.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            ....
            case R.id.btn_unbind_service:
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

                                         

3.3.4.活動與服務之間通信的所有代碼:
MyService.java:

//繼承自Service,是一個服務。每一個服務都是註冊在XML中的。
public class MyService extends Service {
    private DowloadBinder mBinder = new DowloadBinder();
    private final static String TAG = "MyService" ;
    public MyService() {
    }
    //服務創建的時候調用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate exec: ");
    }
    //每次服務啓動的時候調用,希望服務一旦啓動就立刻執行某個動作,則將其寫在onStartCommand中。
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand exec: ");
        return super.onStartCommand(intent, flags, startId);
    }
    //服務銷燬的時候調用,回收不再使用的資源。
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy exec: ");
        super.onDestroy();
    }
    //唯一的抽一的抽象方法,需要在子類中實現,暫時忽略。
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
    class DowloadBinder extends Binder{
        public void startDownload(){
            Log.d(TAG, "startDownload exec: ");
        }
        public int getProgress(){
            Log.d(TAG, "getProgress exec: ");
            return 0;
        }
    }
}

MainActivity.java:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MyService.DowloadBinder dowloadBinder;
    private Button btn_start, btn_stop,btn_bind_service,btn_unbind_service;
   private ServiceConnection connection = new ServiceConnection() {
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           //向下轉型得到DownloadBinder實例,有了這個實例,活動和服務之間建立了密切的關係。
           dowloadBinder = (MyService.DowloadBinder) service;
           //可根據不同場景具體調用dowloadBinder中的不同方法,指揮服務去幹什麼
           dowloadBinder.startDownload();
           dowloadBinder.getProgress();
       }

       @Override
       public void onServiceDisconnected(ComponentName name) {
       }
   };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_start = (Button) findViewById(R.id.btn_start);
        btn_stop = (Button) findViewById(R.id.btn_stop);
        btn_bind_service = (Button) findViewById(R.id.btn_bind_service);
        btn_unbind_service = (Button) findViewById(R.id.btn_unbind_service);
        btn_start.setOnClickListener(this);
        btn_stop.setOnClickListener(this);
        btn_bind_service.setOnClickListener(this);
        btn_unbind_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_start:
                //構建Intent對象
                Intent startIntent = new Intent(this,MyService.class);
                //調用startService來啓動MyService這個任務。定義於Context類中的,所以可以直接調用startService方法。
                startService(startIntent);//啓動服務
                break;
            case R.id.btn_stop:
                Intent stopIntent = new Intent(this,MyService.class);
                //除了這種調用方式外,還有那些停止方法?MyService內部的任何一處位置調用StopSelf即可讓該服務停止。
                stopService(stopIntent);//停止服務
                break;
            case R.id.btn_bind_service:
                //構建Intent對象,調用bindService實現Activity和Service的綁定
                Intent binIntent = new Intent(this,MyService.class);
                //第一個參數是剛構建出來的Intent對象,第二個參數是前面創建的ServiceConnection實例;
                //第三個參數BIND_AUTO_CREATE是活動和服務綁定後自動創建服務。會使得onCreate方法得以執行但onStartCommand不會執行。
                bindService(binIntent,connection,BIND_AUTO_CREATE);//綁定服務
                break;
            case R.id.btn_unbind_service:
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

3.4.服務的生命週期
       項目中任何位置調用Context的startService後的生命週期:
       onCreate(如果未創建,創建時使用)->onStartCommand(每調用一次startService執行一次)->onDestroy(但調用stopService或者stopSelf時候會停止,因爲不管調用了多少次startService,系統中只會創建一個實例)
        Context中使用bindService來獲取服務的持久連接:
        onCreate->onBind(獲取IBinder對象的實例,活動服務開始通信)->onUnbind(解綁服務)->onDestroy(銷燬服務)
3.5.服務的更多技巧
3.5.1.使用前臺服務

       服務在後臺很容易被殺死回收掉,如果希望服務一直保持運行狀態,不會因爲系統內存不足導致被回收,就考慮使用前臺服務吧。前臺服務與普通服務最大區別就是:它一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄後可以看到詳細信息,類似於通知的效果。如何創建前臺服務?

public class MyService extends Service {   
.....
  public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate exec: ");
        Intent intent = new Intent(MyService.this,MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        //構建Notification對象及相應的PendingIntent方法。
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher_round))
                .setContentIntent(pi)
                .build();
        //並沒有使用建NotificationManager讓他顯示出來,而是使用startForeground讓MyService變成一個前臺服務。
        startForeground(2,notification);
    }
....
}

3.5.2.使用IntentService
       服務中的代碼都是默認在主線程中的,如果直接在服務中處理耗時任務,會導致ANR。解決方法:在onStartCommad方法裏開啓子線程,處理耗時邏輯。
       但是這種服務一旦啓動,會一直處於運行狀態,必須調用stopService或者stopSelf使其停止下來。

  @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand exec: ");
        new Thread(new Runnable() {
            @Override
            public void run() {
                //處理具體邏輯
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

       這種寫法不復雜,但程序員們老忘。所以Android提供了一個IntrentService解決了忘記開啓線程、忘記調用stopSelf的尷尬。
       步驟一:建立MyIntentService繼承自IntentService;提供無參構造函數,必須在其內部調用父類有參構造方法;子類實現在onHandleIntent方法中,用來處理耗時任務。打印當前線程的ID。XML中記得註冊。

public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";
    public MyIntentService(){
        super("MyIntentService");//調用有參構造函數
    }
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the wqwidong1orker thread, important only for debugging.
     */
    public MyIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.i(TAG, "onHandleIntent MyIntentService: Thread is:"+Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy MyIntentService: ");
    }
}

        步驟二:啓動Service,與之前一樣。

   Log.d("MainActivity", "Main Thread is: "+Thread.currentThread().getId());
   Intent intentservice = new Intent(this,MyIntentService.class);
   startService(intentservice);

                 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章