【问题标题】:How to replace an Android Activity with ByteBuddy如何用 ByteBuddy 替换 Android Activity
【发布时间】:2017-11-17 11:46:14
【问题描述】:

我正在尝试开发一个支持动态活动的 Android 应用程序。

由于所有活动都需要在清单 XML 中声明,因此我创建了一个什么都不做的“虚拟”活动。

   <activity
    android:name="com.research.Dummy"
    android:label="@string/title_activity_dummy"
    android:theme="@style/AppTheme.NoActionBar" />

我希望实现的是,使用 ByteBuddy 我实例化了一个新版本的 Dummy,它扩展了不同的超类和/或实现了一个或多个接口。

我有这段代码来测试扩展超类:-

    final Class<? extends SomeClass> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)                    .subclass(com.pspdfkit.ui.PdfActivity.class, IMITATE_SUPER_CLASS)
    .name("com.research.Dummy")
    .method(ElementMatchers.named("toString"))
    .intercept(FixedValue.value("Hello World!"))
    .make()
    .load(getClass().getClassLoader(), new AndroidClassLoadingStrategy.Wrapping(this.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();

final Intent intent = new Intent(this, dynamicType);
startActivity(intent);

我的 Dummy 活动扩展了 android.support.v7.app.AppCompatActivity,但是,我希望我的 ByteBuddy 版本扩展“SomeClass”。

上面的代码没有给我想要的效果,虚拟活动确实显示,但它扩展了android.support.v7.app.AppCompatActivity 而不是 SomeClass。

ByteBuddy 能否将扩展 AppCompatActivity 的原始 Dummy 类替换为扩展 SomeClass 的所需版本?

更新

当我尝试这个注入我的新类时:-

final File jarFile = new File(getFilesDir(), "buddyDummy.jar");

final DynamicType.Unloaded<? extends AppCompatActivity> dynamicType = new ByteBuddy()
        .subclass(AppCompatActivity.class)
        .name("com.research.buddy.Dynamic")
        .make();

final File dexInternalStoragePath = dynamicType.toJar(jarFile);

// Internal storage where the DexClassLoader writes the optimized dex file to.
final File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);

// Initialize the class loader with the secondary dex file.
final DexClassLoader dexClassLoader = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(), optimizedDexOutputPath.getAbsolutePath(),null, getClassLoader());
dexClassLoader.loadClass("com.research.buddy.Dynamic");

我收到此异常

java.lang.ClassNotFoundException: Didn't find class "com.research.buddy.Dynamic" on path: 
DexPathList[[zip file "/data/user/0/com.research.buddy/files/buddyDummy.jar"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
   at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
   at com.research.buddy.Buddy.findRuntimeDependencies(Buddy.java:117)
   at com.research.buddy.Buddy.onClick(Buddy.java:78)
   at android.view.View.performClick(View.java:5204)
   at android.view.View$PerformClick.run(View.java:21153)
   at android.os.Handler.handleCallback(Handler.java:739)
   at android.os.Handler.dispatchMessage(Handler.java:95)
   at android.os.Looper.loop(Looper.java:148)
   at android.app.ActivityThread.main(ActivityThread.java:5417)
   at java.lang.reflect.Method.invoke(Native Method)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
 Suppressed: java.io.IOException: No original dex files found for dex location /data/user/0/com.research.buddy/files/buddyDummy.jar
   at dalvik.system.DexFile.openDexFileNative(Native Method)
   at dalvik.system.DexFile.openDexFile(DexFile.java:295)
   at dalvik.system.DexFile.<init>(DexFile.java:111)
   at dalvik.system.DexFile.loadDex(DexFile.java:151)
   at dalvik.system.DexPathList.loadDexFile(DexPathList.java:282)
   at dalvik.system.DexPathList.makePathElements(DexPathList.java:248)
   at dalvik.system.DexPathList.<init>(DexPathList.java:120)
   at dalvik.system.BaseDexClassLoader.<init>(BaseDexClassLoader.java:48)
   at dalvik.system.DexClassLoader.<init>(DexClassLoader.java:57)
   at com.research.buddy.Buddy.findRuntimeDependencies(Buddy.java:116)
         ... 10 more
 Suppressed: java.lang.ClassNotFoundException: Didn't find class "com.research.buddy.Dynamic" on path: 
 DexPathList[[zip file "/data/app/com.research.buddy-1/base.apk"],nativeLibraryDirectories=[/data/app/com.research.buddy-1/lib/arm, /vendor/lib, /system/lib]]
   at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
         ... 12 more
     Suppressed: java.lang.ClassNotFoundException: com.research.buddy.Dynamic
   at java.lang.Class.classForName(Native Method)
   at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
   at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
   at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
             ... 13 more
Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available

当我查看设备上的文件位置时,我可以在正确的位置看到 buddyDummy.jar 文件。

为什么我会收到ClassNotFoundException

更新

我已经设法使用 ByteBuddy 实例化我的 Android Activity,并使用以下代码“startActivity”它。

final DynamicType.Unloaded<? extends AppCompatActivity> dynamicType = new ByteBuddy()
        .subclass(AppCompatActivity.class)
        .name(CLASS_NAME)
        .make();

final Class<? extends AppCompatActivity> dynamicTypeClass = dynamicType.load(getClassLoader(), new AndroidClassLoadingStrategy.Injecting(this.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();

final Intent intent = new Intent(this, dynamicTypeClass);
startActivity(intent);

我现在需要拦截onCreate 方法以允许我设置content

这一步似乎有很多问题,即onCreate是一个受保护的方法,我需要调用超类的onCreate方法传递Bundle。

是否可以使用 ByteBuddy 拦截受保护的方法? 如何在我的方法拦截器中调用 super.onCreate(savedInstance)?

@Super、@SuperCall 等只有 call() 例如没有参数,我错过了什么?

更新

我已经成功地显示了我的动态活动,设置了所需的布局并调用了 super.onCreate()。虽然我相信超级调用是乱序的。

final DynamicType.Unloaded<? extends AppCompatActivity> dynamicType = new ByteBuddy(ClassFileVersion.JAVA_V8)
        .subclass(AppCompatActivity.class)
        .name(CLASS_NAME)
        .method(named("onCreate").and(takesArguments(1)))
        .intercept(MethodDelegation.to(TargetActivity.class).andThen(SuperMethodCall.INSTANCE))
        .make();

final Class<? extends AppCompatActivity> dynamicTypeClass = dynamicType.load(getClassLoader(), new AndroidClassLoadingStrategy.Injecting(this.getDir("dexgen", Context.MODE_PRIVATE))).getLoaded();

final Intent intent = new Intent(this, dynamicTypeClass);
startActivity(intent);

我的 TargetActivity 类似于:-

public class TargetActivity {
    public static void intercept(Bundle savedInstanceState, @This AppCompatActivity thiz) {
        thiz.setContentView(R.layout.activity_fourth);
    }
}

由于我对 SuperMethod 的 MethodDelegation 调用是“.andThen”,这听起来像是在我设置内容后调用了 super.onCreate(),我如何调用 Super .previous 而不是 .andThen

【问题讨论】:

  • 为什么要否决这个问题?下次请留言。

标签: android byte-buddy


【解决方案1】:

您正在使用Wrapping 策略,将类加载到新的类加载器中。如果你想加载一个类而不是另一个类,你必须注入它。

但是,这种模式可能很尴尬,因为您假设您可以在加载原始类之前注入该类,这通常取决于执行 JVM 的细节。

至于您的问题更新:您是否指定了正确的父类加载器?而不是getClass().getClassLoader() 尝试com.research.buddy.Dynamic.class.getClassLoader()。看来您将类加载到错误的范围内。

【讨论】:

  • 感谢您回答我的问题。我是 ByteBuddy 的新手,你是说我无法达到我想要的结果吗?如何使用 ByteBuddy 注入我的替换类?这甚至可能吗?另外...我对替换类还有其他要求,例如我需要它来实现通常我无权访问的包私有接口。
猜你喜欢
  • 1970-01-01
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多