Android IPC 之Service 還可以這么理解

前言

IPC 系列文章:
建議按順序閱讀。

Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎
Android IPC 之Binder應用
Android IPC 之AIDL應用(上)
Android IPC 之AIDL應用(下)
Android IPC 之Messenger 原理及應用
Android IPC 之服務端回調
Android IPC 之獲取服務(IBinder)
Android Binder 原理換個姿勢就頓悟了(圖文版)

Android四大組件:Activity、Service、BroadcastReceiver、ContentProvider。它們的作用分別是:

Activity--->配合View展示界面
Service--->長時間在后臺運行不與用戶直接交互
BroadcastReceiver--->接收廣播
ContentProvider--->提供數據給其他模塊使用

本篇文章著重分析Service,通過它,你將了解到:

1、Service 開啟與停止
2、Service 執行耗時操作
3、Service 與Thread、Manager關系
4、Service 進程間通信初相識

1、Service 開啟與停止

先定義一個Service類,名為MyService,繼承自Service。

public class MyService extends Service {

    public MyService() {
        super();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //必須重寫該方法,該方法為抽象方法
        //綁定開啟Service會調用該方法
        return null;
    }

    @Override
    public void onCreate() {
        //Service初次創建會調用該方法,我們可以做一些初始化操作, 與onDestroy()相對
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //每次顯示啟動Service都會調用該方法
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        //Service銷毀時調用該方法,在該方法里我們可以做釋放資源的操作,與onCreate()相對
        super.onDestroy();
    }
}

這是一個最簡單的Service Demo。
接著想要使用該Service,還需要在AndroidManifest.xml里注冊:

        <service android:name=".service.MyService">
        </service>

Service定義好了,怎么使用呢?開啟Service有兩種方式:

1、顯示開啟------> startService(Intent intent)
2、綁定開啟------> bindService(Intent intent)
這倆都是Context里的方法

顯示開啟Service

構造Intent,傳入startService(Intent intent)里。

    private void startService() {
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }

通過此種方式,Service調用方法如下:


image.png

需要注意的點是:

當再次開啟一個已經存在的Service的時候,onStartCommand(xx)依然會被調用。

顯示關閉Service

顯示開啟Service后,Service就已經啟動了。
若要關閉Service,通過如下方法:

    private void stopService() {
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }

或者在Service做完了事自己結束:

stopSelf();

綁定開啟Service

通過上面的例子,可以看出顯示開啟Service后,調用者就和Service沒有關聯了。比如調用者是個Activity,Service的作用是不斷地計數。在顯示開啟Service的場景下,會存在兩個問題:

1、Activity無法直接(間接通過廣播等方法)拿到Service計數結果,也就是說沒法拿到Service引用。
2、當Activity退出的時候,若不是主動停止Service,那么Service將不會被關閉。不太恰當的比喻是:"管生不管養"

而綁定開啟Service正好可以解決上面的問題。
為了實現綁定開啟Service,在上面Demo的基礎上稍微做修改。

定義MyBinder繼承自Binder:

public class MyBinder extends Binder {
    //持有Service引用
    private Service service;
    public MyBinder(Service service) {
        this.service = service;
    }

    //返回Service引用
    public Service getService() {
        return service;
    }
}

在顯示開啟Service過程中,我們重寫了onBind(xx),直接返回的是null,該場景下該方法并沒有調用。而當綁定開啟Service時,需要返回IBiner的引用給綁定者使用。

#MyService.java
    public IBinder onBind(Intent intent) {
        return new MyBinder(this);
    }

返回的是MyBinder對象的引用,該對象持有了MyService引用。
綁定者在哪里接收IBinder的引用呢?
在Activity里定義ServiceConnection匿名內部類:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //service即是從onBind(xx)方法返回的(綁定者和Service同一進程)
            MyBinder myBinder = (MyBinder)service;
            //獲取Service的引用
            MyService myService = (MyService)myBinder.getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //Service被銷毀時調用(內存不足等,正常解綁不會走這)
        }
    };

有了ServiceConnection 引用,接著就需要和Service建立聯系,建立聯系的過程即是綁定開啟Service的過程。

    private void bindService() {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

從上面可以看出,綁定開啟的流程:

1、將Service和ServiceConnection建立聯系(綁定開啟)
2、在Service的onBind(xx)方法里返回IBinder引用,該引用持有Service引用
3、綁定成功后會調用ServiceConnection的onServiceConnected(xx)返回IBinder引用
4、通過IBinder引用就能拿到Service引用,進而操作Service

以上回答了上面的第一個問題:綁定者(Activity)無法拿到Service引用。
來看看綁定開啟Service調用方法流程:

image.png

解綁Service

既然綁定時傳入了ServiceConnection引用,可以猜測解綁時也需要傳入ServiceConnection引用,不然無法確定解綁哪個Service。

    private void unBindService() {
        unbindService(serviceConnection);
    }

手動調用該方法即可解綁Service。
當Activity綁定開啟Service后,若是Activity銷毀了,那么相應的Service也會被銷毀掉。這就解答了第二個問題。

值得注意的是:
若是顯示開啟了Service,則無法用解綁方法關閉Service。
若是綁定開啟了Service,則無法用顯示關閉Service方法。

2、Service 執行耗時操作

在Service的onCreate(xx)方法里循環計數:

#MyService.java
    @Override
    public void onCreate() {
        super.onCreate();

        while(true) {
            count++;
            try {
                Thread.sleep(1000);
            } catch (Exception e) {

            }
        }
    }

服務開啟后,過一會就會提示ANR錯誤,說明onCreate(xx)是在主線程執行的。
來看看onCreate(xx)調用棧。

#ActivityThread.java
    private class ApplicationThread extends IApplicationThread.Stub {
        ...
        //IPC通信調用該方法,表明要創建服務
        public final void scheduleCreateService(IBinder token,
                                                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;
            //發送Message
            sendMessage(H.CREATE_SERVICE, s);
        }
        ...
    }

//中間調用省略
    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        ...
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        //mH 為在ActivityThread 里構造的Handler,也就是說在主線程里構造的Handler。
        mH.sendMessage(msg);
    }

再看看接收Message的地方:

#ActivityThread.java
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ...
            case CREATE_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                //創建Service
                handleCreateService((CreateServiceData) msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case BIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
                //綁定Service
                handleBindService((BindServiceData) msg.obj);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
            case UNBIND_SERVICE:
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
                handleUnbindService((BindServiceData) msg.obj);
                //解綁Service
                schedulePurgeIdler();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                break;
                ...
        }
    }

以上分支最終會調用到MyService重寫的onCreate(xx)、onBind(xx)、onUnbind(xx)里,這些方法都是在主線程里被調用的。
既然Service各個方法是在主線程里執行,那么想要實現計數功能得子開啟線程來完成此事。

#MyService.java
    @Override
    public void onCreate() {
        super.onCreate();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    Log.d("time:", count + "");
                    count++;
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {

                    }
                }
            }
        }).start();
    }

    public int getCount() {
        return count;
    }

Service一直在計數,計數結果怎么通知給調用者呢,此處假設調用者是Activity。
依然是兩種方式:

1、如果是顯示開啟的Service,則Service可選擇廣播將數據發送給Activity
2、如果是綁定開啟的Service,Activity拿到IBinder引用,進而拿到Service引用,最終可以調用getCount()獲得計數值,并更新UI。

當然如果覺得每次開啟子線程很麻煩,可以選擇Android 提供的IntentService,該類里封裝了HandlerThread,并提供回調方法用以執行耗時任務。

3、Service 與Thread、Manager關系

Service 與 Thread聯系

既然Service無法直接執行耗時操作,那么需要Service干嘛呢,還不如直接開啟子線程執行任務呢?
前面說了:Service是長時間在后臺運行。
實際上說的是Service的生命周期,也就是說Service對象一直存在,當我們需要使用Service的時候,通過Intent或者IBinder找到它,進而使用它提供的功能。
同樣實現計數功能:

  • 如果直接在Activity里開啟Thread計數,當Activity退出的時候,要把Thread關閉了。再次開啟Activity的時候,已經找不到Thread引用了,無法繼續上次的累計計數。再者,就算不考慮內存泄漏,Activity退出時候不關閉Thread,再次開啟Activity的時候,依然找不到Thread引用。
  • 另外如果想將計數功能抽出來,供多個Activity使用,直接使用Thread也無法實現多Activity共用計數功能。

上面問題的本質就是需要維護一個對Thread對象的引用。而Thread僅僅是個工具而已,沒必要維護全局的引用(那是線程池要做的工作)。

Service 與 Manager

既然維護Thread全局引用方法不太推薦,那么實現一個單例的Manager(管理類)來持有Thread,進而使用Thread執行耗時任務,而外界通過調用這個Manager來獲取數據,如下:

public class CountManager {
    private static volatile CountManager instance;

    private CountManager(){
        startCount();
    }

    private int count = 0;

    public static CountManager getInstance() {
        if (instance == null) {
            synchronized (CountManager.class) {
                if (instance == null) {
                    instance = new CountManager();
                }
            }
        }

        return instance;
    }

    private void startCount() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    Log.d("time:", count + "");
                    count++;
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {

                    }
                }
            }
        }).start();
    }

    public int getCount() {
        return count;
    }
}

事實上不少的項目都是采用Activity + Manager方式來實現頁面展示 + 數據獲取。
Activity展示UI,后臺通過Manager獲取數據,如從數據庫獲取或者從從網絡獲取等,最后將數據反饋給Activity用以刷新UI。
到此你可能疑惑了,都有了Manager了,Service還有使用的必要嗎?
答案是肯定的。
Service作為Android 四大組件之一,是廣泛使用于Android 系統里的。

1、Service可以調整優先級,盡可能避免在資源緊張的時候被銷毀
2、借助Service + Binder,實現Android 進程間通信

4、Service 進程間通信初相識

之前的例子分析的都是調用者和被調用處于同一進程,試想一下,如果它們不在同一進程還能互相調用嗎?如進程A里的Activity需要使用進程B里的CountManager,顯然無法直接調用。
而對于Service來說,借助于Binder可以實現此功能。


image.png

進程A的Activity展示UI需要數據,這些數據從進程B獲取。進程B開啟Service生產數據,進程A通過綁定進程B的Service,從而獲得IBinder引用,最終調用Service的方法獲取數據。關鍵點就是中間的連接橋梁--Binder。
可以看出,Service的重點應該是在進程間通信。

接下來的文章,將重點分析IPC 過程涉及的Binder、AIDL等知識。

本文基于Android 10.0

您若喜歡,請點贊、關注,您的鼓勵是我前進的動力

持續更新中,和我一起步步為營學習Android

1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發全套服務
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執行原因
8、Android事件驅動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發系列不再疑惑
16、Java 線程池系列

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容