Android性能优化-verifyclass

1 背景

同事反映说,京东电器界面打开加载慢,我就趁机抓了下trace分析一下这个界面。 

 

 通过trace,我们可以看到打卡过程主要是下面三个部分耗时较多,第一部分是打卡dex文件(注意这是一个二级页面,理论上不需要加载dex文件,难道是插件化机制?),第二部分里面很多verifyclass,第三部分是加载x5webview。

1.1 插件加载

我们看下第一部分,OpenDexFilesFromOat(/data/app/~~ls6d4SRFl1SZ2RFZBY1KCA==/com.jingdong.app.mall-myJ8jZF4jcpYW2rLgrWQNA==/lib/arm64/libcom.jd.lib.babel.so)

 

 看到后缀,不禁想问,这个是加载的so? 不过,咋可能呢,障眼法吧,后缀名是so,但是其实是apk吧?

我们将京东的apk拖出来,反编译看下。

adb shell pm path com.jingdong.app.mall 

package:/data/app/~~ls6d4SRFl1SZ2RFZBY1KCA==/com.jingdong.app.mall-myJ8jZF4jcpYW2rLgrWQNA==/base.apk

adb pull /data/app/~~ls6d4SRFl1SZ2RFZBY1KCA==/com.jingdong.app.mall-myJ8jZF4jcpYW2rLgrWQNA==/base.apk  jingdong.apk

我们找到libcom.jd.lib.babel.so这个文件,用010editor打开发现需要用dex脚本解析。

 

 继续用jadx反编译一下,发现确实是个插件apk,并且资源id是0x58开头 

 

京东插件加载用了什么框架呢?通过反编译京东的apk和搜索,发现他们是用了自研的框架叫aura,这个框架四大组件的代理,资源的处理,系统接口的hook,由于代码混淆了我们就先不研究了,不过从截图可以大致认为,这个框架自定义了classloader。

 

 因为用了自定义的classloader,所以这个插件只能应用自己加载,系统是没法替应用去做的。

1.2 verifyclass

 

通过trace可以看到,插件里class加载的时候,都需要去verifyclass,那什么是verifyclass呢?我们咨询下最近比较火的chatgpt老师: 

 

 

在 ART 虚拟机中,VerifyClass 会在编译期和加载期进行检查,以确保类文件的完整性和正确性。如果类文件未通过验证,ART 虚拟机将不会加载该类文件,并向操作系统报告错误。

1.2.1 为啥加载主apk非插件的文件,不会发生verifyclass呢?

android/art/runtime/oat_file_manager.cc

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
    const char* dex_location,
    jobject class_loader,
    jobjectArray dex_elements,
    const OatFile** out_oat_file,
    std::vector<std::string>* error_msgs) {
......
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::unique_ptr<ClassLoaderContext> context(
      ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements));

  // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
  // directly with DexFile APIs instead of using class loaders.
  if (class_loader == nullptr) {
    LOG(WARNING) << "Opening an oat file without a class loader. "
                 << "Are you using the deprecated DexFile APIs?";
  } else if (context != nullptr) {
    // 京东主apk加载的时候走这里
    OatFileAssistant oat_file_assistant(dex_location,
                                        kRuntimeISA,
                                        context.get(),
                                        runtime->GetOatFilesExecutable(),
                                        only_use_system_oat_files_);
  }
.......
}

京东主apk加载的时候可以走到OatFileAssistant相关逻辑,加载oat文件,但是插件无法走到这里,因为

std::unique_ptr<ClassLoaderContext> context(
      ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements));

这里获取的context为null。

android/art/runtime/class_loader_context.cc
std::unique_ptr<ClassLoaderContext> ClassLoaderContext::CreateContextForClassLoader(
    jobject class_loader,
    jobjectArray dex_elements) {
  ScopedTrace trace(__FUNCTION__);

  if (class_loader == nullptr) {
    return nullptr;
  }
  ScopedObjectAccess soa(Thread::Current());
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::ClassLoader> h_class_loader =
      hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
  Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
      hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(dex_elements));
  std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext(/*owns_the_dex_files=*/ false));
  if (!result->CreateInfoFromClassLoader(
          soa, h_class_loader, h_dex_elements, nullptr, /*is_shared_library=*/ false)) {
    return nullptr;
  }
  return result;
}



bool ClassLoaderContext::CreateInfoFromClassLoader(
      ScopedObjectAccessAlreadyRunnable& soa,
      Handle<mirror::ClassLoader> class_loader,
      Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
      ClassLoaderInfo* child_info,
      bool is_shared_library)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  if (ClassLinker::IsBootClassLoader(soa, class_loader.Get())) {
    // Nothing to do for the boot class loader as we don't add its dex files to the context.
    return true;
  }

  ClassLoaderContext::ClassLoaderType type;
  if (IsPathOrDexClassLoader(soa, class_loader)) {
    type = kPathClassLoader;
  } else if (IsDelegateLastClassLoader(soa, class_loader)) {
    type = kDelegateLastClassLoader;
  } else if (IsInMemoryDexClassLoader(soa, class_loader)) {
    type = kInMemoryDexClassLoader;
  } else {
    //京东插件加载使用了自定义的classloader,会走到这里,最后无法加载oat文件。
    LOG(WARNING) << "Unsupported class loader";
    return false;
  }

逻辑比较简单,最关键是是:京东插件加载使用了自定义的classloader,会走到return false分支,最后无法加载oat文件,最后回到dex文件加载,从而进行verifyclass的执行。 主apk可以使用oat文件加载,oat文件中class和method已经校验过,写入到了oat文件中,所以加载的时候就不需要执行verifyclass了。 如果主apk没有oat文件呢?他加载dex文件也会verifyclass的。

1.2.2 verifyclass流程

verifyclass流程是什么样子的

1.2.3 如何规避verifyclass呢?

 

(1)插件classloader使用系统的DexClassloader,而非自定义的classloader

(2)hook runtime.cc中的verify_, 让verify_ = verifier::VerifyMode::kNone;

// If kNone, verification is disabled. kEnable by default.
verifier::VerifyMode verify_;
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值