用户登录
用户注册

分享至

Android 占位式插件化开发

  • 作者: 用户101447756
  • 来源: 51数据库
  • 2021-07-08

先上效果图

项目结构:

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的代理类,四大组件都是要创建对应的代理类,不然是启动不了的,所以相对来说比较繁琐

软件
前端设计
程序设计
Java相关