插件化基础:如何启动未经注册的activity和service

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_28268507/article/details/79458241

所谓的实践出真知,没有理论的实践是瞎忙,没有实践的理论是纸上谈兵,只有学习了理论,用实践验证理论—非常有用的一句话。

涉及到的关键类:ActivityThread,ContextImp,ActivityManager

涉及到的技术:activity启动流程,hook(也就是动态代理),java反射,AMS的交互过程

ActivityThread:

ActivityThread位于android.app包下,他是安卓应用的真正入口,以下代码拷贝自android SDK 26中,去掉了注释部分

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new EventLoggingReporter());
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");
        //准备ui线程的Looper,这也是为什么我们平时可以直接在ui线程中使用handler的原因,而子线程中必须手工进行初始化。
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            //就是mH,它是一个handler
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //开始loop,这里面就开始无限循环了,如果这里结束了那么就表示整个应用程序结束了
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

通过这段代码我们可以得到几个知识点,简单进行下总结:

  • 安卓应用程序的入口是什么?

    ActivityThread?Application?Activity? 想必大家也都知道答案了吧,真正的入口只有一个,就是ActivityThread

  • 为什么在子线程中需要手工进行Looper的初始化?

    因为在ui线程中,系统已经默认帮我们初始化了,而在子线程中必须手工进行初始化,可以参考HandlerThread

  • 安卓不能在ui线程中执行耗时操作,但是在程序的入口处却开始了无限循环,这种现象如何解释?

    准确的说不能再ui线程中进行耗时操作,指的是四大组件中的生命周期内不能进行耗时操作。而Looper的无限循环是Handler异步通讯的基础,不能结束,如果结束了,就代表应用程序结束了。ANR的异常检查是在AMS中进行校验的,可以参考 http://blog.csdn.net/vrix/article/details/54134124

ContextImp:

安卓四大组件运行时的上下文环境,以下代码拷贝自android SDK 26中,去掉了注释部分
代码路径为ActivityThread.handleLaunchActivity() -> performLaunchActivity()

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }
        //开始创建ContextImp
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        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);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        ......//忽略了好多代码
        return activity;
}


private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    final int displayId;
    try {
        displayId = ActivityManager.getService().getActivityDisplayId(r.token);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }

    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

    final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
    // For debugging purposes, if the activity's package name contains the value of
    // the "debug.use-second-display" system property as a substring, then show
    // its content on a secondary display if there is one.
    String pkgName = SystemProperties.get("debug.second-display.pkg");
    if (pkgName != null && !pkgName.isEmpty()
            && r.packageInfo.mPackageName.contains(pkgName)) {
        for (int id : dm.getDisplayIds()) {
            if (id != Display.DEFAULT_DISPLAY) {
                Display display =
                        dm.getCompatibleDisplay(id, appContext.getResources());
                appContext = (ContextImpl) appContext.createDisplayContext(display);
                break;
            }
        }
    }
    return appContext;
}

//通过一个静态方法创建的ContextImp的
static ContextImpl createActivityContext(ActivityThread mainThread,
        LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
        Configuration overrideConfiguration) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

    String[] splitDirs = packageInfo.getSplitResDirs();
    ClassLoader classLoader = packageInfo.getClassLoader();

    if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
        try {
            classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
            splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
        } catch (NameNotFoundException e) {
            // Nothing above us can handle a NameNotFoundException, better crash.
            throw new RuntimeException(e);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

    //开始实例化上下文
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
            activityToken, null, 0, classLoader);

    // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
    displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;

    final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
            ? packageInfo.getCompatibilityInfo()
            : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;

    final ResourcesManager resourcesManager = ResourcesManager.getInstance();

    // Create the base resources for which all configuration contexts for this Activity
    // will be rebased upon.
    context.setResources(resourcesManager.createBaseActivityResources(activityToken,
            packageInfo.getResDir(),
            splitDirs,
            packageInfo.getOverlayDirs(),
            packageInfo.getApplicationInfo().sharedLibraryFiles,
            displayId,
            overrideConfiguration,
            compatInfo,
            classLoader));
    context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
            context.getResources());
    return context;
}

ActivityManager

这个主要涉及到AMS交互,请参考这篇博客,作者写的确实很棒
http://blog.csdn.net/bjp000111/article/details/52232041

熟悉了上面的基础知识后并对activity的启动流程有一定的了解,就可以开始动手实践了。

坑位埋点是360公司最早提出来的一种解决方案,他们在插件化技术领域研究了好多年,是非常专业的,当然他们也开源了大名鼎鼎的RePlugin插件化框架 RePlugin

本次实践也是借鉴坑位思想实现的,下面开始逐步实现。

项目结构:

https://user-gold-cdn.xitu.io/2018/3/6/161f9e46cb931296?w=280&h=269&f=png&s=10643

App:

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        //在程序入口处进行初始化工作,也就是hook
        Plugin.init();
    }
}

Plugin:

public class Plugin {

    public static void init(){
        try {
            //反射获取ams并设置代理,兼容高低版本api差异的问题
            Object singletonObject = null;
            if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
                Field iActivityManagerSingletonField = Class.forName("android.app.ActivityManagerNative").getDeclaredField("gDefault");
                iActivityManagerSingletonField.setAccessible(true);
                singletonObject = iActivityManagerSingletonField.get(null);
            }else{
                Field iActivityManagerSingletonField = Class.forName("android.app.ActivityManager").getDeclaredField("IActivityManagerSingleton");
                iActivityManagerSingletonField.setAccessible(true);
                singletonObject = iActivityManagerSingletonField.get(null);
            }
            Field mInstanceField = Class.forName("android.util.Singleton").getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object iActivityManagerObject = mInstanceField.get(singletonObject);
            Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
            PluginInvocationHandler handler = new PluginInvocationHandler(iActivityManagerObject);
            //开始动态代理,也就是hook
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{IActivityManagerIntercept}, handler);
            mInstanceField.set(singletonObject, proxy);

            //反射获取activityThread中的mH
            Field sCurrentActivityThread = Class.forName("android.app.ActivityThread").getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread.setAccessible(true);
            Object activityThread = sCurrentActivityThread.get(null);
            Field mH = activityThread.getClass().getDeclaredField("mH");
            mH.setAccessible(true);
            Handler mHobject = (Handler) mH.get(activityThread);
            Field mCallback = Handler.class.getDeclaredField("mCallback");
            mCallback.setAccessible(true);
            //利用handler的特殊机制,开始进行半路拦截
            mCallback.set(mHobject,new PluginCallback());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

PluginInvocationHandler:

public class PluginInvocationHandler implements InvocationHandler {
    public static final String OLD_INTENT_FLAG = "old_intent_flag";

    Object activityManagerImp;
    public PluginInvocationHandler(Object activityManagerImp) {
        this.activityManagerImp = activityManagerImp;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if(name.equals("startActivity")){//替换坑位的activity
            Intent oldIntent = (Intent) args[2];
            ComponentName componentName = new ComponentName("com.example.administrator.myapplication", StubActivity.class.getName());
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);
            newIntent.putExtra(OLD_INTENT_FLAG,oldIntent);
            args[2] = newIntent;
        }else if(name.equals("startService")){//替换坑位的service
            Intent oldIntent = (Intent) args[1];
            ComponentName componentName = new ComponentName("com.example.administrator.myapplication", StubService.class.getName());
            Intent newIntent = new Intent();
            newIntent.setComponent(componentName);
            newIntent.putExtra(OLD_INTENT_FLAG,oldIntent);
            args[1] = newIntent;
        }
        return method.invoke(activityManagerImp,args);
    }
}

PluginCallback:

public class PluginCallback implements Handler.Callback {

    @Override
    public boolean handleMessage(Message msg) {
        if(msg.what == 100){
            processStartActivity(msg);
        }else if(msg.what == 114){
            processCreateService(msg);
        }
        return false;
    }

    /**
     * 换回原来的activity
     * @param msg
     */
    private void processStartActivity(Message msg){
        try {
            Object obj = msg.obj;
            Field field = obj.getClass().getDeclaredField("intent");
            field.setAccessible(true);
            Intent intent = (Intent) field.get(obj);
            Intent oldIntent = intent.getParcelableExtra(PluginInvocationHandler.OLD_INTENT_FLAG);
            if(oldIntent != null){
                ComponentName component = oldIntent.getComponent();
                intent.setComponent(component);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    /**
     * 换回原来的service
     * @param msg
     */
    private void processCreateService(Message msg){
        try {
            Object obj = msg.obj;
            Field field = obj.getClass().getDeclaredField("info");
            field.setAccessible(true);
            ServiceInfo serviceInfo = (ServiceInfo) field.get(obj);
            serviceInfo.name = TestService.class.getName();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

StubActivity和StubService 均是坑位埋点,需要在AndroidManifest中预先进行注册

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

    <application
        android:name=".App"
        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=".test.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <!-- 埋点的actiivty -->
        <activity android:name=".plugin.StubActivity" />
        <!-- 埋点的service -->
        <service android:name=".plugin.StubService"></service>
    </application>

</manifest>

完成以上工作就开始对demo进行测试了,下面代码中TestActivity和TestService均未在清单文件进行注册,我们通过点击按钮开始进行界面的跳转

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(v.getContext(),TestActivity.class));
            }
        });
        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(new Intent(v.getContext(),TestService.class));
            }
        });
    }
}

http://upload-images.jianshu.io/upload_images/10971164-765d0dd43ad7fc63.gif?imageMogr2/auto-orient/strip

猜你喜欢

转载自blog.csdn.net/qq_28268507/article/details/79458241