안드로이드 시작 속도 최적화 --- ARouter의 초기화 속도 향상

ARouter의 초기화 속도를 향상시키는 방법

  1. 앱 모듈의 build.gradle을 추가합니다.
apply plugin: 'com.alibaba.arouter'
  1. 프로젝트의 build.gradle을 추가합니다.
buildscript {
    
    
    repositories {
    
    
        jcenter()
    }

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

내에서 화웨이 P40 이상 향상시킬 수 있습니다800ms에서 영광 (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)모든 방법 획득하여덱스Path, == 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;
    }

이 방법은 다음과 같습니다.

지원되지 않는 경우 Multidex, 그런 다음 보조 폴더로 이동하여 Classesx.zip 로드하면 반환 된 경로가 ==. zip ==으로 끝납니다.

이전 순서도 :
여기에 사진 설명 삽입

위의 분석은 플러그인을 사용하지 않고 초기화 한 것인데, 플러그인 사용 후는 어떨까요?

라우팅 테이블의 자동 로딩은 router-register를 통해 이루어지며 , 주로 컴파일 과정에서 register 메소드에서 호출 한 코드를 LogisticsCenter의 loadRouterMap 메소드에 삽입하여 이루어지며, 디 컴파일 된 코드는 다음과 같습니다.

여기에 사진 설명 삽입
라우팅 테이블 자동으로로드하는 Arouter 용 플러그인은 gradle 계측 기술을 사용하여 컴파일 중에 코드를 삽입하여 라우팅 테이블 정보를 자동으로로드합니다. 따라서 ARouter 초기화는 com.alibaba.android.arouter.routes초기화 시간을 줄이는 목적을 달성하기 위해 클래스 이름의 시작 부분에 해당하는 필터를 검색하지 않습니다 .

앞으로 기사는 개인 공개 계정에 동기화되며 교환에 관심을 기울이는 것을 환영합니다.

여기에 사진 설명 삽입

추천

출처blog.csdn.net/zhujiangtaotaise/article/details/112857966