原生SharedPreferences ANR问题的分析

一、Android8.0以下SharedPreferences任务调度的实现

  • SharedPreferencesImpl.apply()的实现:
public void apply() {
        //创建一个result,它内部有一个计数器(初始值为1)
    final MemoryCommitResult mcr = commitToMemory();
    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);

    // Okay to notify the listeners before it's hit disk
    // because the listeners should always get the same
    // SharedPreferences instance back, which has the
    // changes reflected in memory.
    notifyListeners(mcr);
}

每次进行apply操作时,创建了两个runnable:

  1. awaitCommit被添加到QueuedWork的finishers队列中:执行该runnable需要等待MemoryCommitResult中的计数器归零------即任务写入完成;
  2. postWriteRunnable
    1. 会被放入到一个单线程的线程池中执行:QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
    2. 执行结束后,会remove掉QueuedWork中被添加的awaitCommit;
  3. MemoryCommitResult.writtenToDiskLatch何时归零:执行写入操作writeToFile()方法完成后;
  • QueuedWork:SharedPreferences的任务标记执行队列
    • finisher队列:sPendingWorkFinishers-------保存awaitCommit的队列
    • 单线程池:sSingleThreadExecutor------执行写入操作postWriteRunnable的线程池
    • 产生ANR的源头:waitToFinish()方法
/**
* Finishes or waits for async operations to complete.
* (e.g. SharedPreferences$Editor#startCommit writes)
*
* Is called from the Activity base class's onPause(), after
* BroadcastReceiver's onReceive, after Service command handling,
* etc.  (so async work is never lost)
*/
public static void waitToFinish() {
    Runnable toFinish;
    while ((toFinish = sPendingWorkFinishers.poll()) != null) {
        toFinish.run();
    }
}

该方法会循环的取出sPendingWorkFinishers中的runnable,并等待全部执行完成;

  • 在ActivityThread消息队列的处理

    以下是需要需要等待waitToFinish()方法执行完成,ActivityThread.H中的处理事件:

SERVICE_ARGS------handleServiceArgs()
STOP_SERVICE-------handleStopService()
PAUSE_ACTIVITY_FINISHING-------handlePauseActivity()
STOP_ACTIVITY_HIDE------handleStopActivity()
SLEEPING--------handleSleeping()

问题明了:

执行完apply()方法后,会产生一一对应的awaitCommit和postWriteRunnable,postWriteRunnable执行完成后会清除掉MemoryCommitResult中的计数器,并从删除QueuedWork.sPendingWorkFinishers中对应的awaitCommit。

即:当线程池sSingleThreadExecutor中写入任务未被全部完成,QueuedWork中sPendingWorkFinishers队列就不为空,QueuedWork.waitToFinish()方法就会依次执行sPendingWorkFinishers队队列中的任务------awaitCommit,awaitCommit在写入操作完成,会被writtenToDiskLatch阻塞。此时如果ActivityThread要处理以上事件,UI线程被waitToFinish()方法block,就有可能发生ANR。

解决思路:实现一个自定义的ConcurrentLinkedQueue,重写poll()方法强制返回null,然后动态代理掉QueuedWork中的sPendingWorkFinishers,waitToFinish()方法就不会产生阻塞。

二、Android8.0及以上上SharedPreferences任务调度的实现

我们先来看一下官方优化的改动点:

1. 将原来的单线程的线程池修改为HandlerThread;

2. QueuedWork中同时持有sWork工作队列和sFinishers锁队列,并将任务队列由ConcurrentLinkedQueue改为LinkedList实现; 
3. 将写入文件的工作从原来直接抛到线程池中,改为直接移动到QueueWork中的sWork中。

SharedPreferencesImpl.apply()的实现与之前差异不大,核心在于QueuedWork的变化

新QueuedWork源码分析:

  • 可延迟 Runnable 的延迟值。
/** Delay for delayed runnables, as big as possible but low enough to be barely perceivable */
private static final long DELAY = 100; 
  • waitToFinish() 运行超过 MAX_WAIT_TIME_MILLIS 毫秒,发出警告
private static final long MAX_WAIT_TIME_MILLIS = 512; 
  • 本类使用的锁对象
private static final Object sLock = new Object();
  • 执行任务时的锁对象
private static Object sProcessingWork = new Object();
  • 两个队列:
//任务链表
private static final LinkedList sWork = new LinkedList<>();
//存放 Finisher 的链表
private static final LinkedList sFinishers = new LinkedList<>();
  • 执行任务的handler
@GuardedBy("sLock")
private static Handler sHandler = null;
  • 新任务是否能被延迟,默认为 true
@GuardedBy("sLock")
private static boolean sCanDelay = true;
  • QueuedWorkHandler相关实现:
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 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;
    }
}

这是一个运行在HandlerThread上的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);
        }
    }
}

任务入队并执行,判断delay or not,执行processPendingWork();

由apply()触发的入队操作,shouldDelay都为true,即sCanDelay为true时,均延迟100ms执行;即默认情况下,processPendingWork()每100ms,触发一次打包执行操作--------将写入操作进行分片处理,每次只处理100ms内被添加进的任务;

  • waitToFinish()方法:
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);

            if (DEBUG) {
                hadMessages = true;
                Log.d(LOG_TAG, "waiting");
            }
        }

        // We should not delay any work as this might delay the finishers
        sCanDelay = false;
    }

    StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
    try {
        processPendingWork();
    } finally {
        StrictMode.setThreadPolicy(oldPolicy);
    }

    try {
        while (true) {
            Runnable finisher;

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

            if (finisher == null) {
                break;
            }

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

    synchronized (sLock) {
        long waitTime = System.currentTimeMillis() - startTime;

        if (waitTime > 0 || hadMessages) {
            mWaitTimes.add(Long.valueOf(waitTime).intValue());
            mNumWaits++;

            if (DEBUG || mNumWaits % 1024 == 0 || waitTime > MAX_WAIT_TIME_MILLIS) {
                mWaitTimes.log(LOG_TAG, "waited: ");
            }
        }
    }
}
  1. 清空handler中的消息,lsCanDelay = false;
  2. 执行processPendingWork();
  3. 循环执行sFinishers队列中的finisher,等在sFinishers中的锁集合的执行,即等待HandlerThread线程执行完写入操作;

由于processPendingWork()方法中的写入操作是加锁的(sProcessingWork),所以UI线程和HandlerThread触发的写入任务不会同时进行--------即当UI线程触发waitToFinish()时,需要等待HandlerThread线程执行写入操作完成,释放sProcessingWork锁,然后处理下一个100ms内等待执行的写入任务,而后续再被添加进的任务,UI线程不会再处理,而是继续交由HandlerThread线程执行;

waitToFinish()中任务执行前,会将sCanDelay置位false,processPendingWork()执行完成后,置为true。即当在UI线程触发waitToFinish()方法的过程中,如果此时再有写入工作,会向HandlerThread发送没有delay的写入操作消息,会在ui线程执行完成写入操作后,立即执行。

  • processPendingWork()
private static void processPendingWork() {
    long startTime = 0;

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

    synchronized (sProcessingWork) {
        LinkedList work;

        synchronized (sLock) {
            work = (LinkedList) 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();
            }

            if (DEBUG) {
                Log.d(LOG_TAG, "processing " + work.size() + " items took " +
                        +(System.currentTimeMillis() - startTime) + " ms");
            }
        }
    }
}

执行逻辑:

  1. clone工作队列sWork,并清空sWork;
  2. 清空QueueWork中的HandlerThread的消息队列中的MSG_RUN消息;
  3. 依次执行任务队列中的runnable;

procressPendingWork的执行入口有两个:

  1. handler在HandlerThread线程处理MSG_RUN消息;
  2. UI线程执行waitToFinish,

执行时会处理所有当前消息时间节点之前,sWorker队列中所有已存在的任务,所以需要清空队列中的MSG_RUN消息。

优化思路:对sWork进行动态代理,复写链表的clone和clear方法:当clone方法是UI线程调用时,返回一个空的集合,避免主线程执行写入文件的操作导致block。当clear方法被UI调用时,不做清空直接return。实际上将原来UI线程处理的写入任务,交给HandlerThread线程写入。

你可能感兴趣的:(原生SharedPreferences ANR问题的分析)