Android查缺補漏--Service和IntentService

來源:互聯網
上載者:User

標籤:override   通過   inter   context   other   無反應   gpo   也會   9.1   

Service的運行不依賴介面,即使程式被切換到後台,Service仍然能夠保持正常運行。當某個應用程式進程被殺掉時,所有依賴於該進程的Service也會停止運行。

Service 分為啟動狀態和綁定狀態。當處於僅啟動狀態時,通過 stopService或 stopSelf 即可停止 Service。當處於綁定狀態時需要通過 unBindService 和 stopService 結合使用才能完全停止 Service。

一、Service的生命週期(onCreate()-onStartCommand()-onDestory()):
  • 無論在項目的任何地方調用Context.startService(),相應的Service就會啟動,首次建立時會調用onCreate(),接著回調onStartCommand()。服務啟動後就一直保持運行狀態,直到stopService()或stopSelf()被調用。
  • onCreate:只會在建立Service時調用一次。
  • onStartCommand:每調用一次startService(),就會調用一次此方法。
  • onDestory:調用stopService或stopSelf時,會調用此方法。
  • onBind:調用bindService時回調。
二、如何建立一個運行在前台的Service

Service預設運行在前台,優先順序較低,當系統記憶體不足時就面臨會被回收的危險。如果不希望被回收或者處於某種需要,我們就可以通過startForeground將Service運行在前台。
下面是一個將Service設定為前台服務的樣本,同時在啟動Service時添加了通知欄可以更直觀的看到前台服務的運行情況,點擊通知欄訊息可以跳轉到MainActivity。

/** * 前台Service */public class ForegroundService extends Service {    @Override    public void onCreate() {        super.onCreate();        showNotification();    }    private void showNotification() {        final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)                .setSmallIcon(R.mipmap.ic_launcher)                .setContentTitle("我是ContentTitle")                .setContentText("我是ContentText");        // 建立通知被點擊時出發的Intent        Intent intent = new Intent(this, MainActivity.class);        // 建立任務棧        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);        stackBuilder.addParentStack(MainActivity.class);        stackBuilder.addNextIntent(intent);        PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);        builder.setContentIntent(pendingIntent);        final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);        // 構建通知        final Notification notification = builder.build();        // 顯示通知        notificationManager.notify(0, notification);        // 啟動為前台服務        startForeground(0, notification);    }    @Override    public IBinder onBind(Intent intent) {        throw new UnsupportedOperationException("Not yet implemented");    }}
三、IntentService和用Service執行耗時任務。

普通的Service雖然運行在後台,但這並不代表它運行在子線程中,它仍然運行在UI線程中。可以實驗一下看看Service到底有沒有運行在UI線程裡面,建立一個MyService,然後在其onCreate方法裡面添加一個耗時任務:

public class MyService extends Service {    private static final String TAG = MyService.class.getSimpleName();        // 省略其他代碼...    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG, "onCreate: 線程:" + Thread.currentThread());        int n = 10;        while (n-- > 0) {            try {                Thread.sleep(1000);                Log.i(TAG, "我是一個耗時任務,執行剩餘時間:" + n);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}// 輸出:12-07 14:50:54.683 5506-5506/cn.codingblock.androidadvancestudy I/MyService: onCreate: 線程:Thread[main,5,main]12-07 14:50:55.684 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:912-07 14:50:56.684 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:812-07 14:50:57.685 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:712-07 14:50:58.685 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:612-07 14:50:59.686 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:512-07 14:51:00.686 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:412-07 14:51:01.687 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:312-07 14:51:02.687 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:212-07 14:51:03.688 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:112-07 14:51:04.688 5506-5506/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:0

從log可以看到線程的名字是Thread[main,5,main]即主線程,並且程式在10s只能執行這個耗時任務,點擊其他動作均無反應,成功的“卡住了”。這個樣本證明了普通Service運行在UI線程中。

那麼如果我們需要用Service做一些耗時任務該怎麼辦:

  • 1、在普通的Service中開啟一個子線程,讓子線程去執行耗時任務。
public class MyService extends Service {    private static final String TAG = MyService.class.getSimpleName();    // 省略其他代碼...    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG, "onCreate: 線程:" + Thread.currentThread());        new Thread(new Runnable() {            @Override            public void run() {                int n = 10;                while (n-- > 0) {                    try {                        Thread.sleep(1000);                        Log.i(TAG, "我是一個耗時任務,執行剩餘時間:" + n);                        Thread.yield();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }).start();    }}
  • 2、使用IntentService,在onHandleIntent()方法中執行耗時任務。
public class MyIntentService extends IntentService {        String TAG = MyIntentService.class.getSimpleName();    public MyIntentService() {        super("MyIntentService");    }    @Override    protected void onHandleIntent(Intent intent) {        // 這裡非主線程,可在這裡做一些耗時任務        Log.i(TAG, "onHandleIntent: 線程:" + Thread.currentThread());        int n = 10;        while (n-- > 0) {            try {                Thread.sleep(1000);                Log.i(TAG, "我是一個耗時任務,執行剩餘時間:" + n);                Thread.yield();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

接下來在Activity中我們也建立一個耗時任務(mainTask),並讓這個任務跑在UI線程,然後同時 start MyService、MyIntentService 和 mainTask 並觀察Log。

public class ServiceTestActivity extends AppCompatActivity implements View.OnClickListener {    String TAG = ServiceTestActivity.class.getSimpleName();        Context context;    private Button btn_start_3_thread;    // ignore other code...    @Override    public void onClick(View view) {        switch (view.getId()) {            case R.id.btn_start_3_thread:                // 同時啟動 MyService 、MyIntentService 和 mainTask 並觀察log                startService(new Intent(context, MyService.class));                startService(new Intent(context, MyIntentService.class));                mainTask();                break;        }    }    private void mainTask() {        int n = 10;        while (n-- > 0) {            try {                Thread.sleep(1000);                Log.i(TAG, "我是一個耗時任務,執行剩餘時間:" + n);                Thread.yield();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}// 輸入如下:12-07 15:08:50.074 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:912-07 15:08:51.075 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:812-07 15:08:52.076 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:712-07 15:08:53.076 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:612-07 15:08:54.077 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:512-07 15:08:55.077 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:412-07 15:08:56.078 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:312-07 15:08:57.078 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:212-07 15:08:58.079 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:112-07 15:08:59.079 22693-22693/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:012-07 15:08:59.095 22693-22693/cn.codingblock.androidadvancestudy I/MyService: onCreate: 線程:Thread[main,5,main]12-07 15:08:59.096 22693-22693/cn.codingblock.androidadvancestudy I/MyService: onStartCommand: 12-07 15:08:59.102 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: onHandleIntent: 線程:Thread[IntentService[MyIntentService],5,main]12-07 15:09:00.099 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:912-07 15:09:00.103 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:912-07 15:09:01.100 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:812-07 15:09:01.104 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:812-07 15:09:02.100 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:712-07 15:09:02.104 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:712-07 15:09:03.101 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:612-07 15:09:03.105 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:612-07 15:09:04.101 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:512-07 15:09:04.105 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:512-07 15:09:05.101 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:412-07 15:09:05.106 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:412-07 15:09:06.102 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:312-07 15:09:06.106 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:312-07 15:09:07.103 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:212-07 15:09:07.107 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:212-07 15:09:08.103 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:112-07 15:09:08.107 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:112-07 15:09:09.103 22693-23357/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:012-07 15:09:09.107 22693-23358/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:0

從log中可以看到,先執行完了 ServiceTestActivity 中的 mainTask 耗時任務才啟動兩個 Service,實驗姿勢貌似有點不對。我們先分析一下這三行代碼的執行順序:

// 標記1startService(new Intent(context, MyService.class));startService(new Intent(context, MyIntentService.class));mainTask();

按理說應該是先啟動MyService,並開始執行MyService的非同步任務,再啟動MyIntentService並開始執行其非同步任務,然後再開始執行mainTask任務,由於前兩者都是非同步,所以輸入結果應該三個非同步任務交叉執行。

但是,從真正的輸入結果中看到執行完了mainTask任務後才開始啟動了兩個Service,這是怎麼一回事呢?

其實這跟 Service 的啟動流程有關係,Service的啟動比較複雜,這裡說一下大體過程,大體總結起來 Service 的啟動流程經曆了三個階段:

  1. 先從App進程調用startSesrvice;
  2. 然後通過IPC調用進入系統進程(system_service)在AMS(ActvitiyManagerService)中完成生命週期管理及其他一系列的工作;
  3. 最後再回到App進程中完成Service對象的建立。

其中第1步和第3步都是同步執行的,第2步在AMS中對Service的相關操作是非同步執行的,所以導致了實驗的這個結果。

整理一下,再重新對這三行代碼(標記1)分析一下:

  1. 調用第一行startService來啟動MyService,這一步是同步執行,直到都到AMS階段開始非同步執行,而此時就有機會執行主線程中的第二行startService代碼了。轉2
  2. 與1同理,在啟動MyIntentService時執行到AMS階段開始非同步,而此時就有機會執行第三行代碼。轉3
  3. 調用mainTask()方法,開始執行主線程的耗時任務,而此時是同步的,所以在此任務執行完之前線程不會被切換。即使上面兩個啟動的Service的方法在AMS已經執行完畢,轉入到主線程時發現主線程正在執行mainTask的耗時任務,此時也只能等待其執行完畢,然後建立Service,再開啟兩個Service中的非同步任務。

好了,原因查明了。真是處處有驚喜,處處有收穫!

解決上面問題的方法很簡單,就是等Service啟動完成之後我們再調用Activity中的mainTask()方法即可,比如把他們用兩個按鈕來控制,修改後輸出的log如下:

12-07 17:53:10.905 29898-29898/cn.codingblock.androidadvancestudy I/MyService: onCreate: 線程:Thread[main,5,main]12-07 17:53:10.910 29898-29898/cn.codingblock.androidadvancestudy I/MyService: onStartCommand: 12-07 17:53:10.916 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: onHandleIntent: 線程:Thread[IntentService[MyIntentService],5,main]12-07 17:53:11.907 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:912-07 17:53:11.916 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:912-07 17:53:12.824 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:912-07 17:53:12.907 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:812-07 17:53:12.916 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:812-07 17:53:13.825 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:812-07 17:53:13.908 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:712-07 17:53:13.917 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:712-07 17:53:14.826 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:712-07 17:53:14.910 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:612-07 17:53:14.918 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:612-07 17:53:15.827 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:612-07 17:53:15.911 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:512-07 17:53:15.919 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:512-07 17:53:16.827 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:512-07 17:53:16.912 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:412-07 17:53:16.920 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:412-07 17:53:17.828 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:412-07 17:53:17.913 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:312-07 17:53:17.922 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:312-07 17:53:18.829 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:312-07 17:53:18.914 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:212-07 17:53:18.923 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:212-07 17:53:19.829 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:212-07 17:53:19.915 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:112-07 17:53:19.924 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:112-07 17:53:20.830 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:112-07 17:53:20.916 29898-30561/cn.codingblock.androidadvancestudy I/MyService: 我是一個耗時任務,執行剩餘時間:012-07 17:53:20.924 29898-30563/cn.codingblock.androidadvancestudy I/MyIntentService: 我是一個耗時任務,執行剩餘時間:012-07 17:53:21.830 29898-29898/cn.codingblock.androidadvancestudy I/ServiceTestActivity: 我是一個耗時任務,執行剩餘時間:0

從log來看,普通Service中開始子線程執行的耗時任務和IntentService中的任務還有主線程中的任務確實在交叉執行。

最後想說的是,本系列文章為博主對Android知識進行再次梳理,查缺補漏的學習過程,一方面是對自己遺忘的東西加以複習重新掌握,另一方面相信在重新學習的過程中定會有巨大的新收穫,如果你也有跟我同樣的想法,不妨關注我一起學習,互相探討,共同進步!

參考文獻:

  • 《Android開發藝術探索》
  • 《Android開發進階從小工到專家》

Android查缺補漏--Service和IntentService

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.