• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

Android性能优化之Android-10-dex2oat实践

android 搞代码 3年前 (2022-03-01) 28次浏览 已收录 0个评论
文章目录[隐藏]

作者:字节跳动终端技术——郭陆地

背景

对于Android App的性能优化来说,形式办法以及工具都有很多,而dex2oat作为其中的一员,却可能不被公众所熟知。它是Android官网利用于运行时,针对dex进行编译优化的程序,通过对dex进行一系列的指令优化、编译机器码等操作,晋升dex加载速度代码运行速度,从而晋升装置速度、启动速度、以及利用应用过程中的晦涩度,最终晋升用户日常的应用体验。

它的适用范围也比拟广,能够用于Primary ApkSecondary Apk惯例场景插件场景。(Primary Apk是指的惯例场景下的主包base.apk)或者插件场景下的宿主包Secondary Apk是指的惯例场景下的自行加载的包(.apk)或者插件场景下的插件包(.apk))。

而随着Android零碎版本的更迭,发现本来能够在利用过程上触发dex2oat编译的形式,却在targetSdkVersion>=29Android 10+的零碎上,不再容许应用。其起因是零碎在targetSdkVersion=29的时候,对此做了限度,不容许利用过程上触发dex2oat编译(Android 运行时 (ART) 不再从利用过程调用 dex2oat。这项变更意味着 ART 将仅承受系统生成的 OAT 文件)(OATdex2oat后的产物)。

那以后是否会受到这个限度的影响呢?

2020年的时候Android 11零碎正式公布,各大利用市场就开始限度ApptargetSdkVersion>=29,而Android 11零碎距今曾经公布一年之久,也就意味着,现如今ApptargetSdkVersion>=29是不可避免的。而且随着新Android设施的一直迭代,越来越多的用户,应用上了携带新零碎的新机器,使得Android 10+零碎的占有量逐渐减少,目前为止Android 10+零碎的占有量约占整体的30%~40%左右,也就是说这部分机器将会受到这个限度的影响。

那这个限度有什么影响呢?

这个限度的要害是,不容许利用过程上触发dex2oat编译,换句话说就是并不影响零碎本身去触发dex2oat编译,那么限度的影响也就是,影响那些须要通过利用过程去触发dex2oat编译的场景。

对于Primary ApkSecondary Apk,它们在惯例场景插件场景下,零碎都会收集其运行时的热点代码并用于dex2oat进行编译优化。此处触发dex2oat编译是零碎行为,并不受限于上述限度。但触发此处dex2oat编译的条件是比拟刻薄的,它要求设施必须处于闲暇状态且要连贯电源,而且其校验的距离是一天。

在上述条件下,由零碎触发的dex2oat编译,基本上很难触发,从而导致dex加载速度降落80%以上,代码运行速度降落11%以上,使得利用的ANR率晋升、晦涩度降落,最终影响用户的日常应用体验。

对于之前来说改良计划就是通过利用过程触发dex2oat编译来补救零碎触发dex2oat编译的有余,而现在因限度会导致局部机器无奈失效。

如何能力让用户领会到dex2oat带来的体验晋升呢?问题又如何解决呢?

上面通过摸索,一步步的迫近假相,解决问题~

摸索

摸索之前,先明确下外围点,本次摸索的指标就是为了让用户领会到dex2oat带来的体验晋升,其最大的妨碍就是零碎触发dex2oat的编译条件太刻薄,导致难以触发,之前的成功实践就是基于App维度手动触发dex2oat编译来补救零碎触发dex2oat的编译的有余。

而当初仍需摸索的起因就是,本来的成功实践,目前在某些机器上曾经受限,为了实现指标,解决掉现有的问题,自然而然的想法就是,限度到底是什么?限度是如何失效的?是否能够绕过?

限度是什么?

目前对于限度的了解,应该仅限于背景中的形容,那Google官网是怎么说的呢?

Android 运行时 (ART) 不再从利用过程调用 dex2oat。这项变更意味着 ART 将仅承受系统生成的 OAT 文件。(Android 运行时只承受系统生成的 OAT 文件)

通过Google官网的形容大抵能够了解为,本来ART会从利用过程调用dex2oat,当初不再从利用过程调用dex2oat了,从而使得利用过程没有机会触发dex2oat,从而达到限度App维度触发dex2oat的目标。

但问题的确有这么简略嘛?

通过比照Android 9Android 10的代码时发现,Android 9在构建ClassLoader的时候会触发dex2oat,然而 Android 10 上相干代码曾经被移除,此处同Google官网的说法统一。

但如果限度仅仅如此的话,能够依照本来ART从利用过程调用dex2oat的形式,而后手动从利用过程调用就能够了。

因为Android` `10相干代码曾经移除,所以查看下Android 9的代码,看下之前是如何从利用过程调用dex2oat的,相干代码链接:https://android.googlesource….,通过查看代码能够看出,是通过拼接dex2oat的命令来触发执行的,依照如上代码,拼接dex2oat命令的伪代码如下:

//step1 拼接命令

List<String> commandAndParams = new ArrayList<>();

commandAndParams.add("dex2oat");

if (Build.VERSION.SDK_INT >= 24) {

    commandAndParams.add("--runtime-arg");

    commandAndParams.add("-classpath");

    commandAndParams.add("--runtime-arg");

    commandAndParams.add("&");

}

commandAndParams.add("--instruction-set=" + getCurrentInstructionSet());

// verify-none|interpret-only|verify-at-runtime|space|balanced|speed|everything|time

//编译模式,不同的模式,影响最终的运行速度和磁盘大小的占用

if (mode == Dex2OatCompMode.FASTEST_NONE) {

    commandAndParams.add("--compiler-filter=verify-none");

} else if (mode == Dex2OatCompMode.FASTER_ONLY_VERIFY) {

    //疾速编译

    if (Build.VERSION.SDK_INT > 25) {

        commandAndParams.add("--compiler-filter=quicken");

    } else {

        commandAndParams.add("--compiler-filter=interpret-only");

    }

} else if (mode == Dex2OatCompMode.SLOWLY_ALL) {

    //全量编译

    commandAndParams.add("--compiler-filter=speed");

}

//源码门路(apk or dex门路)

commandAndParams.add("--dex-file=" + sourceFilePath);

//dex2oat产物门路

commandAndParams.add("--oat-file=" + optimizedFilePath);



String[] cmd= commandAndParams.toArray(new String[commandAndParams.size()]);



//step2 执行命令

Runtime.getRuntime().exec(cmd)

将上述拼接的dex2oat命令在Android` `9机器的App过程触发执行,的确失去合乎预期的dex2oat产物,并能够失常加载和应用,阐明命令拼接的是OK的,而后将上述命令在Android 10targetSdkVersion>=29机器的App过程触发执行,发现并没有失去dex2oat产物,并且失去如下日志:

type=1400 audit(0.0:569): avc: denied { execute } for name="dex2oat" dev="dm-2" ino=222 scontext=u:r:untrusted_app:s0:c12,c257,c512,c768 tcontext=u:object_r:dex2oat_exec:s0 tclass=file permissive=0

这个日志阐明了什么呢?

能够看到日志信息里有avc: denied关键词,阐明此操作受SELinux规定管控,并被回绝。

在进行日志剖析之前,先补充一下SELinux的相干常识,上面是Google官网的阐明:

Android 应用平安增强型 Linux (SELinux) 对所有过程强制执行强制访问控制 (MAC),甚至包含以 Root/超级用户权限运行的过程(Linux 性能)

简略说,SELinux就是Android零碎以过程维度对其进行强制访问控制的管理体系。SELinux是依附配置的规定对过程进行束缚拜访权限。

上面回归正题,剖析下日志。

日志细节剖析如下:

  • type=1400 :示意SYSCALL
  • denied { `execute` } :示意执行权限被回绝;
  • scontext=u:r:`untrusted_app`:s0:c12,c257,c512,c768:示意主体的平安上下文,其中untrusted_appsource type
  • tcontext=u:object_r:`dex2oat_exec`:s0 :示意指标资源的平安上下文,其中dex2oat_exectarget type
  • tclass=file:示意指标资源的class类型
  • permissive=0:以后的SELLinux模式,1示意permissive(宽松的),0示意enforcing(严格的)

简略的说就是,当在Android 10targetSdkVersion>=29的机器上的App过程上执行拼接的dex2oat命令的时候,是由untrusted_app *触发dex2oat_exec ,* 而因为untrusted_app的规定限度,导致其触发dex2oat_execexecute权限被回绝。

上面简略总结一下:

  1. 限度1:Android 10+零碎删除了在构建ClassLoader时触发dex2oat的相干代码,来限度从利用过程触发dex2oat的入口。
  2. 限度2:Android 10+零碎的相干SELinux规定变更,限度targetSdkVersion>=29的时候从利用过程触发dex2oat

当初通过查阅相干代码和SELinux规定以及应用代码验证,真正的见识到了限度到底是什么样子的,又是如何失效的,以及真真切切的感触到它的威力……

那既然晓得限度是什么以及限度如何失效的了,那是否能够绕过呢?

限度是否绕过?

通过上面对限度的理解,能够先大胆的假如:

  1. targetSdkVersion设置小于29
  2. 假装利用过程为零碎过程
  3. 敞开Android零碎的SELinux检测
  4. 批改规定移除限度

上面开始小心求证,上述假如是否可行?

对于假如1来说,如果全局设置targetSdkVersion小于29的话,则会影响App后续在利用商店的上架,如果部分设置targetSdkVersion小于29的话,不仅难以批改且机会难以把握,dex2oat是独自的过程进行编译操作的,不同的过程对其进行触发编译的时候,会将过程的targetSdkVersion信息作为参数传给它,用于它外部逻辑的判断,而过程信息是存在于零碎过程的。

对于假如2来说,目前还没相干的已知操作能够做到相似成果…

对于假如3来说,Android零碎的确也提供了敞开SELinux检测的办法,然而须要Root权限。

对于假如4来说,如果全局批改规定,须要从新编译系统,才能够失效,如果部分批改规定(内存中批改),此处所需的权限也比拟高,也无权操作。

所以,从目前来看,绕过根本不可行了…

那怎么办?限度绕不过来,指标无奈达成了…

或者谜底就在谜面上,既然Android零碎限度只能应用系统生成的,那咱们就用系统生成的?

只须要让零碎能够感知到咱们的操作,能够依据咱们提供的操作去生成,能够由咱们去管制生成的机会以及成果,这样不如同在利用过程触发dex2oat有一样的成果了嘛?

那如何操作呢?

借助零碎的能力?

零碎是否提供了能够供利用过程触发零碎行为,而后由零碎触发dex2oat的形式?

通过查阅Android的官网文档以及相干代码发现能够通过如下形式进行操作(强制编译):

  • 基于配置文件编译:adb shell cmd package compile -m speed-profile -f my-package
  • 全面编译:adb shell cmd package compile -m speed -f my-package

上述命令不仅反对抉择编译模式(speed-profile or speed),而且还能够抉择特定的App进行操作(my-package)。

通过运行上述命令发现的确能够在targetSdkVersion>=29Android 10+的零碎上编译出对应的dex2oat产物,且能够失常加载应用!!!

然而上述命令仅反对Primary Apk并不反对Secondary Apk,感觉它的性能还不止于此,还能够持续开掘一下这个命令的后劲,上面看下这个命令的实现。

剖析之前须要先确定命令对应的代码实现,这里应用了个小技巧,通过成心输错命令,发现最终解体的地位在PackageManagerShellCommand,而后通过debug源码,梳理了一下残缺的代码调用流程,细节如下。

为了不便了解,上面将代码的调用流程应用时序图形容进去。

下图为Primary Apk的编译流程:

无奈复制加载中的内容

在梳理Primary Apk的编译流程的时候,发现代码中也有解决Secondary Apk的办法,上面梳理流程如下:

无奈复制加载中的内容

而后依据其代码,梳理其编译命令为:adb shell cmd package compile -m speed -f --secondary-dex my-package

至此,咱们曾经失去了一种能够借助命令使零碎触发dex2oat编译的形式,且能够反对Primary ApkSecondary Apk

还有一些细节须要留神,Primary Apk的命令传入的是App的包名,Secondary Apk的命令传入的也是包名,那哪些Secondary Apk会参加编译呢?

这就波及到Secondary Apk的注册了,只有注册了的Secondary Apk才会参加编译。

上面是Secondary Apk注册的流程:

无奈复制加载中的内容

对于Secondary Apk来说只注册不反注册也不行,因为对于Secondary Apk来说,每次编译仅想编译新增的或者未被编译过的,对于曾经编译过的,是不想其仍参加编译,所以这些曾经编译过的,就须要进行反注册。

上面是Secondary Apk反注册的流程:

无奈复制加载中的内容

而且通过查看源码发现,触发此处的形式其实有两种:

  1. 形式一:应用adb shell cmd package + 命令。例如adb shell cmd package compile -m quicken com.bytedance.demo ,其含意就是触发runCompile办法,而后指定编译模式为quicken,指定编译的包名为com.bytedance.demo,因为没有指定是Secondary,所以依照Primary编译。而后其底层通过socket+binder实现通信,最终交由PackageManagerBinder解决。
  2. 形式二:应用PackageManagerBinder,并设定code=SHELL_COMMAND_TRANSACTION,而后将命令以数组的模式封装到data内即可。

对于形式一来说,依赖adb的实现,底层通信须要依赖socket + binder,而对于形式二来说,底层通信间接应用binder,相比来说更高效,所以最终抉择第二种形式。

上面简略的总结一下。

在得悉限度无奈被绕过后,就想到是否能够使得利用过程能够触发零碎行为,而后由零碎触发dex2oat,而后通过查阅官网文档找到对应的adb命令能够满足诉求,不过此时仅看到Primary Apk的相干实现,而后持续通过查看代码验证其流程,找到Secondary Apk的相干实现,而后依据理论场景的须要,又持续查看代码,找到注册Secondary Apk和反注册Secondary Apk的办法,而后通过比照adb命令的实现和binder的实现差别,最终选用binder的实现形式,来实现上述操作。

既然摸索曾经实现,那么上面就依据摸索的后果,实现落地实际,并验证其成果。

实际

操作

示例代码如下:

//执行疾速编译

@Override

public void dexOptQuicken(String pluginPackageName, int version) {

    //step1:如果没有初始化则初始化

    maybeInit();

    //step2:将apk门路进行注册到PMS

    registerDexModule(pluginPackageName, version);

    //step3:应用binder触发疾速编译

    dexOpt(COMPILE_FILTER_QUICKEN, pluginPackageName, version);

    //step4:将apk门路反注册到PMS

    unregisterDexModule(pluginPackageName, version);

}



//执行全量编译

@Override

public void dexOptSpeed(String pluginPackageName, int version) {

    //step1:如果没有初始化则初始化

    maybeInit();

    //step2:将apk门路进行注册到PMS

    registerDexModule(pluginPackageName, version);

    //step3:应用binder触发全量编译

    dexOpt(COMPILE_FILTER_SPEED, pluginPackageName, version);

    //step4:将apk门路反注册到PMS

    unregisterDexModule(pluginPackageName, version);

}

实现

 /**

 * Try To Init (Build Base env)

 */

private void maybeInit() {

    if (mContext == null || mPmBinder != null) {

        return;

    }



    PackageManager packageManager = mContext.getPackageManager();



    Field mPmField = safeGetField(packageManager, "mPM");

    if (mPmField == null) {

        return;

    }



    mPmObj = safeGetValue(mPmField, packageManager);

    if (!(mPmObj instanceof IInterface)) {

        return;

    }



    IInterface mPmInterface = (IInterface) mPmObj;

    IBinder binder = mPmInterface.asBinder();

    if (binder != null) {

        mPmBinder = binder;

    }

}



 /**

 * DexOpt (Add Retry Function)

 */

private void dexOpt(String compileFilter, String pluginPackageName, int version) {

    String tempFilePath = PluginDirHelper.getTempSourceFile(pluginPackageName, version);

    String tempCacheDirPath = PluginDirHelper.getTempDalvikCacheDir(pluginPackageName, version);

    String tempOatDexFilePath = tempCacheDirPath + File.separator + PluginDirHelper.getOatFileName(tempFilePath);

    File tempOatDexFile = new File(tempOatDexFilePath);



    for (int retry = 1; retry <= MAX_RETRY_COUNT; retry++) {

        execCmd(buildDexOptArgs(compileFilter), null);

        if (tempOatDexFile.exists()) {

            break;

        }

    }

}



 /**

 * Register DexModule(dex path) To PMS

 */

private void registerDexModule(String pluginPackageName, int version) {

    if (pluginPackageName == null || mContext == null) {

        return;

    }



    String originFilePath = PluginDirHelper.getSourceFile(pluginPackageName, version);

    String tempFilePath = PluginDirHelper.getTempSourceFile(pluginPackageName, version);

    safeCopyFile(originFilePath, tempFilePath);



    String loadingPackageName = mContext.getPackageName();

    String loaderIsa = getCurrentInstructionSet();

    notifyDexLoad(loadingPackageName, tempFilePath, loaderIsa);

}



 /**

 * Register DexModule(dex path) To PMS By Binder

 */

private void notifyDexLoad(String loadingPackageName, String dexPath, String loaderIsa) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {

        //deal android 11\12

        realNotifyDexLoadForR(loadingPackageName, dexPath, loaderIsa);

    } else {

        //deal android 10

        realNotifyDexLoad(loadingPackageName, dexPath, loaderIsa);

    }

}



 /**

 * Register DexModule(dex path) To PMS By Binder for R+

 */

private void realNotifyDexLoadForR(String loadingPackageName, String dexPath, String loaderIsa) {

    if (mPmObj == null || loadingPackageName == null || dexPath == null || loaderIsa == null) {

        return;

    }

    Map<String, String> maps = Collections.singletonMap(dexPath, "PCL[]");

    safeInvokeMethod(mPmObj, "notifyDexLoad",

            new Object[]{loadingPackageName, maps, loaderIsa},

            new Class[]{String.class, Map.class, String.class});

}



 /**

 * Register DexModule(dex path) To PMS By Binder for Q

 */

private void realNotifyDexLoad(String loadingPackageName, String dexPath, String loaderIsa) {

    if (mPmObj == null || loadingPackageName == null || dexPath == null || loaderIsa == null) {

        return;

    }

    List<String> classLoadersNames = Collections.singletonList("dalvik.system.DexClassLoader");

    List<String> classPaths = Collections.singletonList(dexPath);

    safeInvokeMethod(mPmObj, "notifyDexLoad",

            new Object[]{loadingPackageName, classLoadersNames, classPaths, loaderIsa},

            new Class[]{String.class, List.class, List.class, String.class});

}



 /**

 * UnRegister DexModule(dex path) To PMS

 */

private void unregisterDexModule(String pluginPackageName, int version) {

    if (pluginPackageName == null || mContext == null) {

        return;

    }



    String originDir = PluginDirHelper.getSourceDir(pluginPackageName, version);

    String tempDir = PluginDirHelper.getTempSourceDir(pluginPackageName, version);

    safeCopyDir(tempDir, originDir);



    String tempFilePath = PluginDirHelper.getTempSourceFile(pluginPackageName, version);

    safeDelFile(tempFilePath);



    reconcileSecondaryDexFiles();

}



 /**

 * Real UnRegister DexModule(dex path) To PMS (By Binder)

 */

private void reconcileSecondaryDexFiles() {

    execCmd(buildReconcileSecondaryDexFilesArgs(), null);

}



 /**

 * Process CMD (By Binder)(Have system permissions)

 */

private void execCmd(String[] args, Callback callback) {

    Parcel data = Parcel.obtain();

    Parcel reply = Parcel.obtain();

    data.writeFileDescriptor(FileDescriptor.in);

    data.writeFileDescriptor(FileDescriptor.out);

    data.writeFileDescriptor(FileDescriptor.err);

    data.writeStringArray(args);

    data.writeStrongBinder(null);



    ResultReceiver resultReceiver = new ResultReceiverCallbackWrapper(callback);

    resultReceiver.writeToParcel(data, 0);



    try {

        mPmBinder.transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);

        reply.readException();

    } catch (Throwable e) {

        //Report info

    } finally {

        data.recycle();

        reply.recycle();

    }

}



 /**

 * Build dexOpt args

 *

 *  @param compileFilter compile filter

 *  @return cmd args

 */

private String[] buildDexOptArgs(String compileFilter) {

    return buildArgs("compile", "-m", compileFilter, "-f", "--secondary-dex",

            mContext == null ? "" : mContext.getPackageName());

}



 /**

 * Build ReconcileSecondaryDexFiles Args

 *

 *  @return cmd args

 */

private String[] buildReconcileSecondaryDexFilesArgs() {

    return buildArgs("reconcile-secondary-dex-files", mContext == null ? "" : mContext.getPackageName());

}



 /**

 * Get the InstructionSet through reflection

 */

private String getCurrentInstructionSet() {

    String currentInstructionSet;

    try {

        Class vmRuntimeClazz = Class.forName("dalvik.system.VMRuntime");

        currentInstructionSet = (String) MethodUtils.invokeStaticMethod(vmRuntimeClazz,

                "getCurrentInstructionSet");

    } catch (Throwable e) {

        currentInstructionSet = "arm64";

    }

    return currentInstructionSet;

}

验证

Android 10+ dex2oat计划兼容状况

上面是针对本计划兼容性验证的后果:

指标版本 零碎版本 手机品牌 Register Dex Module Dex Opt UnRegister Dex Module 手机型号
Target29 Android 10 Vivo – Yes – Yes – Yes Vivo IQOO
Target29 Android 10 Oppo – Yes – Yes – Yes Oppo R15
Target29 Android 10 MI – Yes – Yes – Yes MI 8
Target29 Android 10 华为 – Yes – Yes – Yes 华为 nova 7
Target29 Android 11 Vivo – Yes – Yes – Yes Vivo V20
Target29 Android 11 Oppo – Yes – Yes – Yes Oppo PDPM00(Oppo Android 11 对Rom进行了批改,目前暂不反对)
Target29 Android 11 MI – Yes – Yes – Yes MI M2011K2C
Target29 Android 11 华为 – Yes – Yes – Yes 无此机器
Target29 Android 12 Piexl – Yes – Yes – Yes 本地真机
Target30 Android 10 Vivo – Yes – Yes – Yes Vivo S1
Target30 Android 10 Oppo – Yes – Yes – Yes Oppo Find X
Target30 Android 10 MI – Yes – Yes – Yes MI 8
Target30 Android 10 华为 – Yes – Yes – Yes 华为 P20
Target30 Android 11 Vivo – Yes – Yes – Yes Vivo V2046A
Target30 Android 11 Oppo – Yes – Yes – Yes Oppo PDPM00(Oppo Android 11 对Rom进行了批改,目前暂不反对)
Target30 Android 11 MI – Yes – Yes – Yes MI M2011K2C
Target30 Android 11 华为 – Yes – Yes – Yes 无此机器
Target30 Android 12 Piexl – Yes – Yes – Yes 本地真机

目前来看,对于手机品牌来说,该计划均能够兼容,仅Oppo且Android 11的机器上,因为对Rom进行了批改限度,导致此款机器不兼容。

兼容成果还算良好。

Android 10+ 优化前后Dex加载速度比照

上面针对高中低端的机器上,验证下优化前后Dex加载速度的差别:

机器性能 机器型号 包大小 优化前均匀耗时 优化后均匀耗时 缩小耗时占总耗时百分比
低端机 Piexl 2 1.9m 269.5ms 12ms 95.5%
中端机 Vivo S1 1.9m 159ms 8.8ms 94%
高端机 MI 8 1.9m 48.3ms 6.5ms 86%

对于Dex加载耗时的统计,是采纳统计首次new ClassLoaderDex加载的耗时。

Dex加载耗时同包大小属于正相干,包越大,加载耗时越多;同机器性能属于负相关,机器性能越好,加载耗时越少。

通过上述数据能够看出,优化前后耗时差距还是非常明显的,机器性能越差优化越显著。

Dex加载速度优化显著。

Android 10+ 优化前后场景运行耗时比照

上面针对高中低端的机器上,验证下优化前后场景运行速度的差别:

机器性能 机器型号 优化前均匀耗时 优化后均匀耗时 缩小耗时占总耗时百分比
低端机 Piexl 2 45ms 36ms 20%
中端机 Vivo S1 36.75ms 31.23ms 13.6%
高端机 MI 8 13ms 11.5ms 11.5%

对于场景运行耗时的统计,是采纳对场景启动前后打点,而后计算时间差。

因为非全量编译对运行速度影响较小,上述数据为未优化同全量编译优化的比照数据。

场景耗时场景复杂度属于正相干,场景复杂度越高,场景耗时越多;同机器性能属于负相关,机器性能越好,场景耗时越少。

通过上述数据能够看出,优化后对运行速度还是有质的晋升的,且会随场景复杂度的晋升,带来更大的晋升。

总结

最终,通过假借零碎之手来触发dex2oat的形式,绕过targetSdkVersion>=29Android10+上的限度,成果较为显著,dex加载速度晋升80%以上,场景运行速度晋升11%以上。

对于字节终端技术团队

字节跳动终端技术团队(Client Infrastructure)是大前端根底技术的全球化研发团队(别离在北京、上海、杭州、深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,晋升公司全产品线的性能、稳定性和工程效率;反对的产品包含但不限于抖音、今日头条、西瓜视频、飞书、懂车帝等,在挪动端、Web、Desktop等各终端都有深入研究。

就是当初!客户端/前端/服务端/端智能算法/测试开发 面向寰球范畴招聘!一起来用技术扭转世界,感兴趣请分割[email protected],邮件主题简历-姓名-求职意向-冀望城市-电话。

字节跳动利用开发套件MARS是字节跳动终端技术团队过来九年在抖音、今日头条、西瓜视频、飞书、懂车帝等 App 的研发实际成绩,面向挪动研发、前端开发、QA、 运维、产品经理、项目经理以及经营角色,提供一站式整体研发解决方案,助力企业研发模式降级,升高企业研发综合老本。


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Android性能优化之Android-10-dex2oat实践

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址