Activity插件化原理第一种方案:Hook Instrumentation

栏目: Android · 发布时间: 6年前

内容简介:温馨提示:阅读本文需要15-20分钟(一大波代码)今天,我们来解决一个问题:人生一切难题,知识给你答案。
Activity插件化原理第一种方案:Hook Instrumentation

温馨提示:阅读本文需要15-20分钟(一大波代码)

今天,我们来解决一个问题:

Activity插件化原理第一种方案:Hook Instrumentation

人生一切难题,知识给你答案。

Activity的插件化解决的一个根本性问题就是插件中的Activity并没有在宿主的AndroidManifest.xml中进行注册,也就是说我们需要启动一个未注册的Activity,因此需要对Activity的启动过程有个了解。

启动Activity时会请求AMS创建Activity,这里的AMS指的是ActivityManagerService,AMS所属的进程与宿主(发起者)不属于同一个进程,AMS位于SystemServer进程中。

Activity插件化原理第一种方案:Hook Instrumentation

应用程序进程与AMS之间的通信是通过Binder来实现的,AMS要管理所有APP的启动请求,因此我们不能在SystemServer进程中进行相应的Hook,那么我们只能在应用进程中进行相应的Hook。

如果我们启动一个未注册的Activity,AMS会去检查AndroidManifest中是否注册了该Activity,如果未注册会报错。

Activity插件化原理第一种方案:Hook Instrumentation

为了让AMS验证通过,需要启动一个预先在AndroidManifest中注册的Activity,我们称之为占坑,在启动插件Activity时替换为占坑Activity,达到一个欺上瞒下的作用,当AMS验证通过之后,需要将启动的占坑Activity替换为插件Activity。

Activity插件化原理第一种方案:Hook Instrumentation

总结下来Activity的插件化需要做两件事:

  • 将请求启动的插件Activity替换为占坑Activity。
  • 绕过AMS验证后,将占坑Activity替换为插件Activity。

什么时候将插件Activity替换为占坑Activity?又是什么时候还原插件Activity?这需要我们对Activity的启动流程有个相应的认识。

我们在Activity中调用startActivity方法如下:

@Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }
    
        @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
           startActivityForResult(intent, -1, options);
        } else {
           startActivityForResult(intent, -1);
        }
    }
复制代码

调用startActivityForResult方法:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            //Activity启动
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

复制代码

startActivityForResult方法中通过调用mInstrumentation的execStartActivity方法来启动Activity,这个mInstrumentation是Activity的成员变量,在ActivityThread的performLaunchActivity方法中通过Activity的attach方法传入,同时Activity的创建也是在performLaunchActivity方法中创建的,通过mInstrumentation.newActivity。

//:/frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    }
    ...
    activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
    ...
}
复制代码

综上所述Instrumentation提供了execStartActivity方法来启动Activity,newActivity方法来创建Activity。因此,第一种方案就是用代理Instrumentation来替代Activity的Instrumentation,并在代理Instrumentation的execStartActivity方法中替换为占坑Activity,在newActivity方法还原插件Activity。

现在我们基于第一种方案Hook Instrumentation来实现Activity的插件化。

首先创建占坑Activity:

public class StubActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stub);
    }
}
复制代码

创建插件Activity:

public class TargetActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_target);
    }
}
复制代码

并在AndroidManifest.xml中注册占坑Activity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.glh.haiproject01">

    <application
        android:name=".MyApplication"
        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"
        tools:ignore="AllowBackup,GoogleAppIndexingWarning">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>
复制代码

在AndroidManifest.xml中没有注册插件Activity,这时如果启动插件Activity会报错。

最后Hook Instrumentation,将ActivityThread中的成员变量Instrumentation替换成代理的Instrumentation。

创建代理Instrumentation类:

public class InstrumentationProxy extends Instrumentation {

    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;

    public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
        this.mInstrumentation = instrumentation;
        this.mPackageManager = packageManager;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        List<ResolveInfo> resolveInfo = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        //判断启动的插件Activity是否在AndroidManifest.xml中注册过
        if (null == resolveInfo || resolveInfo.size() == 0) {
            //保存目标插件
            intent.putExtra(HookHelper.REQUEST_TARGET_INTENT_NAME, intent.getComponent().getClassName());
            //设置为占坑Activity
            intent.setClassName(who, "com.glh.haiproject01.StubActivity");
        }

        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
            IllegalAccessException, ClassNotFoundException {
        String intentName=intent.getStringExtra(HookHelper.REQUEST_TARGET_INTENT_NAME);
        if(!TextUtils.isEmpty(intentName)){
            return super.newActivity(cl,intentName,intent);
        }
        return super.newActivity(cl,className,intent);
    }

}
复制代码

代理类InstrumentationProxy的execStartActivity方法先判断插件Activity是否在AndroidManifest.xml中注册过,如果没有注册过就需要替换占坑的Activity,在newActivity方法中还原插件Activity。

代理类InstrumentationProxy写完后,需要对ActivityThread的成员变量mInstrumentation进行替换。

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        hookActivityThreadInstrumentation();
    }


    private void hookActivityThreadInstrumentation(){
        try {
            Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");
            Field activityThreadField=activityThreadClass.getDeclaredField("sCurrentActivityThread");
            activityThreadField.setAccessible(true);
            //获取ActivityThread对象sCurrentActivityThread
            Object activityThread=activityThreadField.get(null);

            Field instrumentationField=activityThreadClass.getDeclaredField("mInstrumentation");
            instrumentationField.setAccessible(true);
            //从sCurrentActivityThread中获取成员变量mInstrumentation
            Instrumentation instrumentation= (Instrumentation) instrumentationField.get(activityThread);
            //创建代理对象InstrumentationProxy
            InstrumentationProxy proxy=new InstrumentationProxy(instrumentation,getPackageManager());
            //将sCurrentActivityThread中成员变量mInstrumentation替换成代理类InstrumentationProxy
            instrumentationField.set(activityThread,proxy);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
复制代码

这时我们在主界面点击跳转插件Activity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_startActivity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(MainActivity.this,TargetActivity.class);
                startActivity(intent);
            }
        });
    }
}
复制代码

运行效果:

Activity插件化原理第一种方案:Hook Instrumentation
Activity插件化原理第一种方案:Hook Instrumentation

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Is Parallel Programming Hard, And, If So, What Can You Do About

Is Parallel Programming Hard, And, If So, What Can You Do About

Paul E. McKenney

The purpose of this book is to help you understand how to program shared-memory parallel machines without risking your sanity.1 By describing the algorithms and designs that have worked well in the pa......一起来看看 《Is Parallel Programming Hard, And, If So, What Can You Do About 》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具