插件化一:占位式实现

179 阅读5分钟

完整版:宿主Apk和插件Apk

1. 定义宿主和插件之间的标准

  • 接口定义:创建一个接口(如IActivityInterface),包含Activity的生命周期方法,以便插件Activity实现并供宿主调用。
public interface IActivityInterface {
    void onCreate(Bundle savedInstanceState);
    void onStart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    // 其他可能需要的生命周期方法
}

2. 实现插件模块

  • 插件基类:在插件项目中,创建一个基类(如BasePluginActivity)实现IActivityInterface接口,并处理生命周期方法。
public class BasePluginActivity extends Activity implements IActivityInterface {
    private Activity hostActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // 插件的实际onCreate逻辑,但通常不会直接调用super.onCreate()
    }

    // 注入宿主Activity的上下文
    public void setHostActivity(Activity hostActivity) {
        this.hostActivity = hostActivity;
    }

    // 实现其他生命周期方法...

    // 代理方法,用于在插件中调用宿主Activity的方法
    public void setContentView(int layoutResID) {
        hostActivity.setContentView(layoutResID);
    }

    // ... 其他需要代理的方法
}

3. 宿主中加载插件模块apk

  • 插件加载器:在宿主应用中,实现一个插件加载器(如PluginManager),用于加载插件APK中的DEX和资源。
public class PluginManager {
    private DexClassLoader dexClassLoader;
    private Resources pluginResources;

    public void loadPlugin(Context context, String pluginApkPath) {
        // 加载DEX和资源...
        // 示例代码略,需实现DexClassLoader的加载和AssetManager的addAssetPath反射调用
    }

    public Class<?> loadClass(String className) throws ClassNotFoundException {
        return dexClassLoader.loadClass(className);
    }

    public Resources getResources() {
        return pluginResources;
    }
}

4. 定义代理的占位Activity

  • 占位Activity:在宿主应用中,定义一个占位Activity(如ProxyActivity),用于加载和启动插件Activity。
public class ProxyActivity extends Activity {
    private IActivityInterface pluginActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("pluginClassName");
        try {
            PluginManager pluginManager = new PluginManager();
            pluginManager.loadPlugin(this, "path_to_plugin.apk");
            Class<?> pluginClass = pluginManager.loadClass(className);
            pluginActivity = (IActivityInterface) pluginClass.newInstance();
            pluginActivity.setHostActivity(this);
            pluginActivity.onCreate(savedInstanceState);

            // 注意:这里只是示例,实际中需要处理更多生命周期和异常
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 代理其他生命周期方法给插件Activity...
}

简单版:framework层的系统选择页

1. 定义宿主与插件之间的标准

  • 接口定义:类似于完整版,但可能更简化,只包含必要的hook点。

2. 实现插件hook方法

  • Hook实现:直接在宿主中通过反射或代理方式调用插件中的方法,无需打包成APK。

3. 宿主生命周期调用插件方法

  • 生命周期集成:在宿主的生命周期方法中,直接调用插件的相应方法,并传递必要的上下文。

简单版代码示例

在Android开发中,ResolverActivityChooserActivity是系统提供的用于处理应用间交互的Activity。ResolverActivity通常用于处理多个应用可以处理的Intent,而ChooserActivity则是ResolverActivity的一个子类,专门用于提供一个选择器界面,让用户选择使用哪个应用来处理特定的Intent。

在这个场景中,我们可以通过自定义Hook逻辑来修改这些系统Activity的行为。以下是一个简单的代码示例,展示了如何在ResolverActivityChooserActivity中集成自定义的Hook逻辑。

1. 定义宿主与插件之间的标准接口

首先,我们定义一个简单的接口,用于宿主Activity和插件之间的交互。这个接口可以包含一些必要的Hook点,比如onCreateonDestroy

public interface ActivityHook {
    void onCreate(Activity activity);
    void onDestroy();
}

2. 实现插件Hook方法

接下来,我们实现具体的Hook类,比如ResolveActivityHookChooserActivityHook。这些类将实现ActivityHook接口,并在其中定义自定义的逻辑。

public class ResolveActivityHook implements ActivityHook {
    private static ResolveActivityHook instance;

    private ResolveActivityHook() {}

    public static ResolveActivityHook getInstance() {
        if (instance == null) {
            instance = new ResolveActivityHook();
        }
        return instance;
    }

    @Override
    public void onCreate(Activity activity) {
        // 自定义的布局和初始化逻辑
        activity.setContentView(R.layout.resolve_activity_hook);
        init();
    }

    @Override
    public void onDestroy() {
        // 自定义的销毁逻辑
        cleanup();
    }

    private void init() {
        // 初始化操作
    }

    private void cleanup() {
        // 清理操作
    }
}

public class ChooserActivityHook implements ActivityHook {
    private static ChooserActivityHook instance;

    private ChooserActivityHook() {}

    public static ChooserActivityHook getInstance() {
        if (instance == null) {
            instance = new ChooserActivityHook();
        }
        return instance;
    }

    @Override
    public void onCreate(Activity activity) {
        // 自定义的布局和初始化逻辑
        activity.setContentView(R.layout.chooser_activity_hook);
        init();
    }

    @Override
    public void onDestroy() {
        // 自定义的销毁逻辑
        cleanup();
    }

    private void init() {
        // 初始化操作
    }

    private void cleanup() {
        // 清理操作
    }
}

3. 宿主生命周期调用插件方法

ResolverActivityChooserActivity中,我们通过调用Hook类的相应方法来实现自定义逻辑。

package com.android.internal.app;

public class ResolverActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ResolveActivityHook hook = ResolveActivityHook.getInstance();
        hook.onCreate(this);
        return;
        // 其他代码可以在这里提前返回
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ResolveActivityHook hook = ResolveActivityHook.getInstance();
        hook.onDestroy();
        return;
        // 其他代码可以在这里提前返回
    }
}

package com.android.internal.app;

public class ChooserActivity extends ResolverActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ChooserActivityHook hook = ChooserActivityHook.getInstance();
        hook.onCreate(this);
        return;
        // 其他代码可以在这里提前返回
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ChooserActivityHook hook = ChooserActivityHook.getInstance();
        hook.onDestroy();
        return;
        // 其他代码可以在这里提前返回
    }
}

4. 自定义布局文件

res/layout目录下,我们可以定义自定义的布局文件,比如resolve_activity_hook.xmlchooser_activity_hook.xml

<!-- resolve_activity_hook.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Custom Resolver Activity" />
</LinearLayout>

<!-- chooser_activity_hook.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Custom Chooser Activity" />
</LinearLayout>

总结

通过这种方式,我们可以在不修改系统源码的情况下,通过Hook机制来自定义ResolverActivityChooserActivity的行为。这种方式非常适合在插件化开发中使用,允许我们在宿主应用中动态地修改系统Activity的行为。

注意事项与总结

  • 生命周期管理:确保插件Activity能够正确响应宿主的生命周期事件。
  • 资源访问:插件中的资源访问需要通过宿主代理实现。
  • 异常处理:加载和调用插件时,需妥善处理异常,保证宿主应用的稳定性。
  • 安全性:考虑插件来源的可靠性和代码的安全性。
  • 总结:占位式插件化提供了一种灵活的方式来扩展宿主应用的功能,但需要注意插件与宿主之间的交互和生命周期管理。