系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
文章目录
- 系列文章目录
- 前言
- 一、Blocked状态
- 1.案例一
- 2.案例二
- 3.案例三
- 二、高低端机区分
- 1.WebView预加载
- 三、磁盘IO耗时
- 1.外置存储卡路径复用
- 2.SD卡路径复用
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、Blocked状态
1.案例一
该案例为应用内主子线程锁抢占,由于应用内主线程下载View展示与子线程中下载缓存刷新锁抢占,部分低端机上系统资源不足,从而产生的ANR。
查看trace文件中主线程堆栈可知,主线程中实例对象被237线程所持有,继而查看237线程堆栈如下:
通过堆栈中transact调用可知,系统资源不足。分析系统相关类调用,可以发现主线程和thread-237同时调用了ContextImp中方法。
主线程中getTheme调用
线程237中ensureExternalDirsExistOrFilter调用:
ensureExternalDirsExistOrFilter调用:
咨询了框架组的同事,得到如下回复
1.调用getPhoneStorageState()和getExternalStorageState()等接口时,
此接口存在I0耗时且容易卡在PKMS\MountService binder返回
2.手机硬件老化时,IO耗时变长,调用此类接口有很大的几率会被卡住
3.插入损坏的SD卡时,调用此类接口有很大的几率会被卡住
由上可知ContextImp中ensureExternalDirsExistOrFilter和getTheme都锁住了同一个对象mSync,追踪原来是mSync这把锁锁住主线程,因此本案例的最终解决方案是,减少子线程237中系统类的交互,继而减少耗时等待。通过FileCache工具类初始化一个变量存储FileUtils.getStoragePath文件路径,减少doRefreshVideoCache时交互的频率,减小卡住的机率,详细可参考:
private static List<String> mSdcardPaths = null;
/**
* SD卡路径获取
* @return SdcardPath列表
*/
public static List<String> getSdcardPaths(){
if (mSdcardPaths == null){
mSdcardPaths = FileUtils.getStoragePath(XxxApplication.getApplication());
}
return mSdcardPaths;
}
解决思路:采取空间换时间的原则。
2.案例二
该案例为应用启动阶段主线程与FacebookSDK锁抢占,比案例一相对复杂。
可借鉴思路:从问题源头逐一追踪阻塞主线程锁的持有链。
由堆栈可知,主线程与线程39之间产生了资源等待,继而查看thread-39堆栈:
由关键字held by可知,线程39与主线程资源等待的原因为,同时都在使用Shareprefrence,线程39通过getPreferencesDir方法获取sp路径时候产生了等待。同时可以看到,线程39与线程38产生了线程等待,继而查看thread-38堆栈:
这里可能会有个疑问,线程38中获取文件路径为什么会跟线程39中Sharepreference获取产生资源竞争呢?如果你熟悉sp就会马上反应过来,sp的本质也是一个xml文件。不熟悉也没关系,我们接着观察分析,会发现thread-38和thread-39最终都调用了ContextImp中方法,继而查看:
thread-38中ContextImpl.getExternalFilesDirs调用
内部ensureExternalDirsExistOrFilter调用:
ensureExternalDirsExistOrFilter方法分析可参考案例一。
thread-39中ContextImpl.getPreferencesDir调用
由上可知,thread-38中ContextImpl.getExternalFilesDirs方法,以及thread-39中ContextImpl.getPreferencesDir方法,都锁住了同一个对象mSync。接下来再去分析具体业务堆栈,thread-38中详细调用:
由于保密原则,只能贴出大概的调用,后续的追踪为:通过此处可以定位到thread-38中,我们应用通过RxJava开启了一个子线程进行相关的初始化,而这个调用的时机是自定义的一个ContentProvider的onCreate阶段。而主线程中FacebookSDK的初始化,SDK内部也是通过一个Provider进行初始化,二者在同一时间段进行,故而造成了thread-38中Rx子线程抢占了资源,从而导致主线程中FacebookSDK的初始化阻塞。最终解决方式是,将二者的初始化时机进行排序,错开调度。当然,也可以将thread-38中的初始化时机放到应用Application的onCreate阶段,经测试也是ok的。原则就是将二者调度时机避开,避免资源抢夺。
总结,该问题相对复杂,解决的思路一是依赖trace.txt文件的分析经验,找出阻塞点,其次是业务代码的追踪和SDK内部逻辑的熟悉。
3.案例三
可借鉴思路:涉及三方库沟通,单一日志无法明确问题时,可同一问题的多个场景整理综合分析,得出最终结论。
由主线程中关键字held by后面信息可知,主线程被thread-27所阻塞,继而查看thread-27堆栈如下:
比较二者的调用可知,thread-27和主线程同时调用了ContextImp中方法,继而查看:
thread-27中ContextImpl.getExternalFilesDirs调用:
内部ensureExternalDirsExistOrFilter调用:
ensureExternalDirsExistOrFilter方法分析可参考案例一。
main-thread中ContextImpl.getTheme调用:
分析:thread-27中ContextImpl.getExternalFilesDirs与main-thread中ContextImpl.getTheme方法,竞争的是同一把锁mSync,主线程获取不到锁从而阻塞。由于thread-27中为应用接入的xx广告SDK,故而将同一类场景进行归纳进行了分析,
场景二:
场景二同样为xx广告与业务动画调用时ContextImpl.getTheme资源争夺,导致的主线程阻塞。
场景三:
场景三为底部播放栏设置Drable时,调用ContextImpl.getTheme资源争夺,导致的主线程阻塞。
由上述场景综合分析,基本可以确定此处是,xx广告SDK内部的问题,SDK内部代码追踪发现StorageUtils.getOfflineCacheDirectory调用较为频繁,最终与SDK厂商进行沟通反馈解决该问题。
二、高低端机区分
1.WebView预加载
main (native):tid=1 systid=1184
#00 pc 0x65cb78 TrichromeLibrary.apk + 50409472
at J.N.M1Y_XVCN(N.java)
at org.chromium.content.browser.BrowserStartupControllerImpl.a(BrowserStartupControllerImpl.java:2)
at org.chromium.content.browser.BrowserStartupControllerImpl.g(BrowserStartupControllerImpl.java:8)
at y8.run(y8.java:23)
at org.chromium.base.ThreadUtils.f(ThreadUtils.java:2)
at mA0.h(mA0.java:41)
at mA0.b(mA0.java:20)
at mA0.j(mA0.java:2)
at com.android.webview.chromium.WebViewChromiumFactoryProvider.g(WebViewChromiumFactoryProvider.java:2)
at com.android.webview.chromium.WebViewChromium.init(WebViewChromium.java:14)
at android.webkit.WebView.<init>(WebView.java:435)
at android.webkit.WebView.<init>(WebView.java:355)
at android.webkit.WebView.<init>(WebView.java:337)
at android.webkit.WebView.<init>(WebView.java:324)
at android.webkit.WebView.<init>(WebView.java:314)
at com.xxx.ui.mall.view.BPWebView.<init>(BPWebView.java:100)
at com.xxx.ui.main.MainActivity$4.queueIdle(MainActivity.java:595)
at android.os.MessageQueue.next(MessageQueue.java:404)
at android.os.Looper.loop(Looper.java:183)
at android.app.ActivityThread.main(ActivityThread.java:7740)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:612)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)
日志分析定位到MainActivity$4.queueIdle,而该方法只是进行了自定义BPWebView的预加载,BPWebView只是简单继承了WebView,并无过多额外操作,最终通过区分高低端机,低端机不进行WebView预加载,解决该问题。
MainActivity$4.queueIdle
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Looper.myQueue().removeIdleHandler(this);
long[] m = PhoneLevelUtil.getDeviceMemory();
if (m[1] * 100 / m[0] > 15) {
//BPWebView wb = new BPWebView(MusicApplication.getInstance());
String gameCenterUrl = DynamicIconUtil.getUrl();
if (!TextUtils.isEmpty(gameCenterUrl)) {
BPWebView wbGame = new BPWebView(MusicApplication.getInstance());
wbGame.setOnPageFinished(() -> {
wbGame.freeMemory();
});
wbGame.loadUrl(gameCenterUrl);
}
String subHomeLink;
if (TextUtils.isEmpty(SubscribePageUtil.subHomeLinkByService)) {
subHomeLink = ApiUrl.H5_BASE_URL + SubscribePageUtil.SUBSCRIBE_PAGE_URL;
} else {
subHomeLink = ApiUrl.H5_BASE_URL + SubscribePageUtil.subHomeLinkByService;
}
BPWebView wbSub = new BPWebView(MusicApplication.getInstance());
wbSub.setOnPageFinished(() -> {
wbSub.freeMemory();
});
wbSub.loadUrl(subHomeLink);
}
return false;
}
});
修改:
if (!GlobalVariate.isHighDevice) {
return;
}//低端机预加载,系统WebView产生的ANR较多
三、磁盘IO耗时
1.外置存储卡路径复用
main (blocked):tid=1 systid=17151 | waiting to lock <0x05dfb3bd> (java.lang.Object) held by thread 46
at android.app.ContextImpl.getExternalCacheDirs(ContextImpl.java:836)
at android.content.ContextWrapper.getExternalCacheDirs(ContextWrapper.java:311)
at androidx.core.content.ContextCompat$Api19Impl.getExternalCacheDirs(ContextCompat.java:840)
at androidx.core.content.ContextCompat.getExternalCacheDirs(ContextCompat.java:459)
at androidx.core.content.FileProvider.parsePathStrategy(FileProvider.java:696)
at androidx.core.content.FileProvider.getPathStrategy(FileProvider.java:635)
at androidx.core.content.FileProvider.attachInfo(FileProvider.java:416)
at android.app.ActivityThread.installProvider(ActivityThread.java:7673)
at android.app.ActivityThread.installContentProviders(ActivityThread.java:7209)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7121)
at android.app.ActivityThread.access$1600(ActivityThread.java:268)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2056)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:268)
at android.app.ActivityThread.main(ActivityThread.java:8107)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:627)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:997)
RxCachedThreadScheduler-4 (native):tid=46 systid=17212
at libcore.io.Linux.access(Linux.java)
at libcore.io.ForwardingOs.access(ForwardingOs.java:72)
at libcore.io.BlockGuardOs.access(BlockGuardOs.java:73)
at libcore.io.ForwardingOs.access(ForwardingOs.java:72)
at android.app.ActivityThread$AndroidOs.access(ActivityThread.java:7982)
at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:281)
at java.io.File.exists(File.java:815)
at android.app.ContextImpl.ensureExternalDirsExistOrFilter(ContextImpl.java:3022)
at android.app.ContextImpl.getExternalFilesDirs(ContextImpl.java:779)
at android.app.ContextImpl.getExternalFilesDir(ContextImpl.java:768)
at android.content.ContextWrapper.getExternalFilesDir(ContextWrapper.java:276)
at com.xxx.storage.cache.ScopeStorageUtils.getBPRootDir(ScopeStorageUtils.java:121)
at com.xxx.storage.cache.ScopeStorageUtils.getBPRootPath(ScopeStorageUtils.java:134)
at com.xxx.storage.cache.ScopeStorageUtils.shouldUseScopeStorage(ScopeStorageUtils.java:49)
at com.xxx.util.PhoneDeviceUtil.getMusicLocalDir(PhoneDeviceUtil.java:881)
at com.xxx.util.PhoneDeviceUtil.getCacheDirPath(PhoneDeviceUtil.java:914)
at com.xxx.biz.xxx.db.FilexxxerDBHelper.<init>(FilexxxerDBHelper.java:43)
at com.xxx.biz.xxx.db.FilexxxerDB.initDBAndCheckUpdate(FilexxxerDB.java:49)
at com.xxx.biz.xxx.db.FilexxxerDB.<init>(FilexxxerDB.java:44)
at com.xxx.biz.xxx.db.FilexxxerDB.<init>(FilexxxerDB.java:38)
at com.xxx.biz.xxx.db.FilexxxerDB$Holder.<clinit>(FilexxxerDB.java:60)
at com.xxx.biz.xxx.db.FilexxxerDB.getInstance(FilexxxerDB.java:65)
at com.xxx.storage.cache.PlaylistDB.getDB(PlaylistDB.java:57)
at com.xxx.storage.cache.PlaylistDB.query(PlaylistDB.java:349)
at com.xxx.storage.cache.HistoryPlaylistCache.queryDB(HistoryPlaylistCache.java:192)
at com.xxx.storage.cache.HistoryPlaylistCache.<init>(HistoryPlaylistCache.java:39)
at com.xxx.storage.cache.ItemCache.init(ItemCache.java:151)
at com.xxx.common.base.XxApplicationInitor.init1(XxApplicationInitor.java:285)
at com.xxx.common.base.XxApplicationInitor.access$500(XxApplicationInitor.java:115)
at com.xxx.common.base.XxApplicationInitor$4.subscribe(XxApplicationInitor.java:177)
at io.reactivex.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)
at io.reactivex.Observable.subscribe(Observable.java:12284)
at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
ScopeStorageUtils.getBPRootDir
public static File getBPRootDir() {
File externalFilesDir = MusicApplication.getInstance().getExternalFilesDir(null);
if (externalFilesDir == null) {
// Android4.4以下,externalFilesDir返回null,直接自己创建目录
externalFilesDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "Android"
+ File.separator + "data"
+ File.separator + MusicApplication.getInstance().getPackageName()
+ File.separator + "files");
}
return externalFilesDir;
}
由于手机硬件老化,主要耗时点为getExternalFilesDir,故解决方法为缓存getExternalFilesDir路径。
FileCache类缓存路径
private static File mExternalFilesDir = null;
/**
* 外置的存储卡获取
* @return 外置存储卡File
*/
public static File getExternalFilesDir(){
if (mExternalFilesDir == null){
mExternalFilesDir = XxApplication.getInstance().getExternalFilesDir("");
}
return mExternalFilesDir;
}
修改后ScopeStorageUtils.getBPRootDir调用:
public static File getBPRootDir() {
File externalFilesDir = FileCache.getExternalFilesDir();
...
return externalFilesDir;
}
2.SD卡路径复用
main (blocked):tid=1 systid=545 | waiting to lock <0x08ff64d2> (java.lang.Object) held by thread 237
at android.app.ContextImpl.getTheme(ContextImpl.java:387)
at android.content.ContextWrapper.getTheme(ContextWrapper.java:139)
at android.content.Context.getColor(Context.java:704)
at androidx.core.content.ContextCompat$Api23Impl.getColor(ContextCompat.java:889)
at androidx.core.content.ContextCompat.getColor(ContextCompat.java:536)
at com.xxx.ui.dialog.xxx.DownLoadHintView.setViewColor(DownLoadHintView.java:228)
at com.xxx.ui.dialog.xxx.DownLoadHintView.loadData(DownLoadHintView.java:361)
at com.xxx.ui.dialog.xxx.DownLoadHintView.access$100(DownLoadHintView.java:79)
at com.xxx.ui.dialog.xxx.DownLoadHintView$ShowRunnable.run(DownLoadHintView.java:522)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7815)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1094)
RxCachedThreadScheduler-49 (native):tid=237 systid=13518
#00 pc 0x9ae90 libc.so (__ioctl + 8)
#01 pc 0x6932f libc.so (ioctl + 26)
#02 pc 0x39a13 libbinder.so (android::IPCThreadState::talkWithDriver(bool) + 238)
#03 pc 0x3a655 libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*) + 32)
#04 pc 0x3a42b libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int) + 122)
#05 pc 0x35267 libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int) + 98)
#06 pc 0xc73ff libandroid_runtime.so (android_os_BinderProxy_transact(_JNIEnv*, _jobject*, int, _jobject*, _jobject*, int) + 82)
at android.os.BinderProxy.transactNative(BinderProxy.java)
at android.os.BinderProxy.transact(BinderProxy.java:540)
at android.os.storage.IStorageManager$Stub$Proxy.mkdirs(IStorageManager.java:2695)
at android.os.storage.StorageManager.mkdirs(StorageManager.java:1405)
at android.app.ContextImpl.ensureExternalDirsExistOrFilter(ContextImpl.java:2895)
at android.app.ContextImpl.getExternalFilesDirs(ContextImpl.java:771)
at android.content.ContextWrapper.getExternalFilesDirs(ContextWrapper.java:278)
at com.xxx.util.FileUtils.getScopeStoragePath(FileUtils.java:107)
at com.xxx.util.FileUtils.getStoragePath(FileUtils.java:96)
at com.xxx.biz.download.utils.LocalMediaCache.doRefreshVideoCache(LocalMediaCache.java:1065)
at com.xxx.biz.download.utils.LocalMediaCache.doScanLocalVideo(LocalMediaCache.java:1155)
at com.xxx.biz.download.utils.LocalMediaScaner$1.onNext(LocalMediaScaner.java:40)
at com.xxx.biz.download.utils.LocalMediaScaner$1.onNext(LocalMediaScaner.java:28)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:201)
at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:255)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
FileUtils.getScopeStoragePath
List<String> getScopeStoragePath(Context mContext, boolean... isRemoveAble) {
List<String> allSdcardPath = new ArrayList<>();
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
File[] externalFilesDirs = mContext.getExternalFilesDirs("");
...
} else {
File externalFilesDir = mContext.getExternalFilesDir("");
...
}
} catch (Exception e) {
Log.e("FileUtils", "getScopeStoragePath: ", e);
}
return allSdcardPath;
}
由于手机硬件老化,主要耗时点为getExternalFilesDirs,故解决方法为缓存getExternalFilesDirs路径。
FileCache类缓存路径
private static List<String> mSdcardPaths = null;
/**
* SD卡路径获取
* @return SdcardPath列表
*/
public static List<String> getSdcardPaths(){
if (mSdcardPaths == null){
mSdcardPaths = XxApplication.getApplication().getExternalFilesDirs("");
}
return mSdcardPaths;
}
修改后FileUtils.getScopeStoragePath
List<String> getScopeStoragePath(Context mContext, boolean... isRemoveAble) {
List<String> allSdcardPath = new ArrayList<>();
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
File[] externalFilesDirs = FileCache.getSdcardPaths();
...
} else {
File externalFilesDir = FileCache.getExternalFilesDir();
...
}
} catch (Exception e) {
Log.e("FileUtils", "getScopeStoragePath: ", e);
}
return allSdcardPath;
}