ホストが AndroidManifest で宣言されていないアクティビティ/サービスを開始する

AndroidManifestで宣言されていないプラグインActivityを開始する

前の記事では、ホストのプラグインでアクティビティを開始することができましたが、アクティビティはホストの AndroidManifest ファイルで宣言する必要があります。そうしないと、ActivityNotFound 例外がスローされます。
やるべきことは、上位と下位を欺くことです。つまり、AMS の startActivity メソッドで、ホスト App の AndroidManifest.xml ファイルで宣言されている StubActivity を使用して、実際に実行されるプラグインの Activity を一時的に置き換えます。イベントで、ActivityThread のハンドラーを使用して、StubActivity をプラグインの Activity に置き換えます。

public class AMSHookHelper {
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";

    /**
     * Hook AMS
     * 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity",进而骗过AMS
     */
    public static void hookAMS() throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException,
            IllegalAccessException, NoSuchFieldException {

        // 获取AMN的gDefault对象实例
        Object singletonObject = null;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityTaskManagerSingleton");
        }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityTaskManager","IActivityManagerSingleton");
        }else{
            // 获取AMN的gDefault单例,gDefault是final静态的
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        }

        // singletonObject 是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", singletonObject, "mInstance");

        // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> activityManagerInterface;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
            activityManagerInterface = Class.forName("android.app.IActivityTaskManager");
        }else {
            activityManagerInterface = Class.forName("android.app.IActivityManager");
        }

        Object proxy = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{ activityManagerInterface },
                new AMSProxy(mInstance));

        RefInvoke.setFieldObject("android.util.Singleton", singletonObject, "mInstance",proxy);
    }

    /**
     * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity
     * 不然就真的启动替身了, 狸猫换太子...
     * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
     * H 会完成这个消息转发; 最终调用它的callback
     */
    public static void hookActivityThread() throws Exception {

        // 先获取到当前的ActivityThread对象
        Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

        // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
        Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");

        //把Handler的mCallback字段,替换为new MockClass2(mH)
        RefInvoke.setFieldObject(Handler.class,
                mH, "mCallback", new HandlerProxy(mH));
    }
}

AMSProxy クラスは次のとおりです。

class AMSProxy implements InvocationHandler {

    private static final String TAG = "MockClass1";
	private static final String stubPackage = "jianqiang.com.hostapp";
    
    Object mBase;

    public AMSProxy(Object base) {
        mBase = base;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Log.e(TAG, method.getName());

        if ("startActivity".equals(method.getName())) {
            // 只拦截这个方法
            // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱

            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];

            Intent newIntent = new Intent();

            // 这里我们把启动的Activity临时替换为 StubActivity
            ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
            newIntent.setComponent(componentName);

            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);

            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }

        return method.invoke(mBase, args);
    }
}

HandlerProxy クラスは次のとおりです。

class HandlerProxy implements Handler.Callback {

    Handler mBase;

    public HandlerProxy(Handler base) {
        mBase = base;
    }

    @Override
    public boolean handleMessage(Message msg) {

        switch (msg.what) {
            // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
            // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
            case 100:   //for API 28以下
                handleLaunchActivity(msg);
                break;
            case 159: // //for API 28以上
                handleActivity(msg);
                break;
        }

        mBase.handleMessage(msg);
        return true;
    }

   private void handleActivity(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj; // ClientTransaction对象

        List<Object> mActivityCallbacks = (List<Object>) RefInvoke.getFieldObject(obj, "mActivityCallbacks");
        if(mActivityCallbacks.size() > 0) {
            String className = "android.app.servertransaction.LaunchActivityItem";
            if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {
                // 找到LaunchActivityItem
                Object object = mActivityCallbacks.get(0);
                // 找到传递过来的intent
                Intent intent = (Intent) RefInvoke.getFieldObject(object, "mIntent");
                Intent target = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
                intent.setComponent(target.getComponent());
            }
        }
    }

    private void handleLaunchActivity(Message msg) {
        // 取出TargetActivity
        Object obj = msg.obj; // ActivityClientRecord

        // 把替身恢复成真身,拿到 ActivityClientRecord 中的intent
        Intent raw = (Intent) RefInvoke.getFieldObject(obj,"intent");
        Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
        raw.setComponent(target.getComponent());
    }

}

これにより、AndroidManifest.xmlで宣言されていないActivityを正常に起動することができます。

AndroidManifestで宣言されていないプラグインサービスを開始する

ActivityThread は最終的に、インストルメンテーションを通じてアクティビティを開始します。ActivityThread は、Instrumentation を使用して Service を開始するのではなく、Service を直接反映して開始します。インストルメンテーションはアクティビティにのみサービスを提供します

startService ソリューション

1. ホスト アプリで複数のプレースホルダー サービスを宣言します。番号はプラグインのサービスと同じです。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jianqiang.com.hostapp">
	...
    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
			...

        <service android:name="jianqiang.com.hostapp.StubService1"/>
        <service android:name="jianqiang.com.hostapp.StubService2"/>
    </application>

</manifest>
2. ホストのプレースホルダー サービスがプラグイン内のサービスと 1 対 1 で対応するように、プラグインの Assets ディレクトリに plugin_config.json 構成ファイルを作成します。
{
  "plugins": [
    {
      "PluginService": "jianqiang.com.plugin1.MyService1",
      "StubService": "jianqiang.com.hostapp.StubService1"
    },
    {
      "PluginService": "jianqiang.com.plugin1.MyService2",
      "StubService": "jianqiang.com.hostapp.StubService2"
    }
  ]
}

構成ファイルを解析するメソッドを定義します。

 /**
     * 读取zip文件中某个文件为字符串,参看自Zeus的PluginUtil
     * @param zipFile     压缩文件
     * @param fileNameReg 需要获取的文件名
     * @return 获取的字符串
     */
    public static String readZipFileString(String zipFile,String fileNameReg){
        final int BUF_SIZE = 8192;

        String result = null;
        byte[] buffer = new byte[BUF_SIZE];
        InputStream in = null;
        ZipInputStream zipIn = null;
        ByteArrayOutputStream bos = null;
        try {
            File file = new File(zipFile);
            if (!file.exists()) return null;
            in = new FileInputStream(file);
            zipIn = new ZipInputStream(in);
            ZipEntry entry;
            while (null != (entry = zipIn.getNextEntry())) {
                String zipName = entry.getName();
                if (zipName.equals(fileNameReg)) {
                    int bytes;
                    int count = 0;
                    bos = new ByteArrayOutputStream();

                    while ((bytes = zipIn.read(buffer, 0, BUF_SIZE)) != -1) {
                        bos.write(buffer, 0, bytes);
                        count += bytes;
                    }
                    if (count > 0) {
                        result = bos.toString();
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                closeSilently(in);
                closeSilently(zipIn);
                closeSilently(bos);
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }
3. プラグインとホスト dex をマージします。
/**
 * 由于应用程序使用的ClassLoader为PathClassLoader
 * 最终继承自 BaseDexClassLoader
 * 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做
 * dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组
 * 系统的classLoader就能帮助我们找到这个类
 *
 * 这个类用来进行对于BaseDexClassLoader的Hook
 * 类名太长, 不要吐槽.
 * @author weishu
 * @date 16/3/28
 */
public final class BaseDexClassLoaderHookHelper {

    public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
            throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        // 获取 BaseDexClassLoader : pathList
        Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");

        // 获取 PathList: Element[] dexElements
        Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");

        // Element 类型
        Class<?> elementClass = dexElements.getClass().getComponentType();

        // 创建一个数组, 用来替换原始的数组
        Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

        // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
        Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
        Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
        Object o = RefInvoke.createObject(elementClass, p1, v1);

        Object[] toAddElementArray = new Object[] { o };
        // 把原始的elements复制进去
        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
        // 插件的那个element复制进去
        System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);

        // 替换
        RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
    }
}
4.「上の者を騙し、下の者を隠す」手法をとる
public class AMSHookHelperForService {
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";

    /**
     * Hook AMS
     * 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity",进而骗过AMS
     */
    public static void hookAMS() throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException,
            IllegalAccessException, NoSuchFieldException {

        // 获取AMN的gDefault对象实例
        Object singletonObject = null;
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManager","IActivityManagerSingleton");
        }else{
            // 获取AMN的gDefault单例,gDefault是final静态的
            singletonObject = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        }

        // singletonObject 是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
        Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", singletonObject, "mInstance");

        // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> activityManagerInterface;
        activityManagerInterface = Class.forName("android.app.IActivityManager");

        Object proxy = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{ activityManagerInterface },
                new AMSProxy(mInstance));

        RefInvoke.setFieldObject("android.util.Singleton", singletonObject, "mInstance",proxy);
    }

    /**
     * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity
     * 不然就真的启动替身了, 狸猫换太子...
     * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
     * H 会完成这个消息转发; 最终调用它的callback
     */
    public static void hookActivityThread() throws Exception {

        // 先获取到当前的ActivityThread对象
        Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

        // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
        Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");

        //把Handler的mCallback字段,替换为new HandlerProxy(mH)
        RefInvoke.setFieldObject(Handler.class,
                mH, "mCallback", new HandlerProxy(mH));
    }
}

Activity とは異なり、startService は ActivityManagerService の startService メソッドに委任されます。

AMSProxy ファイルに startService キャプチャと stopService キャプチャを追加します。

public class AMSProxy implements InvocationHandler {
    private static final String TAG = "AMSProxy";
    private static final String stubPackage = "jianqiang.com.hostapp";

    Object mBase;

    public AMSProxy(Object mBase) {
        this.mBase = mBase;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.e(TAG, method.getName());

        if ("startActivity".equals(method.getName())) {
            // 只拦截这个方法
            // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱

            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];

            Intent newIntent = new Intent();

            // 替身Activity的包名, 也就是我们自己的包名
            String stubPackage = "jianqiang.com.hostapp";

            // 这里我们把启动的Activity临时替换为 StubActivity
            ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
            newIntent.setComponent(componentName);

            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);

            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }else if("startService".equals(method.getName())){
            // hook startService

            // 找到参数里面的第一个Intent 对象
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            // 拿到启动Service时的Intent
            Intent rawIntent = (Intent) args[index];
            // jianqiang.com.plugin1.MyService1
            String rawServiceName = rawIntent.getComponent().getClassName();
            // 拿到插件Service对应的Service  jianqiang.com.hostapp.StubService1
            String stubServiceName = MyApplication.pluginServices.get(rawServiceName);
            // replace Plugin Service of StubService
            ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);
            // Replace Intent, cheat AMS
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }else if("stopService".equals(method.getName())){
            // 找到参数里面的第一个Intent 对象
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }

            //get StubService form UPFApplication.pluginServices
            Intent rawIntent = (Intent) args[index];
            String rawServiceName = rawIntent.getComponent().getClassName();
            String stubServiceName = MyApplication.pluginServices.get(rawServiceName);
            // replace Plugin Service of StubService
            ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);

            // Replace Intent, cheat AMS
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        } 
        return method.invoke(mBase, args);
    }
}

AMS が騙された後、本来は StubService を開始するように APP に通知しますが、ActivityThread の mH オブジェクトの mCallback オブジェクトをフックし、その handleMessage メソッドをインターセプトする必要がありますが、今回は CREATE_SERVICE (value=114) ブランチをインターセプトします。 ActivityThreadでhandleCreateServiceメソッドを実行します。ソースコードの実装を見てみましょう。

public void handleMessage(Message msg) {
    switch (msg.what) {
       ...

        case CREATE_SERVICE:
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        ("serviceCreate: " + String.valueOf(msg.obj)));
            }
            handleCreateService((CreateServiceData)msg.obj);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;

        ... 
    }
}
private void handleCreateService(CreateServiceData data) {
	LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        //创建service的context
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        //创建Application
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //获取类加载器
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //加载service实例
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
                ...
                //初始化service
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        //调用service的onCreate方法
        service.onCreate();
     } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ": " + e.toString(), e);
        }
    }
}

handleCreateServiceメソッドでは、CreateServiceDataを通じて起動するServiceの情報を取得できます。data.info.name の値をプラグイン サービスとしてフックするだけです。

public class HandlerProxy implements Handler.Callback {
    Handler mBase;

    public HandlerProxy(Handler mBase) {
        this.mBase = mBase;
    }

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 100: // //for API 28以下
                handleLaunchActivity(msg);
                break;
            case 159: // //for API 28以上
                handleActivity(msg);
                break;
            case 114: // CREATE_SERVICE
                handleCreateService(msg);
                break;
        }
        mBase.handleMessage(msg);
        return true;
    }

    private void handleCreateService(Message msg) {
        Object obj = msg.obj;
        ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldObject(obj, "info");

        String realServiceName = null;

        for (String key : MyApplication.pluginServices.keySet()) {
            String value = MyApplication.pluginServices.get(key);
            if(value.equals(serviceInfo.name)) {
                realServiceName = key;
                break;
            }
        }

        if(TextUtils.isEmpty(realServiceName)){
            // 只有在plugin_config中注册的才不会为空
            return;
        }

        serviceInfo.name = realServiceName;
    }

    private void handleActivity(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj; // ClientTransaction对象

        List<Object> mActivityCallbacks = (List<Object>) RefInvoke.getFieldObject(obj, "mActivityCallbacks");
        if(mActivityCallbacks.size() > 0) {
            String className = "android.app.servertransaction.LaunchActivityItem";
            if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {
                // 找到LaunchActivityItem
                Object object = mActivityCallbacks.get(0);
                // 找到传递过来的intent
                Intent intent = (Intent) RefInvoke.getFieldObject(object, "mIntent");
                Intent target = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
                intent.setComponent(target.getComponent());
            }
        }
    }

    private void handleLaunchActivity(Message msg) {
        // 取出TargetActivity
        Object obj = msg.obj; // ActivityClientRecord

        // 把替身恢复成真身,拿到 ActivityClientRecord 中的intent
        Intent raw = (Intent) RefInvoke.getFieldObject(obj,"intent");
        Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
        raw.setComponent(target.getComponent());
    }
}

これで startService のサポートが完了しました。

ホストに電話をかける

 public void startUnRegisterService1InPlugin1(View view) {
        try {

            Intent intent = new Intent();
            intent.setComponent(
                    new ComponentName("jianqiang.com.plugin1",
                            "jianqiang.com.plugin1.MyService1"));
            startService(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stopUnRegisterService1InPlugin1(View view) {
        try {
            Intent intent = new Intent();
            intent.setComponent(
                    new ComponentName("jianqiang.com.plugin1",
                            "jianqiang.com.plugin1.MyService1"));
            stopService(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

バインドサービスソリューション

AMSProxy にブランチを追加し、bindService のときに AMS を欺くだけです。

else if("bindIsolatedService".equals(method.getName())){
            // 找到参数里面的第一个Intent 对象
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }

            Intent rawIntent = (Intent) args[index];
            String rawServiceName = rawIntent.getComponent().getClassName();
            String stubServiceName = MyApplication.pluginServices.get(rawServiceName);


            // replace Plugin Service of StubService
            ComponentName componentName = new ComponentName(stubPackage, stubServiceName);
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);

            // Replace Intent, cheat AMS
            args[index] = newIntent;

            Log.d(TAG, "hook success");
            return method.invoke(mBase, args);
        }

その後、ホスト内でそれを呼び出すことができます。

public void bindUnRegisterService1InPlugin1(View view) {
        try {

            Intent intent = new Intent();
            intent.setComponent(
                    new ComponentName("jianqiang.com.plugin1",
                            "jianqiang.com.plugin1.MyService2"));
            bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i("TAG","onServiceConnected   " + name);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i("TAG","onServiceConnected   " + name);
        }
    };
    public void unbindUnRegisterService1InPlugin1(View view) {
        try {
            if(null != serviceConnection){
                unbindService(serviceConnection);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

質問 1: アンバインド時に AMS をチートしないのはなぜですか?

アンバインド構文は unbindService(conn) であるため、AMS は conn に基づいて対応するサービスを検索するため、MyService2 を StubService2 に置き換える必要はありません。

質問 2: MockClass2 で StubService2 を MyService2 に戻す必要がないのはなぜですか?

なぜなら、bindService は最初に handleCreateService を受け取り、次に handleBindService メソッドを受け取るからです。StubService2 は handleCreateService メソッドで MyService2 に戻されているため、後で切り替える必要はありません。

ここに画像の説明を挿入します
ソースコード

おすすめ

転載: blog.csdn.net/jxq1994/article/details/130891267