跨进程访问(AIDL服务)
我們都知道Service的主要的作用是后臺(tái)運(yùn)行和跨進(jìn)程訪問。
關(guān)于Service后臺(tái)運(yùn)行請(qǐng)查看鄙人的另外一篇文章Service基礎(chǔ)
本篇博文主要探討的是跨進(jìn)程訪問~
什么是AIDL
Android系統(tǒng)中的進(jìn)程之間是不能共享內(nèi)存,因此,需要提供一些機(jī)制在不同的進(jìn)程之間進(jìn)行數(shù)據(jù)通信,Activity BroadCast 和 Content Provider都可以跨進(jìn)程通信,Service同樣也可以跨進(jìn)程通信。
其中Activity可以跨進(jìn)程調(diào)用其他應(yīng)用程序的Activity 看看這里;還有這里
Content Provider可以跨進(jìn)程訪問其他應(yīng)用程序中的數(shù)據(jù)(以Cursor對(duì)象形式返回),當(dāng)然,也可以對(duì)其他應(yīng)用程序的數(shù)據(jù)進(jìn)行增、刪、改操 作;
Broadcast可以向android系統(tǒng)中所有應(yīng)用程序發(fā)送廣播,而需要跨進(jìn)程通訊的應(yīng)用程序可以監(jiān)聽這些廣播;
Service和Content Provider類似,也可以訪問其他應(yīng)用程序中的數(shù)據(jù),但不同的是,Content Provider返回的是Cursor對(duì)象,而Service返回的是Java對(duì)象,這種可以跨進(jìn)程通訊的服務(wù)叫AIDL服務(wù)。
為了使其他應(yīng)用程序也可以訪問本應(yīng)用程序提供的服務(wù),Android系統(tǒng)采用了遠(yuǎn)程過程調(diào)用(Remote Procedure Call,RPC)方式來實(shí)現(xiàn)。 與很多其他基于RPC的解決方案一樣,Android使用了一種接口定義語言(Interface Definition Lanuage)來公開服務(wù)的接口,因此可以將這種跨進(jìn)程訪問的服務(wù)稱為 AIDL (Android Interface Definition Language);
建立AIDL的步驟
建立AIDL服務(wù)要比建立普通服務(wù)的步驟要復(fù)雜一些,工具:AS
具體步驟如下
看看這里
看看這里
1. New —-AIDL—-AIDL File ,建立AIDL文件
2. 如果aidl文件正確,Build–Rebulild Project之后,會(huì)自動(dòng)生成一個(gè)Java接口文件
3. 建立一個(gè)服務(wù)類(Service子類)
4. 實(shí)現(xiàn)有aidl文件生成的java接口
5. 在AndroidManifest.xml中配置AIDL服務(wù),尤其要注意的是,action標(biāo)簽中android:name的屬性值就是客戶端要引用該服務(wù)的id,也就是Intent類構(gòu)造方法的參數(shù)值。
建立AIDL服務(wù)
首先需要明確,兩個(gè)工程。ProjectAIDL 和ProjectAIDLClient 。這樣就可以實(shí)現(xiàn)跨進(jìn)程訪問啦。
功能說明:
建立一個(gè)簡單的AIDL服務(wù),這個(gè)AIDL服務(wù)只有一個(gè)getValue的方法,改方法返回一個(gè)字符串, 在安裝完服務(wù)后,會(huì)在客戶端調(diào)用這個(gè)getValue方法,并將返回值在TextView控件顯示。
ProjectAIDL:
A. 建立AIDL文件
// IMyService.aidl package com.turing.base.activity.service.aidl;// Declare any non-default types here with import statementsinterface IMyService {String getValue(); }但是此時(shí)并沒有AIDL的java文件產(chǎn)生,其實(shí)android studio也是帶有自動(dòng)生成的,只不過需要確認(rèn)一些信息后才能生成。此時(shí),我們可以在目錄 build–>generated–>source–>aidl–>test–>debug下面發(fā)現(xiàn)還沒有任何文件
此時(shí),打開AndroidManifest.xml,確認(rèn)package的值,
關(guān)鍵性的一步,確認(rèn)aidl文件所在的包名和AndroidMainifest.xml的package名是否一致。如果一致,點(diǎn)擊
Build–>Make Project,生成相應(yīng)的java文件。
經(jīng)驗(yàn)證,貌似不一樣也沒問題
同樣生成了IMyService.java文件
B. 編寫Service子類,在子類中定義一個(gè)內(nèi)部類,該內(nèi)部類繼承自 IMyService.Stub
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException;public class AIDLService extends Service {public class MyServiceImpl extends IMyService.Stub {@Overridepublic String getValue() throws RemoteException {return "AIDL.....";}}@Overridepublic IBinder onBind(Intent intent) {return new MyServiceImpl();} }注意事項(xiàng):
I: IMyService.Stub是根據(jù)IMyService.aidl文件自動(dòng)生成的,一般不需要了解這個(gè)類的內(nèi)容,只需要編寫一個(gè)繼承自IMyService.Stub的類即可
II:onBind方法必須返回MySeviceImpl對(duì)象,否則客戶端無法獲取服務(wù)對(duì)象。
C: 在AndroidManifest.xml中配置MyService類
<service android:name=".activity.service.aidl.AIDLService"android:enabled="true"android:exported="true"><intent-filter><action android:name="com.turing.base.activity.service.aidl.AIDLService" /></intent-filter></service>其中com.turing.base.activity.service.aidl.AIDLService是客戶端訪問AIDL服務(wù)的ID
至此 ,AIDL服務(wù)端的工作完成。
ProjectAIDLClient:
A. 建立AIDLClient工程,并將服務(wù)端自動(dòng)生成的IMyService.java文件連通同包目錄一起復(fù)制到該工程的src目錄下。
首先要拷貝AIDL文件,這里要保證文件的內(nèi)容一模一樣,包括包的名稱,比如本例子中服務(wù)器端AIDL文件所在包的名稱是com.sysu.aidlclient.aidlcilent,如何做到這一點(diǎn),先新建一個(gè)項(xiàng)目,然后在:項(xiàng)目文件夾/app/src/main目錄下建立一個(gè)aidl文件夾,與java文件夾同級(jí),在Android Studio中就可以看到這個(gè)目錄,在這個(gè)目錄上右鍵New>Package,建立一個(gè)com.sysu.aidlclient.aidlclient的包,再將aidl文件拷進(jìn)去。這樣才能保證生成的java接口文件完全一樣,否則會(huì)提示找不到接口。
B 調(diào)用AIDL服務(wù),首先要綁定服務(wù),然后才可以獲得服務(wù)對(duì)象
import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast;import com.turing.base.R;public class AIDLActivityDemo extends AppCompatActivity implements View.OnClickListener {private Button btn_bindAIDL, btn_callAIDL;private TextView tv_aidlResult;private IMyService myService ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_aidlactivity_demo);initView();initEvents();}/*** 初始化組件*/private void initView() {btn_bindAIDL = (Button) findViewById(R.id.id_btn_aidl_bind);btn_callAIDL = (Button) findViewById(R.id.id_btn_aidl_call);// 現(xiàn)將調(diào)用AIDL按鈕設(shè)置為灰色禁用,等初始化AIDL服務(wù)之后在設(shè)置為可點(diǎn)擊btn_callAIDL.setEnabled(false);tv_aidlResult = (TextView) findViewById(R.id.id_tv_aidl_result);}/*** 按鈕注冊(cè)監(jiān)聽事件*/private void initEvents() {btn_bindAIDL.setOnClickListener(this);btn_callAIDL.setOnClickListener(this);tv_aidlResult.setOnClickListener(this);}/*** 按鈕監(jiān)聽事件** @param v*/@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.id_btn_aidl_bind:bindService(new Intent("com.turing.base.activity.service.aidl.AIDLService"),serviceConnection,Service.BIND_AUTO_CREATE);break;case R.id.id_btn_aidl_call:// 調(diào)用服務(wù)端getValue方法try {tv_aidlResult.setText(myService.getValue().toString());} catch (RemoteException e) {e.printStackTrace();}break;case R.id.id_tv_aidl_result:Toast.makeText(this,"鬧著玩",Toast.LENGTH_SHORT).show();break;}}private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 獲取服務(wù)對(duì)象myService = IMyService.Stub.asInterface(service);btn_callAIDL.setEnabled(true);}@Overridepublic void onServiceDisconnected(ComponentName name) {}} ; }注意事項(xiàng):
運(yùn)行效果演示:
首先,運(yùn)行AIDL服務(wù)程序,然后運(yùn)行客戶端程序,單擊綁定AIDL服務(wù)按鈕,如果綁定成功,調(diào)用AIDL按鈕 會(huì)變成可點(diǎn)擊狀態(tài),單擊此按鈕,輸出getValue方法的返回值,
傳遞復(fù)雜數(shù)據(jù)的AIDL服務(wù)
AIDL服務(wù)只支持有限的數(shù)據(jù)類型,因此如果使用AIDL傳遞復(fù)雜的數(shù)據(jù)就需要做進(jìn)一步的處理。
AIDL服務(wù)支持的數(shù)據(jù)類型
- Java簡單類型(int 、char 、boolean等),無需import
- String 和 CharSequence,無需import
- List 和 Map,但是List和Map對(duì)象的元素類型必須是AIDL服務(wù)支持的數(shù)據(jù)類型,不需要import
- AIDL指定生成的接口,需要import
- 實(shí)現(xiàn)android.os.Parcelable接口的類,需要import
工程目錄:
傳遞不需要import的數(shù)據(jù)類型值的方式相同,傳遞一個(gè)需要import的數(shù)據(jù)類型值(例如實(shí)現(xiàn)android.os.Parceable接口的類)的步驟略顯復(fù)雜,除了要建一個(gè)實(shí)現(xiàn)android.os.Parceable接口的類外,還需要為這個(gè)類單獨(dú)建立一個(gè)aidl文件,并使用parceable關(guān)鍵字進(jìn)行定義,具體的實(shí)現(xiàn)步驟如下:
ComplexTypeAIDL:
建立一個(gè)IMyService.aidl文件
IMyService.aidl
package mobile.android.ch12.complex.type.aidl; import mobile.android.ch12.complex.type.aidl.Product;interface IMyService { Map getMap(in String country, in Product product);Product getProduct(); }注意事項(xiàng):
- Product是一個(gè)實(shí)現(xiàn)了android.os.Parcelable接口的類,需要使用import導(dǎo)入這個(gè)類
- 如果方法的類型是非簡單類型,例如String、List或者自定義的類,需要使用in 、out或者inout 進(jìn)行修飾,其中in表示這個(gè)值被客戶端設(shè)置,out表示這個(gè)值被服務(wù)端設(shè)置;inout表示這個(gè)值既被客戶端設(shè)置,又要被服務(wù)端設(shè)置。
- -
編寫Product類,該類用于傳遞的數(shù)據(jù)類型
Produt.java
package mobile.android.ch12.complex.type.aidl;import android.os.Parcel; import android.os.Parcelable;public class Product implements Parcelable {private int id;private String name;private float price;public static final Parcelable.Creator<Product> CREATOR = new Parcelable.Creator<Product>(){public Product createFromParcel(Parcel in){return new Product(in);}public Product[] newArray(int size){return new Product[size]; }};public Product(){}private Product(Parcel in){readFromParcel(in);}@Overridepublic int describeContents(){// TODO Auto-generated method stubreturn 0;}public void readFromParcel(Parcel in){id = in.readInt();name = in.readString();price = in.readFloat();}@Overridepublic void writeToParcel(Parcel dest, int flags){dest.writeInt(id);dest.writeString(name);dest.writeFloat(price);}public int getId(){return id;}public void setId(int id){this.id = id;}public String getName(){return name;}public void setName(String name){this.name = name;}public float getPrice(){return price;}public void setPrice(float price){this.price = price;}}注意事項(xiàng):
- Product類必須實(shí)現(xiàn)android.os.Parcelable接口。該接口用于序列化對(duì)象。在Android中之所以使用Parcelable接口序列化,而不是使用java.io.Serializable接口,主要是為了提高效率。
- 在Product類中必須有一個(gè)靜態(tài)常量,常量名必須是CREATOR,而且CREATOR常量的數(shù)據(jù)類型必須是Parcelable.Creator.
- 在writeToParcel方法中需要將序列化的值寫入Parcel對(duì)象
建立一個(gè)Proudct.aidl
Proudct.aidl
parcelable Product;編寫MySevice類
MyService.java
import java.util.HashMap; import java.util.Map;import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException;public class MyService extends Service { public class MyServiceImpl extends IMyService.Stub{@Overridepublic Product getProduct() throws RemoteException{Product product = new Product();product.setId(1234);product.setName("汽車");product.setPrice(31000); return product;}@Overridepublic Map getMap(String country, Product product)throws RemoteException{Map map = new HashMap<String, String>();map.put("country", country);map.put("id", product.getId());map.put("name", product.getName());map.put("price", product.getPrice());map.put("product", product);return map;}}@Overridepublic IBinder onBind(Intent intent){ return new MyServiceImpl();}}在AndroidManifest.xml文件中配置MyService類
<service android:name=".MyService" ><intent-filter> <action android:name="mobile.android.ch12.complex.type.aidl.IMyService" /></intent-filter></service>至此,服務(wù)端的AIDL服務(wù)已經(jīng)完成,下面看下客戶端的操作
ComplexTypeAIDLClient:
將IMyservice.java和Product.java文件連同目錄一起復(fù)制到客戶端工程
綁定AIDL服務(wù),并獲取AIDL服務(wù),最后調(diào)用AIDL服務(wù)中的方法
Main.java
package mobile.android.ch12.complex.type.aidlclient;import mobile.android.ch12.complex.type.aidl.IMyService; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView;public class Main extends Activity implements OnClickListener {private IMyService myService = null;private Button btnInvokeAIDLService;private Button btnBindAIDLService;private TextView textView;private ServiceConnection serviceConnection = new ServiceConnection(){ @Overridepublic void onServiceConnected(ComponentName name, IBinder service){ // 獲取AIDL服務(wù)對(duì)象myService = IMyService.Stub.asInterface(service);btnInvokeAIDLService.setEnabled(true);}@Overridepublic void onServiceDisconnected(ComponentName name){// TODO Auto-generated method stub}};@Overridepublic void onClick(View view){switch (view.getId()){case R.id.btnBindAIDLService:// 綁定AIDL服務(wù)bindService(new Intent("mobile.android.ch12.complex.type.aidl.IMyService"),serviceConnection, Context.BIND_AUTO_CREATE);break;case R.id.btnInvokeAIDLService:try{String s = "";// 調(diào)用AIDL服務(wù)中的方法s = "Product.id = " + myService.getProduct().getId() + "\n";s += "Product.name = " + myService.getProduct().getName()+ "\n";s += "Product.price = " + myService.getProduct().getPrice()+ "\n";s += myService.getMap("China", myService.getProduct()).toString();textView.setText(s);}catch (Exception e){}break;}}@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.main);btnInvokeAIDLService = (Button) findViewById(R.id.btnInvokeAIDLService);btnBindAIDLService = (Button) findViewById(R.id.btnBindAIDLService);btnInvokeAIDLService.setEnabled(false);textView = (TextView) findViewById(R.id.textview);btnInvokeAIDLService.setOnClickListener(this);btnBindAIDLService.setOnClickListener(this);} }運(yùn)行效果演示:
首選運(yùn)行服務(wù)端,在運(yùn)行客戶端,即可在客戶端獲取如下信息
AIDL與來去電自動(dòng)掛斷
真機(jī)親測有效
概述
雖然可以通過Activity Action來撥打電話,但是使用常規(guī)的方法卻無法掛斷電話,不過我們可以利用反射,使用AIDL文件自動(dòng)生成接口來實(shí)現(xiàn)。
在Android SDK 源碼中可以找到如下接口
com.android.internal.telephony.ITelephony這個(gè)接口在外部是無法訪問的,只有將程序嵌入到Android SDK 內(nèi)部才可以訪問,這個(gè)接口提供了一個(gè)endCall方法可以掛斷電話,現(xiàn)在我們就想辦法來調(diào)用ITelephony.endCall方法。
盡管不能直接訪問ITelephony接口,但是我們發(fā)現(xiàn)在TelephonyManager類中有一個(gè)getITelephhony方法,可以返回一個(gè)ITelephony對(duì)象,不過改方法是private方法,so..我們可以通過反射來調(diào)用改方法
private ITelephony getITelephony() {return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));}在調(diào)用getITelephony方法獲得ITelephony對(duì)象之前,我們需要在SDK源碼中找到 NeighboringCellInfo.aidl和 ITelephony.aidl,并將這兩個(gè)文件連同所在的包復(fù)制到我們自己的工程中來。
目錄如下:
ADT會(huì)根據(jù)ITelephony.aidl文件自動(dòng)生成ITelephony.java文件,在gen目錄下。
下面我們編寫一個(gè)接收來電的廣播接收器,并在這個(gè)廣播中自動(dòng)掛斷指定號(hào)碼的來電,
Code
InCallReceiver.java
package mobile.android.ch12.call.aidl;import java.lang.reflect.Method; import com.android.internal.telephony.ITelephony; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.widget.Toast;public class InCallReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent){TelephonyManager tm = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);switch (tm.getCallState()){case TelephonyManager.CALL_STATE_RINGING: // 響鈴// 獲得來電的電話號(hào)String incomingNumber = intent.getStringExtra("incoming_number");if ("1234576".equals(incomingNumber)){try{// 獲取TelephoneManager對(duì)象TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Service.TELEPHONY_SERVICE);// 獲取TelephoneManager的class對(duì)象Class<TelephonyManager> telephonyManagerClass = TelephonyManager.class;// 獲得getITelephony方法Method telephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony",(Class[]) null);// 允許訪問private方法telephonyMethod.setAccessible(true);// 調(diào)用getITelephony方法返回ITelephony對(duì)象ITelephony telephony = (com.android.internal.telephony.ITelephony) telephonyMethod.invoke(telephonyManager, (Object[]) null);// 掛斷電話telephony.endCall();}catch (Exception e){Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();}}break;}}}配置權(quán)限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/><uses-permission android:name="android.permission.CALL_PHONE"/>小結(jié)
服務(wù)除了可以在內(nèi)部調(diào)用,還可以使用AIDL服務(wù)實(shí)現(xiàn)跨應(yīng)用的調(diào)用,其中的AIDL文件應(yīng)用很廣泛,可以利用AIDL文件自動(dòng)生成接口文件,并可以將相應(yīng)的對(duì)象轉(zhuǎn)換成指定的接口,這大大方便了服務(wù)的調(diào)用。
總結(jié)
以上是生活随笔為你收集整理的跨进程访问(AIDL服务)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 按钮和复选框控件
- 下一篇: Android接收短信-createFr