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();
}
}
// ......
}
如下圖所示:
原因就是:
前邊說過 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肴敛,它是一個接口
第二:在MyAIDLService.aidl接口中署海,定義兩個方法吗购,分別是兩個int類型數(shù)據(jù)相加,把傳遞的字符串全部轉為大寫砸狞;
第三:然后點擊 Build - Clean Project
第四:如果成功捻勉,就會生成MyAIDLService.java類,如下圖
第五:然后在 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類超凳,如下圖所示:
第二:然后在 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