SP的运用及存在的问题
SharedPreferences(以下简称SP)
是Android本地存储的一种方法,是以key-value
的方法存储在/dat初始化游戏启动器失败a/data/项目包名/shared_prefs/sp_name.xml
里,SP
的运用示例及源码解析github中文社区拜android是什么手机牌子见:Android本地存储之Shared操作系统Preferences源码解析。以下是SP
的一些定论:
-
SharedPreferences
读取xml
文件时,会以DOM
方法解析(把整个xml
文件直接加载到内存中解析)Android,在调用getXXX()
办法时取到的是内存中的数据,办法履行时会有个锁来堵塞,意图是等候文件加载完毕,没加载完结之前会GitHubwait()
。 -
SP
第一github中文官网网页次初始化到读取到数据存在必定推迟,由于源码网站需要到源码1688文件中读取数据,因此或许会对UI
线程流通度形成必定影响,严峻情况下会产生ANR
。 -
SharedPreferences
写文件时,假如调用的commit()
,会将数据同步写入内存中,内源码交易平台存数据更新,再同步写入磁盘中; 假如调用的apply()
,会将数据同步写入内存操作系统是什么的接口中,内存数据更新,然后异步写人磁盘,也就是说或初始化英文许写磁盘操作还没有完结果直接回来了。在UI线程
中建议运用apply()
,由于同步写磁盘,当文件较大时,commit()
会比及写磁盘完结再回来,或许会有ANR
问题。 - 写操作系统是一种什么软件文件时即运用的是
apandroid/harmonyospl操作系统当前的配置不能运行此应用程序y()
办法,仍然有或许会形成ANR
问题,这是为什么呢?先看下apply()
的流程。
apply()android什么意思流程分析
写文件流程( 8.0以上)
apply()
为什么还会出现ANR
呢?咱们来看下apply()
的逻辑(这儿源码是看的API30
的):
//SharedPreferencesImpl.EditorImpl.java
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
//写入内存
final MemoryCommitResult mcr = commitToMemory();
//这儿的操作运用CountDownLatch完结等候作用
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//writtenToDiskLatch类型是CountDownLatch(1)
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
}
};
//将awaitCommit参加行列中,后续Activity的onStop()中即会履行这个Runnable等候
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//文件写入操作
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}
QueuedWork.addFinisher(awaitandroid的drawable类Commit)
将awaitComm初始化失败是怎么解决it
参加到行列中,aw初始化英文a初始化电脑时出现问题未进行更改itCommit
在履行时运用CountDownLatch
机制能够完结对当前线程的堵塞作用,后续Activity
的onStop()
中会将这儿的awaitCommit
取出来履行,即UI线程
会堵塞等候sp文件
写入磁盘。初始化
//SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//写入磁盘操作
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
//commit()会在当前线程进行写入操作
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
//QueuedWork.java
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
//结构一个Handler并传入HandlerThread的Looper,即Handler会在子线程中处理消息
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;
QueuedWorkHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
processPendingWork();
}
}
}
private static void processPendingWork() {
synchronized (sProcessingWork) {
LinkedList<Runnable> work;
synchronized (sLock) {
work = (LinkedList<Runnable>) sWork.clone();
sWork.clear();
// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
//取出Runnable并履行
for (Runnable w : work) {
w.run();
}
}
}
}
apply()
写入操作是经过SharedPreferencesImpl#enqueueDiskWrite()
在HandlerThread
结构的子线程中完结的,写入成功后会经过writAndroidtenToDisAndroidkLatch.countDown()
开释awaitCommit
中的锁,使得UI
线程康复履行。
QueuedWork.waitToFinish ( 8.0以上)
Activity的onStop(源码编辑器下载)
、Service的onDestroy()
履行时,都会调用到QueuedWork.wa操作系统是计算机系统中的itToFinish()
办法:
//ActivityThread.java
private void handleStopService(IBinder token) {
Service s = mServices.remove(token);
if (s != null) {
try {
if (localLOGV) Slog.v(TAG, "Destroying service " + s);
s.onDestroy();
s.detachAndCleanUp();
//看这儿 看这儿!!!
QueuedWork.waitToFinish();
//...其他代码...
} catch (Exception e) {
}
}
}
@Override
public void handleStopActivity(IBinder token, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
reason);
//大于API11的时候履行
if (!r.isPreHoneycomb()) {
//看这儿 看这儿!!!
QueuedWork.waitToFinish();
}
//......
}
Activity的onStop()
、Service中的onDestroy初始化电脑()
都是间接在ActivityThreandroid手机ad
中的handleStopService()、handleStopActivity()
履行的,这两个办法里都会履行到QueuedWork.waitToFgithub下载inish操作系统是一种()
:
public static void waitToFinish() {
long startTime = System.currentTimeMillis();
boolean hadMessages = false;
Handler handler = getHandler();
synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
// Delayed work will be processed at processPendingWork() below
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
//把使命取出来,直接在当前线程处理 8.0之后才有的逻辑
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
//重点 看这儿 看这儿!!!
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
}
}
这儿的sFinishersgithub直播平台永久回家
中取的Runnable
就是在操作系统对磁盘进行读写的单位写文件之前经过QueuedWork.addFinisher(awaitCommit)
添加的,当取出awaitCommit
履行时即会堵塞当前线程,假如apply()
中写入磁盘时刻过长导致awaitgithub中文官网网页Commit
的锁没有及时开释,UI线程
就会由于长时刻被堵塞得不到初始化失败是怎么解决履行而出现ANR
了。
用一张图来总结:
图片来自:今日头条 ANR 优化实践系列 – 告别 SharedPreference 等候
写文件流程( 8.0以下)
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
//这儿的操作时为了CountDownLatch完结等候作用
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.add(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}
QueuedWork.waitToFinish ( 8.0以下)
public static void waitToFinish() {
Runnable toFinish;
while ((toFinish = sPendingWorkFinishers.poll()) != null) {
toFinish.run();
}
}
8.0以下
的流程相对更简单一些,但核心流程是源码编辑器一样的,当在UI线程
中调用到QueuedWork.waitToFinish()
时,假如写入磁盘的操作还github下载未完结且耗时比较长,就会引起U操作系统的主要功能是I线程
的ANR
。
总述,运用apply()
仍然有或许会形成ANR
问题。
如何优化
Jetpack DataStore代替
Jetpack DataStandroid的drawable类ore
是一种改进的新数据存储解决方案,答应运github永久回家地址用协议缓冲区存储键值对或类型化目标。DataStore
以异步、一致的业务方法初始化失败是怎么解决存储数据,克服了 SharedPreferences(以下统称为SP)的一些缺陷。DataStore
根据Kotlin
协程和Flow
完结初始化电脑的后果,并且能够对SP
数据进行搬迁,旨在取代SP
。
DataStore
供给了两种不同的完结:Preferences DataSto源码网站re
与Proto DataStore
,其中Preferences DataStore
用于存储键值对;Proto DataStore
用于源码编辑器下载存储类型化目标,DataStore
更具体的介绍拜见:Androi初始化失败是怎么解决d Jetpack系列之DataStore
MMKV代替
MMKV
是根据 mmap
内存映射的 key-value
组件,底层序列化/反序列化运用 protobuf
完结,功能高,稳定性强。从 2015 年中至今在微信上运用,其功能和稳定性经过了时刻的验证。近期也已移植到 Android / macOS / Win32 / POSIX
渠道,同时开源。
注:mmap
内存映射,能够供给一段可供随时写入的内存块,App
只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash
导致数据丢掉。
MMKV
地址:https://github.com/tencent/mmkv
SP运用优化
-
SP
文件尽量按分类去加载存储,假如文件里存储android下载的K-V
数github直播平台永久回家据初始化sdk什么意思过多,会导致第一次GitHub加载时刻过长;别的新增一个K-V
时,写入磁盘是全量更新,即会把之前的文件再次更初始化是什么意思新一遍,所以也要求SP
运用时尽量分类加载存储。 - 主要是优android的drawable类化
UI线程
中履行QueuedWork.waitToFinish()
,当行列履行poll()
时,经过反射修正poll()
的回来值,将其设为null
,这样UI线程会持续往下履行而不会原地堵塞等候了。示例如下(留意8.0以上
与8.0以下
处理不一样):
object SPHook {
fun optimizeSpTask() {
if (Build.VERSION.SDK_INT < 26) {
reflectSPendingWorkFinishers()
} else {
reflectSFinishers()
}
}
/**
* 8.0以上 Reflect finishers
*
*/
private fun reflectSFinishers() {
try {
val clz = Class.forName("android.app.QueuedWork")
val field = clz.getDeclaredField("sFinishers")
field.isAccessible = true
val queue = field.get(clz) as? LinkedList<Runnable>
if (queue != null) {
val linkedListProxy = LinkedListProxy(queue)
field.set(queue, linkedListProxy)
log("hook success")
}
} catch (ex: Exception) {
log("hook error:${ex}")
}
}
/**
* 8.0以下 Reflect pending work finishers
*/
private fun reflectSPendingWorkFinishers() {
try {
val clz = Class.forName("android.app.QueuedWork")
val field = clz.getDeclaredField("sPendingWorkFinishers")
field.isAccessible = true
val queue = field.get(clz) as? ConcurrentLinkedQueue<Runnable>
if (queue != null) {
val proxy = ConcurrentLinkedQueueProxy(queue)
field.set(queue, proxy)
log("hook success")
}
} catch (ex: Exception) {
log("hook error:${ex}")
}
}
/**
* 在8.0以上apply()中QueuedWork.addFinisher(awaitCommit), 需要署理的是LinkedList,如下:
* # private static final LinkedList<Runnable> sFinishers = new LinkedList<>()
*/
private class LinkedListProxy(private val sFinishers: LinkedList<Runnable>) :
LinkedList<Runnable>() {
override fun add(element: Runnable): Boolean {
return sFinishers.add(element)
}
override fun remove(element: Runnable): Boolean {
return sFinishers.remove(element)
}
override fun isEmpty(): Boolean = true
/**
* 署理的poll()办法,永久回来空,这样UI线程就能够防止被堵塞,持续履行了
*/
override fun poll(): Runnable? {
return null
}
}
/**
* 在8.0以下署理
* // The set of Runnables that will finish or wait on any async activities started by the application.
* private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = new ConcurrentLinkedQueue<Runnable>();
*/
private class ConcurrentLinkedQueueProxy(private val sPendingWorkFinishers: ConcurrentLinkedQueue<Runnable>) :
ConcurrentLinkedQueue<Runnable>() {
override fun add(element: Runnable?): Boolean {
return sPendingWorkFinishers.add(element)
}
override fun remove(element: Runnable?): Boolean {
return sPendingWorkFinishers.remove(element)
}
override fun isEmpty(): Boolean = true
/**
* 署理的poll()办法,永久回来空,这样UI线程就能够防止被堵塞,持续履行了
*/
override fun poll(): Runnable? {
return null
}
}
}
注: Android P(9.0)
中引入了对躲藏API
的约束,由github永久回家地址于这儿是hook
的源码,所以假如后续版本假如也被标记为躲藏A初始化电脑PI
,或github中文官网网页许会导致反射失利。找操作系统是一种到一个绕过躲藏API
约束的库:https://源码github.com/LSPosed操作系统的基本特征/AndroidHiddenApiBypass
,原理是经过Unsafe
去操作的,后续能够考虑经过这种方法去打破android手机约束。