Androidの起動速度の最適化--- ARouterの初期化速度を改善します

ARouterの初期化速度を向上させる方法

  1. appモジュールのbuild.gradleを追加します。
apply plugin: 'com.alibaba.arouter'
  1. プロジェクトのbuild.gradleを追加します。
buildscript {
    
    
    repositories {
    
    
        jcenter()
    }

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

上記のHuaweiP40では改善できます800ミリ秒名誉10に増加1.1秒

2.プラグインを使用すると、起動速度が向上するのはなぜですか。

初期化コードを見てください:

    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.");
        }
    }

_ARouter.init(application)次のように、真ん中に:

    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;
    }

LogisticsCenter.init(mContext, executor)実際の実装で見ることができます

    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() + "]");
        }
    }

主な外観ClassUtils.getFileNameByPackageNameは次のとおりです。

    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;
    }

ここでのロジックは単純です。

  1. つまりgetSourcePaths(context)、ずっと取得することによってdexパス、== getSourcePaths(context)==後で説明します最初に、getFileNameByPackageNameのプロセスを終了しましょう

  2. スレッドプールを介してdexパスをDexFileに変換します。ここで違いがあります
    ●パスが.zipで終わる場合(つまり、仮想マシンがMultiDexをサポートしていない場合、以下の分析を行う理由)、DexFile.loadDexを使用してDexFile、DexFile.loadDexプロセス生成します。通常のdexファイルをodexに変換しますが、このプロセスは非常に遅くなります。
    ●MultiDexをサポートしている場合は、dexのパスに従ってDexFileを直接新しいものにします。

  3. すべてフィルタリング com.alibaba.android.arouter.routesコレクションに保存されている最初のクラス名

  4. CountDownLatchを使用して、スレッドプール内のすべてのタスクが実行され、すべてのクラス名のコレクションが返されるようにします。

getSourcePathsメソッドをもう一度見てください。

    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;
    }

この方法は次のようになります。

サポートされていない場合 マルチデックス、次にセカンダリフォルダに移動してClassesx.zipロードすると、返されるパスは==。zip ==で終わります。

前のフローチャート:
ここに写真の説明を挿入

上記の分析はプラグインなしの初期化ですが、プラグインを使用した後はどうでしょうか?

ルーティングテーブルの自動ロードは、主に、コンパイル中にregisterメソッドによって呼び出されたコードをLogisticsCenterのloadRouterMapメソッドに挿入することにより、router-registerを介して実行されます。逆コンパイルされたコードは次のとおりです。

ここに写真の説明を挿入
Arouterがルーティングテーブル自動的にロードするためのプラグインは、gradle Instrumentationテクノロジーを使用して、コンパイル中にコードを挿入し、ルーティングテーブル情報を自動的にロードします。したがって、ARouterの初期化が行われない場合com.alibaba.android.arouter.routes、初期化時間を短縮する目的を達成するために、クラス名の先頭に対応するフィルターを検索します。

将来的には、記事は個人の公開アカウントに同期されます。交換に注意を払うことを歓迎します

ここに写真の説明を挿入

おすすめ

転載: blog.csdn.net/zhujiangtaotaise/article/details/112857966