Service(七) - 遠程Service

1. 概述


前邊幾篇文章記錄了Service的基礎知識點:Service基本用法Service與Activity通信(本地通信)Service銷毀方式Service與Thread關系及如何創(chuàng)建前臺進程等, 這篇文章記錄下遠程Service供屉。

2. 現(xiàn)象


1>:通過Service演示:

下邊先看一種現(xiàn)象:直接在MyService的 onCreate() 中添加 Thread.sleep(60000),讓線程睡眠60秒溺蕉,然后點擊MainActivity中的 startService或者bindService伶丐,過一會會ANR,

代碼如下:
public class MyService extends Service {

     // ......

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG" , "onCreate__executed") ;

        try {
            Thread.sleep(60000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

     // ......
}
如下圖所示:
ANR.jpg
原因就是:

前邊說過 Service與Activity一樣疯特,四大組件都一樣哗魂,都是運行到主線程中的,如果在它們初始化比如 onCreate 方法中執(zhí)行 Thread.sleep(60000)等耗時操作就會ANR漓雅;

2>:通過遠程Service演示:

把普通Service變成 遠程Service很簡單啡彬,直接在 清單文件中添加 android:process=":remote"即可羹与;
然后直接運行項目,不會ANR庶灿,onCreate__executed直接打印纵搁,1分鐘后 onStartCommand__executed打印(我的沒有打油摺)

原因就是:

遠程Service腾誉,即就是MyService已經(jīng)在另一個進程中運行,ANR只會阻塞它自己進程中的主線程峻呕,如果不是同一個進程利职,那么就不能阻塞另一個進程中的主線程;

在MainActivity的onCreate和MyService的onCreate中打印所在進程id瘦癌,可以看到猪贪,遠程MyService的包名也變了,如下:

com.novate.usual Activity process id is__18044
com.novate.usual:remote Service process id is__18276

如果把 MyService中的 Thread.sleep(60000)注釋讯私,點擊 startService可以热押,點擊 bindService,程序崩潰斤寇,原因是bindService用于 綁定 Service和Activity桶癣,但是僅限于同一個進程中,由于遠程MyService已經(jīng)在另一個進程中了娘锁,此時的Activity與MyService 就是兩個不同的進程中牙寞,所以就不能用這種方式綁定了。

怎樣讓 Activity與遠程MyService 建立關聯(lián)呢莫秆,就是用 AIDL進行跨進程通信间雀;

3. AIDL


AIDL:Android Interface Definition Language,安卓接口定義語言镊屎,用于跨進程通信惹挟,簡單說,就是多個進程通信杯道,不管是同一個項目下的多個進程,還是多個不同app項目中的多個進程责蝠;
可以實現(xiàn) 多個應用程序(多個app項目)共享一個Service 的功能党巾;

1>:案例一:同一個app項目下的,當前進程和遠程MyService進行通信

步驟如下:
第一:首先在之前項目包下創(chuàng)建 aidl 包霜医,然后在aidl包下齿拂, New - Folder - AIDL Folder,創(chuàng)建 MyAIDLService.aidl肴敛,它是一個接口
圖片.png
第二:在MyAIDLService.aidl接口中署海,定義兩個方法吗购,分別是兩個int類型數(shù)據(jù)相加,把傳遞的字符串全部轉為大寫砸狞;
第三:然后點擊 Build - Clean Project
圖片.png
第四:如果成功捻勉,就會生成MyAIDLService.java類,如下圖
圖片.png
第五:然后在 MyService中:首先實現(xiàn)MyAIDLService.Stub刀森,實例對象為mBinder踱启,然后重寫 MyAIDLService接口中的兩個方法,然后在 onBind方法中返回 mBinder對象研底,代碼如下:
public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    /**
     * Binder是父類埠偿,Stub是子類
     *
     * 第一:這里首先對MyAIDLService.Stub進行實現(xiàn),重寫sum()和toUppercase()方法
     */
    MyAIDLService.Stub mBinder = new MyAIDLService.Stub() {

        /**
         * 兩個整數(shù)相加
         */
        @Override
        public int sum(int a, int b) throws RemoteException {
            return a+b;
        }

        /**
         * 將傳入的字符串全部轉為大寫
         */
        @Override
        public String toUppercase(String str) throws RemoteException {
            if (str != null){
                return str.toUpperCase() ;
            }
            return null;
        }
    } ;

    /**
     * 第二:在 onBinde方法中直接返回 Stub的對象mBinder
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("TAG" , "onStartCommand__executed") ;
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

}
第六:然后在 ServiceActivity中修改 ServiceConnection中的代碼榜晦,把 IBinder對象轉為 MyAIDLService對象冠蒋,就可以調用 MyAIDLService.aidl接口中所有的方法了,代碼如下:
public class ServiceActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Intent intent;

    private Button bindService;
    private Button unbindService;

    private MyAIDLService myAIDLService ;
    /**
     * 創(chuàng)建匿名內(nèi)部類乾胶,重寫 onServiceConnected 和 onServiceDisconnected方法
     * AIDL方式對應的 ServiceConnection
     *
     */
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 這里把 IBinder對象轉為 MyAIDLService對象抖剿,就可以調用MyAIDLService.aidl文件中所有接口了
            myAIDLService = MyAIDLService.Stub.asInterface(service) ;
            try {
                int result = myAIDLService.sum(5,5) ;
                String upperStr = myAIDLService.toUppercase("yintao") ;
                Log.e("TAG" , "result: "+result + ", upperStr: "+upperStr) ;
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    } ;


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service);

        startService = (Button) findViewById(R.id.start_service);
        stopService = (Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);


        bindService = (Button) findViewById(R.id.bind_service);
        unbindService = (Button) findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

        Log.e("TAG" , "Activity process id is__"+ Process.myPid()) ;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){

            // 啟動服務
            case R.id.start_service:
                 intent = new Intent(ServiceActivity.this , MyService.class);
                 startService(intent) ;
                 break;

            // 停止服務
            case R.id.stop_service:
                 intent = new Intent(ServiceActivity.this , MyService.class);
                 stopService(intent) ;
                 break;

            // 綁定服務:將 Activity與Service綁定,接收3個參數(shù):
            // param_1: intent對象
            // param_2: 上邊的 ServiceConnection實例對象
            // param_3: 一個標志位胚吁,BIND_AUTO_CREATE表示Activity與Service建立關聯(lián)后牙躺,會自動創(chuàng)建Service
            case R.id.bind_service:
                 Intent intent = new Intent(ServiceActivity.this , MyService.class) ;
                 bindService(intent , connection , BIND_AUTO_CREATE) ;
                 break;

            // 解綁服務:解除 Activity與Service的關聯(lián)
            case R.id.unbind_service:
                 unbindService(connection);
                 break;
        }
    }
}

運行項目,先點擊 startService開啟服務腕扶,然后 點擊bindService綁定服務孽拷,點擊bindService就表示把當前進程 和 遠程 MyService進行綁定,這個就是 跨進程通信了半抱,綁定服務后 打印結果如下 :

result: 10, upperStr: YINTAO

由上邊可知:已經(jīng)實現(xiàn)了跨進程通信脓恕,實現(xiàn)了一個進程中訪問另一個進程的方法

2>:案例二:兩個不同app下進程通信

我們知道,要讓 Activity與Service通信窿侈,需要綁定二者炼幔,如果是同一個項目的Activity和Service,可以用如下方式:

Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);

但是如果是兩個app下的史简,第二個app肯定沒有 MyService這個類乃秀,此時可以用 隱式intent,直接在第一個app的 清單文件中 給 MyService添加 action:

      <service android:name=".test_service.MyService"
            android:process=":remote"
            >
                <intent-filter >
                    <action android:name="com.novate.usual.test_service.aidl.MyAIDLService"/>
                </intent-filter>

            </service>

然后重新運行這第一個app項目圆兵,此時就把 遠程Service 端的工作全部完成跺讯;

然后新建一個項目 ClientTest項目,需要做以下幾步:
第一:把第一個項目中的 MyAIDLService.aidl文件連同包名所在路徑全部拷貝到 ClientTest項目的包名下殉农,然后重新 clean項目刀脏,和上邊第一個項目處理方式一樣,clean完成后就會生成MyAIDLService的java類超凳,如下圖所示:
圖片.png
第二:然后在 ClientTest項目中的 activity_main.xml 中添加一個 button 按鈕愈污,如下耀态,用于綁定服務:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <Button
        android:id="@+id/bind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service"
        />
</LinearLayout>
第三:然后在 MainActivity中,首先點擊 bindService用于綁定第二個項目和 第一個項目的MyService服務暂雹,通過 隱式 intent首装,然后創(chuàng)建匿名內(nèi)部類 ServiceConnection,重新兩個方法 onServiceConnected 和 onServiceDisconnected 擎析,在 前者中:把 IBinder對象轉為 MyAIDLService簿盅,然后就可以調用 該接口下的所有方法了:
public class MainActivity extends AppCompatActivity {


    private MyAIDLService myAIDLService ;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            // 這里把 IBinder 對象轉為 MyAIDLService對象,就可以調用MyAIDLService.aidl接口中所有方法了
            myAIDLService = MyAIDLService.Stub.asInterface(service) ;
            try {
                int result = myAIDLService.sum(50 , 50) ;
                String upperStr = myAIDLService.toUppercase("come from ClientTest") ;
                Log.e("TAG" , "跨進程通信的:result: "+result + ", upperStr: "+upperStr) ;
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button bind_service = (Button) findViewById(R.id.bind_service);
        bind_service.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.novate.usual.test_service.aidl.MyAIDLService") ;
                final Intent eintent = new Intent(createExplicitFromImplicitIntent(MainActivity.this,intent));
                bindService(eintent , connection , BIND_AUTO_CREATE) ;
            }
        });
    }



    /***
     * 隱式調用intent揍魂,5.0以上手機會報錯:Service Intent must be explicit桨醋,
     * 重寫下邊這個方法就可以
     */
    public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

        // Make sure only one match was found
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }

        // Get component info and create ComponentName
        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);

        // Create a new intent. Use the old one for extras and such reuse
        Intent explicitIntent = new Intent(implicitIntent);

        // Set the component to be explicit
        explicitIntent.setComponent(component);

        return explicitIntent;
    }
}

此時第二個項目 ClientTest所有代碼就寫完了,然后直接運行這個項目现斋,點擊bindService喜最,此時這第二個項目就會和第一個項目的MyService進行綁定,打印結果如下:

跨進程通信的:result: 100, upperStr: COME FROM CLIENTTEST

到這里就實現(xiàn)了 2個app項目下的 跨進程通信

4. 2個app跨進程通信步驟總結


其實就是第二個app項目 和 第一個app的 MyService進行通信庄蹋,實現(xiàn)步驟如下:

對于第一個app項目:

1>:首先創(chuàng)建 MyService瞬内,然后創(chuàng)建aidl包,在aidl包下創(chuàng)建 MyAIDLService.aidl接口文件限书,然后clean虫蝶,生成MyAIDLService.java類;
2>:然后在清單文件中:把 MyService聲明成遠程的倦西,同時添加intent-filter和action能真,說明是隱式的;

對于第二個app項目:ClientTest

1>:首先把第一個項目的MyAIDLService.aidl接口文件包括所在的包名全部拷貝到 ClientTest項目的包名下扰柠,然后clean粉铐;
2>:然后創(chuàng)建 activity_main.xml文件,里邊寫一個 bindService的按鈕卤档,用于綁定第二個項目和第一個項目的MyService遠程服務蝙泼;
3>:然后在MainActivity中,創(chuàng)建匿名內(nèi)部類ServiceConnection劝枣,重寫兩個方法汤踏,在服務連接成功方法中:把 IBinder 對象轉為 MyAIDLService,然后就可以調用 該接口中所有的方法了舔腾;

以上就是2個app項目進行跨進程通信的具體步驟了溪胶;
可以延伸,如果是多個app進行跨進程通信琢唾,都可以按照第二個app項目的操作方式载荔,這樣就可以實現(xiàn):讓一個Service和多個app進行跨進程通信盾饮,

5. 注意


2個進程或者多個進程通信采桃,是需要傳遞數(shù)據(jù)的懒熙,Android對于跨進程通信傳遞數(shù)據(jù)類型支持:8種基本數(shù)據(jù)類型、String字符串普办、List或者Map工扎, 如果想要傳遞自定義的對象或者服務器返回的對象,這個時候就必須讓其實現(xiàn) Parcelable或者Serializable 衔蹲,并且給這個對象也定義一個同名的AIDL文件肢娘,如果項目中有這方面需要,百度下就可以

代碼已上傳至github:
https://github.com/shuai999/ServiceTest.git
https://github.com/shuai999/ClinetTest.git

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舆驶,一起剝皮案震驚了整個濱河市橱健,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沙廉,老刑警劉巖拘荡,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撬陵,居然都是意外死亡珊皿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門巨税,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟋定,“玉大人,你說我怎么就攤上這事草添∈欢担” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵果元,是天一觀的道長促王。 經(jīng)常有香客問我,道長而晒,這世上最難降的妖魔是什么蝇狼? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮倡怎,結果婚禮上迅耘,老公的妹妹穿的比我還像新娘。我一直安慰自己监署,他們只是感情好颤专,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钠乏,像睡著了一般栖秕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晓避,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天簇捍,我揣著相機與錄音只壳,去河邊找鬼。 笑死暑塑,一個胖子當著我的面吹牛吼句,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播事格,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼惕艳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驹愚?” 一聲冷哼從身側響起远搪,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逢捺,沒想到半個月后终娃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蒸甜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年棠耕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柠新。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡窍荧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恨憎,到底是詐尸還是另有隱情蕊退,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布憔恳,位于F島的核電站瓤荔,受9級特大地震影響,放射性物質發(fā)生泄漏钥组。R本人自食惡果不足惜输硝,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望程梦。 院中可真熱鬧点把,春花似錦、人聲如沸屿附。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挺份。三九已至褒翰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背优训。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工错邦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人型宙。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像伦吠,于是被迫代替她去往敵國和親妆兑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容