Android进阶——借助远程服务AIDL完成跨进程更新UI初探

文章大纲

  • 引言
  • 一、Android 跨进程更新UI 的方式
  • 二、实现AIDL 跨进程更新View
    • 1、创建更新UI的aidl并定义业务接口
    • 2、封装基本的父类和一些工具类
    • 3、实现对应AIDL的远程Service
    • 4、实现另一个进程的Activity
    • 5、在清单上注册服务和Activity
    • 6、MainActivity的实现
  • 三、借助Aidl和RemoteViews 实现跨进程更新UI
    • 1、创建AIDL文件
    • 2、实现远程服务
    • 3、绑定并调用远程服务

引言

前面两篇文章Android进阶——使用远程服务AIDL实现进程间简单通信小结(一)和Android进阶——使用远程服务AIDL实现进程间带远程回调接口较复杂通信小结(二)介绍了AIDL的基础语法和简单应用,AIDL 不仅仅是只能用于跨进程业务建的通信方面,对于跨进程操作界面也是可行的,当然AIDL 的用武之地不仅仅是列举的这些方面,这仅仅是冰山一角。

一、Android 跨进程更新UI 的方式

不借助框架的话,Android 跨进程更新UI 的方式主要有两大类:全局广播AIDL 进程间通信,其中AIDL进程间通信又可以分为两小类,下文直接通过一个案例讲解下,这个案例是在不同进程中分别有两个Activity,MainActivity是在主进程中的,而另一个XActivity 则是在另一个进程的,在XActivity的界面上通过调用AIDL来实现更新MainActivity的View。

二、实现AIDL 跨进程更新View

由于进程之间互相隔离不能直接通信,所以无论是需要传递数据还是直接更新UI都需要通过IPC机制才能通信,所以呢跨进程更新View可以分为两大步:先跨进程通信然后再像一个进程内实现更新UI跨进程可以通过AIDL完成,更更新UI就使用常见的Handler方式即可,分析完了背后的原理,就开始编码吧。

1、创建更新UI的aidl并定义业务接口

具体步骤参见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);
}

2、封装基本的父类和一些工具类

为了处理高版本下仅仅通过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();
}

3、实现对应AIDL的远程Service

  • 继承AbstractService 并重写onBind方法和checkPermission方法
  • 继承对应的Stub类,实现AIDL中定义的方法的具体的逻辑
  • 在onBind方法中返回我们自定义的Stub子类Binder对象
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);
        }
    }
}

4、实现另一个进程的Activity

  • 绑定远程服务
  • 使用远程服务更新UI
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();//做重连
                }
            }
        }
    }
}

5、在清单上注册服务和Activity

<?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>

6、MainActivity的实现

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。

三、借助Aidl和RemoteViews 实现跨进程更新UI

无论是使用原始方式接收基本参数信息还是使用RemoteViews间接接收,核心步骤和思想都是一样的,这里为了方便对比把这种方式放到单独的aidl和Service中,其实本质上是无任何差别,可以更具自己的需要放到同一个aidl下。

1、创建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);
}

2、实现远程服务

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);
        }
    }
}

3、绑定并调用远程服务

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:源码传送门

你可能感兴趣的:(AIDL,跨进程更新,远程服务,Android,进阶)