Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

初始化壳子类的时候无法找到 #505

Closed
Heart-Beats opened this issue Apr 14, 2021 · 12 comments · Fixed by #520
Closed

初始化壳子类的时候无法找到 #505

Heart-Beats opened this issue Apr 14, 2021 · 12 comments · Fixed by #520

Comments

@Heart-Beats
Copy link

运行时总是报错:

2021-04-14 21:38:54.101 10121-10121/com.example.zhanglei.myapplication.test E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.zhanglei.myapplication.test, PID: 10121
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.zhanglei.myapplication.test/com.hl.my_runtime.PluginDefaultProxyActivity}: java.lang.ClassNotFoundException: Didn't find class "com.hl.my_runtime.PluginDefaultProxyActivity" on path: DexPathList[[zip file "/data/app/com.example.zhanglei.myapplication.test-gqeKhx0oVxhB_YnymbuOOQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.zhanglei.myapplication.test-gqeKhx0oVxhB_YnymbuOOQ==/lib/arm64, /data/app/com.example.zhanglei.myapplication.test-gqeKhx0oVxhB_YnymbuOOQ==/base.apk!/lib/arm64-v8a, /system/lib64, /hw_product/lib64, /system/product/lib64, /prets/lib64]]
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3812)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4077)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:91)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2458)
        at android.os.Handler.dispatchMessage(Handler.java:110)
        at android.os.Looper.loop(Looper.java:219)
        at android.app.ActivityThread.main(ActivityThread.java:8387)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "com.hl.my_runtime.PluginDefaultProxyActivity" on path: DexPathList[[zip file "/data/app/com.example.zhanglei.myapplication.test-gqeKhx0oVxhB_YnymbuOOQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.zhanglei.myapplication.test-gqeKhx0oVxhB_YnymbuOOQ==/lib/arm64, /data/app/com.example.zhanglei.myapplication.test-gqeKhx0oVxhB_YnymbuOOQ==/base.apk!/lib/arm64-v8a, /system/lib64, /hw_product/lib64, /system/product/lib64, /prets/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:209)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
        at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:45)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1264)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3797)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4077) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:91) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:149) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:103) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2458) 
        at android.os.Handler.dispatchMessage(Handler.java:110) 
        at android.os.Looper.loop(Looper.java:219) 
        at android.app.ActivityThread.main(ActivityThread.java:8387) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) 

宿主中定义的壳子类如下:

        <activity
            android:name="com.hl.my_runtime.PluginDefaultProxyActivity"
            android:launchMode="standard"
            android:screenOrientation="portrait"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
            android:hardwareAccelerated="true"
            android:theme="@style/PluginContainerActivity"
            android:multiprocess="true"
            android:process=":plugin"
            />

壳子类定义如下图:
image

插件的 applicationId 已与宿主保持一致,不知道为啥会出现此问题,希望可以指点迷津,提前谢谢!

@shifujun
Copy link
Collaborator

你的这个my-runtime看起来也是个apk模块,那跟sample中的就是一样的。所以它也是动态加载的,你需要关注一下DynamicRuntime的加载情况。先去sample里debug了解一下过程,再看看你的应用吧。

boolean loaded = DynamicRuntime.loadRuntime(installedRuntimeApk);

另外,如果不关心宿主方法数增量,也可以直接把my-runtime打包到宿主中。DynamicRuntime不是Shadow的关键实现。

@Heart-Beats
Copy link
Author

好的,我再看看,不行就放宿主中,谢谢

@Heart-Beats
Copy link
Author

您好:

我在研究调用流程时测试自己的APK发现一个问题,主要代码在如下位置:

   // FastPluginManager 类

    public InstalledPlugin installPlugin(String zip, String hash, boolean odex) throws IOException, JSONException, InterruptedException, ExecutionException {
    
        ...... //加载解压插件操作

        onInstallCompleted(pluginConfig);

        return getInstalledPlugins(1).get(0);
    }

首先说一下我的前置操作:
我是用同一个 PluginManager 来加载两个不同的插件压缩包 sample-plugin 和 my-plugin。

发现 BasePluginManager 调用 onInstallCompleted(pluginConfig) 时,会向数据库插入插件的配置信息:

       db.beginTransaction();
       try {
           for (ContentValues contentValues : contentValuesList) {
               db.replace(InstalledPluginDBHelper.TABLE_NAME_MANAGER, null, contentValues);
           }
           //把最后一次uuid的插件安装时间作为所有相同uuid的插件的安装时间
           ContentValues values = new ContentValues();
           values.put(InstalledPluginDBHelper.COLUMN_INSTALL_TIME, pluginConfig.storageDir.lastModified());
           db.update(InstalledPluginDBHelper.TABLE_NAME_MANAGER, values, InstalledPluginDBHelper.COLUMN_UUID + " = ?", new String[]{pluginConfig.UUID});

           db.setTransactionSuccessful();
       } finally {
           db.endTransaction();
       }

这时如果插件压缩包始终未修改时,每次加载插件调用仍会向数据库中插入数据,数据库会变成如下图所示:
image

可以看到 sample-plugin 的安装时间(即修改时间)比较新,随后 getInstalledPlugins(1).get(0) 会获取最后安装的插件,这样就会导致即使加载 my-plugin 插件,但获取到的安装插件仍为 sample-plugin 插件,即使 getInstalledPlugins(1) 传入的参数比较大也不好获取到想要的插件,所以我不清楚这个限制是不是设计如此?

是否设计时就要求每个插件包必须要有一个 PluginManager 还是目前逻辑上有些缺陷?

  • getInstalledPlugins(int limit) 方法是否可考虑加上 uuid 参数?
  • 数据库插入相同的插件配置时做到覆盖?

期待您的解惑,谢谢!

@Heart-Beats
Copy link
Author

或者插件的安装时间进行修改?不以插件文件的修改时间为准,而以插入数据库的时间为准。

@shifujun
Copy link
Collaborator

core.manager是Shadow带的一个插件包管理的实现,这个实现不是非常关键的设计,但和插件加载流程关系比较大。有需要的话可以自己定制修改。

wiki里有一篇Shadow对插件包管理的设计可以读一下。

注意一下,在core.manager的设计中。一个Manager只管理同一个业务的一组插件。这一组插件是同一组,是用UUID关联起来的。一组插件中的某个插件是用parkKey区分的。后安装的插件是最新的插件。

在这个设计中考虑的是一组插件是一起构建发布的,它们之间的版本关系是1对1的,可以通过ClassLoader关系相互依赖。但是它们和宿主之间的版本关系是1对多的,不能任意引用宿主中的内容。

所以只要考虑好版本对应关系,这个Manager的逻辑可以自己根据业务的需要定义。只需要参考core.manager的一些插件安装时必要的odex、复制so等操作的实现就行了。

@Heart-Beats
Copy link
Author

好的,谢谢您的解惑,我已可以成功加载启动插件 APK 中的 Activity,当前使用 mPluginLoader.startActivityInPluginProcess(intent); 来启动,先前使用 context.startActivity(intent) 来启动的,我看 demo 中两种方式都有,但实际上我这边却仅仅只有第一种方式可以生效。

还有问题是我这边尝试在一个插件包中通过 partKey 使用多插件,结构如下:
image

  1. 然后我就碰到一个疑惑的地方,shadow {} 该闭包我应该置于哪个下面,按正常构想应该是只需要运行此任务就可以打所有需要的 APK 打包进插件,应为一个全局任务但实际使用时发现应用 shadow 的插件时,必须要apply plugin: 'com.android.application'(是否理解有误?),这就导致只能在 APK 的 build.gradle 下使用,我本想按上图抽出公共脚本文件再在 apk 中引用,但执行时会出错,目前是放在 test 下作为它的 task, 这就觉得很怪。

  2. 当我使用如下方式打包好插件后:

shadow {
    packagePlugin {
        pluginTypes {
            debug {
                loaderApkConfig = new Tuple2('my-loader-debug.apk', ':my-loader:assembleDebug')
                runtimeApkConfig = new Tuple2('my-runtime-debug.apk', ':my-runtime:assembleDebug')
                pluginApks {
                    pluginApk1 {
                        businessName = 'test-debug'
                        //businessName相同的插件,context获取的Dir是相同的。businessName留空,表示和宿主相同业务,直接使用宿主的Dir。
                        partKey = 'test'
                        buildTask = ':test:assembleDebug'
                        apkName = 'test-debug.apk'
                        apkPath = '/plugin/test/build/outputs/apk/debug/test-debug.apk'
                        //hostWhiteList = ["com.tencent.shadow.sample.host.lib"]
                    }

                    pluginApk2 {
                        businessName = 'sunflower-debug'
                        //businessName相同的插件,context获取的Dir是相同的。businessName留空,表示和宿主相同业务,直接使用宿主的Dir。
                        partKey = 'sunflower'
                        buildTask = ':sunflower:assembleDebug'
                        apkName = 'sunflower-debug.apk'
                        apkPath = '/plugin/sunflower/build/outputs/apk/debug/sunflower-debug.apk'
                        //hostWhiteList = ["com.tencent.shadow.sample.host.lib"]
                    }
                }
            }
}

使用如下方式来分别打开不同的插件:
image

结果:test 可以正常启动,但 sunflower 启动报错:
image
错误原因很简单,但是我并不是很清楚同样的流程为何它发生了,LoadPluginBloc.loadPlugin()该方法里执行的流程对我来说有一定的研究难度,因时间紧迫还是希望能尽快找到具体的原因。

期待您的再次回复解惑,谢谢!

@shifujun
Copy link
Collaborator

看起来就是你那个sunflower工程没有apply plugin: 'com.tencent.shadow.plugin'。可以参考一下projects/test/plugin里的那些插件。

确实只有test-plugin-general-cases里面写了shadow {} DSL。这个DSL设计的确实不好,它只是定义在一个插件中的,但是写了所有插件的信息。其他插件就不用写了。这个DSL主要是业务用上了就不太会改了,所以我们也不太需要改进它了。它也不是Shadow的关键技术。

@Heart-Beats
Copy link
Author

sunflower工程确实没有 apply plugin: 'com.tencent.shadow.plugin,我给加上了这句之后实际运行:
image

这是 sunflower 中还需要加入其他的配置吗?

@shifujun
Copy link
Collaborator

看起来是API 29的onActivityPreCreated新生命周期适配错误,是Bug。我晚点看看怎么修复。

这个回调早于onCreate了。插件之前是在onCreate里初始化的,所以会报找不到插件的那个字段crash。

建议先在低于29的机器上测试。

https://developer.android.com/reference/android/app/Application.ActivityLifecycleCallbacks#onActivityPreCreated(android.app.Activity,%20android.os.Bundle)

@Heart-Beats
Copy link
Author

Heart-Beats commented May 31, 2021

嗯嗯,我这边在 Android 9 (API 28) 的模拟器上测试确实没有此问题,我自己的手机是 Android 10 的切换插件时会闪退,大佬有时间可以修复下此问题。

@shifujun
Copy link
Collaborator

已经抽时间在写了。修复代码容易,就是写自动化测试比较费时间。

@Heart-Beats
Copy link
Author

确实,写测试 case 比写代码还麻烦,一般也只有大佬才会特别注重这个方面,像我这样的小菜鸟实现个逻辑功能没啥 bug 就感到比较舒服了,还有看大佬写的代码真是如浴春风,设计层次分明、代码逻辑清晰、注释简单易懂...
尽量争取向大佬看齐
image

shifujun added a commit to shifujun/Shadow that referenced this issue May 31, 2021
API 29以上ActivityLifecycleCallbacks有一个默认实现的接口onActivityPreCreated,
这个方法回调早于onCreate方法,也是唯一一个。此时PluginActivity尚未构造,因此不能正常工作。

统一ActivityLifecycleCallbacks映射实现到ShadowActivityLifecycleCallbacks.Holder,
由它持有所有ActivityLifecycleCallbacks。
ShadowActivityDelegate在PluginActivity onCreate之前通知Holder分发onActivityPreCreated事件。

Tencent#505
shifujun added a commit to shifujun/Shadow that referenced this issue Jun 1, 2021
API 29以上ActivityLifecycleCallbacks有一个默认实现的接口onActivityPreCreated,
这个方法回调早于onCreate方法,也是唯一一个。此时PluginActivity尚未构造,因此不能正常工作。

统一ActivityLifecycleCallbacks映射实现到ShadowActivityLifecycleCallbacks.Holder,
由它持有所有ActivityLifecycleCallbacks。
ShadowActivityDelegate在PluginActivity onCreate之前通知Holder分发onActivityPreCreated事件。

Tencent#505
Tencent#519
shifujun added a commit that referenced this issue Jun 1, 2021
API 29以上ActivityLifecycleCallbacks有一个默认实现的接口onActivityPreCreated,
这个方法回调早于onCreate方法,也是唯一一个。此时PluginActivity尚未构造,因此不能正常工作。

统一ActivityLifecycleCallbacks映射实现到ShadowActivityLifecycleCallbacks.Holder,
由它持有所有ActivityLifecycleCallbacks。
ShadowActivityDelegate在PluginActivity onCreate之前通知Holder分发onActivityPreCreated事件。

#505
#519
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants