SharedPreferences ANR 总结

2019-02-14  本文已影响12人  海盗的帽子

csdn
个人博客

一.简介

SharedPreferences 是 Android 中一种轻量级的数据存储方式,数据以键值对,文件以 xml 的形式存储在 /data/data/<package name>/shared_prefs目录下(在真机上需要 root 权限才能查看)

在源码中 SharedPreferences 是一个接口,具体的实现是
SharedPreferencesImpl

/**
 * Interface for accessing and modifying preference data returned by {@link
 * Context#getSharedPreferences}.  For any particular set of preferences,
 * there is a single instance of this class that all clients share.
 * Modifications to the preferences must go through an {@link Editor} object
 * to ensure the preference values remain in a consistent state and control
 * when they are committed to storage.  Objects that are returned from the
 * various <code>get</code> methods must be treated as immutable by the application.
 *
 * <p><em>Note: This class does not support use across multiple processes.</em>
 *
 * ...
 */
public interface SharedPreferences {
    ...
}

final class SharedPreferencesImpl implements SharedPreferences {
    ...
}

在注释中对 SharedPreferences 以下称(SP)解释如下:

注意:此类提供强大的一致性保证。它使用昂贵的操作可能会减慢应用程序的速度。经常改变可以容忍损失的属性或属性应该使用其他机制。有关详细信息读取上的评论 SharedPreferences.Editor.commit() 和SharedPreferences.Editor.apply()。
(换句话说就是 commit 和 apply 用于对数据进行保存,为了保证一致性这个过程可能会减慢应用程序的速度,如果对一致性要求不高则可以使用其他数据存储机制。)
==注意:此类不支持跨多个进程使用。==

二.获取 Sp

获取一个 Sp 有三种方式

第一种方式和第二种方式最后都会使用 第三种方式,不同的使用的名字不同,模式现在都是为 MODE_PRIVATE 其他的都已经废弃不使用

//第一种
//Activity 
//
public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }
//第二种
//PreferenceManager
public static SharedPreferences getDefaultSharedPreferences(Context context) {
        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
                getDefaultSharedPreferencesMode());
    }

    /**
     * Returns the name used for storing default shared preferences.
     *
     * @see #getDefaultSharedPreferences(Context)
     * @see Context#getSharedPreferencesPath(String)
     */
    public static String getDefaultSharedPreferencesName(Context context) {
        return context.getPackageName() + "_preferences";
    }

    private static int getDefaultSharedPreferencesMode() {
        return Context.MODE_PRIVATE;
    }

下面到 context 的具体实现类 contextImpl 中

 @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        // At least one application in the world actually passes in a null
        // name.  This happened to work because when we generated the file name
        // we would stringify it to "null.xml".  Nice.
        
        // 允许名字为 null, 即文件的名字为 null.xml 
        if (mLoadedApk.getApplicationInfo().targetSdkVersion <
                Build.VERSION_CODES.KITKAT) {
            if (name == null) {
                name = "null";
            }
        }

        File file;
        synchronized (ContextImpl.class) {
        //mSharedPrefsPaths 的数据结构为  ArrayMap<String, File> 
        //用于保存名字 和对应的文件
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name);
            //如果没有这个文件就创建这个文件
            if (file == null) {
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

    @Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        //使用 synchronized 进行线程安全的保证
        synchronized (ContextImpl.class) {
            //一个 SP xml 文件对应 一个 SharedPreferencesImpl 实例
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                checkMode(mode);
                ...
                //创建 SharedPreferencesImpl 
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        ....
        return sp;
    }

   // 获取用于缓存 SharedPreferencesImpl 的 packagePrefs, 一个包名对应一个 
   // packagePrefs 
    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }

        final String packageName = getPackageName();
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }

        return packagePrefs;
    }

下面就到 SharedPreferencesImpl.java 中 查看创建一个 SharedPreferencesImpl 的过程。


//SharedPreferencesImpl 的构造器
 SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);//创建备份文件,用于 xml 写失败时进行恢复
        mMode = mode;
        mLoaded = false;//判断从磁盘加载到内存的标志
        mMap = null;
        mThrowable = null;
        startLoadFromDisk();//创建 SharedPreferencesImpl 的时候就将 xml 文件中的数据从磁盘加载到内存 
    }

    private void startLoadFromDisk() {
        synchronized (mLock) {
            mLoaded = false;
        }
        //开启一个线程进行加载 
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                loadFromDisk();
            }
        }.start();
    }

    private void loadFromDisk() {
        synchronized (mLock) {
            //如果已经加载完成就返回
            if (mLoaded) {
                return;
            }
            
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

       

        Map<String, Object> map = null;
        StructStat stat = null;
        Throwable thrown = null;
        try {
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16 * 1024);
                    map = (Map<String, Object>)
                    //从 xml 文件中加载数据到 map 
                    //键值对为 String 和 Object
                    XmlUtils.readMapXml(str);
                } catch (Exception e) {
                    Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        } catch (ErrnoException e) {
            // An errno exception means the stat failed. Treat as empty/non-existing by
            // ignoring.
        } catch (Throwable t) {
            thrown = t;
        }

        synchronized (mLock) {
            // 设置加载完成
            mLoaded = true;
            mThrowable = thrown;

            //对出现异常情况进行处理
            try {
                if (thrown == null) {
                    if (map != null) {
                        mMap = map;
                        mStatTimestamp = stat.st_mtim;
                        mStatSize = stat.st_size;
                    } else {
                        mMap = new HashMap<>();
                    }
                }
                //
            } catch (Throwable t) {
                mThrowable = t;
            } finally {
            //注意这一句,唤醒其他等待的线程
                mLock.notifyAll();
            }
        }
    }
获取 SharedPreferences 总结:

三.get 方法

Sp 支持的数据类型为 int , long , float, boolean ,String 和 Set<String> .

下面以 getString 为例

//SharedPreferencesImpl.java 
  @Override
    @Nullable
    public String getString(String key, @Nullable String defValue) {
    //进行线程安全保证 
        synchronized (mLock) {
        //等待加载完成后才能读
            awaitLoadedLocked();
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }
 @GuardedBy("mLock")
    private void awaitLoadedLocked() {
       //如果没有加载就开启一个线程进行加载
        if (!mLoaded) {
            // Raise an explicit StrictMode onReadFromDisk for this
            // thread, since the real read will be in a different
            // thread and otherwise ignored by StrictMode.
            BlockGuard.getThreadPolicy().onReadFromDisk();
        }
        //循环判断
        while (!mLoaded) {
            try {
            //在上面的分析知道只有加载后 mLoaded 就设置 为 true
            //并调用 notifyAll 唤醒其他线程
            //所以这个时候从 wait 进行返回
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
        if (mThrowable != null) {
            throw new IllegalStateException(mThrowable);
        }
    }
getXXX 方法 总结:

四.putXXX 和 apply/commit

提交数据的时候首先要获取 Editor 对象

 @Override
    public Editor edit() {
        //这里也是需要等待加载 xml 文件到内存完成后
        //才能创建 EditorImpl
        synchronized (mLock) {
            awaitLoadedLocked();
        }

        return new EditorImpl();
    }

以 putString 为例

 @Override
        public Editor putString(String key, @Nullable String value) {
        //使用 synchronized 进行线程安全保证
            synchronized (mEditorLock) {
            //将数据暂时保存到 mModified 这个 Map 中
                mModified.put(key, value);
                return this;
            }
        }
1.apply
 @Override
        public void apply() {
            final long startTime = System.currentTimeMillis();
            //将修改先写入内存 
            final MemoryCommitResult mcr = commitToMemory(); 
            //先看 commitToMemory 方法
  private MemoryCommitResult commitToMemory() {
            ...
            //将 mModified 的中暂存的数据写入内存
                    for (Map.Entry<String, Object> 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();

                   ....
                   //返回一个 MemoryCommitResult 对象。
            return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                    mapToWriteToDisk);
        }
        
        //MemoryCommitResult 的实现
        private static class MemoryCommitResult {
        final long memoryStateGeneration;
        @Nullable final List<String> keysModified;
        @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
        final Map<String, Object> mapToWriteToDisk;
        final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);//注意这里 有一个 CountDownLatch 设置为 1,说明只要有一个线程调用了 countDown 就可以从 writtenToDiskLatch.await(); 返回

        @GuardedBy("mWritingToDiskLock")
        volatile boolean writeToDiskResult = false;
        boolean wasWritten = false;

        private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
                @Nullable Set<OnSharedPreferenceChangeListener> listeners,
                Map<String, Object> mapToWriteToDisk) {
            this.memoryStateGeneration = memoryStateGeneration;
            this.keysModified = keysModified;
            this.listeners = listeners;
            this.mapToWriteToDisk = mapToWriteToDisk;
        }
         //调用这个就会从 执行 countDown ,相应的 writtenToDiskLatch.await() 就能返回
        void setDiskWriteResult(boolean wasWritten, boolean result) {
            this.wasWritten = wasWritten;
            writeToDiskResult = result;
            writtenToDiskLatch.countDown();
        }
    }

回到 apply 方法中

@Override
        public void apply() {
            final long startTime = System.currentTimeMillis();
            //将修改先写入内存 
            final MemoryCommitResult mcr = commitToMemory(); 
            //这里只是创建了一个 Runnable ,并不是一个线程
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                           //注意这里会进行等待也就是 需要 MemoryCommitResult 的 setDiskWriteResult 方法执行后
                           //才能返回 
                           
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }

                        if (DEBUG && mcr.wasWritten) {
                            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                    + " applied after " + (System.currentTimeMillis() - startTime)
                                    + " ms");
                        }
                    }
                };
            
            //添加到队列中
            QueuedWork.addFinisher(awaitCommit);

            //这里也只创建一个 Runnable 
            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                    //这里执行了上面的 awaitCommit 的 run 方法
                    //不是 start 
                    //并将队列中的 awaitCommit 移除
                        awaitCommit.run();
                        QueuedWork.removeFinisher(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);
        }
private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        final boolean isFromSyncCommit = (postWriteRunnable == null);
        //创建一个 Runnable ,同样也没有 start 
        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };
         //如果是 commit 则执行这里并返回
        // 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;
            }
        }
         //如果是 apply 就执行这里
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

在 QueuedWork.java 中

    public static void queue(Runnable work, boolean shouldDelay) {
       //getHandler 获取的是一个 handlerThread 的hanlder ,也就是一个子线程
        Handler handler = getHandler();

        synchronized (sLock) {
            sWork.add(work);

            if (shouldDelay && sCanDelay) {
            //发送一个消息 MSG_RUN 到 handler 所在线程,也就是 handlerThread 子线程中去
                handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
            } else {
                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
            }
        }
    }
    
    private static Handler getHandler() {
        synchronized (sLock) {
            if (sHandler == null) {
            //创建一个 handlerThread ,并执行 start 方法
            //这就是 apply 写到磁盘的线程
                HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                        Process.THREAD_PRIORITY_FOREGROUND);
                handlerThread.start();

                sHandler = new QueuedWorkHandler(handlerThread.getLooper());
            }
            return sHandler;
        }
    }
// Handler 的处理
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 void processPendingWork() {
        long startTime = 0;

       
        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);
            }
            //一个一个执行 run 方法,
            if (work.size() > 0) {
                for (Runnable w : work) {
                    w.run();
                }

                
                }
            }
        }
    }
image.png
QueuedWork.addFinisher(awaitCommit);

在上面的方法中注意到 对每个 apply 都会创建一个相应的 awaitCommit,并添加到 QueuedWork 的一个队列中,但是在 QueuedWork 注意到有这样一个方法 waitToFinish


    /**
     * Trigger queued work to be processed immediately. The queued work is processed on a separate
     * thread asynchronous. While doing that run and process all finishers on this thread. The
     * finishers can be implemented in a way to check weather the queued work is finished.
     *
     * 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() {}

这个方法会在 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理后被调用,并且调用这个方法的目的是为了确保异步任务被及时完成。


image.png

可以看到 waitToFinish 都是在 ActivityThread 中,也就是主线程调用的

public static void waitToFinish() {
       
        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);

            }

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

        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        try {
        //这个方法就会执行 所有的 Runnable 的run 返回
        //这个时候 processPendingWork 是执行在主线程中
        
            processPendingWork();
        } finally {
            StrictMode.setThreadPolicy(oldPolicy);
        }

        try {
            while (true) {
                Runnable finisher;

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

                if (finisher == null) {
                    break;
                }

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

       ...
    }

这种设计是为了保证 SP 可靠的、保证写入完成的存储机制。

apply 总结
2.commit
 public boolean commit() {
            long startTime = 0;

            if (DEBUG) {
                startTime = System.currentTimeMillis();
            }
            //这个也是先写入内存 
            MemoryCommitResult mcr = commitToMemory();
    
    
            //这里传入 null 即 postWriteRunnable 为 null
            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;
        }
 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.
        // 为 true 
        // 直接在主线程中执行 run 方法。
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }

image.png
commit 总结

总结

上一篇下一篇

猜你喜欢

热点阅读