android 占位式插件开发

开头2张tu

android 占位式插件开发_第1张图片android 占位式插件开发_第2张图片

1.占位式插件化,宿主启动插件activity流程

插件开发三大要素:宿主 标准,插件包

第一步创建宿主工程 

android 占位式插件开发_第3张图片

布局文件




    

2.制定标准

android 占位式插件开发_第4张图片

创建插件工程(一定是工程不是library):

android 占位式插件开发_第5张图片准备工作做完了,之后会很长废话也比较多,我尽量简洁多贴图少说废话

首先我们要把标准添加到宿主工程和插件工程里面

android 占位式插件开发_第6张图片

插件工程同上不在贴图,在宿主模块里面定义一个插件管理类

public class PluginManager {
    private static final String TAG = PluginManager.class.getSimpleName();
    private Context context;
    private DexClassLoader dexClassLoader;

    private static PluginManager pluginManager;
    private Resources resources;

    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;
    }

    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public Resources getResources() {
        return resources;
    }

    /**
     * 加载插件activity
     */
    private void loadPlugin() {
        try {
            File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
            if (!file.exists()) {
                Log.e(TAG, "文件不存在");
                return;
            }
            //获取到文件的位置
            String pluginPath = file.getAbsolutePath();

            //加载插件里面的class dexClassLoader 需要一个缓存目录 /data/data/包名/pDir
            File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
            dexClassLoader = new DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, context.getClassLoader());

            //加载插件里面的布局
            AssetManager assetManager = AssetManager.class.newInstance();
            //把插件包的路径添加进去
            Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);
            //执行此方法,参数为插件包的路径
            addAssetPathMethod.invoke(assetManager, pluginPath);

            //宿主的配置
            Resources r = context.getResources();
            //使用宿主的resource加载插件里面的资源resource
            resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

管理类准备好了现在要准备标准了,在standard里面定义ActivityInterface(我太懒了所以标准接口里面就写了我用到的几个方法,可以多写一些)

public interface ActivityInterface {
    /**
     * 把宿主的(app)的环境给插件
     * @param appActivity
     */
    void  insertAppContext(Activity appActivity);
    void onCreate(Bundle savedInstanceState) ;

    void onStart() ;

    void onResume() ;

    void onDestroy() ;
}

在插件模块里面定义基础类 在PluginActivity中用的所有有关activity的方法都要在基础类中重写(看不懂继续往下|)

public class BaseActivity extends AppCompatActivity implements ActivityInterface {
    //宿主的环境
    public Activity appActivity;

    @Override
    public void insertAppContext(Activity appActivity) {
        this.appActivity = appActivity ;
    }

    /**
     * 添加注释因为要实现的是接口里面的方法而不是重写AppCompatActivity的
     * @param savedInstanceState
     */
    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

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

    }

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

    }

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

    }

    @Override
    public void setContentView(int sourceId) {
        appActivity.setContentView(sourceId);
    }

    @Override
    public View findViewById(int id) {
        return appActivity.findViewById(id);
    }

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

PluginActivity继承基础类

public class PluginActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);
        Toast.makeText(appActivity,"我是插件",Toast.LENGTH_LONG).show();
    }
}

这里一定要是全路径

android 占位式插件开发_第7张图片

因为插件apk 没有组件环境所以当宿主activity调用插件的activity时需要在宿主工程里面定义一个代理activity作为媒介

/**
 * 代理activity 占位插件里面的activity
 **/
public class ProxyActivity  extends ComponentActivity {

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

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

    /**
     * 代理类里面会加载插件正在的类
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(@Nullable 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.insertAppContext(this);

            activityInterface.onCreate(savedInstanceState);

        }catch (Exception e){

        }
    }
}

在宿主的mainActivity中加载插件

public class MainActivity extends AppCompatActivity {

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

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

    /**
     * 9.0以上需要动态获取权限
     *
     * @param view
     */
    public void startPluginActivity(View view) {
        int hasWriteStoragePermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (hasWriteStoragePermission == PackageManager.PERMISSION_GRANTED) {
            instant();
        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);

        }
    }

    private void instant() {
        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];
        //占位 代理activtiy
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", activityInfo.name);
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //用户同意,执行操作
                instant();
            } else {
                //用户不同意,向用户展示该权限作用
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    new AlertDialog.Builder(this)
                            .setMessage(R.string.storage_permissions_remind)
                            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    ActivityCompat.requestPermissions(MainActivity.this,
                                            new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
                                }
                            })
                            .setNegativeButton("Cancel", null)
                            .create()
                            .show();
                }
            }
        }
    }
}

插件工程new出apk

android 占位式插件开发_第8张图片

将生成的apk(我这边改了名字)放入手机内存卡

android 占位式插件开发_第9张图片

插件里面的activity跳转

在plugin 添加testActivity

android 占位式插件开发_第10张图片

proxyActivty中添加

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

pluginActivty 正常跳转

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

因为插件和宿主都有修改所以要重新打包上传。

你可能感兴趣的:(android)