欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 占位式插件化开发

程序员文章站 2022-06-24 19:49:13
先上效果图项目结构:app:宿主apkplugin:插件apkstandard:标准,插件activity创建时候需要实现里面的接口1、创建标准/** * activity标准接口 */public interface ActivityInterface { /** * 把宿主的环境给 插件 * @param activity */ void insertAppActivity(Activity activity); void...

先上效果图
Android 占位式插件化开发

项目结构:
Android 占位式插件化开发

app:宿主apk
plugin:插件apk
standard:标准,插件activity创建时候需要实现里面的接口

1、创建标准

/**
 * activity标准接口
 */
public interface ActivityInterface {

    /**
     * 把宿主的环境给 插件
     * @param activity
     */
    void insertAppActivity(Activity activity);

    void onCreate(Bundle savedInstanceState);

    void onResume() ;

    void onStart();

    void onDestroy();
}

2、创建插件apk
tips:其中activity在插件中无需注册
创建BaseActivity,实现ActivityInterface

public class BaseActivity extends Activity implements ActivityInterface {
    //宿主环境,因为插件未安装是没有上下文的
    public Activity activity;

    @Override
    public void insertAppActivity(Activity activity) {
        this.activity = activity;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {

    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {

    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {

    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    public void setContentView(int layoutResID) {
        activity.setContentView(layoutResID);
    }

    public <T extends View> T findViewById(int id) {
        return activity.findViewById(id);
    }

    @Override
    public void startActivity(Intent intent) {
        Intent intentNew = new Intent();
        intentNew.putExtra("className",intent.getComponent().getClassName());
        activity.startActivity(intentNew);
    }
}

创建PluginActivity

public class PluginActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);

        // this 会报错,因为插件没有安装,也没有组件的环境,所以必须使用宿主环境
        Toast.makeText(activity, "我是插件", Toast.LENGTH_SHORT).show();

        findViewById(R.id.bt_start_activity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(activity, TestActivity.class));
            }
        });
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PluginActivity"
    android:background="@android:color/holo_blue_bright"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- android:onClick="start" 会报错,因为在插件里面 没有组件环境 -->
    <Button
        android:id="@+id/bt_start_activity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="插件内部跳转插件的Activity"
        />

</LinearLayout>

插件内部跳转TestActivity

public class TestActivity extends BaseActivity {

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

3、创建宿主apk内容
tips:首先是要配置权限,因为我将插件apk放在了sdcard目录下,所以需要配置权限,同时需要动态申请权限,否则每次都获取不到插件中的activity信息
Manifest中配置

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
public class MainActivity extends AppCompatActivity {

    // 要申请的权限
    private String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            };

    //未授权的权限
    private List<String> mPermissionList = new ArrayList<>();

    //权限请求码
    private final int mRequestCode = 100;

    private AlertDialog mPermissionDialog;

    private String mPackName = "com.example.pluginproject";

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

    private void initPermission() {
        mPermissionList.clear();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //逐个判断是否还有未通过的权限
            for (int i = 0; i < permissions.length; i++) {
                if (ContextCompat.checkSelfPermission(this, permissions[i]) !=
                        PackageManager.PERMISSION_GRANTED) {
                    mPermissionList.add(permissions[i]);//添加还未授予的权限到mPermissionList中
                }
            }
            //申请权限
            if (mPermissionList.size() > 0) {//有权限没有通过,需要申请
                ActivityCompat.requestPermissions(this, permissions, mRequestCode);
            }

        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        boolean hasPermissionDismiss = false;//有权限没有通过
        if (mRequestCode == requestCode) {
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == -1) {
                    hasPermissionDismiss = true;
                    break;
                }
            }
        }
        if (hasPermissionDismiss) {//如果有没有被允许的权限
            showPermissionDialog();
        }
    }

    /**
     * 不再提示权限时的展示对话框
     */
    private void showPermissionDialog() {
        if (mPermissionDialog == null) {
            mPermissionDialog = new AlertDialog.Builder(this)
                    .setMessage("已禁用权限,请手动授予")
                    .setPositiveButton("设置", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            cancelPermissionDialog();

                            Uri packageURI = Uri.parse("package:" + mPackName);
                            Intent intent = new Intent(Settings.
                                    ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                            startActivity(intent);
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //关闭页面或者做其他操作
                            cancelPermissionDialog();
                            MainActivity.this.finish();
                        }
                    })
                    .create();
        }
        mPermissionDialog.show();
    }

    private void cancelPermissionDialog() {
        mPermissionDialog.cancel();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }


    public void loadPlugin(View view) {
       PluginManager.getInstance(this).loadPlugin();
    }

    // 启动插件里面的Activity
    public void startPluginActivity(View view) {
        File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
        String path = file.getAbsolutePath();

        // 获取插件包 里面的 Activity
        PackageManager packageManager = getPackageManager();
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
        ActivityInfo activityInfo = packageInfo.activities[0];

        // 占位  代理Activity
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", activityInfo.name);
        startActivity(intent);
    }
}

插件加载管理PluginManager

/**
 * 插件管理,负责加载插件类
 */
public class PluginManager {
    private static final String TAG = PluginManager.class.getSimpleName();

    private static PluginManager pluginManager;

    private Context context;

    public static PluginManager getInstance(Context context){
        if (pluginManager == null){
            synchronized (PluginManager.class){
                if (pluginManager == null){
                    pluginManager = new PluginManager(context);
                }
            }
        }

        return pluginManager;
    }

    private PluginManager(Context context){
        this.context = context;
    }

    private DexClassLoader dexClassLoader;

    private Resources resources;

    /**
     * (1.Activity.class, 2.layout)
     * 加载插件
     */
    public void loadPlugin(){
        try{
            File file = new File(Environment.getExternalStorageDirectory()+File.separator+"p.apk");
            if (!file.exists()){
                Log.e(TAG,"插件包不存在。。");
                return;
            }

            //加载插件里面的class

            String pluginPath = file.getAbsolutePath();

            //dexClassLoader需要一个缓存目录 /data/data/当前应用的包名/pDir
            File fileDir = context.getDir("pDir",Context.MODE_PRIVATE);

            dexClassLoader = new DexClassLoader(pluginPath,fileDir.getAbsolutePath(),null,context.getClassLoader());

            //下面是加载插件里面的layout

            //加载资源
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(assetManager,pluginPath);

            Resources r = context.getResources();

            resources = new Resources(assetManager,r.getDisplayMetrics(),r.getConfiguration());

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public Resources getResources() {
        return resources;
    }
}

代理Activity,每次都是跳转到ProxyActivity,然后加载对应的插件中的Activity,所以ProxyActivity必须是Standard启动模式的

public class ProxyActivity extends Activity {

    @Override
    public Resources getResources() {
        return PluginManager.getInstance(this).getResources();
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance(this).getDexClassLoader();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("className");

        try {
            Class<?> mPluginActivityClass = getClassLoader().loadClass(className);
            //实例化插件里面的activity
            Constructor<?> constructor = mPluginActivityClass.getConstructor(new Class[]{});

            Object mPluginActivity = constructor.newInstance(new Object[]{});
            ActivityInterface activityInterface = (ActivityInterface) mPluginActivity;
            activityInterface.insertAppActivity(this);

            Bundle bundle = new Bundle();
            bundle.putString("appName","我是宿主传递过来的消息");
            activityInterface.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent proxyIntent = new Intent(this,ProxyActivity.class);
        proxyIntent.putExtra("className",className);//包名+TestActivity
        //要给TestActivity进栈
        super.startActivity(proxyIntent);
    }
}

plugin项目和宿主app都需要依赖standard库

最后打包apk,plugin项目打包apk,命名为p.apk,放在sdcard目录下。
安装宿主app项目即可,然后进行开始的操作即可。

说明:该项目只创建了activity的代理类,四大组件都是要创建对应的代理类,不然是启动不了的,所以相对来说比较繁琐

本文地址:https://blog.csdn.net/StudyOfAndroid/article/details/108117112