Android插件化学习之路(六)之动态创建Activity

静态代理Activity模式的限制

我们在代理Activity模式一文里谈到启动插件APK里的Activity的两个难题吗,由于插件里的Activity没在主项目的Manifest里面注册,所以无法经历系统Framework层级的一系列初始化过程,最终导致获得的Activity实例并没有生命周期和无法使用res资源。

使用代理Activity能够解决这两个问题,但是有一些限制

  1. 实际运行的Activity实例其实都是ProxyActivity,并不是真正想要启动的Activity;
  2. ProxyActivity只能指定一种LaunchMode,所以插件里的Activity无法自定义LaunchMode;
  3. 不支持静态注册的BroadcastReceiver;
  4. 往往不是所有的apk都可作为插件被加载,插件项目需要依赖特定的框架,还有需要遵循一定的”开发规范”;

特别是最后一个,无法直接把一个普通的APK作为插件使用。怎么避开这些限制呢?插件的Activity不是标准的Activity对象才会有这些限制,使其成为标准的Activity是解决问题的关键,而要使其成为标准的Activity,则需要在主项目里注册这些Activity。

想到代理模式需要注册一个代理的ProxyActivity,那么能不能在主项目里注册一个通用的Activity(比如TargetActivity)给插件里所有的Activity用呢?解决对策就是,在需要启动插件的某一个Activity(比如PlugActivity)的时候,动态创建一个TargetActivity,新创建的TargetActivity会继承PlugActivity的所有共有行为,而这个TargetActivity的包名与类名刚好与我们事先注册的TargetActivity一致,我们就能以标准的方式启动这个Activity。

动态创建Activity模式

运行时动态创建并编译一个Activity类,这种想法不是天方夜谭,动态创建类的工具有dexmakerasmdex,二者均能实现动态字节码操作,最大的区别是前者是创建dex文件,而后者是创建class文件。

使用dexmaker动态创建一个类

运行时创建一个编译好并能运行的类叫做“动态字节码操作(runtime bytecode manipulation)”,使用dexmaker工具能创建一个dex文件,之后我们再反编译这个dex看看创建出来的类是什么样子。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onMakeDex(View view){
        try {
            DexMaker dexMaker = new DexMaker();
            // Generate a HelloWorld class.
            TypeId<?> helloWorld = TypeId.get("LHelloWorld;");
            dexMaker.declare(helloWorld, "HelloWorld.generated", Modifier.PUBLIC, TypeId.OBJECT);
            generateHelloMethod(dexMaker, helloWorld);
            // Create the dex file and load it.
            File outputDir = new File(Environment.getExternalStorageDirectory() + File.separator + "dexmaker");
            if (!outputDir.exists())outputDir.mkdir();
            ClassLoader loader = dexMaker.generateAndLoad(this.getClassLoader(), outputDir);
            Class<?> helloWorldClass = loader.loadClass("HelloWorld");
            // Execute our newly-generated code in-process.
            helloWorldClass.getMethod("hello").invoke(null);
        } catch (Exception e) {
            Log.e("MainActivity","[onMakeDex]",e);
        }
    }

    /**
     * Generates Dalvik bytecode equivalent to the following method.
     *    public static void hello() {
     *        int a = 0xabcd;
     *        int b = 0xaaaa;
     *        int c = a - b;
     *        String s = Integer.toHexString(c);
     *        System.out.println(s);
     *        return;
     *    }
     */
    private static void generateHelloMethod(DexMaker dexMaker, TypeId<?> declaringType) {
        // Lookup some types we'll need along the way.
        TypeId<System> systemType = TypeId.get(System.class);
        TypeId<PrintStream> printStreamType = TypeId.get(PrintStream.class);

        // Identify the 'hello()' method on declaringType.
        MethodId hello = declaringType.getMethod(TypeId.VOID, "hello");

        // Declare that method on the dexMaker. Use the returned Code instance
        // as a builder that we can append instructions to.
        Code code = dexMaker.declare(hello, Modifier.STATIC | Modifier.PUBLIC);

        // Declare all the locals we'll need up front. The API requires this.
        Local<Integer> a = code.newLocal(TypeId.INT);
        Local<Integer> b = code.newLocal(TypeId.INT);
        Local<Integer> c = code.newLocal(TypeId.INT);
        Local<String> s = code.newLocal(TypeId.STRING);
        Local<PrintStream> localSystemOut = code.newLocal(printStreamType);

        // int a = 0xabcd;
        code.loadConstant(a, 0xabcd);

        // int b = 0xaaaa;
        code.loadConstant(b, 0xaaaa);

        // int c = a - b;
        code.op(BinaryOp.SUBTRACT, c, a, b);

        // String s = Integer.toHexString(c);
        MethodId<Integer, String> toHexString
                = TypeId.get(Integer.class).getMethod(TypeId.STRING, "toHexString", TypeId.INT);
        code.invokeStatic(toHexString, s, c);

        // System.out.println(s);
        FieldId<System, PrintStream> systemOutField = systemType.getField(printStreamType, "out");
        code.sget(systemOutField, localSystemOut);
        MethodId<PrintStream, Void> printlnMethod = printStreamType.getMethod(
                TypeId.VOID, "println", TypeId.STRING);
        code.invokeVirtual(printlnMethod, null, localSystemOut, s);

        // return;
        code.returnVoid();
    }

}

在SD卡的dexmaker目录下找到刚创建的文件“Generated1.jar”,把里面的“classes.dex”解压出来,然后再用“dex2jar”工具转化成jar文件,最后再用“jd-gui”工具反编译jar的源码。

这里写图片描述

至此,已经成功在运行时创建一个编译好的类。

修改需要启动的目标Activity

接下来的问题是如何把需要启动的、在Manifest里面没有注册的PlugActivity换成有注册的TargetActivity。 在Android,虚拟机加载类的时候,是通过ClassLoader的loadClass方法,而loadClass方法并不是final类型的,这意味着我们可以创建自己的类去继承ClassLoader,以重载loadClass方法并改写类的加载逻辑,在需要加载PlugActivity的时候,偷偷把其换成TargetActivity。

大致思路如下

public class CJClassLoader extends ClassLoader{

    @override
    public Class loadClass(String className){
        if(当前上下文插件不为空) {
            if( className 是 TargetActivity){
                找到当前实际要加载的原始PlugActivity,动态创建类(TargetActivity extends PlugActivity )的dex文件
                return  从dex文件中加载的TargetActivity
            }else{
                return  使用对应的PluginClassLoader加载普通类
            }
        }else{
            return super.loadClass() //使用原来的类加载方法
        }
    }
}

不过还有一个问题,主项目启动插件Activity的时候,我们可以替换Activity,但是如果在插件Activity(比如MainActivity)启动另一个Activity(SubActivity)的时候怎么办?插件时普通的第三方APK,我们无法更改里面跳转Activity的逻辑。其实,从主项目启动插件MainActivity的时候,其实启动的是我们动态创建的TargetActivity(extends MainActivity),而我们知道Activity启动另一个Activity的时候都是使用其“startActivityForResult”方法,所以我们可以在创建TargetActivity时,重写其“startActivityForResult”方法,让它在启动其他Activity的时候,也采用动态创建Activity的方式,这样就能解决问题。

动态类创建Activity缺陷

动态类创建的方式,使得注册一个通用的Activity就能给多给Activity使用,对这种做法存在的问题也是明显的 1. 使用同一个注册的Activity,所以一些需要在Manifest注册的属性无法做到每个Activity都自定义配置; 2. 插件中的权限,无法动态注册,插件需要的权限都得在宿主中注册,无法动态添加权限; 3. 插件的Activity无法开启独立进程,因为这需要在Manifest里面注册; 4. 动态字节码操作涉及到Hack开发,所以相比代理模式起来不稳定; 其中不稳定的问题出现在对Service的支持上,使用动态创建类的方式可以搞定Activity和Broadcast Receiver,但是使用类似的方式处理Service却不行,因为“ContextImpl.getApplicationContext” 期待得到一个非ContextWrapper的context,如果不是则继续下次循环,目前的Context实例都是wrapper,所以会进入死循环。 推荐一个动态代理的开源项目:android-pluginmgr

代理Activity模式与动态创建Activity模式的区别

简单地说,最大的不同是代理模式使用了一个代理的Activity,而动态创建Activity模式使用了一个通用的Activity。

代理模式中,使用一个代理Activity去完成本应该由插件Activity完成的工作,这个代理Activity是一个标准的Android Activity组件,具有生命周期和上下文环境(ContextWrapper和ContextCompl),但是它自身只是一个空壳,并没有承担什么业务逻辑;而插件Activity其实只是一个普通的Java对象,它没有上下文环境,但是却能正常执行业务逻辑的代码。代理Activity和不同的插件Activity配合起来,就能完成不同的业务逻辑了。所以代理模式其实还是使用常规的Android开发技术,只是在处理插件资源的时候强制调用了系统的隐藏API,因此这种模式还是可以稳定工作和升级的。

动态创建Activity模式,被动态创建出来的Activity类是有在主项目里面注册的,它是一个标准的Activity,它有自己的Context和生命周期,不需要代理的Activity。

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Android Studio 中创建一个 Activity 需要以下步骤: 1. 打开 Android Studio 并打开你的项目. 2. 在项目资源管理器中右键单击包名. 3. 选择 New > Activity > Empty Activity. 4. 在 "Create New Activity" 对话框中输入 Activity 名称并点击 Finish. 5. 新的 Activity 会被添加到项目中, 你可以在代码中编写你的 Activity 逻辑. ### 回答2: Android Studio是一种用于开发Android应用程序的IDE(集成开发环境)。在应用程序开发中,Activity是一个非常重要的部分。Activity是应用程序可视界面的组成部分之一。在Android Studio中,创建Activity只需遵循以下步骤: 1. 打开Android Studio并创建新项目。 2. 在“Create New Project”对话框中,务必选择“Empty Activity”标签。这将为您创建一个空的Activity。 3. 按照提示填写“Configure your new project”表单中的所有字段。确保所有信息都正确。 4. 点击“Finish”来创建您的新项目。这将在Project视图中显示所有相关文件和文件夹。 5. 在Project视图中,您会看到您的新项目的目录结构。 找到java目录,它位于您的新项目的src目录下。右键单击该目录,选择New➞Activity➞Empty Activity。 6. 填写所有必要的信息,例如Activity类名、布局文件名等等。确认信息是否正确后,单击“Finish”。 7. 现在,您已经创建了一个新的Activity。在代码中,您可以看到Activity类的继承结构、其布局文件和其他模板代码。这时,您可以修改Activity的UI、添加事件处理程序等等。 创建Activity时,请务必注意正确填写所有信息,这样才能保证Activity正常工作。此外,也应该根据自己的需要来修改Activity的UI界面和功能。这样,您的应用程序才能更好地满足用户的需求。在Coding时,也可以使用代码模板来加快开发速度。好的编码习惯和良好的代码文档注释,也能够帮助开发者快速编写高效的应用程序。 ### 回答3: Android Studio是一款专业的Android开发工具,它为开发者提供了丰富的功能,帮助我们快速构建出高质量的Android应用程序。其中最基本的组件是Activity,它是Android应用程序的核心组成部分。在Android Studio中,创建Activity非常简单,只需要按照以下步骤逐步操作即可。 第一步,打开Android Studio,选择“Start a new Android Studio project”或者“File”->“New”-> “New Project”菜单项: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/214379/1644714726509-08f7a8fe-9ce0-4a35-a954-861ed1f429e1.png) 第二步,填写应用程序的信息,如应用程序名称、包名、项目路径、项目存放的目录等等。接下来选择“Phone and Tablet”作为目标设备,然后点选“Empty Activity”作为应用程序的模板: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/214379/1644714933926-c69c1f4e-e1f2-4a2d-a6be-baf5910183a0.png) 第三步,填写Activity的信息,如Activity名称、布局文件名称、导航方式等等。点击“Finish”按钮后,Android Studio会为我们自动创建一个Activity类和一个布局文件。 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/214379/1644715163278-1d5c541b-bf8d-4160-b4a1-219418f81dcc.png) 最后,如果需要给Activity添加一些操作,可以在Activity类中重写一些方法,比如onCreate()方法,该方法会在Activity创建完成后被自动调用。在onCreate()方法中,我们可以通过setContentView()方法来设置Activity的布局文件,实现用户界面的显示效果。 至此,我们就成功地创建了一个Activity,通过这个例子,我们可以了解到,在Android Studio中创建Activity非常简单,开发者只需要按照手册上的步骤逐步操作,即可轻松实现应用程序的开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值