源码理解apply()与commit()的区别

QuibblerQuibbler 2020-07-22 约 24 分钟 1835 次阅读

源码理解apply()与commit()的区别

为什么SharedPreference保存数据首选apply()?commit()和apply()有什么区别?

①commit()是同步的:将数据直接写入存储;apply()是异步的,先将修改提交到内存不会阻塞,之后再写入存储。

②commit()有返回值标识是否成功写入;而apply()没有返回值。

现在来看看源码到底是如何实现apply()和commit()方法的。

1、SharedPreference接口和SharedPreferencesImpl实现

SharedPreference保存数据借助SharedPreference中定义的接口SharedPreferences.Editor,它们都是接口,定义在android.content包中。

public interface Editor {
        Editor putString(String key, @Nullable String value);
        Editor putStringSet(String key, @Nullable Set<String> values);
        Editor putInt(String key, int value);
        Editor putLong(String key, long value);
        Editor putFloat(String key, float value);
        Editor putBoolean(String key, boolean value);
        
        Editor remove(String key);
        
        Editor clear();
        
        boolean commit();
        
        void apply();
    }

SharedPreference接口的实现类是SharedPreferencesImpl,而SharedPreferences.Editor接口的实现是SharedPreferencesImpl.EditorImpl类,在android.app包中。

2、apply()

定义在SharedPreferences.Editor接口中的方法,在SharedPreferencesImpl.EditorImpl类中实现该方法,来看看该方法的完整实现:

public void apply() {
        final long startTime = System.currentTimeMillis();
        
        //提交到内存
        final MemoryCommitResult mcr = commitToMemory();
        final Runnable awaitCommit = new Runnable() {
                @Override
                public void run() {
                    try {
                        mcr.writtenToDiskLatch.await();
                    } catch (InterruptedException ignored) {
                    }
                    if (DEBUG && mcr.wasWritten) {
                        Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                + " applied after " + (System.currentTimeMillis() - startTime)
                                + " ms");
                    }
                }
            };
        //放入QueuedWork中的sFinishers队列
        QueuedWork.addFinisher(awaitCommit);
        
        Runnable postWriteRunnable = new Runnable() {
                @Override
                public void run() {
                    awaitCommit.run();
                    QueuedWork.removeFinisher(awaitCommit);//移除QueuedWork中的sFinishers队列
                }
            };
        //任务压入写磁盘的任务队列
        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);
    }

①先commitToMemory()提交到内存并返回MemoryCommitResult实例保存修改的集合。

②再构造第一个Runnable任务,标记等待commit到磁盘。

final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
                }
                if (DEBUG && mcr.wasWritten) {
                    Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                            + " applied after " + (System.currentTimeMillis() - startTime)
                            + " ms");
                }
            }
        };

writtenToDiskLatch是定义在MemoryCommitResult类中,当数据commit后会减一

// EditorImpl.commitToMemory()方法的返回值类型
    private static class MemoryCommitResult {
        ...
        final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
        ...
        void setDiskWriteResult(boolean wasWritten, boolean result) {
            this.wasWritten = wasWritten;
            writeToDiskResult = result;
            writtenToDiskLatch.countDown();
        }
    }

③接着将放入QueuedWork中的sFinishers队列:

public static void addFinisher(Runnable finisher) {
        synchronized (sLock) {
            sFinishers.add(finisher);
        }
    }

④再构造另一个Runnable任务postWriteRunnable,

Runnable postWriteRunnable = new Runnable() {
                @Override
                public void run() {
                    awaitCommit.run();
                    QueuedWork.removeFinisher(awaitCommit);
                }
            };

先执行awaitCommit任务等待提交,再将其移除QueuedWork中的sFinishers队列:

public static void removeFinisher(Runnable finisher) {
        synchronized (sLock) {
            sFinishers.remove(finisher);
        }
    }

⑤apply()方法最后将任务压入写磁盘的任务队列:

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();
                    }
                }
            };
        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

enqueueDiskWrite()最后调用了QueuedWork的queue()方法:是否延迟执行写入的Runnable队列。

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常量为异步延迟时间 100ms

/** 该延迟应尽可能大,但应低到几乎无法感知*/
    private static final long DELAY = 100;

向内部定义的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();
            }
        }
    }

最终processPendingWork()方法开始执行真正的写入磁盘任务。

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();
                }
                if (DEBUG) {
                    Log.d(LOG_TAG, "processing " + work.size() + " items took " +
                            +(System.currentTimeMillis() - startTime) + " ms");
                }
            }
        }
    }

3、commit()

commit()方法的实现如下,看着有没有和apply()很眼熟?

public boolean commit() {
        long startTime = 0;
        
        if (DEBUG) {
            startTime = System.currentTimeMillis();
        }
        
        MemoryCommitResult mcr = commitToMemory();
        
        SharedPreferencesImpl.this.enqueueDiskWrite(
            mcr, null /* sync write on this thread okay */);
        try {
            mcr.writtenToDiskLatch.await();
        } catch (InterruptedException e) {
            return false;
        } finally {
            if (DEBUG) {
                Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                        + " committed after " + (System.currentTimeMillis() - startTime)
                        + " ms");
            }
        }
        notifyListeners(mcr);
        return mcr.writeToDiskResult;
    }

注意区别,虽然也是先commitToMemory()写入内存。但是调用enqueueDiskWrite(mcr, null)传入的第二个参数为null,并不需要delay任务,前面刚刚提到!

private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        //为true
        final boolean isFromSyncCommit = (postWriteRunnable == null);
        ...
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

所以commit()执行到后面不会有延迟异步,而是立即写入磁盘。

public static void queue(Runnable work, boolean shouldDelay) {
        Handler handler = getHandler();
        synchronized (sLock) {
            sWork.add(work);
            if (false&& sCanDelay) {
                handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
            } else {
                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
            }
        }
    }

推荐博客:

        SharedPreference的使用以及commit()和apply()的区别

        关于SharedPreference.Editor的apply()和commit()方法异同

        SharedPreferences 的源码实现

“背书”们只知其一,不知其二。就像三角形内角之和是多少,相信99%的人都能脱口而出:180°。再问为什么呢,又有几个能说出为什么三角形内角之和是180°?因为只是被告诉是这样:

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w