前面两篇文章Android进阶——使用远程服务AIDL实现进程间简单通信小结(一)和Android进阶——使用远程服务AIDL实现进程间带远程回调接口较复杂通信小结(二)介绍了AIDL的基础语法和简单应用,AIDL 不仅仅是只能用于跨进程业务建的通信方面,对于跨进程操作界面也是可行的,当然AIDL 的用武之地不仅仅是列举的这些方面,这仅仅是冰山一角。
不借助框架的话,Android 跨进程更新UI 的方式主要有两大类:全局广播和AIDL 进程间通信,其中AIDL进程间通信又可以分为两小类,下文直接通过一个案例讲解下,这个案例是在不同进程中分别有两个Activity,MainActivity是在主进程中的,而另一个XActivity 则是在另一个进程的,在XActivity的界面上通过调用AIDL来实现更新MainActivity的View。
由于进程之间互相隔离不能直接通信,所以无论是需要传递数据还是直接更新UI都需要通过IPC机制才能通信,所以呢跨进程更新View可以分为两大步:先跨进程通信然后再像一个进程内实现更新UI,跨进程可以通过AIDL完成,更更新UI就使用常见的Handler方式即可,分析完了背后的原理,就开始编码吧。
具体步骤参见Android进阶——使用远程服务AIDL实现进程间简单通信小结(一)
// IUpdateView.aidl
package com.crazymo.crossprocessupdui;
// Declare any non-default types here with import statements
interface IUpdateView {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void updateView(in int id,in String text);
}
为了处理高版本下仅仅通过action 匿名启动服务引发的异常而坐的封装工具类。
package com.crazymo.crossprocessupdui.aidl;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.util.Log;
import java.util.List;
/**
* 这是为了处理高版本下仅仅通过action 匿名启动服务引发的异常
* @author : Crazy.Mo
*/
public class AidlUtil {
/**
*
* @param context
* @param conn 实现ServiceConnection接口的类
* @param action 要启动服务的action
* @return
*/
public static boolean bindAidlService(Context context, ServiceConnection conn, String action){
boolean isBind=false;
if(context!=null && action!=null && conn!=null) {
try {
Intent intent = new Intent(getExplicitIntent(context, new Intent().setAction(action)));
isBind = context.bindService(intent, conn, Context.BIND_AUTO_CREATE);
}catch (Exception e){
Log.e("AIDL",e.getMessage());
}
}
return isBind;
}
private static Intent getExplicitIntent(Context context, Intent implicitIntent) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
Intent explicitIntent = new Intent(implicitIntent);
explicitIntent.setComponent(component);
return explicitIntent;
}
}
定义远程服务基类,定义了基本的逻辑。
package com.crazymo.crossprocessupdui.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
/**
* 所有远程服务必须继承的父类
* @author Crazy.Mo
*/
public abstract class AbstractService extends Service {
protected IBinder mBinder;
@Nullable
@Override
public IBinder onBind(Intent intent) {
if(checkPermission()) {
if (mBinder == null) {
mBinder = initBinder();
}
//与客户端成功连接上的时候返回给客户端使用的对象
return mBinder;
}else {
return null;
}
}
/**
* @return IBinder
*/
protected abstract IBinder initBinder();
/**
* 如果设置了权限需要检查是否有权限
* @return
*/
protected abstract boolean checkPermission();
}
package com.crazymo.crossprocessupdui.service;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import com.crazymo.crossprocessupdui.IUpdateView;
import com.crazymo.crossprocessupdui.MainActivity;
/**
* @author : Crazy.Mo
*/
public class RemoteUpdService extends AbstractService {
@Override
protected IBinder initBinder() {
if(mBinder==null){
mBinder=new RemoteBinder();
}
//与客户端成功连接上的时候返回给客户端使用的对象
return mBinder;
}
@Override
protected boolean checkPermission() {
/*
int check = checkCallingOrSelfPermission("com.crazymo.crossprocessupdui");
Log.e("cmo","check:"+check);
return check != PackageManager.PERMISSION_DENIED;
*/
return true;
}
/**
* 继承AIDL文件里的Stub封装IBinder对象,可以理解为AIDL接口的实现
*/
private final class RemoteBinder extends IUpdateView.Stub{
@Override
public void updateView(int id, String text) throws RemoteException {
//直接传递更新参数并通过Handler机制更新
Message msg=new Message();
msg.what=1;
Bundle bundle=new Bundle();
bundle.putInt("id",id);
bundle.putString("title",text);
msg.setData(bundle);
new MainActivity.UpdHandler(RemoteUpdService.this).sendMessage(msg);
}
}
}
package com.crazymo.crossprocessupdui;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.crazymo.crossprocessupdui.aidl.AidlUtil;
/**
* @author Crazy.Mo
*/
public class RemoteActivity extends AppCompatActivity {
private final static String ACTION_COMM="com.crazymo.crossprocess.updui";
private boolean isUnbind=false;
/**
* 声明并创建ServiceConnection对象实例,只要是进程通信都需要实现,因为系统是在这个ServiceConnecttion类的相关方法通过回调返回Ibinder对象的
*/
private RemoteUpdConn mRemoteUpdConn=new RemoteUpdConn();
/**
*声明远程服务对象实例
*/
private IUpdateView mRemoteUpdViewApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_remote);
}
@Override
protected void onDestroy() {
super.onDestroy();
disconnRemoteApi();
}
/**
*主动断开
*/
private void disconnRemoteApi() {
if(!isUnbind) {
unbindService(mRemoteUpdConn);
isUnbind = true;
}
}
//第二调用远程服务更新
public void remoteUpd(View view) {
if(mRemoteUpdViewApi ==null){
connectAidl();
}
try {
//这里有必要判空下,因为假如你还未执行过连接的话,第一次就使用会去先连接这需要些时间,而且连接是异步不阻塞的,所以有可能造成空指针异常
if(mRemoteUpdViewApi !=null) {
Log.e("cmo", "【客户端】线程"+Thread.currentThread().getName()+"通过AIDL调用远程接口,客户端");
//点击之后更新另一个进程上的TextView
mRemoteUpdViewApi.updateView(R.id.txt_title,"这是通过跨进程更新的信息");
Log.e("cmo", "【客户端】线程"+Thread.currentThread().getName());
}
finish();
} catch (RemoteException pE) {
pE.printStackTrace();
}
}
//第一步 bind 远程服务
private void connectAidl() {
boolean isBind;
isBind= AidlUtil.bindAidlService(this,mRemoteUpdConn,ACTION_COMM);
Log.e("cmo","【客户端】线程"+Thread.currentThread().getName()+"绑定远程接口(成功true,失败false): "+isBind);
if(!isBind){
Toast.makeText(this,"连接远程服务发生异常",Toast.LENGTH_SHORT).show();
}
}
public void bind(View view) {
connectAidl();
}
private final class RemoteUpdConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
////初始化远端服务对象实例
mRemoteUpdViewApi =IUpdateView.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if(isUnbind){
//主动断开直接置为null
mRemoteUpdViewApi =null;
}else{
while(mRemoteUpdViewApi ==null) {
connectAidl();//做重连
}
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.crazymo.crossprocessupdui">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".service.RemoteUpdService"
android:permission="com.crazymo.crossprocessupdui"
>
<intent-filter>
<action android:name="com.crazymo.crossprocess.updui"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
<!--
:的意思是指要在当前进程名称前面附加上当前的包名,所以“remoteui”和”:remoteui”不是同一个意思,
前者的进程名称为:remoteui,而后者的进程名称为:App-packageName:remoteui。
-->
<activity
android:name=".RemoteActivity"
android:process=":remoteui"/>
</application>
</manifest>
package com.crazymo.crossprocessupdui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
import java.lang.ref.WeakReference;
/**
* @author Crazy.Mo
*/
public class MainActivity extends AppCompatActivity {
static LinearLayout mLayout;
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout=findViewById(R.id.ll_main_layout);
mTextView=findViewById(R.id.txt_title);
}
public void jump(View view) {
startActivity(new Intent(MainActivity.this,RemoteActivity.class));
}
public static class UpdHandler extends Handler{
WeakReference<Context> mWeakReference;
public UpdHandler(Context ctx){
super(Looper.getMainLooper());
mWeakReference=new WeakReference<>(ctx);
}
@Override
public void handleMessage(Message msg) {
doHandleMessage(msg);
}
//通过Handler 机制更新UI
private void doHandleMessage(Message msg) {
if(msg!=null){
switch (msg.what) {
case 1:
Bundle bundle = msg.getData();
TextView textView = mLayout.findViewById(bundle.getInt("id"));
textView.setText(bundle.getString("title"));
break;
case 2:
RemoteViews remoteViews = msg.getData().getParcelable("remoteView");
if (remoteViews != null) {
View view = remoteViews.apply(mWeakReference.get(), mLayout);
mLayout.addView(view);
}
break;
default:
break;
}
}
}
}
}
以上使用最原始的方式来接收更新View 所需的基本信息,在处理大部分远程更新UI都是可行的,但是在处理复杂情况的话就不那么优雅了,比如说更新View的操作方法过多、每一次IPC消耗都不小、传递非序列化的参数时,正是基于以上种种,Android 还为我们提供了另一种传递参数的形式——RemoteViews。
无论是使用原始方式接收基本参数信息还是使用RemoteViews间接接收,核心步骤和思想都是一样的,这里为了方便对比把这种方式放到单独的aidl和Service中,其实本质上是无任何差别,可以更具自己的需要放到同一个aidl下。
// IRemoteViewsApi.aidl
package com.crazymo.crossprocessupdui;
// Declare any non-default types here with import statements
interface IRemoteViewsApi {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void updRemoteViews(in RemoteViews remoteViews);
}
package com.crazymo.crossprocessupdui.service;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.widget.RemoteViews;
import com.crazymo.crossprocessupdui.IRemoteViewsApi;
import com.crazymo.crossprocessupdui.MainActivity;
/**
* @author : Crazy.Mo
*/
public class RemoteViewsUpdService extends AbstractService {
@Override
protected IBinder initBinder() {
if(mBinder==null){
mBinder=new RemoteViewsBinder();
}
//与客户端成功连接上的时候返回给客户端使用的对象
return mBinder;
}
@Override
protected boolean checkPermission() {
/* int check = checkCallingOrSelfPermission("com.crazymo.crossprocessupdui");
return check != PackageManager.PERMISSION_DENIED;*/
return true;
}
/**
* 继承AIDL文件里的Stub封装IBinder对象,可以理解为AIDL接口的实现
*/
private final class RemoteViewsBinder extends IRemoteViewsApi.Stub{
@Override
public void updRemoteViews(RemoteViews remoteViews) throws RemoteException {
//通过RemoteViews 间接接收参数
Message msg=new Message();
msg.what=2;
Bundle bundle=new Bundle();
bundle.putParcelable("remoteView",remoteViews);
msg.setData(bundle);
new MainActivity.UpdHandler(RemoteViewsUpdService.this).sendMessage(msg);
}
}
}
package com.crazymo.crossprocessupdui;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.Toast;
import com.crazymo.crossprocessupdui.aidl.AidlUtil;
/**
* @author Crazy.Mo
*/
public class RemoteViewsActivity extends AppCompatActivity {
private final static String ACTION_COMM="com.crazymo.crossprocess.remoteviews";
private boolean isUnbind=false;
private RemoteViewsUpdConn mRemoteViewsUpdConn=new RemoteViewsUpdConn();
private IRemoteViewsApi mRemoteViewsApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_remoteviews);
}
@Override
protected void onDestroy() {
super.onDestroy();
disconnRemoteApi();
}
/**
*主动断开
*/
private void disconnRemoteApi() {
if(!isUnbind) {
unbindService(mRemoteViewsUpdConn);
isUnbind = true;
}
}
private void connectAidl() {
boolean isBind;
isBind= AidlUtil.bindAidlService(this,mRemoteViewsUpdConn,ACTION_COMM);
Log.e("cmo","【客户端】线程"+Thread.currentThread().getName()+"绑定远程接口(成功true,失败false): "+isBind);
if(!isBind){
Toast.makeText(this,"连接远程服务发生异常",Toast.LENGTH_SHORT).show();
}
}
public void bind(View view) {
connectAidl();
}
public void remoteViewsUpd(View view) {
if(mRemoteViewsApi ==null){
connectAidl();
}
try {
//这里有必要判空下,因为假如你还未执行过连接的话,第一次就使用会去先连接这需要些时间,而且连接是异步不阻塞的,所以有可能造成空指针异常
if(mRemoteViewsApi !=null) {
Log.e("cmo", "【客户端】线程"+Thread.currentThread().getName()+"通过AIDL RemoteViews调用远程接口,客户端");
//由于需要传递RemoteViews 所以需要构造
RemoteViews remoteViews = new RemoteViews(RemoteViewsActivity.this.getPackageName(),R.layout.remoteviews_layout);
mRemoteViewsApi.updRemoteViews(remoteViews);
}
finish();
} catch (RemoteException pE) {
pE.printStackTrace();
}
}
private final class RemoteViewsUpdConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
////初始化远端服务对象实例
mRemoteViewsApi =IRemoteViewsApi.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if(isUnbind){
//主动断开直接置为null
mRemoteViewsApi =null;
}else{
while(mRemoteViewsApi ==null) {
connectAidl();//做重连
}
}
}
}
}
其他步骤和实现见源码,不过呢RemoteViews也不是万能的,虽然内部封装了很多方法用于实现更新UI,从官网得知,目前支持以下的Layout布局和View(不支持自定义View ):FrameLayout 、LinearLayout 、RelativeLayout 、GridLayout 、 ImageButton ImageView、 ProgressBar 、TextView、 ViewFlipper、 ListView 、GridView 、StackView 、AdapterViewFlipper 、ViewStub等。
ps:源码传送门