前言
最近在维护Neptune框架的时候,遇到了一个问题,这里简单的记录下问题原因和解决方案。
Neptune
这是爱奇艺开源的两个插件化框架之一,一个是Neptune,一个是Qigsaw。
Neptune is a flexible, powerful and lightweight plugin framework for Android.
Neptune早期借鉴了百度的插件化框架,后续借鉴了滴滴推出的插件化框架方案VirtualAPK,慢慢发展成为一套较为稳定的方案。
目前服务于爱奇艺和随刻两个App中,爱奇艺主App目前有25+插件,随刻有10+。
问题
[entrypoint_utils-inl.h:97] Inlined method resolution crossed dex file boundary: from java.lang.String com.xxx.xxx.xxx.a(android.content.Context, org.json.JSONObject) in /data/user/0/com.qiyi.video/app_pluginapp/...
分析
这是一个跨Dex调用的常见问题:
Google在Android P中添加了新的检测项,对国内大多数应用造成了严重影响:在调用resolve inline method时,如果检测到caller与callee处于不同的dex file,会主动发起abort(inline不允许跨dex文件),导致应用出现闪退等异常问题。
art/runtime/entrypoints/entrypoint_utils-inl.h
让我们看看相关的源码:
namespace art {
inline ArtMethod* GetResolvedMethod(ArtMethod* outer_method,
const CodeInfo& code_info,
const BitTableRange<InlineInfo>& inline_infos)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(!outer_method->IsObsolete());
// This method is being used by artQuickResolutionTrampoline, before it sets up
// the passed parameters in a GC friendly way. Therefore we must never be
// suspended while executing it.
ScopedAssertNoThreadSuspension sants(__FUNCTION__);
{
InlineInfo inline_info = inline_infos.back();
if (inline_info.EncodesArtMethod()) {
return inline_info.GetArtMethod();
}
uint32_t method_index = code_info.GetMethodIndexOf(inline_info);
if (inline_info.GetDexPc() == static_cast<uint32_t>(-1)) {
// "charAt" special case. It is the only non-leaf method we inline across dex files.
ArtMethod* inlined_method = jni::DecodeArtMethod(WellKnownClasses::java_lang_String_charAt);
DCHECK_EQ(inlined_method->GetDexMethodIndex(), method_index);
return inlined_method;
}
}
// Find which method did the call in the inlining hierarchy.
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
ArtMethod* method = outer_method;
for (InlineInfo inline_info : inline_infos) {
DCHECK(!inline_info.EncodesArtMethod());
DCHECK_NE(inline_info.GetDexPc(), static_cast<uint32_t>(-1));
uint32_t method_index = code_info.GetMethodIndexOf(inline_info);
ArtMethod* inlined_method = class_linker->LookupResolvedMethod(method_index,
method->GetDexCache(),
method->GetClassLoader());
if (UNLIKELY(inlined_method == nullptr)) {
LOG(FATAL) << "Could not find an inlined method from an .oat file: "
<< method->GetDexFile()->PrettyMethod(method_index) << " . "
<< "This must be due to duplicate classes or playing wrongly with class loaders";
UNREACHABLE();
}
DCHECK(!inlined_method->IsRuntimeMethod());
if (UNLIKELY(inlined_method->GetDexFile() != method->GetDexFile())) {
// TODO: We could permit inlining within a multi-dex oat file and the boot image,
// even going back from boot image methods to the same oat file. However, this is
// not currently implemented in the compiler. Therefore crossing dex file boundary
// indicates that the inlined definition is not the same as the one used at runtime.
bool target_sdk_at_least_p =
IsSdkVersionSetAndAtLeast(Runtime::Current()->GetTargetSdkVersion(), SdkVersion::kP);
// 这里便是抛异常的地方
LOG(target_sdk_at_least_p ? FATAL : WARNING)
<< "Inlined method resolution crossed dex file boundary: from "
<< method->PrettyMethod()
<< " in " << method->GetDexFile()->GetLocation() << "/"
<< static_cast<const void*>(method->GetDexFile())
<< &#