由于android系统中应用程序之间不能共享内存。因此,在不同应用程序之间交互数据(跨进程通讯IPC(Inter-Process Communication))就稍微麻烦一些。
在Linux系统进程间通信方式:
(1)传统机制。如管道(Pipe)、信号(Signal)和跟踪(Trace),适用于父子进程或兄弟进程,其中命名管道(Named Pipe),支持多种类型进程
(2)System V IPC机制。如报文队列(Message)、共享内存(Share Memory)和信号量(Semaphore)
(3)Socket通信
然而,以上方式在性能和安全性方面存在不足:
(1)性能方面。管道和队列采用存储转发方式,数据从发送方缓存区->内核缓存区->接收方缓存区,会经历两次拷贝过程;共享内存无拷贝但控制复杂;Socket传输效率低下,且连接的建立与中断有一定开销。
(2)安全性方面。传统IPC方式无法获得对方进程可靠的UID和PID,无法鉴别对方身份,若采用在数据包里填入UID/PID的方式,容易被恶意程序利用;传统IPC方式的接入点是开放性的,无法建立私有通道,容易被恶意程序猜测出接收方地址,获得连接。
基于以上种种原因,android 使用了Binder,Binder是基于C/S通信模式,传输过程只需要一次拷贝,且为Client添加UID/PID身份,性能和安全性更好,因此Android进程间通信使用了Binder。
在android SDK中提供了4种用于跨进程通讯的方式。这5种方式正好对应于android系统中4种应用程序组件:Activity、Content Provider、Broadcast和Service。
方式一: 访问其他应用程序的Activity
Activity既可以在进程内(同一个应用程序)访问,也可以跨进程访问。如果想在同一个应用程序中访问Activity,需要指定Context对象和Activity的Class对象,代码如下:
Intent intent = new Intent(this , TestActivity.class );
startActivity(intent);
Activity的跨进程访问与进程内访问略有不同。虽然它们都需要Intent对象,但跨进程访问并不需要指定Context对象和Activity的 Class对象,而需要指定的是要访问的Activity所对应的Action(一个字符串),有些Activity还需要指定一个Uri(通过 Intent构造方法的第2个参数指定)。
比如下面的代码可以直接调用拨打电话的Activity。
Intent callIntent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:12345678"));
startActivity(callIntent);
要将应用程序的Activity共享出来,必须按如下几步来共享Activity:
1. 在AndroidManifest.xml文件中指定Action。指定Action要使用标签,并在该标签的android:name属性中指定Action 字符串
2. 在AndroidManifest.xml文件中指定访问协议。在指定Uri(Intent类的第2个参数)时需要访问协议。访问协议需要使 用标签的android:scheme属性来指定。比如该属性的值是“abc”,那么Uri就应该是“abc://Uri的主体部分”。
<activity android:name=".Aactiviy"
android:label="@string/app_name" >
<intent-filter>
<action android:name="com.nick.a" />
<data android:scheme="abc" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
通过上面两步,其他app就可以访问这个activity了,并能通过相应的Uri传递数据。如下:
Intent intent = new Intent("com.nick.a" , Uri.parse("abc://xxxxxxx" ));
intent.putExtra("value" , "调用成功" );
startActivity(intent);
当然,也可以使用startActivityForResult方法来启动其他应用程序的Activity,以便获得Activity的返回值。
那么在上面的Aactivity里就可以按如下方式获取其他应用传递来的数据:
if (getIntent().getData() != null) {
//获得Host,也就是abc://后面的内容
String host = getIntent().getData().getHost();
Bundle bundle = getIntent().getExtras();
//其他的应用程序会传递过来一个value值,在该应用程序中需要获得这个值
String value = bundle.getString("value");
}
方式二:Content Provider
Android应用程序可以使用文件或SqlLite数据库来存储数据。Content Provider提供了一种在多个应用程序之间数据共享的方式(跨进程共享数据)。应用程序可以利用Content Provider完成下面的工作
- 查询数据
- 修改数据
- 添加数据
- 删除数据
虽然Content Provider也可以在同一个应用程序中被访问,但这么做并没有什么意义。Content Provider存在的目的向其他应用程序共享数据和允许其他应用程序对数据进行增、删、改操作。 Android系统本身提供了很多Content Provider,例如,音频、视频、联系人信息等等。我们可以通过这些Content Provider获得相关信息的列表。这些列表数据将以Cursor对象返回。因此,从Content Provider返回的数据是二维表的形式。
与Activity一样,Content Provider也需要与一个URI对应。每一个Content Provider可以控制多个数据集,在这种情况下,每一个数据集会对应一个单独的URI。所有的URI必须以“content://”开头。
下面来看一下编写Content Provider的具体步骤。
1. 编写一个继承于android.content.ContentProvider的子类。该类是ContentProvider的核心类。在该类中会实现 query、insert、update及delete方法。访问这些方法时通过Uri传递参数,然后在这些方法中接收这些参数,做处理。
2. 在AndroidManifest.xml文件中配置ContentProvider。要想唯一确定一个ContentProvider,需要指定这个 ContentProvider的URI,除此之外,还需要指定URI所对应的ContentProvider类。
<provider
android:authorities="com.nick.dictionary"
android:name=".DictionaryContentProvider" />
现在来看一下Uri的具体格式,先看一下如图所示的URI。
下面对上图所示的URI的4个部分做一下解释。
A:Content Provider URI的固定前缀,也就是说,所有的URI必须以content://开头。
B:URI中最重要的部分。该部分是Content Provider的唯一标识。必须确保URI的唯一性。
C:这部分是URI的路径(path)。表示URI中各种被请求的数据。这部分是可选的, 如果Content Provider仅仅提供一种请求的数据,那么这部分可以省略。如果Content Provider要提供多种请求数据。就需要添加多个路径,甚至是子路径。
D:这部分也是可选的。如果要传递一个值给Content Provider,可以通过这部分传递。当然,如果不需要传值,这部分也可以省略。
具体的ContentProvider代码如下:
public class DictionaryContentProvider extends ContentProvider {
private static final String AUTHORITY = "com.nick.dictionary";
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
......
}
OK,现在来看看应用程序如何调用ContentProvider。调用ContentProvider的关键是使用 getContentResolver方法来获得一个ContentResolver对象,并通过ContentResolver对象的query方法来 访问ContentProvider。
Uri uri = Uri.parse("com.nick.dictionary");
Cursor cursor = getContentResolver().query(uri, null, "english=?"
,new String[]{ "abc" }, null);
方式三:广播(Broadcast)
广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。
在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。
//通过Intent类的构造方法指定广播的ID
Intent intent = new Intent("com.nick.MYBROADCAST");
//将要广播的数据添加到Intent对象中
intent.putExtra("text", "xxxxxx");
//发送广播
sendBroadcast(intent);
发送广播并不需要在AndroidManifest.xml文件中注册,但接收广播必须在AndroidManifest.xml文件中注册 receiver,该类必须是 BroadcastReceiver的子类,代码如下:
public class MyReceiver extends BroadcastReceiver
{
// 当sendbroadcast发送广播时,系统会调用onReceive方法来接收广播
@Override
public void onReceive(Context context, Intent intent){
// 判断是否为sendbroadcast发送的广播
if ("com.nick.MYBROADCAST".equals(intent.getAction()))
{
Bundle bundle = intent.getExtras();
if (bundle != null)
{
String text = bundle.getString("text");
Toast.makeText(context, "成功接收广播:" + text, Toast.LENGTH_LONG).show();
}
}
}
}
最后还需要在AndroidManifest.xml文件中注册receiver,代码如下:
<!-- 注册receiver
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="com.nick.MYBROADCAST" />
</intent-filter>
</receiver>
方式四:AIDL服务
服务(Service)是android系统中非常重要的组件。Service可以脱离应用程序运行。也就是说,Service被启动,就算应用程序关闭,Service仍然会在后台运行。
android系统中的Service主要有两个作用:后台运行和跨进程通讯。如果想让Service可以跨进程通讯,就要使用AIDL服务,AIDL的全称是Android Interface Definition Language,也就是说,AIDL实际上是一种接口定义语言。通过这种语言定义接口后,IDE工具会自动生成相应的Java接口代码。下面来看一下编写一个AIDL服务的基本步骤:
1.创建 .aidl 文件
AIDL 使用简单语法,语法类似于Java代码。如果aidl文件的内容是正确的,IDE会自动在项目的 gen/ 目录中生成 IBinder 接口文件。生成的文件名与 .aidl 文件名一致,只是使用了 .java 扩展名(例如,IRemoteService.aidl 生成的文件名是 IRemoteService.java)。
interface IRemoteService {
int getPid();
int setValue(String value);
String getValue();
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
2.建立一个服务类(Service的子类),并实现上面aidl所定义的接口:
public class RemoteService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder; //该方法必须返回IRemoteService.Stub对象实例
}
//IRemoteService.Stub类是根据IRemoteService.aidl文件生成的类
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
String value;
public int getPid(){
return Process.myPid();
}
@Override
public int setValue(String value) throws RemoteException {
this.value = value;
return 1;
}
@Override
public String getValue() throws RemoteException {
return "HaHa from RemoteService: "+this.value;
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
3.在AndroidManifest.xml文件中配置AIDL服务,尤其要注意的是,标签的android:name属性值就是客户端要引用该服务的ID,也就是Intent类构造方法的参数值。
<!-- 注册服务 -->
<service android:name="com.nick.app.aidltest.service.RemoteService">
<intent-filter>
<!-- 指定调用AIDL服务的ID -->
<action android:name="com.nick.app.aidltest.service.IRemoteService" />
</intent-filter>
</service>
下面来看看如何调用这个AIDL服务。首先建立另一个android工程:aidlclient。然后把上一步的aidl文件拷贝到当前工程中,注意文件目录要与上面service工程中一致。在调用AIDL服务之前需要先使用bindService方法绑定AIDL服务。 bindService方法需要一个ServiceConnection对象。ServiceConnection有一个 onServiceConnected方法,当成功绑定AIDL服务且,该方法会被调用。并通过service参数返回AIDL服务对象。下面是调用 AIDL服务的完成代码:
public class MainActivity extends AppCompatActivity {
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
Intent intent = new Intent();
//android 5.0以后必须要设置包名,类名才能启动service
ComponentName component = new ComponentName("com.nick.app.aidltest",
"com.nick.app.aidltest.service.RemoteService");
intent.setComponent(component);
// 绑定AIDL服务
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private IRemoteService myService = null;
// 创建ServiceConnection对象
private ServiceConnection serviceConnection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
// 获得AIDL服务对象
myService = IRemoteService.Stub.asInterface(service);
try {
myService.setValue("hello service");
textView.setText(myService.getValue() +" "+myService.getPid());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name)
{
}
};
}
方式五:Messenger
与服务进行远程进程通信,还可使用 Messenger 为您的服务提供接口。利用此方法,您无需使用 AIDL 便可执行进程间通信 (IPC)。
以下是一个使用 Messenger 接口的简单服务示例:
public class RemoteService extends Service {
static String TAG = RemoteService.class.getSimpleName();
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
客户端代码修改:
public class MainActivity extends AppCompatActivity {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
TextView textView;
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
Intent intent = new Intent();
//android 5.0以后必须要设置包名,类名才能启动service
ComponentName component = new ComponentName("com.nick.app.aidltest",
"com.nick.app.aidltest.service.RemoteService");
intent.setComponent(component);
// 绑定AIDL服务
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void sayHello() {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
// 创建ServiceConnection对象
private ServiceConnection serviceConnection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = new Messenger(service);
mBound = true;
sayHello();
}
@Override
public void onServiceDisconnected(ComponentName name)
{
mService = null;
mBound = false;
}
};
}
当您需要执行 IPC 时,使用 Messenger 要比使用 AIDL 实现它更加简单,因为 Messenger 会将所有服务调用排入队列,而纯粹的 AIDL 接口会同时向服务发送多个请求,服务随后必须应对多线程处理。对于大多数应用,服务不需要执行多线程处理,因此使用 Messenger 可让服务一次处理一个调用。如果您的服务必须执行多线程处理,则应使用 AIDL 来定义接口。
完
更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公众号:
Android老鸟