Android startup speed optimization --- Improve the initialization speed of ARouter

How to improve the initialization speed of ARouter

  1. Add in build.gradle of app module:
apply plugin: 'com.alibaba.arouter'
  1. Add in the project's build.gradle:
buildscript {
    
    
    repositories {
    
    
        jcenter()
    }

    dependencies {
    
    
        classpath "com.alibaba:arouter-register:1.0.2"
    }
}

In my Huawei P40 above can improve800 msIn honor 10 increase on1.1 seconds

2. Why can the use of plug-ins improve the startup speed?

Take a look at the initialization code:

    public static void init(Application application) {
    
    
        if (!hasInit) {
    
    
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
    
    
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

Truly in the _ARouter.init(application)middle, as follows:

    protected static synchronized boolean init(Application application) {
    
    
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());

        return true;
    }

We can see in LogisticsCenter.init(mContext, executor)real implementation:

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    
    
        mContext = context;
        executor = tpe;

        try {
    
    
            long startInit = System.currentTimeMillis();
            //billy.qi modified at 2017-12-06
            //load by plugin first
            // 是否使用了插件,如果使用了,那么 registerByPlugin = true,后续的就不会执行了。
            loadRouterMap();
            if (registerByPlugin) {
    
    
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
    
    
                Set<String> routerMap;

                // debug模式或者是最新版本的话每次都会重建路由表,否则从SP中读取路由表
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    
    
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // 获取前缀为com.alibaba.android.arouter.routes的class类,放到set集合里面
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
    
    
                    // 存到 sp 中
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }
                    // 更新版本
                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
    
    // 从 sp 中获取路由表
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                 
                for (String className : routerMap) {
    
    
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
    
    
                        // 将com.alibaba.android.arouter.routes.ARouter$$Root前缀的class类放到Warehouse.groupsIndex中
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
    
    
                        // 将com.alibaba.android.arouter.routes.ARouter$$Interceptors前缀的class类放到Warehouse.interceptorsIndex中
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
    
    
                        // 将com.alibaba.android.arouter.routes.ARouter$$Providers前缀的class类放到Warehouse.providersIndex中
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
    
    
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
    
    
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
    
    
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

The main look ClassUtils.getFileNameByPackageNameas follows:

    public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    
    
        final Set<String> classNames = new HashSet<>();

        // 获取所有的 dex 路径
        List<String> paths = getSourcePaths(context);
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());

        for (final String path : paths) {
    
    
            DefaultPoolExecutor.getInstance().execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    DexFile dexfile = null;

                    try {
    
    
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
    
    
                            //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
    
    
                            dexfile = new DexFile(path);
                        }

                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
    
    
                            String className = dexEntries.nextElement();
                            if (className.startsWith(packageName)) {
    
    
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
    
    
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
    
    
                        if (null != dexfile) {
    
    
                            try {
    
    
                                dexfile.close();
                            } catch (Throwable ignore) {
    
    
                            }
                        }

                        parserCtl.countDown();
                    }
                }
            });
        }

        parserCtl.await();

        Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }

The logic here is simple:

  1. That is, by getSourcePaths(context)acquiring all the waydexPath, ==getSourcePaths(context)==I'll talk about it later, let's finish the process of getFileNameByPackageName first .

  2. Convert the dex path to DexFile through the thread pool . There is a difference here:
    ● If the path ends with .zip (that is, the virtual machine does not support MultiDex, why the analysis below), then use DexFile.loadDex to generate DexFile, DexFile.loadDex process It will convert ordinary dex files into odex, this process is very slow;
    ● If it supports MultiDex, then directly new a DexFile according to the path of dex

  3. Filter all com.alibaba.android.arouter.routesThe class name at the beginning, stored in the collection

  4. Use CountDownLatch to ensure that all tasks in the thread pool are executed, and then a collection of all class names is returned .

Look at the getSourcePaths method again:

    public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
    
    
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        File sourceApk = new File(applicationInfo.sourceDir);

        List<String> sourcePaths = new ArrayList<>();
        sourcePaths.add(applicationInfo.sourceDir); //默认生成apk的目录路径...base.apk

        //the prefix of extracted file, ie: test.classes
        String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;

//        如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
//        通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
        if (!isVMMultidexCapable()) {
    
    
            //the total dex numbers
            int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
            File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);

            for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
    
    
                //for each dex file, ie: test.classes2.zip, test.classes3.zip...
                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
                File extractedFile = new File(dexDir, fileName);
                if (extractedFile.isFile()) {
    
    
                    sourcePaths.add(extractedFile.getAbsolutePath());
                    //we ignore the verify zip part
                } else {
    
    
                    throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
                }
            }
        }

        if (ARouter.debuggable()) {
    
     // Search instant run support only debuggable
            sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
        }
        return sourcePaths;
    }

This method can be seen:

If not supported Multidex, Then go to Secondary Folder to load Classesx.zip, the returned path ends with ==.zip==.

The previous flow chart:
Insert picture description here

The above analysis is the initialization without plug-in, what about after plug-in is used?

The automatic loading of the routing table is achieved through the router-register , mainly by inserting the code called by the register method into the loadRouterMap method of LogisticsCenter during compilation. The decompiled code is as follows:

Insert picture description here
The plug-in for Arouter to automatically load the routing table uses gradle instrumentation technology to insert code during compilation to automatically load the routing table information. So when ARouter initialization will not go search filters corresponding to com.alibaba.android.arouter.routesthe beginning of the class name, so as to achieve the purpose of reducing the initialization time.

The articles will be synchronized to the personal public account in the future, welcome to pay attention

Insert picture description here

Guess you like

Origin blog.csdn.net/zhujiangtaotaise/article/details/112857966