前言:

  • 支付宝中有各种应用例如各种共享单车等,他们并不是直接集成在支付宝app中(也不可能)
  • 那他们是怎么加载如此之多的应用的呢?
  • 当然可以嵌入h5网页实现,但是h5的用户体验和兼容性当然没有原生的好,经过观察这些第三方应用都不是h5页面
  • 那么问题来了,这些应用是如何加载到支付宝上的呢?
  • 答案是:支付宝动态加载了第三方apk应用

原理:

  • 如果我们能访问到第三方apk文件的类、资源文件,是不是就可以直接在主app加载出来,实现第三方app的功能呢?
  • 答案是肯定的,但是在第三方app中,呈现的是一个个Activity,他又通过生命周期进行各种管理
  • 那么主app能直接调用第三方自带生命周期的Activity类吗?很可惜是不能的
  • 主app的生命周期是android系统赋予的,而我们使用主app直接加载第三方类的时候并不存在生命周期,所以我们需要自己赋予他生命周期动态加载第三方apk实现插件化(无需安装)

目标:

  • 加载第三方apk文件的类、资源
  • 赋予Activity类生命周期

步骤:

  1. 首先我们创建个PluginManager   libs模块 主工程和插件工程都需要依赖他(主要实现资源和类的加载和管理)他包含一下几个类:

    public class PluginApk {
        //插件实体对象
    
        //包信息
        private PackageInfo mPackageInfo;
        //资源对象
        private Resources mResources;
        //asset对象
        private AssetManager mAssetManager;
        //类加载器
        private ClassLoader mClassLoader;
    
        public PluginApk(PackageInfo mPackageInfo, Resources mResources, ClassLoader mClassLoader) {
            this.mPackageInfo = mPackageInfo;
            this.mResources = mResources;
            this.mAssetManager = mResources.getAssets();
            this.mClassLoader = mClassLoader;
        }
    
        public PackageInfo getmPackageInfo() {
            return mPackageInfo;
        }
    
        public Resources getmResources() {
            return mResources;
        }
    
        public AssetManager getmAssetManager() {
            return mAssetManager;
        }
    
        public ClassLoader getmClassLoader() {
            return mClassLoader;
        }
    }
    public class PluginManager {
    
        private final static PluginManager mInstance=new PluginManager();
    
        private PluginManager(){
    
        }
    
        public static PluginManager getmInstance() {
            return mInstance;
        }
    
        private PluginApk pluginApk;
        private Context mContext;
    
        public void init(Context context){
            //初始化上下文
            mContext=context.getApplicationContext();
    
        }
        public void loadApk(String apkPath){
            //读取第三方apk的包信息
            PackageInfo packageInfo= mContext.getPackageManager().getPackageArchiveInfo(apkPath,
                    PackageManager.GET_ACTIVITIES|PackageManager.GET_SERVICES);
    
            if (packageInfo==null){
                return;
            }
            //加载dex二进制文件,读取里面的类
            DexClassLoader classLoader=createDexClassLoader(apkPath);
            //加载资源文件
            Resources resources=createResources(createAssetManager(apkPath));
            //生成插件对象
            pluginApk=new PluginApk(packageInfo,resources,classLoader);
        }
    
        public PluginApk getPluginApk() {
            return pluginApk;
        }
    
        private Resources createResources(AssetManager assetManager) {
            return new Resources(assetManager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
        }
    
        private AssetManager createAssetManager(String apkPath) {
    
            try {
                AssetManager assetManager=AssetManager.class.newInstance();
                Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
                addAssetPath.invoke(assetManager, apkPath);
                return assetManager;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private DexClassLoader createDexClassLoader(String apkPath) {
            return new DexClassLoader(apkPath,
                    mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(),
                    null, mContext.getClassLoader());
        }
    
    }
    /**
     * Created by wudh on 2019/3/22.
     * 宿主和插件都需要依赖的共同标准
     **/
    public interface PluginInterface {
    
    
        int FROM_INTERNAL=0;
    
        int FROM_EXTERNAL=1;
    
        void onCreate(Bundle saveBundleInstance);
    
        void attach(Activity activity);
    
        void onStart();
    
        void onRestart();
    
        void onResume();
    
        void onPause();
    
        void onStop();
    
        void onDestroy();
    }

    实现接口 接管生命周期

public class PluginActivity extends FragmentActivity implements PluginInterface{


    private int FROM=FROM_INTERNAL;

    private Activity mPluginActivity;

    @Override
    public void onCreate(Bundle saveBundleInstance) {
        //判断启动该Activity来自本app还是主app
        if (saveBundleInstance!=null){
            FROM=saveBundleInstance.getInt("FROM");
        }
        if (FROM==FROM_INTERNAL){
            super.onCreate(saveBundleInstance);
            mPluginActivity=this;
        }
    }

    //布局文件也需要判断  1.是主app加载apk资源文件传给他的 2.还是插件app自己运行时自带的
    @Override
    public void setContentView(int layoutResID) {
        if (FROM==FROM_INTERNAL){
            super.setContentView(layoutResID);
        }else {
            mPluginActivity.setContentView(layoutResID);
        }
    }

    @Override
    public Resources getResources() {
        if (FROM==FROM_INTERNAL){
            return super.getResources();
        }else {
            return mPluginActivity.getResources();
        }
    }

    //赋予主app代理Activity的上下文
    @Override
    public void attach(Activity activity) {
        this.mPluginActivity=activity;
    }

    @Override
    public void onStart() {
        if (FROM==FROM_INTERNAL){
            super.onStart();
        }
    }

    @Override
    public void onRestart() {
        if (FROM==FROM_INTERNAL){
            super.onRestart();
        }
    }

    @Override
    public void onResume() {
        if (FROM==FROM_INTERNAL){
            super.onResume();
        }
    }

    @Override
    public void onPause() {
        if (FROM==FROM_INTERNAL){
            super.onPause();
        }
    }

    @Override
    public void onStop() {
        if (FROM==FROM_INTERNAL){
            super.onStop();
        }
    }

    @Override
    public void onDestroy() {
        if (FROM==FROM_INTERNAL){
            super.onDestroy();
        }
    }
}

2.主app的类

代理ProxyActivity,赋予第三方apk activity生命周期

public class MainActivity extends FragmentActivity {

    private String apkPath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/plugin.apk";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_load_apk).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //注意:使用运行时权限
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
            }
        });
        findViewById(R.id.btn_goto_plugin).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startApk();
            }
        });
    }

    public void startApk() {
        //初始化插件
        PluginManager.getmInstance().init(MainActivity.this);
        //获取第三方apk的类和资源
        PluginManager.getmInstance().loadApk(apkPath);

        //获取第三方apk中的Activity的完成名称 com.wudh.study.pluginapp.OneActivity
        String otherApkMainActivityName = PluginManager.getmInstance().getPluginApk().getmPackageInfo().activities[0].name;
        //跳转到代理ProxyActivity 他将帮我们实现第三方Activity的功能
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", otherApkMainActivityName);
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        //将assets的资源文件拷贝到android设备上
        try {
            InputStream inputStream=getAssets().open("plugin.apk");
            File file = new File(apkPath);
            if (file.exists()) file.delete();

            FileOutputStream out=new FileOutputStream(file);
            byte[] buffer=new byte[1024];
            int len=-1;
            while ((len=inputStream.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            out.flush();//刷新缓存区
            inputStream.close();
            out.close();

        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}
MainActivity.java
public class MainActivity extends FragmentActivity {

    private String apkPath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/plugin.apk";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_load_apk).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //注意:使用运行时权限
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
            }
        });
        findViewById(R.id.btn_goto_plugin).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startApk();
            }
        });
    }

    public void startApk() {
        //初始化插件
        PluginManager.getmInstance().init(MainActivity.this);
        //获取第三方apk的类和资源
        PluginManager.getmInstance().loadApk(apkPath);

        //获取第三方apk中的Activity的完成名称 com.wudh.study.pluginapp.OneActivity
        String otherApkMainActivityName = PluginManager.getmInstance().getPluginApk().getmPackageInfo().activities[0].name;
        //跳转到代理ProxyActivity 他将帮我们实现第三方Activity的功能
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className", otherApkMainActivityName);
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        //将assets的资源文件拷贝到android设备上
        try {
            InputStream inputStream=getAssets().open("plugin.apk");
            File file = new File(apkPath);
            if (file.exists()) file.delete();

            FileOutputStream out=new FileOutputStream(file);
            byte[] buffer=new byte[1024];
            int len=-1;
            while ((len=inputStream.read(buffer))!=-1){
                out.write(buffer,0,len);
            }
            out.flush();//刷新缓存区
            inputStream.close();
            out.close();

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

3.插件工程依赖pluginlib模块 只需要一个继承PluginActivity的Activity

public class OneActivity extends PluginActivity {


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

 最后将在插件工程中编译得到apk,放入主app工程的assets下

动态加载第三方apk实现插件化(无需安装)

~~~最后附上github地址:demo 

相关文章: