美文网首页设计模式,注解,反射,hook,asp,classloader
插件化从实现原理案例Demon,到大型企业插件化框架(360企业

插件化从实现原理案例Demon,到大型企业插件化框架(360企业

作者: 路很长0o0 | 来源:发表于2018-06-17 14:40 被阅读0次

    插件化开发
    一, 这篇博客我希望你认真一步步往下看写,那么就一定可以实现自己的插件化开发,学习到大型企业牛逼的插件化架构,从原理到项目,如果不能够实现那么联系我。希望带给帮助,我很荣幸的。(对于不知道插件化的朋友可以去百度了解)
    项目分为两个模块:

    1,从原理讲起写个Demon。
    2,然后去学会使用大型企业的插件化框架并附上Demon,我看了好多人的博客,我希望我的博客能给哪些刚刚从事android编程的哥们一写简单易懂图文混搭一步步集成并作出效果。

    模块一:,实现主app加载一个插件app的Activity效果如下:
    如下图:我为了显示比较高大上,布局截图了支付宝里面的界面。看看淘票票和滴滴出行这两个按钮点击各自跳进不同的activity里面。从而实现了插件化。

    这里写图片描述

    一,阻碍:首先我们(支付宝)App打开之后,在我们的手机目录下有两个插件apk(淘票票)和(滴滴打车),如何从主App跳转到这个目录下的apk的Activity呢?

    1,主项目app里面是没有apk的Activity和class文件,上下文,资源。
    2, 我们知道acitivity之间跳转需要在清单文件AndroidManifest.xml中需要注册。

    二,解决:根据一阻碍分析,我们来解决上面这两个问题。

    1,对于加载apk资源和class:估计接触过DexClassLoader很多人都知道这个动态加载类,谷歌大大还是很人性化的。我们可以通过它来加载我们插件apk里面的资源。

    2,我们需要一个中间的依赖库提供一套接口(这套接口必须具有主App里面activity的生命周期并让插件activity实现它),让主项目和插件Modle都依赖它,从而主App可以跳转到它(依赖库),它作为一个中间者Activity,在它的onCreate里面来初始化插件Activity,调用插件Activity的onCreate方法就实现了启动了。

    三,开始撸代码:
    一,新建项目,添加依赖库和两个插件(淘票票,滴滴出行)Modle

    这里写图片描述

    中间依赖库用来定义我们的一套标准假装为插件Activity:File->New ->New Modle


    这里写图片描述 这里写图片描述

    接下来就是淘票票和滴滴出行两个Modle了如下图:New ->New Modle:

    这里写图片描述 这里写图片描述 这里写图片描述

    最后看看项目目录:

    这里写图片描述

    二,让主Activity和两个插件Modle来依赖我们的中间标准依赖库:

    这里写图片描述 这里写图片描述 这里写图片描述

    三,依赖完成我们来写代码部分:
    首先我们去主项目和Modle中写好布局:如下:

    这里写图片描述 这里写图片描述 这里写图片描述

    (1)新建一个PluginManager.java类用来加载我们的淘票票和滴滴出行插件apk,并获取插件apk的基本信息和资源等把它放在我们的主项目或者依赖项目中都行。代码如下:
    package com.example.ls.pluginstudydemon;

    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.InvocationTargetException;
    import java.lang.reflect.Method;
    
     import dalvik.system.DexClassLoader;
    
      /**
      * 作者:王飞
      * 邮箱:1276998208@qq.com
      * create on 2018/6/14 16:23
      */
    public class PluginManager {
      //用来加载我们的插件apk类
    private DexClassLoader dexclassLoader;
       //加载插件的资源文件图片呀,xml等
    private Resources resources;
    private Context context;
    private static PluginManager ourInstance = new    PluginManager();
    //获取插件安装包信息如Activity的类明等
    private PackageInfo packageInfo;
    
    public PackageInfo getPackageInfo() {
        return packageInfo;
    }
    
         public static PluginManager getInstance() {
        return ourInstance;
        }
        public void setContext(Context context){
        this.context=context;
      }
      public DexClassLoader getClassLoader() {
        return dexclassLoader;
      }
    
       public Resources getResources() {
        return resources;
       }
    
       private PluginManager() {
    
       }
    
        //路径
       public void loadPath(String path) {
        File dexoutFile = context.getDir("dex",  Context.MODE_PRIVATE);
        //能加载外置卡的能力了
        dexclassLoader = new DexClassLoader(path, dexoutFile.getAbsolutePath(), null, context.getClassLoader());
        PackageManager  packageManager=context.getPackageManager();
          packageInfo=packageManager.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);
    
        //需要AssetManager
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager,path);
            resources = new Resources(assetManager,      context.getResources().getDisplayMetrics(),    context.getResources().getConfiguration());
    
             } catch (InstantiationException e) {
    
    
              } catch (IllegalAccessException e) {
             e.printStackTrace();
              } catch (NoSuchMethodException e) {
            e.printStackTrace();
              } catch (InvocationTargetException e) {
              e.printStackTrace();
             }
           }
         }   
    

    (2)我们需要定义一个标准接口让插件apk的Activity实现它,并且最后在伪装的依赖项目空壳Activity中将插件Activity强制转换为定义的接口并调用插件activity的onCreate实现启动接口代码如下:

        package com.example.pluginstand;
    
        import android.app.Activity;
        import android.os.Bundle;
        import android.view.MotionEvent;
    
         /**
          * 作者:王飞
          * 邮箱:1276998208@qq.com
          * create on 2018/6/14 15:43
         */
     public interface PluginInterface {
       //注入上下文
        public void attach(Activity proxyActivity);
        public void onCreate(Bundle savedInstanceState);
        public void onStart() ;
        public void onResume();
        public void onPause() ;
        public void onStop() ;
        public void onDestroy();
         public void onSaveInstanceState(Bundle outState);
         public boolean onTouchEvent(MotionEvent event) ;
         public void onBackPressed();
     }
    

    (3) 我们在依赖项库中新建空壳ProxyActivity用来作为伪装的插件Activity并在onCreate中开启插件Activity的onCreate实现启动:

    package com.example.pluginstand;
    
    import android.app.Activity;
    import android.content.res.Resources;
    import android.os.Bundle;
    import android.support.annotation.Nullable;
    
    import com.example.pluginstand.PluginInterface;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    /**
     * 作者:王飞
     * 邮箱:1276998208@qq.com
     * create on 2018/6/14 16:16
     * <p>
     * 空壳  这里加载的资源和类都是插件里面的而且生命周期都是    插件生命周期都是插件的,所以实质启动了插件的activity
     * <p>
     * 耳机
     */
    public class ProxyActivity extends Activity {
    /*
    * 需要加载的插件里的class
    * */
    private String className;
    PluginInterface pluginInterface;
    private String keyString;
    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        className = getIntent().getStringExtra("className");
        keyString=getIntent().getStringExtra("keys");
        try {
            Class activityClass = getClassLoader().loadClass(className);
            Constructor constructor = activityClass.getConstructor(new Class[]{});
            //调用私有的Activity构造函数进行实例化对象。
            Object instance = constructor.newInstance(new Object[]{});
            //这里强行转换Activity为定义的接口,因为我们已经在插件apk里面实现了这个接口。
            pluginInterface = (PluginInterface) instance;
            pluginInterface.attach(this);
            //传入一些信息。这里如果需要住Activity传入插件activity数据可以通过Bundle传递哦
            Bundle bundle = new Bundle();
            bundle.putString("keys",keyString);
            //这里调用开启插件Activity
            pluginInterface.onCreate(bundle);
    
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    protected void onStart() {
        //实质启动了插件activity的方法
        pluginInterface.onStart();
        super.onStart();
    }
    
    @Override
    protected void onPause() {
        //实质启动了插件activity的方法
        pluginInterface.onPause();
        super.onPause();
    }
    
    @Override
    protected void onDestroy() {
        //实质启动了插件activity的方法
        pluginInterface.onDestroy();
        super.onDestroy();
    }
    
    /*
    * 加载Activity
    *
    * class  滴滴出行和
    *
    * 资源 淘票票
    * */
    //加载小黄车的资源和类
    @Override
    public Resources getResources() {
        //让这个空壳Activity中加载了插件apk的资源
        return PluginManager.getInstance().getResources();
    }
    //加载弟弟和淘票票的资源和类
    @Override
    public ClassLoader getClassLoader() {
        //让这个空壳Activity中加载了插件apk的类从而实现开启的是真正意义上的插件ac。
        return PluginManager.getInstance().getClassLoader();
        }
    }
    

    (4) 我们需要在插件Modle的Activity中去实现接口,很重要的是通过 public void attach(Activity proxyActivity);注入上下文,并使用主项目中的acitivy的方法,才能和我们项目Activity进行交互,所以为了方便,我们建立BaseActivity替换所有插件中的Activity所需方法:

     package com.example.plugina;
     import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.content.pm.ApplicationInfo;
    import android.content.res.Resources;
    import android.os.Bundle;
     import android.os.PersistableBundle;
    import android.support.annotation.NonNull;
     import android.support.annotation.Nullable;
     import android.view.LayoutInflater;
    import android.view.Menu;
    import android.view.MenuInflater;
     import android.view.View;
     import android.view.ViewGroup;
    import android.view.Window;
    import android.view.WindowManager;
    
    import com.example.pluginstand.PluginInterface;
    
    /**
     * 作者:王飞
     * 邮箱:1276998208@qq.com
     * create on 2018/6/14 15:51
    */
    public class BaseActivity extends Activity implements    PluginInterface{
    protected Activity that;
    @SuppressLint("NewApi")
    @Nullable
    @Override
    public Intent getParentActivityIntent() {
        return that.getParentActivityIntent();
    }
    
    @Override
    public void attach(Activity proxyActivity) {
        that = proxyActivity;
    }
    
    @Override
    public Context getBaseContext() {
        return that.getBaseContext();
    }
    
    @Override
    public Context getApplicationContext() {
        return that.getApplicationContext();
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
    
    }
    
    @Override
    public void setContentView(View view) {
        that.setContentView(view);
    }
    
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        that.setContentView(view, params);
    }
    
    @Override
    public void setContentView(int layoutResID) {
        that.setContentView(layoutResID);
    }
    
    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        that.addContentView(view, params);
    }
    @Override
    public <T extends View> T findViewById(int id) {
        return that.findViewById(id);
    }
    
    @Override
    public Intent getIntent() {
        return that.getIntent();
    }
    
    @Override
    public ClassLoader getClassLoader() {
        return that.getClassLoader();
    }
    
    @Override
    public Resources getResources() {
        return that.getResources();
    }
    
    @NonNull
    @Override
    public LayoutInflater getLayoutInflater() {
        return that.getLayoutInflater();
    }
    
    @NonNull
    @Override
    public MenuInflater getMenuInflater() {
        return that.getMenuInflater();
    }
    
    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        return that.getSharedPreferences(name, mode);
    }
    
    @Override
    public ApplicationInfo getApplicationInfo() {
        return that.getApplicationInfo();
    }
    
    @Override
    public WindowManager getWindowManager() {
        return that.getWindowManager();
    }
    
    @Override
    public Window getWindow() {
        return that.getWindow();
    }
    
    @Override
    public Object getSystemService(@NonNull String name) {
        return that.getSystemService(name);
    }
    
    @Override
    public void finish() {
        that.finish();
    }
    
    @Override
    public void onBackPressed() {
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return that.onCreateOptionsMenu(menu);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    }
    
    @Override
    public void onStart() {
    }
    
    @Override
    public void onResume() {
    }
    
    @Override
    public void onPause() {
    }
    
    @Override
    public void onStop() {
    }
    
    @Override
    public void onDestroy() {
    }
    
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
    
    }
    
    @Override
    protected void onRestart() {
    
    }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
    
        }
    }
    

    (5) 最后我们去主Activity中进行加载ap并调用(这里很重要的是手机6.0之后权限问题,如果手机是6.0之前的可以在清单文件中注册就行,否则动态申请权限,我一个手机是root权限的不需要动态也行)

    package com.example.ls.pluginstudydemon;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.os.Environment;
    import android.support.v4.app.ActivityCompat;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    
    import com.example.pluginstand.PluginManager;
    import com.example.pluginstand.ProxyActivity;
    
    import java.io.File;
    
    public class MainActivity extends AppCompatActivity {
    //6.0之后动态必须权限,这里估计很多人都怕坑了。很多博客也没写明白导致很多人最后运行失败。
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.WRITE_EXTERNAL_STORAGE"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //这个注入上下文别忘记了
        PluginManager.getInstance().setContext(this);
        //权限动态申请。对于8.0之后没测过哦
        verifyStoragePermissions(this);
    }
    public static void verifyStoragePermissions(Activity activity) {
    
        try {
            //检测是否有写的权限
            int permission = ActivityCompat.checkSelfPermission(activity,
                    "android.permission.WRITE_EXTERNAL_STORAGE");
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 没有写的权限,去申请写的权限,会弹出对话框
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
     public void jumpTaopiaoPiao(View view) {
        File file = new File(Environment.getExternalStorageDirectory(), "taopiaopiao-debug.apk");
         PluginManager.getInstance().loadPath(file.getAbsolutePath());
        //跳转到空壳activity启动插件apk
        Intent intent = new Intent(this,ProxyActivity.class);
        intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        intent.putExtra("keys","我是主Activity里面的数据哦!");
        startActivity(intent);
    }
    
    public void jumpDidi(View view) {
        File file = new   File(Environment.getExternalStorageDirectory(), "didichuxing-debug.apk");
           PluginManager.getInstance().loadPath(file.getAbsolutePath());
        //跳转apk
        Intent intent = new Intent(this,ProxyActivity.class);
        intent.putExtra("className",    PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
        }
    }
    

    (6) 我们去主项目的清单文件中注册ProxActivity并且加读写权限,然后运行两个插件apk
    运行didichuxing我们会在目录下找到apk文件,这里会奔溃因为上下文原因不用管,然后粘贴复制这两个apk到我们的手机目录下:

    这里写图片描述 这里写图片描述 这里写图片描述 这里写图片描述 这里写图片描述

    接下来删除手机桌面的插件app然后运行主项目:如下所示:

    这里写图片描述

    希望帮助到你,下一篇我将直接使用360插件化架构,隔绝插件和项目之间的任何联系,你只需要写你的插件化Demon和项目,四大组件随便调用,我感觉目前来说使用起来最爽的哦。如果帮助到你请你点赞分享,或者不明白的地方可以加我qq1276998208我会对你负责的0o0。

    我的博客地址:https://blog.csdn.net/m0_37667770/article/details/80716073#comments
    项目地址:https://github.com/luhenchang/PluginStudyDemon.git

    相关文章

      网友评论

        本文标题:插件化从实现原理案例Demon,到大型企业插件化框架(360企业

        本文链接:https://www.haomeiwen.com/subject/mvjweftx.html