您的位置:首页 > Web前端

站在巨人的肩膀上好好学习SharedPreferences

2018-01-25 19:10 330 查看
站在巨人的肩膀上好好学习

本人小白菜,看了头条技术团队的文章 额假装有链接

特意read the fuck Souce code…

平时使用只会commit,看了大牛们的分析,才发现知之甚少

public void apply() {

final MemoryCommitResult mcr = commitToMemory();

//放入任务子线程
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
//CountDownLatch .await()会暂停调用线程
//直到创建时设置的count值,减到0
//countDown()方法会进行减1的操作
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};

//添加
QueuedWork.addFinisher(awaitCommit);

//另一个任务
Runnable postWriteRunnable = new Runnable() {
public void run() {

//执行上一个任务,即暂停调用线程
awaitCommit.run();
//清除
QueuedWork.removeFinisher(awaitCommit);
}
};

/**
* 执行方法
* 第二个参数是runnable
* 目前看已经具备了阻塞主线程的能力
* 那么阻塞主线程干什么了
**/
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
//通知监听
notifyListeners(mcr);
}

//大致来说就是:把设置到内存的sp数据,转到了一个map中,这个map是用来写到磁盘的
//同时清空了,sp数据
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;

synchronized (SharedPreferencesImpl.this.mLock) {

//enqueueDiskWrite 在写入的方法中会做mDiskWritesInFlight--
//用来控制什么时候拷贝
if (mDiskWritesInFlight > 0) {
//1.mModified 是sp保存的数据,保存到内存中
//2.在写入磁盘之前,先复制一份
mMap = new HashMap<String, Object>(mMap);
}

//真正写到磁盘时使用的map
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;

boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}

synchronized (mLock) {
boolean changesMade = false;

if (mClear) {
if (!mMap.isEmpty()) {
changesMade = true;
mMap.clear();
}
mClear = false;
}

for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}

//保存已设置过的内容到map,也能保存到mapToWriteToDisk?
mMap.put(k, v);
}

changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}

//清空内存数据
mModified.clear();

if (changesMade) {
mCurrentMemoryStateGeneration++;
}

memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}

添加到静态常量有序的runnable集合中
static final LinkedList<Runnable> sFinishers = new LinkedList<>();
等下:研究这个东西的作用和始末
QueuedWork
public static void addFinisher(Runnable finisher) {
synchronized (sLock) {
sFinishers.add(finisher);
}
}

private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//判定上一步传入的参数2是不是空,也就是区别是commit还是apply
//commit 传null
final boolean isFromSyncCommit = (postWriteRunnable == null);

//真正写到磁盘的操作,是个runnable,那么调用这个任务的线程
//才是区别apply和commit的地方
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {

//写入磁盘
synchronized (mWritingToDiskLock) {
//流操作
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}

//启动阻塞主线程的操作
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};

//commit 同步执行
if (isFromSyncCommit) {
boolean wasEmpty = false;

//前面所有的apply和commit都会调用commitToMemory
//即每次都会递增mDiskWritesInFlight
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}

//当前线程执行,同步执行
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}

//apply 异步执行
//加入到QueuedWork队列
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}

到这儿其实还是没有异步执行的影子,commit的同步执行已经追到
那现在这个QueuedWork就是问题关键了

发现QueuedWork从成员到方法都是静态的,那么好了,这个玩意是随进程走的。
要看apply最终被那个异步线程执行,就看那个线程run了

//handler消息机制,延时不延时的区别
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接收消息,处理,先卖个关子(Looper)
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();
}
}
}

//run了每一个任务
private static void processPendingWork() {
long startTime = 0;

if (DEBUG) {
startTime = System.currentTimeMillis();
}

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) {
for (Runnable w : work) {
w.run();
}
}
}
}

还是没发现线程信息把,关子来了。。。looper
我们知道一个线程对应一个looper,looper可以说是线程的标识
那么看看这个looper

/**
* Lazily create a handler on a separate thread.
* 惰性地在单独的线程上创建处理程序
* goole大神们还是很坦诚的。。。
*/
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;
}
}

最终apply这个写入磁盘的runnable就是在这个线程执行的
我们的主线程就是包名
adb shell ps -t

根据头条大神说
在activityThread 的handleStopService时,回调
4000
用
QueuedWork.waitToFinish();

无限循环,依次获取QueuedWork中linklist中的runnable执行
其中sp中apply放入的那个runnable有个CountDownLatch .await()
所以主线程被阻塞
public static void waitToFinish() {
try {
while (true) {
Runnable finisher;

synchronized (sLock) {
finisher = sFinishers.poll();
}

if (finisher == null) {
break;
}

finisher.run();
}
} finally {
sCanDelay = true;
}
}

那么这个释放线程阻塞的位置在哪?

减到0就释放阻塞
void setDiskWriteResult(boolean wasWritten, boolean result) {
//减1
writtenToDiskLatch.countDown();
}

writeToFile()方法中进行
....
mcr.setDiskWriteResult(false, true);

小结:sp的apply流程
我们存了很多数据putInt()......
sp保存到了内存中 mModified
调用apply(),将写数据到磁盘的任务,发给QueuedWork的线程 "queued-work-looper"
并设置了主线程的线程锁awaitCommit,有序的保存在QueuedWork中(QueuedWor在进程启动的时候初始化)QueuedWork.addFinisher(awaitCommit)
在service停止时,activityThread -> H ->handleStopService ,当前是在主线程中,包名
在ams 停止服务之前, QueuedWork.waitToFinish(), 开启sp的工作,阻塞线程, 写入磁盘
写入完毕,释放线程锁,主线程继续流程


遗留:头条大神们建议的apply的优化,项目亲测后再说

注:欢迎大神们指出问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: