插件化——反射实践

插件化:当项目需求很大时,可以拆分某些独立的业务出来单独编译成一个APK,再通过主APK去调用它,这样如果插件APK需要添加一些新的功能,可以在不修改原有的应用程序情况下,将新添加的功能植入到插件中,这就是所谓的插件化。插件化能大大的降低模块间的耦合性,有利于各模块的独立维护,加快项目的维护更新。

插件化的实现方式目前了解的方式有两种:
1:插桩式——反射
2:hook方式——动态代理

先就第一种方式来展开,第二种方式后续来讲。

插桩式需要解决一下几个问题:
(1)加载未安装的APK
(2) 资源加载
(3)代码加载

接下来就围绕这几个问题来编写代码:
**一、要解决这几个问题,首先就要根据插件apk的路径去获取到相应的DexClassLoad和Resource以及PackageInfo,首先在宿主中编写一个PluginManager **

package com.zzcm.plugin;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;

import java.io.File;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

/**
 * @author swl
 * date 2019/8/8 10:14
 */
public class PluginManager {

    private static PluginManager mInstance=new PluginManager();
    private DexClassLoader mClassLoader;
    private Resources mResources;
    private PackageInfo mPackageInfo;

    public DexClassLoader getClassLoader() {
        return mClassLoader;
    }


    public Resources getResources() {
        return mResources;
    }

    public PackageInfo getPackageInfo(){
        return mPackageInfo;
    }

    public void setContext(Context context) {
        mContext = context;
    }

 /**
     * @param path  插件apk的路径
     */
    public void loadPath(String path){
        setDexClassload(path);
        setResorusce(path);
        setPackageInfo(path);
    }

    private Context mContext;


    /**
     * 加载插件apk类的DexClassLoad
     * @param path  插件apk的路径
     */
    private void setDexClassload(String path){
        File dex = mContext.getDir("dex", Context.MODE_PRIVATE);
        mClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, mContext.getClassLoader());

    }

    /**
     * 插件apk的资源资源
     * @param path  插件apk的路径
     */
    private void setResorusce(String path){
        try {
            AssetManager manager = AssetManager.class.newInstance();
            Method method = manager.getClass().getMethod("addAssetPath", String.class);
            method.invoke(manager,path);
            mResources = new Resources(manager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private PluginManager(){

    }

    public static PluginManager getInstance(){
        return mInstance;
    }
    /**
     * 得到插件apk的activity的信息
     * @param path  插件apk的路径
     */
    private void setPackageInfo(String path) {
        //得到packageManager来获取包信息
        PackageManager packageManager = mContext.getPackageManager();
        //参数一是apk的路径,参数二是希望得到的内容
        mPackageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);

    }

}

二、因为插件apk时未安装到系统的,如何取启动它,因为android对于activity的启动必须是在AndroidManifest注册了的,所以可以用一个占坑的activity去骗过系统,接下来我们定义一套协议(插件所有activity都去实现它)和占坑的activity

定义协议PluginInterface ,把它单独搞成一个library,插件和宿主通过它交互:

package com.zzcm.replugin;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.view.View;

/**
 * @author swl
 * date 2019/8/8 9:30
 */
public interface PluginInterface {

//把宿主中占坑的activity传过来,所有跟上下文有关系啊都用这个activity
    void attach(Activity activity);
    void onCreate(Bundle savedInstanceState);
    void onResume();
    void onStart();
    void onStop();
    void onPause();
    void onDestory();
    void startActivity(Intent intent);

    void setContentView(@LayoutRes int resId);

    <T extends View> T  findViewById(@IdRes int id);
}

这里只是定义了一部分,其他可以根据业务去自行定义

宿主中占坑的activity:

package com.zzcm.plugin;

import android.content.Intent;
import android.content.res.Resources;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.zzcm.replugin.PluginInterface;

import java.lang.reflect.Constructor;

public class ProxyActivity extends AppCompatActivity  {


    private PluginInterface mPlugin;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("className");
        try {
            Class<?> clazz = getClassLoader().loadClass(className);
            Constructor<?> constructor = clazz.getConstructor(new Class[]{});
            Object instance = constructor.newInstance(new Object[]{});
            mPlugin = (PluginInterface) instance;
            mPlugin.attach(this);
            Bundle bundle=new Bundle();
            mPlugin.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onStart() {
        super.onStart();
        mPlugin.onStart();
    }


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

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

    @Override
    public ClassLoader getClassLoader() {
        //不用系统的ClassLoader,用dexClassLoader加载
        return PluginManager.getInstance().getClassLoader();
    }

    @Override
    public Resources getResources() {
        //不用系统的resources,自己实现一个resources
        return PluginManager.getInstance().getResources();
    }

    public void startActivity(Intent intent){
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }
}

三、编写插件apk
定义一个BaseActivity实现我们的协议:

package com.zzcm.thirdapp;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import com.zzcm.replugin.PluginInterface;

import java.lang.reflect.Constructor;

/**
 * @author swl
 * date 2019/8/8 11:57
 */
public class BaseActivity extends AppCompatActivity implements PluginInterface {
    protected Activity that;
    @Override
    public void attach(Activity activity) {
        this.that=activity;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onStart() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onDestory() {

    }

    @Override
    public  void setContentView(@LayoutRes int resId){
        that.setContentView(resId);
    }

    public void startActivity(Intent intent){
        Intent intent1 = new Intent();
        intent1.putExtra("className",intent.getComponent().getClassName());
        that.startActivity(intent1);
    }

    @Override
    public <T extends View> T  findViewById(@IdRes int id){
        if (that == null) {
            return super.findViewById(id);
        } else {
            return that.findViewById(id);
        }
    }
}

这里就没有去写全啦。。。
然后写了两个activity

package com.zzcm.thirdapp;

import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.zzcm.replugin.PluginInterface;

public class ThirdAppMainActivity extends BaseActivity {


    @Override
    public void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_third_app_main);
        //findViewById不能用自身的,切记所有有关上下文的东西都不能用本身,因为是未安装的,拿不到的
        findViewById(R.id.image).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent=new Intent(that,SecondActivity.class);
                startActivity(intent);
            }
        });
    }


}

package com.zzcm.thirdapp;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.zzcm.replugin.PluginInterface;

public class SecondActivity extends BaseActivity {


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


}

四、我们编译一个插件apk,在宿主中进行启动

package com.zzcm.plugin;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class MainActivity extends AppCompatActivity {

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

    public void load(View view) {
        loadPlugin();
    }

//模拟从网络下载插件apk
    private void loadPlugin() {
        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "thirdapp-debug.apk");
        File pluginFile = new File(getFilesDir().getAbsolutePath(), "plugin.apk");
        if(pluginFile.exists()){
            pluginFile.delete();
        }
        FileInputStream in;
        FileOutputStream out;
        try {
            in = new FileInputStream(file);
           out = new FileOutputStream(pluginFile);

            int len;
            byte[] bytes = new byte[1024];
            while((len=in.read(bytes,0,bytes.length))!=-1){
                out.write(bytes,0,len);
            }
            PluginManager.getInstance().loadPath(pluginFile.getAbsolutePath());
            in.close();
            out.close();
            Toast.makeText(this,"success",Toast.LENGTH_LONG).show();
        } catch (Exception e) {

            e.printStackTrace();
        }

    }

//启动插件apk
    public void open(View view) {
        Intent intent=new Intent(this,ProxyActivity.class);
        ActivityInfo activity = PluginManager.getInstance().getPackageInfo().activities[0];
        intent.putExtra("className",activity.name);
        startActivity(intent);
    }
}

最终效果:
在这里插入图片描述
源码下载链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值