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

站在巨人的肩膀上好好学习
本人小白菜,看了头条技术团队的文章 额假装有链接
特意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时,回调用
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的优化,项目亲测后再说
注:欢迎大神们指出问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值