美文网首页安卓开发Android开发Android开发经验谈
Android插件化(二)动态加载Activity 和生命周期

Android插件化(二)动态加载Activity 和生命周期

作者: Kael_祈求者 | 来源:发表于2017-12-13 15:30 被阅读181次

    我们在上篇文章中主要讲了加载jar包和加载apk换肤,没看的可以直接戳着个链接:

    1.Android插件化(一) 动态加载技术

    这篇文章主要讲解如何利用动态代理技术Hook掉系统的AMS服务,来实现拦截Activity的启动流程,这种hook原理方式来自DroidPlugin
    。代码量不是很多,为了更容易的理解,需要掌握JAVA的反射,动态代理技术,以及Activity的启动流程。

    1、寻找Hook点的原则
    Android中主要是依靠分析系统源码类来做到的,首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?一般来说,静态变量和单例变量是相对不容易改变,是一个比较好的hook点,而普通的对象有易变的可能,每个版本都不一样,处理难度比较大。我们根据这个原则找到所谓的Hook点。

    2.寻找Hook点
    通常我们启动一个Activity.这中间发生了什么,我们如何Hook,来实现Activity启动的拦截呢?

        /**
         * hookActivity(启动一个无注册的activity)
         */
        public void hookActivity(View view) {
            Intent intent = new Intent(this, OtherActivity.class);
            intent.putExtra(DLConstants.EXTRA_CLASS, "plugin.charles.com.plugindemo.plugin.OtherActivity");
            intent.putExtra(DLConstants.EXTRA_PACKAGE, "plugin.charles.com.plugindemo");
            startActivity(intent);
        }
    

    我们目的是要拦截startActivity()方法 跟踪源码,跟着这个方法一步一步跟踪,会发现它最后在startActivityForResult里面调用了Instrument对象的execStartActivity方法;其实这个类相当于启动Activity的中间者,启动Activity中间都是由它来操作的

    public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
            IApplicationThread whoThread = (IApplicationThread) contextThread;
            ....
            try {
                intent.migrateExtraStreamToClipData();
                intent.prepareToLeaveProcess(who);
                
            //通过ActivityManagerNative.getDefault()获取一个对象,开始启动新的Activity
                int result = ActivityManagerNative.getDefault()
                    .startActivity(whoThread, who.getBasePackageName(), intent,
                            intent.resolveTypeIfNeeded(who.getContentResolver()),
                            token, target != null ? target.mEmbeddedID : null,
                            requestCode, 0, null, options);
                            
                            
                checkStartActivityResult(result, intent);
            } catch (RemoteException e) {
                throw new RuntimeException("Failure from system", e);
            }
            return null;
        }
    
    

    对于ActivityManagerNative这个东东,熟悉Activity/Service启动过程的都不陌生

    public abstract class ActivityManagerNative extends Binder implements IActivityManager
    

    继承了Binder,实现了一个IActivityManager接口,这就是为了远程服务通信做准备的"Stub"类,一个完整的AID L有两部分,一个是个跟服务端通信的Stub,一个是跟客户端通信的Proxy。ActivityManagerNative就是Stub,阅读源码发现在ActivityManagerNative 文件中还有个ActivityManagerProxy,这里就多不扯了。

    static public IActivityManager getDefault() {
        return gDefault.get();
    }
    

    ActivityManagerNative.getDefault()获取的是一个IActivityManager对象,由IActivityManager去启动Activity,IActivityManager的实现类是ActivityManagerService,ActivityManagerService是在另外一个进程之中,所有Activity 启动是一个跨进程的通信的过程,所以真正启动Activity的是通过远端服务ActivityManagerService来启动的。

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
            protected IActivityManager create() {
                IBinder b = ServiceManager.getService("activity");
                if (false) {
                    Log.v("ActivityManager", "default service binder = " + b);
                }
                IActivityManager am = asInterface(b);
                if (false) {
                    Log.v("ActivityManager", "default service = " + am);
                }
                return am;
            }
    
    

    其实gDefalut借助Singleton实现的单例模式,而在内部可以看到先从ServiceManager中获取到AMS远端服务的Binder对象,然后使用asInterface方法转化成本地化对象,我们目的是拦截startActivity,所以改变IActivityManager对象可以做到这个一点,这里gDefault又是静态的,根据Hook原则,这是一个比较好的Hook点。

    3.我们来拦截startActivity()方法,并且打印日志

     public class HookUtils {
        private Class<?> proxyActivity;
        private Context context;
    
        public HookUtils(Class<?> proxyActivity, Context context) {
            this.proxyActivity = proxyActivity;
            this.context = context;
        }
    
        public void hookAms() {
    
            //一路反射,直到拿到IActivityManager的对象
            try {
                Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative");
                Field defaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault");
                defaultFiled.setAccessible(true);
                Object defaultValue = defaultFiled.get(null);
                //反射SingleTon
                Class<?> SingletonClass = Class.forName("android.util.Singleton");
                Field mInstance = SingletonClass.getDeclaredField("mInstance");
                mInstance.setAccessible(true);
                //到这里已经拿到ActivityManager对象
                Object iActivityManagerObject = mInstance.get(defaultValue);
    
    
                //开始动态代理,用代理对象替换掉真实的ActivityManager,瞒天过海
                Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
    
                AmsInvocationHandler handler = new AmsInvocationHandler(iActivityManagerObject);
    
                Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{IActivityManagerIntercept}, handler);
    
                //现在替换掉这个对象
                mInstance.set(defaultValue, proxy);
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        private class AmsInvocationHandler implements InvocationHandler {
    
            private Object iActivityManagerObject;
    
            private AmsInvocationHandler(Object iActivityManagerObject) {
                this.iActivityManagerObject = iActivityManagerObject;
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                Log.i("HookUtil", method.getName());
                if ("startActivity".contains(method.getName())) {
                    //Todo
                    Log.e("HookUtil", "Activity已经开始启动");
                }
    
                return method.invoke(iActivityManagerObject, args);
            }
        }
    
    

    然后在Application配置下

    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            HookUtils hookUtil = new HookUtils(ProxyActivity.class, this);
            hookUtil.hookAms();
        }
    }
    

    这里的proxyActivity 可以提前在清单文件中注册,这个属于占坑位,下面会讲无需注册启动Activity,就需要再宿主中先注册个占坑位的Activity.
    最后我们只需要 调用上面第二点里面的hookActivity()方法就可以。
    看看执行效果


    image.png

    可以看到我们已经进行拦截了,并且打印我们想要的日志.

    4.无需注册Activity,并且启动
    如下:我们将下面这个OtherActivity 在清单文件中不注册了。当我们去点击跳转时肯定就会报错,

    "Unable to find explicit activity class "
                    + ((Intent)intent).getComponent().toShortString()
                    + "; have you declared this activity in your AndroidManifest.xml?");
    
       /**
        * hookActivity(启动一个无注册的activity)
        */
       public void hookActivity(View view) {
           Intent intent = new Intent(this, OtherActivity.class);
           intent.putExtra(DLConstants.EXTRA_CLASS, "plugin.charles.com.plugindemo.plugin.OtherActivity");
           intent.putExtra(DLConstants.EXTRA_PACKAGE, "plugin.charles.com.plugindemo");
           startActivity(intent);
       }
    

    上面已经拦截了启动Activity流程,在invoke中我们可以得到启动参数intent信息,那么就在这里,我们可以自己构造一个假的Activity信息的intent,这个Intent启动的Activity是在清单文件中注册的,当真正启动的时候(ActivityManagerService校验清单文件之后),用真实的Intent把代理的Intent在调换过来,然后启动即可。

    首先获取真实启动参数intent信息,在拦截地方添加如下代码

           @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                Log.i("HookUtil", method.getName());
                //我要在这里搞点事情
                if ("startActivity".contains(method.getName())) {
                    Log.e("HookUtil", "Activity已经开始启动");
    
                    //替换Intent 先找到原始的intent,然后直接伪造一个Intent 给AMS
                    Intent intent = null;
                    int index = 0;
                    for (int i = 0; i < args.length; i++) {
                        Object arg = args[i];
                        if (arg instanceof Intent) {
                            //说明找到了startActivity的Intent参数
                            intent = (Intent) args[i];
                            //这个意图是不能被启动的,因为Acitivity没有在清单文件中注册
                            index = i;
                        }
                    }
    
                    Intent proxyIntent = new Intent();
                    ComponentName componentName = new ComponentName(context, proxyActivity);
                    proxyIntent.setComponent(componentName);
                    proxyIntent.putExtra(DLConstants.EXTRA_CLASS, intent.getStringExtra(DLConstants.EXTRA_CLASS));
                    proxyIntent.putExtra(DLConstants.EXTRA_PACKAGE, intent.getStringExtra(DLConstants.EXTRA_PACKAGE));
    
                    proxyIntent.putExtra("oldIntent", intent);
                    args[index] = proxyIntent;
                }
    
                return method.invoke(iActivityManagerObject, args);
            }
    

    有了上面的两个步骤,这个代理的Intent是可以通过ActivityManagerService检验的,因为我在清单文件中注册过

         <!-- 替身Activity, 用来欺骗AMS  -->
         <activity android:name=".hook.ProxyActivity"></activity>
    

    为了不启动ProxyActivity,现在我们需要找一个合适的时机,把真实的Intent换过了来,启动我们真正想启动的Activity。看过Activity的启动流程的朋友,我们都知道这个过程是由Handler发送消息来实现的,可是通过Handler处理消息的代码来看,消息的分发处理是有顺序的,下面是Handler处理消息的代码:

    public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    handler处理消息的时候,首先去检查是否实现了callback接口,如果有实现的话,那么会直接执行接口方法,然后才是handleMessage方法,最后才是执行重写的handleMessage方法,我们一般大部分时候都是重写了handleMessage方法,而ActivityThread主线程用的正是重写的方法,这种方法的优先级是最低的,我们完全可以实现接口来替换掉系统Handler的处理过程.

    这个Handler.Callback是一个接口,我们可以使用动态代理或者普通代理完成Hook,这里我们使用普通的静态代理方式;创建一个自定义的Callback类:

        public void hookSystemHandler() {
            try {
                Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
                currentActivityThread.setAccessible(true);
                //获取主线程对象
                Object activityThread = currentActivityThread.invoke(null);
    
                Field mH = activityThreadClass.getDeclaredField("mH");
                mH.setAccessible(true);
                Handler handler = (Handler) mH.get(activityThread);
                Field mCallback = Handler.class.getField("mCallback");
                mCallback.setAccessible(true);
                //设置自己实现的CallBack
                mCallback.set(handler, new ActivityThreadHandlerCallback(handler));
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    自定义Callback类

    public class ActivityThreadHandlerCallback implements Handler.Callback {
        private Handler handler;
    
    
        public ActivityThreadHandlerCallback(Handler handler) {
            this.handler = handler;
        }
    
        @Override
        public boolean handleMessage(Message msg) {
            //代表ActivityThread mH中的launch_activity
            if(msg.what == 100){
                handleLaunchActivity(msg);
            }
            handler.handleMessage(msg);
            return true;
        }
    
        private void handleLaunchActivity(Message msg) {
            //ActivityClientRecord
            Object obj = msg.obj;
            try {
                Field intentField  = obj.getClass().getDeclaredField("intent");
                intentField .setAccessible(true);
                Intent proxyInent  = (Intent) intentField.get(obj);
                Intent realIntent = proxyInent.getParcelableExtra("oldIntent");
                if (realIntent != null) {
                    proxyInent.setComponent(realIntent.getComponent());
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    
    }
    

    最后在Application中配置

    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            HookUtils hookUtil = new HookUtils(ProxyActivity.class, this);
            hookUtil.hookSystemHandler();
            hookUtil.hookAms();
        }
    }
    

    到这里,我们已经成功地绕过AMS,完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的过程。执行hookActivity()方法就能成功跳入.

    能正确收到生命周期回调吗?虽然我们完成了『启动没有在AndroidManifest.xml中显式声明的Activity 』 但是启动的OtherActivity是否有自己的生命周期呢,我们还需要额外的处理过程吗?

    需要额外处理
    

    4.生命周期处理
    把一个Activity类加载之后,怎么使插件里的Activity具有生命周期。
    这里使用Activity代理模式。老套路,在宿主APK注册一个ProxyActivity(代理Activity),就是作为占坑使用。每次打开插件APK里的某一个Activity的时候,都是在宿主里使用启动ProxyActivity,然后在ProxyActivity的生命周期里方法中,调用插件中的Activity实例的生命周期方法,从而执行插件APK的业务逻辑。所以思路就来了。
    第一、ProxyActivity中需要保存一个Activity实例,该实例记录着当前需要调用插件中哪个Activity的生命周期方法。
    第二、ProxyActivity如何调用插件apk中Activity的所有生命周期的方法,使用反射呢?还是其他方式。

    这里借用dynamic-load-apk

    经过优化,改成接口的方式,将activity的生命周期方法封装一个接口,代理activity中实现一下这个接口,然后还是通过代理activity去调用插件activity实现的生命周期方法.

    4.1: 接口类

    public interface DLPlugin {
    
        public void onCreate(Bundle savedInstanceState);
        public void onStart();
        public void onRestart();
        public void onActivityResult(int requestCode, int resultCode, Intent data);
        public void onResume();
        public void onPause();
        public void onStop();
        public void onDestroy();
        public void attach(Activity proxyActivity);
        public void onSaveInstanceState(Bundle outState);
        public void onNewIntent(Intent intent);
        public void onRestoreInstanceState(Bundle savedInstanceState);
        public boolean onTouchEvent(MotionEvent event);
        public boolean onKeyUp(int keyCode, KeyEvent event);
        public void onWindowAttributesChanged(WindowManager.LayoutParams params);
        public void onWindowFocusChanged(boolean hasFocus);
        public void onBackPressed();
        public boolean onCreateOptionsMenu(Menu menu);
        public boolean onOptionsItemSelected(MenuItem item);
    }
    
    public interface DLAttachable {
        public void attach(DLPlugin proxyActivity);
    }
    
    

    4.2在代理类ProxyActivity中的实现

    public class ProxyActivity extends AppCompatActivity implements DLAttachable {
    
        private DLProxyImpl impl = new DLProxyImpl(this);
        protected DLPlugin mRemoteActivity;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            impl.onCreate(getIntent());
            Log.e("Charles2","代理的onCreate");
        }
    
        @Override
        protected void onStart() {
            if(mRemoteActivity != null){
                mRemoteActivity.onStart();
            }
            super.onStart();
            Log.e("Charles2","代理的onStart");
        }
    
        @Override
        protected void onRestart() {
            if(mRemoteActivity != null){
                mRemoteActivity.onRestart();
            }
            super.onRestart();
            Log.e("Charles2","代理的onRestart");
        }
    
        @Override
        protected void onResume() {
            if(mRemoteActivity != null){
                mRemoteActivity.onResume();
            }
            super.onResume();
            Log.e("Charles2","代理的onResume");
        }
    
        @Override
        protected void onPause() {
            if(mRemoteActivity != null){
                mRemoteActivity.onPause();
            }
            super.onPause();
            Log.e("Charles2","代理的onPause");
        }
    
        @Override
        protected void onDestroy() {
            if(mRemoteActivity != null){
                mRemoteActivity.onDestroy();
            }
            super.onDestroy();
            Log.e("Charles2","代理的onDestroy");
        }
    
        @Override
        public void attach(DLPlugin proxyActivity) {
            mRemoteActivity = proxyActivity;
        }
    
    }
    
    

    4.3 代理实现类

    public class DLProxyImpl {
        private Activity mProxyActivity;
        private String mPackageName;
        private String mClass;
        protected DLPlugin mPluginActivity;
    
        public DLProxyImpl(Activity activity) {
            mProxyActivity = activity;
        }
    
        public void onCreate(Intent intent) {
            if (intent != null) {
                mPackageName = intent.getStringExtra(DLConstants.EXTRA_PACKAGE);
                mClass = intent.getStringExtra(DLConstants.EXTRA_CLASS);
            }
            launchTargetActivity();
        }
    
        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        protected void launchTargetActivity() {
            try {
                Class<?> localClass = mProxyActivity.getClassLoader().loadClass(mClass);
                //Class<?> localClass = getClassLoader().loadClass(mClass);
                Constructor<?> localConstructor = localClass.getConstructor(new Class[]{});
                Object instance = localConstructor.newInstance(new Object[]{});
                mPluginActivity = (DLPlugin) instance;
    
                ((DLAttachable) mProxyActivity).attach(mPluginActivity);
                // attach the proxy activity and plugin package to the mPluginActivity
                mPluginActivity.attach(mProxyActivity);
    
                Bundle bundle = new Bundle();
                mPluginActivity.onCreate(bundle);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public ClassLoader getClassLoader() {
            return PluginManager.getInstance(mProxyActivity).createDexClassLoader("");
        }
    
    }
    

    4.4. 最后看看我们的目标Activity-->otherActivity

    public class OtherActivity extends DLBasePluginActivity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.e("Charles2", "plugin--onCreate");
            that.setContentView(R.layout.activity_other);
        }
    
        @Override
        public void onResume() {
            super.onResume();
            Log.e("Charles2", "plugin--onResume");
        }
    
        @Override
        public void onStart() {
            super.onStart();
            Log.e("Charles2", "plugin--onStart");
        }
    
        @Override
        public void onPause() {
            super.onPause();
            Log.e("Charles2", "plugin--onPause");
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.e("Charles2", "plugin--onDestroy");
        }
    
    }
    
    public class DLBasePluginActivity extends Activity implements DLPlugin {
        /**
         * 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
         */
        protected Activity mProxyActivity;
        /**
         * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
         * 可以当作this来使用
         */
        protected Activity that;
        private int type = 0;
    
        @Override
        public void attach(Activity proxyActivity) {
            mProxyActivity = (Activity) proxyActivity;
            that = mProxyActivity;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            if (type == 1) {
                super.onCreate(savedInstanceState);
            }
        }
    
        @Override
        public void onStart() {
            if (type == 1) {
                super.onStart();
            }
        }
    
        @Override
        public void onRestart() {
            if (type == 1) {
                super.onRestart();
            }
        }
    
        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
    
        }
    
        @Override
        public void onResume() {
            if (type == 1) {
                super.onResume();
            }
        }
    
        @Override
        public void onPause() {
            if (type == 1) {
                super.onPause();
            }
        }
    
        @Override
        public void onStop() {
            if (type == 1) {
                super.onStop();
            }
        }
    
        @Override
        public void onDestroy() {
            if (type == 1) {
                super.onDestroy();
            }
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
    
        }
    
        @Override
        public void onNewIntent(Intent intent) {
    
        }
    
        @Override
        public void onRestoreInstanceState(Bundle savedInstanceState) {
    
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return false;
        }
    
        @Override
        public boolean onKeyUp(int keyCode, KeyEvent event) {
            return false;
        }
    
        @Override
        public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
    
        }
    
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
    
        }
    
        @Override
        public void onBackPressed() {
    
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            return false;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            return false;
        }
    
    }
    

    到这里全部就全部结束了。这里我们来看看效果,是否会走我们的生命周期.


    image.png

    demo地址:https://github.com/15189611/pluginDemo
    参考:https://github.com/singwhatiwanna/dynamic-load-apk

    相关文章

      网友评论

        本文标题:Android插件化(二)动态加载Activity 和生命周期

        本文链接:https://www.haomeiwen.com/subject/btewbxtx.html