1.实现类

SharedPreferences 只是一个接口,其实现类是SharedPreferencesImpl。

工作流程分析:
创建sp 的时候,会去查看是否有bak文件,如果有的话,把bak文件,重命名成file的真正文件名,读取到内存。

    SharedPreferencesImpl(File file, int mode) {        mFile = file;        mBackupFile = makeBackupFile(file);        mMode = mode;        mLoaded = false;        mMap = null;        mThrowable = null;        startLoadFromDisk();    }    private void startLoadFromDisk() {        synchronized (mLock) {            mLoaded = false;        }        new Thread("SharedPreferencesImpl-load") {            public void run() {                loadFromDisk();            }        }.start();    }

如果在读取的过程中,你调用了getString,那么该方法会等到io 读取到map 完成,返回结果。

2.getString 会直接从磁盘里面直接读取吗?

不会,会从内存里面读取。

    @Override    @Nullable    public String getString(String key, @Nullable String defValue) {        synchronized (mLock) {            awaitLoadedLocked();            String v = (String)mMap.get(key);            return v != null ? v : defValue;        }    }

3.apply 和 commit 的 区别。

apply 不会立马写在磁盘里面,commit 会的。
关键位置是SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

我们看下这个方法:

    /**     * Enqueue an already-committed-to-memory result to be written     * to disk.     *     * They will be written to disk one-at-a-time in the order     * that they're enqueued.     *     * @param postWriteRunnable if non-null, we're being called     *   from apply() and this is the runnable to run after     *   the write proceeds.  if null (from a regular commit()),     *   then we're allowed to do this disk write on the main     *   thread (which in addition to reducing allocations and     *   creating a background thread, this has the advantage that     *   we catch them in userdebug StrictMode reports to convert     *   them where possible to 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);    }

如果是commit 的话,writeToDiskRunnable.run(); 写文件的这个操作直接进行。如果不是的话,会放到一个队列里面去执行。QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

疑问:

如果我apply 是异步进行的,那么为什么我putString (“aaa”,“111”)之后调用apply ,立马就getString(“aaa”,"")能够返回正确的结果呢?

因为apply 和commit 一样,都会先把改动保存到内存,然后写到文件里面。

@Override        public void apply() {            final long startTime = System.currentTimeMillis();            final MemoryCommitResult mcr = commitToMemory();
        // Returns true if any changes were made        private MemoryCommitResult commitToMemory() {            long memoryStateGeneration;            List keysModified = null;            Set listeners = null;            Map mapToWriteToDisk;            synchronized (SharedPreferencesImpl.this.mLock) {                // We optimistically don't make a deep copy until                // a memory commit comes in when we're already                // writing to disk.                if (mDiskWritesInFlight > 0) {                    // We can't modify our mMap as a currently                    // in-flight write owns it.  Clone it before                    // modifying it.                    // noinspection unchecked                    mMap = new HashMap(mMap);                }                mapToWriteToDisk = mMap;                mDiskWritesInFlight++;                boolean hasListeners = mListeners.size() > 0;                if (hasListeners) {                    keysModified = new ArrayList();                    listeners = new HashSet(mListeners.keySet());                }                synchronized (mEditorLock) {                    boolean changesMade = false;                    if (mClear) {                        if (!mapToWriteToDisk.isEmpty()) {                            changesMade = true;                            mapToWriteToDisk.clear();                        }                        mClear = false;                    }                    for (Map.Entry e : mModified.entrySet()) {                        String k = e.getKey();                        Object v = e.getValue();                        // "this" is the magic value for a removal mutation. In addition,                        // setting a value to "null" for a given key is specified to be                        // equivalent to calling remove on that key.                        if (v == this || v == null) {                            if (!mapToWriteToDisk.containsKey(k)) {                                continue;                            }                            mapToWriteToDisk.remove(k);                        } else {                            if (mapToWriteToDisk.containsKey(k)) {                                Object existingValue = mapToWriteToDisk.get(k);                                if (existingValue != null && existingValue.equals(v)) {                                    continue;                                }                            }                            mapToWriteToDisk.put(k, v);                        }                        changesMade = true;                        if (hasListeners) {                            keysModified.add(k);                        }                    }                    mModified.clear();                    if (changesMade) {                        mCurrentMemoryStateGeneration++;                    }                    memoryStateGeneration = mCurrentMemoryStateGeneration;                }            }            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,                    mapToWriteToDisk);        }

mapToWriteToDisk = mMap; 这一句话就把mMap 赋值给mapToWriteToDisk 。而所有的改动都会在这个mapToWriteToDisk 上去修改。其实最终修改的就是mMap。所以不需要等到写到文件里面,你就可以拿到正确的结果。

4.apply 的实现方法

比如当我们调用sp.setString().apply 的时候,首先会把你设置的String 提交到内存里面,也就是map 里面。

然后会调用QueuedWork.addFinisher(awaitCommit); 把这个等待的runnable 添加到QueueWork 的finish 队列里。
等待的Runnable 代码如下:

            final Runnable awaitCommit = new Runnable() {                    @Override                    public void run() {                        try {                            mcr.writtenToDiskLatch.await();                        } catch (InterruptedException ignored) {                        }                    }                };

然后把写文件的runnable 放入QueueWork 的队列里面。QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);

在QueueWork 里面会发送一个延迟100ms 的消息,在消息里面会去处理写文件的Runnable。

写文件成功之后,会把等待的Runnable 从QueueWork 的 finisher 里面移除。QueuedWork.removeFinisher(awaitCommit);

在ActivityThread hanlderStopActivity 的时候,会调用QueueWork 的waitTofinish() 方法,等待所有的apply 的写文件完成。

commit 的实现

commit 的实现比较简单,当我们commit 的时候,会把写文件的runnable 发送到QueueWork 的 队列里,所以就算是commit 写文件也不是在主线程写的。但是commit 方法会调用mcr.writtenToDiskLatch.await(); 去等待QueueWork 写文件完成。

5.设计优缺点

6.SP设计里面的备份文件

SP会涉及两个文件,一个真正的文件,一个备份文件。
如果sp 改变了,是要写文件的话,如果当前的sp 文件file存在,首先把文件真正的file重命名为file.bak.
如果重命名不成功的话,整个操作以失败告终。

接着创建fileoutputstream.把map 写进去,改文件权限。
如果整个操作成功,那么把备份文件删除掉。如果失败,把file 文件删除掉。

 // Note: must hold mWritingToDiskLock    private void writeToFile(MemoryCommitResult mcr) {        // Rename the current file so it may be used as a backup during the next read        if (mFile.exists()) {            if (!mcr.changesMade) {                // If the file already exists, but no changes were                // made to the underlying map, it's wasteful to                // re-write the file.  Return as if we wrote it                // out.                mcr.setDiskWriteResult(true);                return;            }            if (!mBackupFile.exists()) {                if (!mFile.renameTo(mBackupFile)) {                    Log.e(TAG, "Couldn't rename file " + mFile                          + " to backup file " + mBackupFile);                    mcr.setDiskWriteResult(false);                    return;                }            } else {                mFile.delete();            }        }        // Attempt to write the file, delete the backup and return true as atomically as        // possible.  If any exception occurs, delete the new file; next time we will restore        // from the backup.        try {            FileOutputStream str = createFileOutputStream(mFile);            if (str == null) {                mcr.setDiskWriteResult(false);                return;            }            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);            FileUtils.sync(str);            str.close();            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);            try {                final StructStat stat = Os.stat(mFile.getPath());                synchronized (this) {                    mStatTimestamp = stat.st_mtime;                    mStatSize = stat.st_size;                }            } catch (ErrnoException e) {                // Do nothing            }            // Writing was successful, delete the backup file if there is one.            mBackupFile.delete();            mcr.setDiskWriteResult(true);            return;        } catch (XmlPullParserException e) {            Log.w(TAG, "writeToFile: Got exception:", e);        } catch (IOException e) {            Log.w(TAG, "writeToFile: Got exception:", e);        }        // Clean up an unsuccessfully written file        if (mFile.exists()) {            if (!mFile.delete()) {                Log.e(TAG, "Couldn't clean up partially-written file " + mFile);            }        }        mcr.setDiskWriteResult(false);    }

下次进来去读文件的时候,如果back 文件存在,直接把file 文件删除掉,并且把back 文件,重新命名成file.

private void loadFromDiskLocked() {        if (mLoaded) {            return;        }        if (mBackupFile.exists()) {            mFile.delete();            mBackupFile.renameTo(mFile);        }

总之,所有正确的都以bak 文件为准。

7.wait notify 的使用

8.SharedPreferences 支持多进程吗?

不知道,如果是多进程,可能在一个进程里面写的值,被另外一个进程都给冲掉了。

9.apply 是完全异步的吗?会不会导致ANR?

"main@10722" prio=5 tid=0x2 nid=NA waiting  java.lang.Thread.State: WAITING  at sun.misc.Unsafe.park(Unsafe.java:-1)  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)  at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:868)  at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1023)  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1334)  at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:232)  at android.app.SharedPreferencesImpl$EditorImpl$1.run(SharedPreferencesImpl.java:466)  at android.app.QueuedWork.waitToFinish(QueuedWork.java:194)  at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4318)  at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:41)  at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1872)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:193)  at android.app.ActivityThread.main(ActivityThread.java:6743)  at java.lang.reflect.Method.invoke(Method.java:-1)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:486)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:882)

我们看这段堆栈,发现当activity onStop 的时候,会执行QueuedWork.waitToFinish()方法。这个QueuedWork 类,是供SP apply 异步写文件的一个类,里面会有HandlerThread去负责写文件。
waitToFinish 方法会把没有执行的所有runnable,放到主线程执行。所以handleStopActivity 会等待所有的apply 没有完成的runnable 去执行完成。所以,apply 并不是说完全异步的。也有可能导致ANR。但是,apply 这种只会在调用waitToFinish() 的场景才会触发ANR. 如果一个点击事件,如果里面处理的很多的业务逻辑,最后调用了commit 方法,那么有可能因为commit 产生ANR,但是不会因为apply 产生ANR.

我们看下QueueWork 所有waitToFinish() 方法调用的地方:

我们发现基本上都在ActivityThread 这个类里面。

模拟apply 产生ANR:

Class<?> aClass = null;                        try {                            aClass = Class.forName("android.app.QueuedWork");                            Method addFinisher = aClass.getMethod("addFinisher", Runnable.class);                            if (addFinisher != null) {                                addFinisher.invoke(null, new Runnable() {                                    @Override                                    public void run() {                                        try {                                            Thread.sleep(30000);                                        } catch (InterruptedException e) {                                            e.printStackTrace();                                        }                                    }                                });                            }                        } catch (ClassNotFoundException e) {                            e.printStackTrace();                        } catch (NoSuchMethodException e) {                            e.printStackTrace();                        } catch (IllegalAccessException e) {                            e.printStackTrace();                        } catch (InvocationTargetException e) {                            e.printStackTrace();                        }

我们可以通过以上代码,在QueueWork 里面添加一个finisher,然后点击手机back键,会发现会卡在onStop 方法那里。

总结:

1.面试不会面试业务代码,他们根本不熟悉,只会面android 源码 相关的问题。而sp 从来没有看过。所以很多问题不知道。

更多相关文章

  1. Android(安卓)SQLite 数据库存储详解
  2. android usb大体流程解析
  3. Android(安卓)3D开发,OpenGL ES 的使用(一)
  4. [Android(安卓)Pro] Scroller使用分析
  5. Android(安卓)pcm文件播放方法(AudioTrack)
  6. Android(安卓)dex分包方案
  7. Android获取各种路径方法以及对路径的解释
  8. Android中SharedPreferences存储数据
  9. Android(安卓)studio cannot resolve symbol xxx的解决方法

随机推荐

  1. 创建线程时设置CPU关联
  2. JavaScript 中 Object ,Prototype 相关的
  3. 检查通过程序的C管道 - 边界情况
  4. jQuery tipsy:手动触发和延迟
  5. 使用Chrome开发工具查找造成FOUC的原因
  6. 在平台上获取,可移植,更大和更快(无符号)的整
  7. 【web前端】可筛选[输入搜索]的select
  8. 语法错误:从[{id}]开始的表达式[{id}]第2
  9. javascript中new Date(****)结果为invali
  10. 为什么sizeof(char + char)返回4?