Shell 进程通过 ContentProvider 实现跨进程通信

点击上方"蓝字"关注我们!

背景

Android系统的UI测试框架有Uiautomator1.0和Uiautomator2.0,虽然Uiautomator1.0在Android11及以后的版本被放弃了,但是我们仍然可以通过反射FrameWork代码初的方式始化Uiautomator1.0服务,这样我们原有的测试用例就可以继续运行了。

今天分享一下Uiautomator1.0与server app跨进程通信的方案,我们一般情况下都使用socket进行通信,但是当server app没有运行时就很难及时处理,我们让server app实现ContentProvider来对外提供服务,即使server app没有运行,我们也能正常调用服务,系统会自动帮我们启动server app。

在应用(server app)内提供ContentProvider服务是很简单的,但是Uiautomator1.0和其他Shell进程是没有办法直接访问的,我们需要依赖反射技术进行调用。

实现

server app 创建ContentProvider服务

package com.xxxx.xxxx.ticker.server;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;


/**
 * @author walker
 * @date 2021/1/29.
 * @description 对外提供ContentProvider接口
 */
public class CommonProvider_tme extends ContentProvider  {

    @Override
    public boolean onCreate() {
        init();
        return false;
    }

    private void init() {
    }

    ;

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    /**
     * @author walker
     * @Date 2021/3/11
     * @Description: 如果需要附加信息,用此字段存储
     */
    public static final String RES_DATA = "data";
    /**
     * @author walker
     * @Date 2021/3/11
     * @Description: int类型的处理码
     * 200 流程处理结束
     * 404 指定的方法名错误
     * 100 调用服务的认证信息错误
     * 500 业务错误
     * 501 处理异常
     */
    public static final String RES_CODE = "code";
    /**
     * @author walker
     * @Date 2021/3/11
     * @Description: 处理结果,提示处理的异常信息等
     */
    public static final String RES_INFO = "info";


    /**
     * @author walker
     * @Date 2021/1/29
     * @Description: 对外提供call方法,通过方法名来指定调用的功能,是用arg进行简单的参数传递
     */
    @Override
    public Bundle call(String authority, String method, String arg, Bundle extras) {
        Bundle resBundle = new Bundle();
        System.out.println("®®® call contentprovider  authority=" + authority + "; method=" + method + "; arg=" + arg + "; extras" + extras);
        try {

            //限定调用的方法
            if (method==null || method.trim().length()==0) {
                resBundle.putString(RES_DATA, "");
                resBundle.putInt(RES_CODE, 404);
                resBundle.putString(RES_INFO, "Provider错误:必须指定调用方法名");
                return resBundle;
            }
            //验证访问请求
            if (!"com.xxxx.xxxx.xxxx".equals(authority)) {
                resBundle.putString(RES_DATA, "");
                resBundle.putInt(RES_CODE, 100);
                resBundle.putString(RES_INFO, "Provider错误:必须指定有效authorities信息");
                return resBundle;
            }
            resBundle.putString(RES_INFO, "ok");
            resBundle.putInt(RES_CODE, 200);
            boolean res = true;
            switch (method) {
                //根据不同的方法实现不同的业务
            }
            if (!res) {
                resBundle.putInt(RES_CODE, 500);
            }
        }catch (Exception e){

            resBundle.putInt(RES_CODE, 501);
            resBundle.putString(RES_INFO,e.getMessage());
        }
        return resBundle;
    }
}

  1. 在清单文件中声明
<application
    ...>
     <provider android:name=".server.CommonProvider"
android:authorities="com.xxxx.xxxx.xxxx"
            android:exported="true"
            android:enabled="true"
            />
</application>

在Shell进程或Uiautomator1.0内访问CommonProvider

IActivityManager activityManager = (IActivityManager) ActivityManagerNative.getDefault();

  1. 通过反射机制获取ContentProviderHolder
if (Build.VERSION.SDK_INT > 28) {
    Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class,String.class);
    holder=method.invoke(activityManager,authority, USER_SYSTEM, token, null);
} else {
    Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class);
    holder=method.invoke(activityManager,authority, USER_SYSTEM, token);
}

  1. 读取ContentProviderHolder的provider字段
Field field =holder.getClass().getDeclaredField("provider");
field.setAccessible(true);
provider = (IContentProvider)field.get(holder);

  1. 调用prover服务

**provider:**前面得到的IContentProvider对象

**calling_package:**指定任意字符串即可

**attributionTag:**无意义字符串,传递NULL即可

**authority:**校验字符串,必须与server app内指定的保持一致

**method:**调用的方法名

**arg:**访问字符串参数,一般不用

if (Build.VERSION.SDK_INT >= 30) {
    try {
        Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
        return (Bundle) method1.invoke(provider,calling_package,attributionTag,authority,method,arg,extras);
    } catch (Exception e) {
        System.err.println(e.getMessage());
    }
} else if (Build.VERSION.SDK_INT > 28) {
    try {
        Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class,  String.class, Bundle.class);
        return (Bundle) method1.invoke(provider,calling_package,authority,method,arg,extras);
    } catch (Exception e) {
        System.err.println(e.getMessage());
    }
} else {
    return provider.call(calling_package, method, arg, extras);
}

附完整代码

public class ShellContentProvider {
    int USER_SYSTEM = 0;
    String calling_package = "com.android.shell";
    String authority = "com.xxxx.xxxx.xxxx";

    public ShellContentProvider() {
        initProvider();
    }

    IContentProvider provider = null;

    /**
     * @author walker
     * @Date 2021/3/2
     * @Description: 初始化当前provider
     */
     void initProvider(){
        try {
            IActivityManager activityManager = (IActivityManager) ActivityManagerNative.getDefault();
            final IBinder token = new Binder();
            Object holder = null;
            try {
                if (Build.VERSION.SDK_INT > 28) {
                    Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class,String.class);
                    holder=method.invoke(activityManager,authority, USER_SYSTEM, token, null);
                } else {
                    Method method=activityManager.getClass().getDeclaredMethod("getContentProviderExternal",String.class,int.class, IBinder.class);
                    holder=method.invoke(activityManager,authority, USER_SYSTEM, token);
                }
                if (holder == null) {
                    throw new IllegalStateException("Could not find provider");
                }
                Field field =holder.getClass().getDeclaredField("provider");
                field.setAccessible(true);
                provider = (IContentProvider)field.get(holder);
            } finally {
            }
        } catch (Exception e) {
            System.err.println("Error while accessing settings provider");
            System.err.println(e.getMessage());
        }
    }

    public  Bundle callProvider(  final String authority, final String method, final String arg, final Bundle extras){
       return callProvider(provider,calling_package,null,authority,method,arg,extras);
    }


    public  Bundle callProvider(final String method, final String arg, final Bundle extras){
       return callProvider(provider,calling_package,null,authority,method,arg,extras);
    }

    /**
     * @author walker
     * @Date 2020/12/16
     * @Description: 调用IContentProvider方法,实现数据的查询和插入;不同的Android版本可能存在兼容问题
     * <ul>
     *     <li><a href="https://sourcegraph.com/github.com/aosp-mirror/platform_frameworks_base@android-11.0.0_r24/-/blob/core/java/android/content/IContentProvider.java#L82">Android 11代码</a></li>
     *     <li><a href="https://sourcegraph.com/github.com/aosp-mirror/platform_frameworks_base@android-security-10.0.0_r49/-/blob/core/java/android/content/IContentProvider.java#L83">Android 10代码</a></li>
     * </ul>
     */
    private Bundle callProvider(IContentProvider provider, final String calling_package, final String attributionTag, final String authority, final String method, final String arg, final Bundle extras) {
        if(provider==null){
            initProvider();
        }
        try {
            if (Build.VERSION.SDK_INT >= 30) {
                try {
                    Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class, String.class, String.class, Bundle.class);
                    return (Bundle) method1.invoke(provider,calling_package,attributionTag,authority,method,arg,extras);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
            } else if (Build.VERSION.SDK_INT > 28) {
                try {
                    Method method1=provider.getClass().getDeclaredMethod("call", String.class, String.class, String.class,  String.class, Bundle.class);
                    return (Bundle) method1.invoke(provider,calling_package,authority,method,arg,extras);
                } catch (Exception e) {
                    System.err.println(e.getMessage());
                }
            } else {
                return provider.call(calling_package, method, arg, extras);
            }

        } catch (RemoteException e) {
            System.err.println("Can't set key " + arg  + " for user " + USER_SYSTEM);
        }
        return null;
    }
}

Bundle bundle=new Bundle();
bundle.putBoolean("isCalled",true);
bundle.putInt("callFlag",1);
bundle.putString("arg","test");
Bundle res= new ShellContentProvider().callProvider("testMethod","hello",bundle);

虽然乱码了,但是我还是艰难的看完它,好多文件里没有解决的变量哦,com.android.shell这个是什么来的?调用的testMethod这个函数又是什么来的?