项目描述

先简单描述一下遇到的问题。

项目比较庞大是以组件化的形式进行构建的,记录崩溃日志是由专门的一个组件去做,这里且叫它crash吧。而crash的核心逻辑如下:

//伪代码
public class MyCrash implements UncaughtExceptionHandler {
    
    private static UncaughtExceptionHandler defaultUncaughtExceptionHandler;
    
    public static void init(String path) {
        ...
        //获取到默认的ExceptionHandler
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置自己的ExceptionHandler
        Thread.setDefaultUncaughtExceptionHandler(new MyCrash());
    }
    @Override    
    public void uncaughtException(Thread t, Throwable e) {
        try {
            //日志记录逻辑
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //回调默认的ExceptionHandler
            if (defaultUncaughtExceptionHandler != null) {
                defaultUncaughtExceptionHandler.uncaughtException(t, e);
            }
        }
    }
}

然后该组件利用ContentProvider进行初始化,大概如下所示:

class MyContentProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        //伪代码,初始化。
        MyCrash.init("")
        return true
    }
    ......
}

问题排查

收到反馈说,部分手机不会记录崩溃日志。这就是很奇怪了,因为理论上来说,只要设置了ExceptionHandle都会捕获到传过来的异常呀。难道是没有设置到ExceptionHandle?

后经过断点排查,不会上传崩溃日志的手机,在运行阶段Thread持有的defaultUncaughtExceptionHandler,不是我们设置的MyCrash,而是一个三方组件设置他们自己的CrashExceptionHandle且没有回调我们的MyCrash,而他们也是利用ContentProvider初始化的。

所以这时候就牵扯到ContentProvider的初始化流程了,具体在ActivityThread中,下面放一下伪代码。

ActivityThread
private void handleBindApplication(AppBindData data) {
    ...
    //1.获取到Application
    app = data.info.makeApplication(data.restrictedBackupMode, null);
    ...
    //2.初始化ContentProvider
    installContentProviders(app, data.providers);
    ...
    //3.调用Application的onCreate
    mInstrumentation.callApplicationOnCreate(app);
    ...
}

整体顺序是获取Application->初始化ContentProvider->调用Application#onCreate。也就是说ContentProvider的初始化是要在Application之前的。其中ContentProvider的初始化就是循环便利储存ContentProvider的集合调用它的onCreate方法。

private void installContentProviders(Context context, List<ProviderInfo> providers) {
    ...
    for (ProviderInfo cpi : providers) {
       //这里会获取到ContentProvider,最终会调用到ContentProvider的attachInfo,在attachInfo中调用了onCreate
    }
    ...
}

那ContentProvider的初始化顺序就很清晰明了了。而我们的问题是部分手机记录不了,也就是说ContentProvider在集合中的顺序是不可保证的,这样才能解释部分手机有问题,部分手机正常,那这个顺序是怎么来的呢?

起初我想到顺序是不是和合并后的AndroidManifest.xml文件里面注册的ContentProvider节点顺序有关系,随后就将该想法排除,因为生成的APK是一样的,所以AndroidManifest.xml文件里面注册的ContentProvider节点顺序是一定的。而有问题的是部分手机,所以一定不是这里的问题,没办法只能继续查看源码,看看ContentProvider究竟是如何读到内存中的。

经过一番查找,发现ContentProvider的集合是从ComponentResolver中private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();取得。由于涉及到得源码比较多,这里就不一一列举了,下面放上源码大致的调用链

ActivityManagerService#attachApplicationLocked -> ActivityManagerService#generateApplicationProvidersLocked -> PackageManagerService#queryContentProviders -> ComponentResolver#queryProviders -> ActivityThread#bindApplication -> ActivityThread#handleBindApplication

我们注意一下重点,ContentProvider得信息是被储存在ArrayMap中得,而ArrayMap肯定是无法保证顺序的呀。不了解ArrayMap的下面我简单介绍一下,

ArrayMap是Google专门提供的key-value映射集合,主要为了解决HashMap浪费控件的问题,在小数据量上性能不错,但是它底层是用数组来着,利用二分查找,而二分查找的顺序是根据hash值来的,默认的hash值是通过System.identityHashCode(key)来进行获取的,而这玩意儿又和对象的地址有关系。所以不同的手机顺序肯定就不一样了。

到这里,问题就分析结束了,最终解决方案是,去除了利用ContentProvider的初始化机制,改在Application中直接进行初始化。

总结

上面的问题虽然解决了,但是利用ContentProvider解耦初始化组件真的好吗?直观的有以下几个问题。

  1. 内存泄漏。初始化完成之后ContentProvider会被系统直接持有,无用,但也不删除
  2. 无法保证组件初始化的顺序。这个就是我们上面分析的问题
  3. 会拉长启动时间。上面我们看到了,ContentProvider循环初始化完成之后,才会进行Application#onCreate的调用,所以对于一些非必要在主线程初始化的组件,这无疑会拉长启动时间。

不过如果非要去解耦组件初始化,可以看一看Jetpack startup组件,它也是利用ContentProvider去初始化的,但是它利用AndroidManifest.xml合并的功能最终会合并成一个ContentProvider,而且内存维持有集合可以保证组件初始化顺序。

总之一句话,不要滥用ContentProvider仅仅去做一个初始化。

到此这篇关于Android利用ContentProvider初始化组件踩坑的文章就介绍到这了,更多相关Android ContentProvider初始化组件内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Android利用ContentProvider初始化组件的踩坑记录的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. three.js模拟实现太阳系行星体系功能

    这篇文章主要介绍了three.js模拟实现太阳系行星体系功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

  3. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  4. HTML5页面无缝闪开的问题及解决方案

    这篇文章主要介绍了HTML5页面无缝闪开方案,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  6. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  7. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  8. ios – 为什么,将nil作为参数从Objc C发送到swift类初始化器,用新对象替换nil参数

    除非属性本身被声明为nonnull:

  9. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  10. ios – 在Swift中对MKCircle进行子类化

    我想通过添加另一个String属性来继承MKCircle,我们称之为“代码”.这个属性不是可选的和常量的,所以我必须从初始化器设置它,对吧?有没有办法定义一个单一的便利初始化器,在这种情况下需要3个参数?本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部