背景
之前遇到过一种ResourceNotFoundException,是因为在WebView初始化的时候,AssetManager被重新创建了,所有被添加的插件APK路径都被清空了,所以导致资源找不到。
而这次遇到了两个问题,都是资源ID可以找到,而Context无法找到ID对应的资源
- WindowManagerService.applyAnimationLocked
- Activity.setContentView
以及在7.0以上Asset中的资源可以找到,而8.0系统的Asset资源无法找到
- AssetManager.open FileNotFoundException
WindowManagerService.applyAnimationLocked
当Activity被finish的时候,会出现Crash,而问题日志如下:
11-30 13:11:31.701 1250-1338/? A/WindowManager: Unhandled exception while laying out windows
android.content.res.Resources$NotFoundException: Resource ID #0x7a04001d
at android.content.res.Resources.getValue(Resources.java:1375)
at android.content.res.Resources.loadXmlResourceParser(Resources.java:2894)
at android.content.res.Resources.getAnimation(Resources.java:1191)
at android.view.animation.AnimationUtils.loadAnimation(AnimationUtils.java:73)
at com.android.server.wm.OppoAppTransition.loadAnimation(OppoAppTransition.java:192)
at com.android.server.wm.OppoAppTransition.createOppoCustomAnimLocked(OppoAppTransition.java:376)
at com.android.server.wm.OppoAppTransition.loadAnimation(OppoAppTransition.java:242)
at com.android.server.wm.WindowManagerService.applyAnimationLocked(WindowManagerService.java:3804)
at com.android.server.wm.WindowManagerService.setTokenVisibilityLocked(WindowManagerService.java:4898)
at com.android.server.wm.WindowManagerService.handleAppTransitionReadyLocked(WindowManagerService.java:9902)
at com.android.server.wm.WindowManagerService.performLayoutAndPlaceSurfacesLockedInner(WindowManagerService.java:10669)
at com.android.server.wm.WindowManagerService.performLayoutAndPlaceSurfacesLockedLoop(WindowManagerService.java:9461)
at com.android.server.wm.WindowManagerService.performLayoutAndPlaceSurfacesLocked(WindowManagerService.java:9403)
at com.android.server.wm.WindowManagerService.access$400(WindowManagerService.java:203)
at com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:8282)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:179)
at android.os.HandlerThread.run(HandlerThread.java:61)
跟踪问题
-
通过Android Studio解开插件的APK确认:
- 资源的类型
- 资源的ID
-
根据ID找到对应的资源名称确认:
- 插件的APK中是存在这个资源的
- AAPT将ID和资源名都打进了
resources.arsc
中。
-
搜索资源的引用:
- 发现在Activity的Theme中的
activityOpenEnterAnimation
中使用了该动画
- 发现在Activity的Theme中的
将该资源替换之后,发现还是报同样的错误
-
最后发现在Activity结束的时候,会在
overridePendingTransition
中使用该资源,导致的Crash
原因
因为在ActivityManagerService带过去的PackageName是主包的PackageName,而不是插件的,所以在主包的resource.arsc
中找不到对应的插件资源ID,所以导致的Crash。
解决方案
将使用到的资源,放置到主包即可
Activity.setContentView
当启动Activity的时候,发生Crash,日志如下:
2018-12-05 19:45:14.068 16411-16411/? E/TAG: Crash
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.exapmle.test/com.example.test.TestActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x7f0300f9
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2855)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2930)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1619)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:171)
at android.app.ActivityThread.main(ActivityThread.java:6684)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
Caused by: android.content.res.Resources$NotFoundException: Resource ID #0x790300f9
at android.content.res.ResourcesImpl.getValue(ResourcesImpl.java:211)
at android.content.res.MiuiResourcesImpl.getValue(MiuiResourcesImpl.java:91)
at android.content.res.Resources.loadXmlResourceParser(Resources.java:2158)
at android.content.res.Resources.getLayout(Resources.java:1156)
at android.view.LayoutInflater.inflate(LayoutInflater.java:425)
at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:418)
at android.app.Activity.setContentView(Activity.java:2713)
问题追踪
- 当Activity启动的时候,调用setContentView
- 在
setContentView
中创建PhoneWindow
对象,在PhoneWindow
对象中,会调用LayoutInflater.inflate
来解析layout文件 - 在
LayoutInflater.inflate
函数中会通过getContext().getResources().getLayout()
来获取XmlResourceParser
对象
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
解决方案
只需要重写Activity中的getResources(),返回添加了路径的Resources对象即可,从添加了路径的Resources对象中获取layout文件就可以找到这个文件了。
AssetManager.open
在项目后期,发现了在Androidi 系统7.0的手机上可以找到Asset资源,而8.0手机无法找到Asset资源的问题。由于插件资源路径是通过Resource中的AssetManager反射调用addAssetPath
函数加入插件APK路径直接注入的,所以如果AssetManager不对,则无法获取。
打印日志后发现:
- 8.0版本日志:
Activity的Resource与注入的Application的Resource对象是同一个,但是从Activity中直接获取的AssetManager与Application、Context.getResource中的不是同一个。
2018-12-06 19:34:58.865 26210-26954/? E/GLSurfaceView: Context context:com.example.TestActivity@72e38ee
2018-12-06 19:34:58.865 26210-26954/? E/GLSurfaceView: Context Resource:android.content.res.MiuiResources@47759b2 ApplicationResource:android.content.res.MiuiResources@47759b2
2018-12-06 19:34:58.865 26210-26954/? E/GLSurfaceView:
// Activity.getAsset()
Activity Asset:android.content.res.AssetManager@5d19d03
// Activity.getResource().getAsset()
ContextResource Asset:android.content.res.AssetManager@b68f480
// Application.getResource().getAsset()
Application Asset:android.content.res.AssetManager@b68f480
- 7.x版本日志:
可以发现,7.x的手机上获取到的AssetManager都是同一个,所以不会出现FileNotFound的问题。
2018-12-06 19:58:22.771 7971-9450/? E/GLSurfaceView: Context context:com.example.TestActivity@17d6008
2018-12-06 19:58:22.771 7971-9450/? E/GLSurfaceView: Context Resource:android.content.res.HwResources@b16ed87 ApplicationResource:android.content.res.HwResources@b16ed87
2018-12-06 19:58:22.771 7971-9450/? E/GLSurfaceView:
// Activity.getAsset()
Activity Asset:android.content.res.AssetManager@28c74b4
// Activity.getResource().getAsset()
ContextResource Asset:android.content.res.AssetManager@28c74b4
// Application.getResource().getAsset()
Application Asset:android.content.res.AssetManager@28c74b4
解决方案
只需要将Activity中获取getResources()
和getAsset()
重写即可,将对应的Resources以及AssetManager返回正确,就不会出现FileNotFoundException了。