背景

  • 目前咱们国内的游戏 SDK 采用了插件化的技术,优点是 SDK 能够经过热更新来完结自更新,缺陷是会遇到各式各样奇奇怪怪的问题,最近就我个人遇到的一些插件化问题来给咱们做一次分享,主要分为两个部分:

    • 排查和处理资源加载不到导致的报错

    • 排查和处理 so 库加载不到导致的报错

  • 在正式进入主题前,咱们需求简单遍及一波插件化的小知识

    • 何为插件化:插件化便是将运用的内容进行拆分,分为了宿主和插件两个概念,浅显点讲,宿主部分便是代码直接打入到 classex.dex 文件,而插件部分是将代码打成一个 apk,然后在运用运转的时分进行动态加载。

    • 插件化的运用场景:

      • 减缩 apk 包体:跟着业务的高速发展,运用的功用也会跟着迭代会变得愈加丰厚,同时也会导致一个问题,便是咱们的 apk 包体会变得很大,下载的等待时间会被拉长,这样会导致下载的转化量变少,这个时分假如运用插件化的技术,那么能够将一些不常用的功用打入到插件 apk 中,当用户运用到这些功用时,再从服务器下载并加载到运用中来,这样既能确保在功用不变的前提下,又能完结 apk 包体的减缩。

      • apk 功用热更新:从最近几年来看,目前用户更新运用的欲望比较低,这样会导致咱们开发完功用,可是上线之后并没有多少人运用,短期内无法发明大的收益,在这种情况下,咱们能够运用插件化的技术,将一些必要的功用列入到宿主中来(发动就会用到的类,例如 Application,LaunchActivity),而将一些非必要性的功用列入到插件中来,这个时分插件 apk 是能够随时更新的,不需求用户点更新和装置,咱们只需求经过服务器下发最新版的插件 apk 即可完结更新,这样就能用户无感知的情况下完结功用的更新。

    • 插件化的完成原理:

      • 插件中的类如何加载:经过自定义一个 ClassLoader 类,并重写 loadClass 办法,当有类加载恳求时,优先从插件的 apk 中找,找不到再从宿主 apk 中找,终究重写 Context 类中的 getClassLoader 办法,换成咱们的自定义的 ClassLoader 目标。

      • 插件中的资源如何加载:经过反射调用 AssetManager 类中的 addAssetPath 办法,将插件的 apk 加载进去,然后创立一个自定义的 Resources 类,当有资源加载恳求时,优先从插件的 apk 找,找不到再从宿主 apk 中找,终究重写 Context 类中的 getResources 办法,换成咱们的自定义的 Resources 目标。

  • 好了,接下来让咱们正式进入主题吧。

资源加载报错问题

  • 近期 Unity 开发人员(简称 CP)给咱们反馈了一个问题,说是调用咱们 SDK 登录的时分呈现了溃散
Process: com.xxx.xxx, PID: 24617
android.view.InflateException: Binary XML file line #4: Binary XML file line #4: Error inflating class <unknown>
Caused by: android.view.InflateException: Binary XML file line #4: Error inflating class <unknown>
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Constructor.newInstance0(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
    at android.view.LayoutInflater.createView(LayoutInflater.java:647)
    at com.android.internal.policy.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:58)
    at android.view.LayoutInflater.onCreateView(LayoutInflater.java:720)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:788)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
    at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
    at xxx.xxx.xxx.LoginView360.initView(LoginView360.java:78)
    at xxx.xxx.xxx.LoginView360.onAttachedToWindow(LoginView360.java:70)
    at android.view.View.dispatchAttachedToWindow(View.java:18347)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3397)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3404)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1761)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
    at android.view.Choreographer.doCallbacks(Choreographer.java:761)
    at android.view.Choreographer.doFrame(Choreographer.java:696)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
    at android.os.Handler.handleCallback(Handler.java:873)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6718)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
 Caused by: android.content.res.Resources$NotFoundException: Drawable (missing name) with resource ID #0x7f560131
 Caused by: android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f560131
    at android.content.res.ResourcesImpl.getResourceName(ResourcesImpl.java:255)
    at android.content.res.ResourcesImpl.loadDrawableForCookie(ResourcesImpl.java:785)
    at android.content.res.ResourcesImpl.loadDrawable(ResourcesImpl.java:631)
    at android.content.res.Resources.loadDrawable(Resources.java:897)
    at android.content.res.TypedArray.getDrawableForDensity(TypedArray.java:955)
    at android.content.res.TypedArray.getDrawable(TypedArray.java:930)
    at android.view.View.<init>(View.java:5010)
    at android.view.ViewGroup.<init>(ViewGroup.java:659)
    at android.widget.RelativeLayout.<init>(RelativeLayout.java:248)
    at android.widget.RelativeLayout.<init>(RelativeLayout.java:244)
    at android.widget.RelativeLayout.<init>(RelativeLayout.java:240)
    at java.lang.reflect.Constructor.newInstance0(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
    at android.view.LayoutInflater.createView(LayoutInflater.java:647)
  • 我看到这个问题榜首的反应是,会不会资源没有打到插件里边去?后面临插件apk进行了反编译,发现并没有这个问题

插件化系列之解决资源加载异常的问题

  • 那会不会获取资源时分用的便是宿主但便是没有用到插件的?让咱们debug一下

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 从这张截图上面,咱们得到一个信息,AssetManager中并没有插件的apk,正常情况下AssetManager应该有三个apk,分别是体系的apk、宿主的apk、插件的apk,这儿唯一少了插件的apk,那么会不会是插件加载失败了呢?

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 此刻我的脑海中突然有一个斗胆的主意,现在让咱们试一下

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 咦?咋这样就能够获取Drawable资源?那愈加证明了插件是加载成功的,所以可能是插件加载失败原因能够排除了。

  • 为什么插件加载成功了,可是终究获取在获取插件资源的时分,为什么刚刚在ResourcesImpl.getResourceName(int resid)办法就没有看到AssetManager目标中有呈现这个插件的apk呢?

插件化系列之解决资源加载异常的问题

Caused by: android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f560131
at android.content.res.ResourcesImpl.getResourceName(ResourcesImpl.java:255)
at android.content.res.ResourcesImpl.loadDrawableForCookie(ResourcesImpl.java:785)
at android.content.res.ResourcesImpl.loadDrawable(ResourcesImpl.java:631)
at android.content.res.Resources.loadDrawable(Resources.java:897)
at android.content.res.TypedArray.getDrawableForDensity(TypedArray.java:955)
at android.content.res.TypedArray.getDrawable(TypedArray.java:930)
at android.view.View.<init>(View.java:5010)
  • 让咱们先看一下仓库所对应的源码完成是什么样的?

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 看完了源码,咱们基本能够捋出来一个完整的流程了:

    1.View调用了TypedArray.getDrawable

    2.TypedArray再调用了Resources.loadDrawable

    3.Resources再去调用了ResourcesImpl.getResourceName

  • 那么问题来了,TypedArray中的Resources目标又是怎么赋值进去的?这个得看一下TypedArray目标是怎么创立的?

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 咱们到这儿暂停一下,先捋一下Activity到Context的继承联系

Activityextends ContextThemeWrapperextends ContextWrapperextends Context

  • ContextWrappergetTheme办法只是做了静态署理,能够先 pass掉,再看一下ContextThemeWrapper类的getTheme办法完成

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 咦?等等,我好像发现了什么东西?先让咱们实验一下

插件化系列之解决资源加载异常的问题

  • 抛异常是契合预期的,可是刚刚明明试过getResources办法是能够的,现在让咱们再实验一下

插件化系列之解决资源加载异常的问题

  • 这样却能够?让咱们看看getResources回来的Resources目标是什么?

插件化系列之解决资源加载异常的问题

  • 回来的是插件的Resources目标,所以没问题是正常的,所以应该是mTheme的问题了,咱们先看一下getTheme办法的源码完成

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • ContextThemeWrapper.getTheme源码的完成仍是比较简单的,便是做了一下mTheme字段的缓存。等一下,缓存?是不是这个导致的呢?

  • 我忽然又有一个斗胆的主意,把缓存清掉再试一下?话不多说,直接上手

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 这个时分mTheme中的AssetManager就有了插件的apk路径,同时运转也正常了,所以问题的源头便是它没有错了。

  • 可是问题来了,为什么在咱们的Demo或者其他游戏没有呈现,偏偏这个游戏接咱们的SDK就呈现了,莫非?

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

public class EvtActivity extends NativeActivity {
......
@Override
public Resources getResources() {
Resources resources;
return (EvtHelper.getPSDK() == null || (resources = EvtHelper.getPSDK().getResources(super.getResources())) == null) ? super.getResources() : resources;
}
......
}
  • 在这儿,咱们能够看到游戏方会判断EvtHelper.getPSDK()不为空才会调用咱们SDK的办法,而EvtHelper.getPSDK()获取的是sPlatformSDK字段,那么这个字段是什么时分赋值的?

插件化系列之解决资源加载异常的问题

  • 咱们能够看到是在EvtHelper.preInitPlatformSDK办法赋值的,那么这个办法又被谁调用了呢?让咱们接着往下看

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 咱们能够看到是在Activity.onCreate办法调用的,那么这样写是否有问题呢?详细可分为两种情况:

    1.假定Activity.getTheme有在Activity.onCreate之前调用:那么就会导致调用ContextThemeWrapper.getTheme的时分,是依据体系的Resources来给mTheme变量赋值,而不是运用插件的Resources来赋值,下次再调用getTheme办法时,由于mTheme字段之前赋值了,所以会复用之前的值,然后回来回去,直接导致调用了getTheme办法每次都是回来榜首次初始化的那个目标。

    2.假定Activity.getTheme没有在Activity.onCreate之前调用:不会存在mTheme字段缓存的问题,所以不会有问题。

  • 上面便是咱们的一些想象,可是实践出真理,让咱们试一试,看看到底是哪个先走?

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public Resources.Theme getTheme() {
return super.getTheme();
}
}

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 咱们能够看到,是先走了super.onCreate办法,再走了getTheme办法,所以是有问题的,契合刚刚的猜想,现在让咱们再验证一下这个猜想

插件化系列之解决资源加载异常的问题

  • 咱们在ContextThemeWrapper类中,将mTheme字段赋值为空

插件化系列之解决资源加载异常的问题

  • 这样 mTheme就从有值变成了空值,这样会从头进行初始化

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

插件化系列之解决资源加载异常的问题

  • 对比之前的,咱们能够看到这儿的AssetManager目标有了插件apk,很好地证明了便是这个mTheme字段缓存引发的问题,那么咱们该如何处理这一问题呢?

  • 追溯问题,根本原因仍是由于getResources办法榜首次调用的时分仍是并非用的插件的Resources目标,所以才会直接导致mTheme字段赋值的时分用的是过错的Resources(非插件的)目标进行初始化,由于做了缓存,所以mTheme只会赋值一次。

  • 处理方式思路大致分为两种:

    1.提醒CP去除EvtHelper类封装,改成直接调用SQwanCore类

    2.将初始化机遇挪动到attachBaseContext办法中,提前初始化EvtHelper类(即调用EvtHelper.preInitPlatformSDK)

  • 终究经过综合考虑,咱们采用了第二种方案,这个资源报错问题就不会再呈现了

未完待续,下一篇:【插件化系列之处理 so 加载异常的问题】