我们都知道Service的主要的作用是后台运行和跨进程访问。
关于Service后台运行请查看鄙人的另外一篇文章Service基础
本篇博文主要探讨的是跨进程访问~
什么是AIDL
Android系统中的进程之间是不能共享内存,因此,需要提供一些机制在不同的进程之间进行数据通信,Activity BroadCast 和 Content Provider都可以跨进程通信,Service同样也可以跨进程通信。
其中Activity可以跨进程调用其他应用程序的Activity 看看这里;还有这里
Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;
Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;
Service和Content Provider类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider返回的是Cursor对象,而Service返回的是Java对象,这种可以跨进程通讯的服务叫AIDL服务。
为了使其他应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。 与很多其他基于RPC的解决方案一样,Android使用了一种接口定义语言(Interface Definition Lanuage)来公开服务的接口,因此可以将这种跨进程访问的服务称为 AIDL (Android Interface Definition Language);
建立AIDL的步骤
建立AIDL服务要比建立普通服务的步骤要复杂一些,工具:AS
具体步骤如下
1. New —-AIDL—-AIDL File ,建立AIDL文件
2. 如果aidl文件正确,Build–Rebulild Project之后,会自动生成一个Java接口文件
3. 建立一个服务类(Service子类)
4. 实现有aidl文件生成的java接口
5. 在AndroidManifest.xml中配置AIDL服务,尤其要注意的是,action标签中android:name的属性值就是客户端要引用该服务的id,也就是Intent类构造方法的参数值。
<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>
建立AIDL服务
首先需要明确,两个工程。ProjectAIDL 和ProjectAIDLClient 。这样就可以实现跨进程访问啦。
功能说明:
建立一个简单的AIDL服务,这个AIDL服务只有一个getValue的方法,改方法返回一个字符串, 在安装完服务后,会在客户端调用这个getValue方法,并将返回值在TextView控件显示。
ProjectAIDL:
A. 建立AIDL文件
// IMyService.aidl package com.turing.base.activity.service.aidl; // Declare any non-default types here with import statements interface IMyService { String getValue(); }
但是此时并没有AIDL的java文件产生,其实android studio也是带有自动生成的,只不过需要确认一些信息后才能生成。此时,我们可以在目录 build–>generated–>source–>aidl–>test–>debug下面发现还没有任何文件
此时,打开AndroidManifest.xml,确认package的值,
关键性的一步,确认aidl文件所在的包名和AndroidMainifest.xml的package名是否一致。如果一致,点击
Build–>Make Project,生成相应的java文件。
经验证,貌似不一样也没问题
同样生成了IMyService.java文件
B. 编写Service子类,在子类中定义一个内部类,该内部类继承自 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 { @Override public String getValue() throws RemoteException { return "AIDL....."; } } @Override public IBinder onBind(Intent intent) { return new MyServiceImpl(); } }
注意事项:
I: IMyService.Stub是根据IMyService.aidl文件自动生成的,一般不需要了解这个类的内容,只需要编写一个继承自IMyService.Stub的类即可
II:onBind方法必须返回MySeviceImpl对象,否则客户端无法获取服务对象。
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服务的ID
至此 ,AIDL服务端的工作完成。
ProjectAIDLClient:
A. 建立AIDLClient工程,并将服务端自动生成的IMyService.java文件连通同包目录一起复制到该工程的src目录下。
首先要拷贝AIDL文件,这里要保证文件的内容一模一样,包括包的名称,比如本例子中服务器端AIDL文件所在包的名称是com.sysu.aidlclient.aidlcilent,如何做到这一点,先新建一个项目,然后在:项目文件夹/app/src/main目录下建立一个aidl文件夹,与java文件夹同级,在Android Studio中就可以看到这个目录,在这个目录上右键New>Package,建立一个com.sysu.aidlclient.aidlclient的包,再将aidl文件拷进去。这样才能保证生成的java接口文件完全一样,否则会提示找不到接口。
B 调用AIDL服务,首先要绑定服务,然后才可以获得服务对象
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 ; @Override protected 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); // 现将调用AIDL按钮设置为灰色禁用,等初始化AIDL服务之后在设置为可点击 btn_callAIDL.setEnabled(false); tv_aidlResult = (TextView) findViewById(R.id.id_tv_aidl_result); } /** * 按钮注册监听事件 */ private void initEvents() { btn_bindAIDL.setOnClickListener(this); btn_callAIDL.setOnClickListener(this); tv_aidlResult.setOnClickListener(this); } /** * 按钮监听事件 * * @param v */ @Override public 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: // 调用服务端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() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 获取服务对象 myService = IMyService.Stub.asInterface(service); btn_callAIDL.setEnabled(true); } @Override public void onServiceDisconnected(ComponentName name) { } } ; }
注意事项:
使用bindService方法绑定AIDL服务,其中需要使用Intent对象指定AIDL服务的ID,也就是action标签中android:name属性的值
在绑定时需要一个ServiceConnection对象,创建ServiceConnection对象的过程中如果绑定成功,系统会调用ServiceConnection.onServiceConnected方法,通过改方法的service参数值可以获得AIDL服务对象。
运行效果演示:
首先,运行AIDL服务程序,然后运行客户端程序,单击绑定AIDL服务按钮,如果绑定成功,调用AIDL按钮 会变成可点击状态,单击此按钮,输出getValue方法的返回值,
传递复杂数据的AIDL服务
AIDL服务只支持有限的数据类型,因此如果使用AIDL传递复杂的数据就需要做进一步的处理。
AIDL服务支持的数据类型
- Java简单类型(int 、char 、boolean等),无需import
- String 和 CharSequence,无需import
- List 和 Map,但是List和Map对象的元素类型必须是AIDL服务支持的数据类型,不需要import
- AIDL指定生成的接口,需要import
- 实现android.os.Parcelable接口的类,需要import
工程目录:
传递不需要import的数据类型值的方式相同,传递一个需要import的数据类型值(例如实现android.os.Parceable接口的类)的步骤略显复杂,除了要建一个实现android.os.Parceable接口的类外,还需要为这个类单独建立一个aidl文件,并使用parceable关键字进行定义,具体的实现步骤如下:
ComplexTypeAIDL:
建立一个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(); }
注意事项:
Product是一个实现了android.os.Parcelable接口的类,需要使用import导入这个类
如果方法的类型是非简单类型,例如String、List或者自定义的类,需要使用in 、out或者inout 进行修饰,其中in表示这个值被客户端设置,out表示这个值被服务端设置;inout表示这个值既被客户端设置,又要被服务端设置。
编写Product类,该类用于传递的数据类型
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); } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } public void readFromParcel(Parcel in) { id = in.readInt(); name = in.readString(); price = in.readFloat(); } @Override public 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; } }
注意事项:
Product类必须实现android.os.Parcelable接口。该接口用于序列化对象。在Android中之所以使用Parcelable接口序列化,而不是使用java.io.Serializable接口,主要是为了提高效率。
在Product类中必须有一个静态常量,常量名必须是CREATOR,而且CREATOR常量的数据类型必须是Parcelable.Creator.
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]; } };
- 在writeToParcel方法中需要将序列化的值写入Parcel对象
public void readFromParcel(Parcel in) { id = in.readInt(); name = in.readString(); price = in.readFloat(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); dest.writeFloat(price); }
建立一个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 { @Override public Product getProduct() throws RemoteException { Product product = new Product(); product.setId(1234); product.setName("汽车"); product.setPrice(31000); return product; } @Override public 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; } } @Override public 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>
至此,服务端的AIDL服务已经完成,下面看下客户端的操作
ComplexTypeAIDLClient:
将IMyservice.java和Product.java文件连同目录一起复制到客户端工程
绑定AIDL服务,并获取AIDL服务,最后调用AIDL服务中的方法
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() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 获取AIDL服务对象 myService = IMyService.Stub.asInterface(service); btnInvokeAIDLService.setEnabled(true); } @Override public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub } }; @Override public void onClick(View view) { switch (view.getId()) { case R.id.btnBindAIDLService: // 绑定AIDL服务 bindService(new Intent("mobile.android.ch12.complex.type.aidl.IMyService"), serviceConnection, Context.BIND_AUTO_CREATE); break; case R.id.btnInvokeAIDLService: try { String s = ""; // 调用AIDL服务中的方法 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; } } @Override public 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); } }
运行效果演示:
首选运行服务端,在运行客户端,即可在客户端获取如下信息
AIDL与来去电自动挂断
真机亲测有效
概述
虽然可以通过Activity Action来拨打电话,但是使用常规的方法却无法挂断电话,不过我们可以利用反射,使用AIDL文件自动生成接口来实现。
在Android SDK 源码中可以找到如下接口
com.android.internal.telephony.ITelephony
这个接口在外部是无法访问的,只有将程序嵌入到Android SDK 内部才可以访问,这个接口提供了一个endCall方法可以挂断电话,现在我们就想办法来调用ITelephony.endCall方法。
尽管不能直接访问ITelephony接口,但是我们发现在TelephonyManager类中有一个getITelephhony方法,可以返回一个ITelephony对象,不过改方法是private方法,so..我们可以通过反射来调用改方法
private ITelephony getITelephony() { return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); }
在调用getITelephony方法获得ITelephony对象之前,我们需要在SDK源码中找到 NeighboringCellInfo.aidl和 ITelephony.aidl,并将这两个文件连同所在的包复制到我们自己的工程中来。
目录如下:
ADT会根据ITelephony.aidl文件自动生成ITelephony.java文件,在gen目录下。
下面我们编写一个接收来电的广播接收器,并在这个广播中自动挂断指定号码的来电,
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 { @Override public void onReceive(Context context, Intent intent) { TelephonyManager tm = (TelephonyManager) context .getSystemService(Service.TELEPHONY_SERVICE); switch (tm.getCallState()) { case TelephonyManager.CALL_STATE_RINGING: // 响铃 // 获得来电的电话号 String incomingNumber = intent .getStringExtra("incoming_number"); if ("1234576".equals(incomingNumber)) { try { // 获取TelephoneManager对象 TelephonyManager telephonyManager = (TelephonyManager) context .getSystemService(Service.TELEPHONY_SERVICE); // 获取TelephoneManager的class对象 Class<TelephonyManager> telephonyManagerClass = TelephonyManager.class; // 获得getITelephony方法 Method telephonyMethod = telephonyManagerClass .getDeclaredMethod("getITelephony", (Class[]) null); // 允许访问private方法 telephonyMethod.setAccessible(true); // 调用getITelephony方法返回ITelephony对象 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; } } }
配置权限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.CALL_PHONE"/>
小结
服务除了可以在内部调用,还可以使用AIDL服务实现跨应用的调用,其中的AIDL文件应用很广泛,可以利用AIDL文件自动生成接口文件,并可以将相应的对象转换成指定的接口,这大大方便了服务的调用。