Android 插件化框架 Replugin 源码解读(三)插件加载

 
 
//com.qihoo360.loader2.PmBase
 
 final void callAttach() {
        //获取ClassLoader
        mClassLoader = PmBase.class.getClassLoader();
 
        // 挂载
        for (Plugin p : mPlugins.values()) {
            p.attach(mContext, mClassLoader, mLocal);
        }
 
        // 加载默认插件
        if (PluginManager.isPluginProcess()) {
                //默认插件不为空
            if (!TextUtils.isEmpty(mDefaultPluginName)) {
                //获取插件
                Plugin p = mPlugins.get(mDefaultPluginName);
                if (p != null) {
                    boolean rc = p.load(Plugin.LOAD_APP, true);
                    if (!rc) {
                        
                    }
                    if (rc) {
                        mDefaultPlugin = p;
                        mClient.init(p);
                    }
                }
            }
        }
    }

上一篇中我们hook住系统的ClassLoader之后就调用callAttach()方法加载默认插件。首先通过设置的插件名字从插件表中获取插件。是一个Plugin 对象,然后通过Plugin.load 实现加载。

Plugin.load


//com.qihoo360.loader2.Plugin

final boolean load(int load, boolean useCache) {
        PluginInfo info = mInfo;
        //加载插件
        boolean rc = loadLocked(load, useCache);
        // 尝试在此处调用Application.onCreate方法
       
        if (load == LOAD_APP && rc) {
            callApp();
        }
        // 如果info改了,通知一下常驻
        // 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
        if (rc && mInfo != info) {
            UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
            Tasks.post2Thread(task);
        }
        return rc;
    }

1.这里通过调用loadLocked 方法来加载插件

  //com.qihoo360.loader2.Plugin

private boolean loadLocked(int load, boolean useCache) {
        //判断插件是否禁用
        int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
        if (status < PluginStatusController.STATUS_OK) {
            return false;
        }
        //判断是否加载过,加载过就直接返回
        if (mInitialized) {
            if (mLoader == null) {
                
                return false;
            }
            if (load == LOAD_INFO) {
                boolean rl = mLoader.isPackageInfoLoaded();
               
                return rl;
            }
            if (load == LOAD_RESOURCES) {
                boolean rl = mLoader.isResourcesLoaded();
               
                return rl;
            }
            if (load == LOAD_DEX) {
                boolean rl = mLoader.isDexLoaded();
                
                return rl;
            }
            boolean il = mLoader.isAppLoaded();

            return il;
        }
        mInitialized = true;

        。。。

        // 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作
        if (useCache) {
            boolean result = loadByCache(load);
            // 如果缓存命中,则直接返回
            if (result) {
                return true;
            }
        }

        Context context = mContext;
        ClassLoader parent = mParent;
        PluginCommImpl manager = mPluginManager;

      
        String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());

        //创建进程锁
        ProcessLocker lock = new ProcessLocker(context, lockFileName);
   
       
        long t1 = System.currentTimeMillis();
        //加载插件
        boolean rc = doLoad(logTag, context, parent, manager, load);
        //解锁
        lock.unlock();
        
        if (!rc) {
            
        }
        if (rc) {
            
            try {
                // 至此,该插件已开始运行
                PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
            } catch (Throwable e) {
                
            }

            return true;
        }

        再锁一次
        lock = new ProcessLocker(context, lockFileName);
       
        // 删除优化dex文件
        File odex = mInfo.getDexFile();
        if (odex.exists()) {
           
            odex.delete();
        }


        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // support for multidex below LOLLIPOP:delete Extra odex,if need
            try {
                FileUtils.forceDelete(mInfo.getExtraOdexDir());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e2) {
                e2.printStackTrace();
            }
        }

        t1 = System.currentTimeMillis();
        // 尝试再次加载该插件
        rc = tryLoadAgain(logTag, context, parent, manager, load);
        
        lock.unlock();
        if (!rc) {
            
            return false;
        }

        try {
            // 至此,该插件已开始运行
            PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
        } catch (Throwable e) {
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e);
            }
        }

        return true;
    }

2.在loadLocked方法中先判断插件是否被禁用,然后判断是否被加载过,加载过就返回,再判断缓存中有没有,有的话也返回,都没有就去加载,先创建进程锁,锁住进程加载插件过程,调用doLoad 方法加载,加载成功就调用PluginManagerProxy.addToRunningPluginsNoThrows方法添加插件到"当前进程的正在运行插件列表",并同步到Server端。如果失败就再加载一次插件。

  
  //com.qihoo360.loader2.Plugin

    private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
        if (mLoader == null) {
            // 试图释放文件
            PluginInfo info = null;
               //内置插件
            if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
                //判断是否为内置插件,如果是内置插件,在检查是否已经释放了so库到指定位置,如果没有先释放到指定位置,并重新构造PluginInfo

               。。。。。。
     
                //p-n 插件
            } else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
                //判断是否为p-n类型并且未执行安装插件步骤,如果是p-n类型并且未安装,先执行插件的安装操作,并重新构造PluginInfo
              。。。。。。。
              

            } else {
                //
            }

            //
            if (info != null) {
                // 替换
                mInfo = info;
            }

            //插件加载类
            mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
            //加载插件数据
            if (!mLoader.loadDex(parent, load)) {
                return false;
            }

            // 设置插件为“使用过的”
            // 注意,需要重新获取当前的PluginInfo对象,而非使用“可能是新插件”的mInfo
            try {
                PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
            } catch (RemoteException e) {
                // 同步出现问题,但仍继续进行
                if (LOGR) {
                    e.printStackTrace();
                }
            }

            // 若需要加载Dex,则还同时需要初始化插件里的Entry对象
            if (load == LOAD_APP) {
                // NOTE Entry对象是可以在任何线程中被调用到
                if (!loadEntryLocked(manager)) {
                    return false;
                }
                // NOTE 在此处调用则必须Post到UI,但此时有可能Activity已被加载
                //      会出现Activity.onCreate比Application更早的情况,故应放在load外面立即调用
                // callApp();
            }
        }

        if (load == LOAD_INFO) {
            return mLoader.isPackageInfoLoaded();
        } else if (load == LOAD_RESOURCES) {
            return mLoader.isResourcesLoaded();
        } else if (load == LOAD_DEX) {
            return mLoader.isDexLoaded();
        } else {
            return mLoader.isAppLoaded();
        }
    }

3.这里如果没有加载过,先判断插件类型,如果是内置插件判断so库等是否已经释放。没有的话就释放到指定的位置。如果是p-n插件然后创建加载类Loader 通过loadDex方法加载插件。加载成功后设置插件信息为 "使用过"。然后他通过callApp启动插件

//com.qihoo360.loader2.Loader;

final boolean loadDex(ClassLoader parent, int load) {
       
          try {
            //获取PackageManager
            PackageManager pm = mContext.getPackageManager();
            //查看缓存
            mPackageInfo = Plugin.queryCachedPackageInfo(mPath);

            if (mPackageInfo == null) {
                // 缓存没有PackageInfo 创建一个
                mPackageInfo = pm.getPackageArchiveInfo(mPath,
                        PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
                //插件不存在返回
                if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
                   
                    mPackageInfo = null;
                    return false;
                }
                //指定插件资源路径
                mPackageInfo.applicationInfo.sourceDir = mPath;
                mPackageInfo.applicationInfo.publicSourceDir = mPath;

                // 添加针对SO库的加载
                // 此属性最终用于ApplicationLoaders.getClassLoader,在创建PathClassLoader时成为其参数
                // 这样findLibrary可不用覆写,即可直接实现SO的加载
               
                PluginInfo pi = mPluginObj.mInfo;
                File ld = pi.getNativeLibsDir();
                mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();



                // 缓存表: pkgName -> pluginName
                synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
                    Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
                }

                // 缓存表: pluginName -> fileName
                synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
                    Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
                }

                // 缓存表: fileName -> PackageInfo
                synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
                    Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
                }
            }

            。。。。。。

            // 创建或获取ComponentList表
           
            mComponents = Plugin.queryCachedComponentList(mPath);
            if (mComponents == null) {
                // ComponentList
                mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);

                // 动态注册插件中声明的 receiver
                regReceivers();

                // 缓存表:ComponentList
                synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
                    Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
                }

                /* 只调整一次 */
                // 调整插件中组件的进程名称
                adjustPluginProcess(mPackageInfo.applicationInfo);

                // 调整插件中 Activity 的 TaskAffinity
                adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
            }
             //如果是加载插件中组件信息返回
            if (load == Plugin.LOAD_INFO) {
                return isPackageInfoLoaded();
            }
            //获取缓存中插件中的资源
            mPkgResources = Plugin.queryCachedResources(mPath);
            // LOAD_RESOURCES和LOAD_ALL都会获取资源,但LOAD_INFO不可以(只允许获取PackageInfo)
            //没有命中缓存获取插件中的资源
            if (mPkgResources == null) {
                // Resources
                try {
                    if (BuildConfig.DEBUG) {
                        // 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
                        Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
                        mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
                    } else {
                        mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
                    }
                } catch (NameNotFoundException e) {
                    
                    return false;
                }
                if (mPkgResources == null) {
                    
                    return false;
                }
                
                // 缓存表: Resources
                synchronized (Plugin.FILENAME_2_RESOURCES) {
                    Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
                }
            }
            //资源加载在这里返回
            if (load == Plugin.LOAD_RESOURCES) {
                return isResourcesLoaded();
            }
            //获取缓存中插件的ClassLoader
            mClassLoader = Plugin.queryCachedClassLoader(mPath);
            //没有命中缓存
            if (mClassLoader == null) {
                // 先获取父类加载器
                String out = mPluginObj.mInfo.getDexParentDir().getPath();
                
                if (BuildConfig.DEBUG) {
                   
                    parent = ClassLoader.getSystemClassLoader();
                } else {
                    // 线上环境保持不变
                    parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
                }
                //设置so 文件路径
                String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
                
                //创建插件自己的PluginDexClassLoader
                mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
                
                if (mClassLoader == null) {
                   
                    return false;
                }
              
                // 缓存表:ClassLoader
                synchronized (Plugin.FILENAME_2_DEX) {
                    Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader));
                }
            }
            //加载dex在这里返回
            if (load == Plugin.LOAD_DEX) {
                return isDexLoaded();
            }

            //创建插件apk使用的Context对象
            mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
           

        } catch (Throwable e) {
           
            return false;
        }

        return true;
    }

4.在loadDex方法中首先获取插件的mPackageInfo判断插件是否存在,并且指定插件apk的资源路径,包括so库的路径最后把pluginName  mPath mPackageInfo 缓存起来。

然后创建ComponentList对象  这个对象用来快速获取四大组件和Application的系统Info的List 每个Plugin对象维护一份ComponentList,且在第一次加载PackageInfo时被生成。

 /**
     * Class类名 - Activity的Map表
     */
    final HashMap<String, ActivityInfo> mActivities = new HashMap<>();

    /**
     * Class类名 - Provider的Map表
     */
    final HashMap<String, ProviderInfo> mProvidersByName = new HashMap<>();

    /**
     * Authority - Provider的Map表
     */
    final HashMap<String, ProviderInfo> mProvidersByAuthority = new HashMap<>();

    /**
     * Class类名 - Service的Map表
     */
    final HashMap<String, ServiceInfo> mServices = new HashMap<>();

    /**
     * Application对象
     */
    ApplicationInfo mApplication = null;

    /**
     * Class类名 - BroadcastReceiver的Map表
     * 注意:是的,你没有看错,系统缓存Receiver就是用的ActivityInfo
     */
    final HashMap<String, ActivityInfo> mReceivers = new HashMap<>();

ComponentList在创建的时候解析了AndroidManifest.xml文件获取四大组件信息生成组件与 IntentFilter 的对应关系并将这些信息和四大组件生成对应表关系并缓存,缓存插件apk的Application信息对象,接着动态注册插件apk中在AndroidManifest中声明的receiver,调整插件apk中四大组件的自定义进程名称为宿主坑位进程名称和Activity的TaskAffinity

之后先通过pm.getResourcesForApplication创建插件apk的要使用的Resources对象 然后创建插件自己的ClassLoader对象PathClassLoader。这里是PluginDexClassLoader 缓存这个ClassLoader对象

最后创建插件用的Context对象 。到这里加载插件的准备工作都已经做好了,该有的数据都有了,现在再看看第3步中loadEntryLocked方法

//com.qihoo360.loader2.Plugin;

private boolean loadEntryLocked(PluginCommImpl manager) {
        if (mDummyPlugin) {
            
            mLoader.mPlugin = new IPlugin() {
                @Override
                public IModule query(Class<? extends IModule> c) {
                    return null;
                }
            };
        } else {
           //尝试反射调用插件工程中Entry的create方法
            if (mLoader.loadEntryMethod2()) {
                //执行Entry的create方法
                if (!mLoader.invoke2(manager)) {
                    return false;
                }
            } else if (mLoader.loadEntryMethod(false)) {
                if (!mLoader.invoke(manager)) {
                    return false;
                }
            } else if (mLoader.loadEntryMethod3()) {
                if (!mLoader.invoke2(manager)) {
                    return false;
                }
            } else {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "p.lel f " + mInfo.getName());
                }
                return false;
            }
        }
        return true;
    }

 

//com.qihoo360.loader2.Loader

final boolean loadEntryMethod3() {
        
        try {
            // PLUGIN_ENTRY_PACKAGE_PREFIX = "com.qihoo360.plugin";
            //PLUGIN_ENTRY_CLASS_NAME = "Entry";
           
            String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
            Class<?> c = mClassLoader.loadClass(className);

            //PLUGIN_ENTRY_EXPORT_METHOD_NAME = "create"
            mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
        } catch (Throwable e) {
           
        }
        return mCreateMethod2 != null;
    }
 //com.qihoo360.replugin.Entry
 public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
    // 初始化插件框架,就是反射主工程框架中的类或者方法,方便插件工程之后直接调用
    RePluginFramework.init(cl);

    // 初始化主工程传递过来的Context和ClassLoader
    RePluginEnv.init(context, cl, manager);

    //返回插件工程中的服务管理Binder对象
    return new IPlugin.Stub() {
        @Override
        public IBinder query(String name) throws RemoteException {
            return RePluginServiceManager.getInstance().getService(name);
        }
    };
}

5.这边通过反射调用插件工程中的Entry 的create 方法 并执行create 方法。这里create方法反射了主工程框架中的一些方法,让插件可以使用宿主的功能,接着就是缓存持有主工程传递过来的插件Context对象,这个Context是插件工程自己用的,还通过这个Context获取了宿主的Context对象,这个对象主要是为了用来获取一些宿主中的资源,反射类等一些信息的,还缓存了宿主的ClassLoader对象,也是用来方便反射宿主中的一些类的,最后返回了插件工程中管理服务的Binder对象。具体插件初始化过程会在之后详细介绍。

   // 确保在UI线程中调用
   
    private void callApp() {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            callAppLocked();
        } else {
            // 确保一定在UI的最早消息处调用
            mMainH.postAtFrontOfQueue(new Runnable() {
                @Override
                public void run() {
                    callAppLocked();
                }
            });
        }
    }

6.在load完成之后调用callApp方法创建插件apk 的application 在UI线程中调用callAppLocked方法

 private void callAppLocked() {
        // 获取并调用Application的几个核心方法
        if (!mDummyPlugin) {
            // NOTE 不排除A的Application中调到了B,B又调回到A,或在同一插件内的onCreate开启Service/Activity,而内部逻辑又调用fetchContext并再次走到这里
            // NOTE 因此需要对mApplicationClient做判断,确保永远只执行一次,无论是否成功
            if (mApplicationClient != null) {
                // 已经初始化过,无需再次处理
                return;
            }
             //创建插件的application并包装成PluginApplicationClient返回
            mApplicationClient = PluginApplicationClient.getOrCreate(
                    mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);

            if (mApplicationClient != null) {
                //调用Application的AttachBaseContext方法
                mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
                 //调用Application的OnCreate方法
                mApplicationClient.callOnCreate();
            }
        } else {
            
        }
    }

7 创建了插件的appliication 并包装成PluginApplicationClient 。然后执行插件application 的 attach和onCreate方法。到这里插件就加载完毕了,此时虽然没有插件的组件调用,但是插件的application已经启动了,也就是说其实插件已经启动了。

总结

到这里我们讲解了Replugin框架初始化的内容。包含的内容我总结起来画了一个简单的图。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值