Tencent / Shadow

零反射全动态Android插件框架
BSD 3-Clause "New" or "Revised" License
7.47k stars 1.31k forks source link

部分Android14系统上的崩溃问题:lateinit property _pluginClassLoader has not been initialized #1338

Open xiboliya opened 4 months ago

xiboliya commented 4 months ago

我们发现有部分荣耀手机升级到Android14系统后,跳转插件页面时会必现崩溃,卸载重新安装APP也不行。 这个问题是我们崩溃后台收集线上用户的崩溃,我们用Android14的荣耀手机没有复现,当然与用户崩溃的机型不同,所以这可能是某些机型上才会出现的问题。 崩溃堆栈如下: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demo/com.demo.pluginruntime.PluginSingleTaskActivity1}: kotlin.UninitializedPropertyAccessException: lateinit property _pluginClassLoader has not been initialized at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4610) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4806) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:118) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:153) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:104) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:3067) at android.os.Handler.dispatchMessage(Handler.java:117) at android.os.Looper.loopOnce(Looper.java:210) at android.os.Looper.loop(Looper.java:302) at android.app.ActivityThread.main(ActivityThread.java:9652) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:601) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1062) Caused by: kotlin.UninitializedPropertyAccessException: lateinit property _pluginClassLoader has not been initialized at com.tencent.shadow.core.loader.delegates.ShadowDelegate.getMPluginClassLoader(ShadowDelegate.kt:60) at com.tencent.shadow.core.loader.delegates.ShadowActivityDelegate.getClassLoader(ShadowActivityDelegate.kt:288) at com.tencent.shadow.core.runtime.container.GeneratedPluginContainerActivity.getClassLoader(GeneratedPluginContainerActivity.java:124) at com.tencent.shadow.core.runtime.container.PluginContainerActivity.getClassLoader(PluginContainerActivity.java:38) at android.view.LayoutInflater.checkPluginContext(LayoutInflater.java:321) at android.view.LayoutInflater.(LayoutInflater.java:291) at com.android.internal.policy.PhoneLayoutInflater.(PhoneLayoutInflater.java:44) at com.android.internal.policy.HwPhoneLayoutInflater.(HwPhoneLayoutInflater.java:69) at com.android.internal.policy.HwPolicyFactoryImpl.getHwPhoneLayoutInflater(HwPolicyFactoryImpl.java:52) at com.android.internal.policy.HwPolicyFactory.getHwPhoneLayoutInflater(HwPolicyFactory.java:65) at android.app.SystemServiceRegistry$34.createService(SystemServiceRegistry.java:584) at android.app.SystemServiceRegistry$34.createService(SystemServiceRegistry.java:581) at android.app.SystemServiceRegistry$CachedServiceFetcher.getService(SystemServiceRegistry.java:2051) at android.app.SystemServiceRegistry.getSystemService(SystemServiceRegistry.java:1725) at android.app.ContextImpl.getSystemService(ContextImpl.java:2348) at android.view.LayoutInflater.from(LayoutInflater.java:332) at android.view.ContextThemeWrapper.getSystemService(ContextThemeWrapper.java:184) at android.app.Activity.getSystemService(Activity.java:7769) at android.view.LayoutInflater.from(LayoutInflater.java:332) at com.android.internal.policy.PhoneWindow.(PhoneWindow.java:414) at com.android.internal.policy.PhoneWindow.(PhoneWindow.java:477) at com.android.internal.policy.HwPhoneWindow.(HwPhoneWindow.java:106) at com.android.internal.policy.HwPolicyFactoryImpl.getHwPhoneWindow(HwPolicyFactoryImpl.java:42) at com.android.internal.policy.HwPolicyFactory.getHwPhoneWindow(HwPolicyFactory.java:50) at android.app.Activity.attach(Activity.java:8902) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4558) ... 12 more

根据堆栈的初步分析如下: ShadowDelegate中的_pluginClassLoader是通过inject(pluginClassLoader: PluginClassLoader)接口赋值的,而这个接口是在ShadowPluginLoader中的inject(delegate: ShadowDelegate, partKey: String)接口调用的。而此接口中会判断pluginParts是否为null,为null时将直接抛出异常,导致崩溃,而崩溃信息与上述堆栈不同;如果不为null则会调用delegate.inject(pluginParts.classLoader),如果pluginParts.classLoader为null,则又会抛出空指针异常,崩溃信息也与上述堆栈不同。而ShadowPluginLoader中的inject接口是ShadowActivityDelegate中的onCreate接口中调用的,而除非onCreate中没有调用inject接口才会出现异常,但是这种情况感觉是不会存在的。

上述牵涉到的代码片段如下: abstract class ShadowDelegate() { fun inject(pluginClassLoader: PluginClassLoader) { _pluginClassLoader = pluginClassLoader } private lateinit var _pluginClassLoader: PluginClassLoader protected val mPluginClassLoader: PluginClassLoader get() = _pluginClassLoader }

abstract class ShadowPluginLoader(hostAppContext: Context) : DelegateProvider, DI, ContentProviderDelegateProvider { override fun inject(delegate: ShadowDelegate, partKey: String) { mLock.withLock { val pluginParts = mPluginPartsMap[partKey+"-test"] if (pluginParts == null) { throw IllegalStateException("partKey==${partKey}在map中找不到。此时map:${mPluginPartsMap}") } else { delegate.inject(pluginParts.appComponentFactory) delegate.inject(pluginParts.application) delegate.inject(pluginParts.classLoader) delegate.inject(pluginParts.resources) delegate.inject(mComponentManager) } } } }

open class ShadowActivityDelegate(private val mDI: DI) : GeneratedShadowActivityDelegate(), HostActivityDelegate { override fun onCreate(savedInstanceState: Bundle?) { val pluginInitBundle = savedInstanceState ?: mHostActivityDelegator.intent.extras!!

    mCallingActivity = pluginInitBundle.getParcelable(CM_CALLING_ACTIVITY_KEY)
    mBusinessName = pluginInitBundle.getString(CM_BUSINESS_NAME_KEY, "")
    val partKey = pluginInitBundle.getString(CM_PART_KEY)!!
    mPartKey = partKey
    mDI.inject(this, partKey)
    ...
}

}

@shifujun 请问大神有遇到过类似的问题吗?对于此问题,能给一些分析的思路和建议吗?谢谢!

shifujun commented 4 months ago

从经验来说,线上收集的奇奇怪怪的难以理解的崩溃始终存在。这些难以理解的堆栈,简单可以分为两类。

一是难以复现的操作路径,使得一些异步操作执行顺序发生变化,从而暴露了编程错误。比如在子线程初始化变量,在主线程使用变量。子线程总是特别快,所以很少有用户遇到问题。赶上了个别用户的特殊操作,子线程慢了就会暴露问题。不仔细看代码,一时意识不到初始化和调用在两个不同线程,就会觉得堆栈难以理解。

二是超出代码错误范畴的意外。比如灰产在调试篡改代码,但是数据上报没关掉。比如用户突然强行中止APP,多个进程退出时机也没有同步。

分析这种问题,还是要先多关注上报的用户分布情况。用户必须很分散,量又大,才能确认确实存在值得关注的问题。

如果复现频率高,迟迟不能复现,就在启动的关键路径上加log上报。看看这些用户的操作和其他用户有什么区别,尝试本地复现。

你贴的这个堆栈,处于插件启动的关键路径上。这是从shadow上线就没改过的逻辑。所以我看到这种上报是不怎么慌的。即便某个机型的某个系统版本可以稳定复现。大概率也是这个系统错误的魔改了什么奇怪的系统调用。shadow又没有用私有api,那公开api行为如果出错了,要么只能这个系统改bug。要么我们能复现就可以想办法兼容它,动态下发一个逻辑。

zhuqichao commented 4 months ago

这个是定制Rom的问题,堆栈中这行代码:at android.view.LayoutInflater.checkPluginContext(LayoutInflater.java:321) 在Android源码中是不存在的,说明Rom被修改过

xiboliya commented 4 months ago

从经验来说,线上收集的奇奇怪怪的难以理解的崩溃始终存在。这些难以理解的堆栈,简单可以分为两类。

一是难以复现的操作路径,使得一些异步操作执行顺序发生变化,从而暴露了编程错误。比如在子线程初始化变量,在主线程使用变量。子线程总是特别快,所以很少有用户遇到问题。赶上了个别用户的特殊操作,子线程慢了就会暴露问题。不仔细看代码,一时意识不到初始化和调用在两个不同线程,就会觉得堆栈难以理解。

二是超出代码错误范畴的意外。比如灰产在调试篡改代码,但是数据上报没关掉。比如用户突然强行中止APP,多个进程退出时机也没有同步。

分析这种问题,还是要先多关注上报的用户分布情况。用户必须很分散,量又大,才能确认确实存在值得关注的问题。

如果复现频率高,迟迟不能复现,就在启动的关键路径上加log上报。看看这些用户的操作和其他用户有什么区别,尝试本地复现。

你贴的这个堆栈,处于插件启动的关键路径上。这是从shadow上线就没改过的逻辑。所以我看到这种上报是不怎么慌的。即便某个机型的某个系统版本可以稳定复现。大概率也是这个系统错误的魔改了什么奇怪的系统调用。shadow又没有用私有api,那公开api行为如果出错了,要么只能这个系统改bug。要么我们能复现就可以想办法兼容它,动态下发一个逻辑。

感谢提供思路!

xiboliya commented 4 months ago

这个是定制Rom的问题,堆栈中这行代码:at android.view.LayoutInflater.checkPluginContext(LayoutInflater.java:321) 在Android源码中是不存在的,说明Rom被修改过

谢谢提醒,我也查过Android14的源码,在LayoutInflater中的确没有找到checkPluginContext方法,当时感觉非常迷惑。你这么一说,我就明白了,看来是系统被修改过了。感谢!

zhuqichao commented 4 months ago

你知道崩溃的手机型号和系统版本吗,我最近也在看这个问题

xiboliya commented 4 months ago

你知道崩溃的手机型号和系统版本吗,我最近也在看这个问题

目前我们收集到2个型号有此问题:REP-AN00(ROM版本:8.0.0.138CHNC00E138R5P3)、BVL-N49(ROM版本:8.0.0.155SP51C900E110R1P2)。

xiboliya commented 4 months ago

你知道崩溃的手机型号和系统版本吗,我最近也在看这个问题

你们那边收集的手机型号是哪些?你们打算怎么处理,有解决思路吗?

xiboliya commented 4 months ago

从经验来说,线上收集的奇奇怪怪的难以理解的崩溃始终存在。这些难以理解的堆栈,简单可以分为两类。

一是难以复现的操作路径,使得一些异步操作执行顺序发生变化,从而暴露了编程错误。比如在子线程初始化变量,在主线程使用变量。子线程总是特别快,所以很少有用户遇到问题。赶上了个别用户的特殊操作,子线程慢了就会暴露问题。不仔细看代码,一时意识不到初始化和调用在两个不同线程,就会觉得堆栈难以理解。

二是超出代码错误范畴的意外。比如灰产在调试篡改代码,但是数据上报没关掉。比如用户突然强行中止APP,多个进程退出时机也没有同步。

分析这种问题,还是要先多关注上报的用户分布情况。用户必须很分散,量又大,才能确认确实存在值得关注的问题。

如果复现频率高,迟迟不能复现,就在启动的关键路径上加log上报。看看这些用户的操作和其他用户有什么区别,尝试本地复现。

你贴的这个堆栈,处于插件启动的关键路径上。这是从shadow上线就没改过的逻辑。所以我看到这种上报是不怎么慌的。即便某个机型的某个系统版本可以稳定复现。大概率也是这个系统错误的魔改了什么奇怪的系统调用。shadow又没有用私有api,那公开api行为如果出错了,要么只能这个系统改bug。要么我们能复现就可以想办法兼容它,动态下发一个逻辑。

你好,针对这个问题如果要做兼容的话是否可行?可行的话,该怎么修改呢?谢谢!

zhuqichao commented 4 months ago

ShadowActivityDelegate中修改:
override fun getClassLoader(): ClassLoader { return if (mDependenciesInjected) { mPluginClassLoader } else { this::class.java.classLoader } }

xiboliya commented 4 months ago

ShadowActivityDelegate中修改: override fun getClassLoader(): ClassLoader { return if (mDependenciesInjected) { mPluginClassLoader } else { this::class.java.classLoader } }

我给荣耀发了工单,荣耀回复说此问题已经修复,等下一个正式系统版本就会上线。具体发布时间还不清楚。

xiboliya commented 4 months ago

@zhuqichao nkyn08DIAc 荣耀回复了,在八月初的版本中会修复。