美文网首页
SharedPreferences ANR 总结

SharedPreferences ANR 总结

作者: 海盗的帽子 | 来源:发表于2019-02-14 19:13 被阅读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)解释如下:

    • 对于任何特定的 SP ,所有客户端共享此类的单个实例(也就是应该使用单例模式)。对 SP 数据的修改必须通过一个 SharedPreferences.Editor 对象来确保 SP 数据保持一致状态,并在它们提交存储时进行控制。从各种get 方法返回的对象必须被应用程序视为不可变的。

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

    二.获取 Sp

    获取一个 Sp 有三种方式

    • 在一个 Activity 中调用 getPreferences(int mode)
    • 使用 PreferenceManager.getDefaultSharedPreferences(Context context)
    • context.getSharedPreferences(String name, int mode)

    第一种方式和第二种方式最后都会使用 第三种方式,不同的使用的名字不同,模式现在都是为 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 总结:
    • 获取 SP 的过程是通过 synchronized 关键字保证多线程安全的。
    • 通过 Map 进行缓存 Sp 实例,因此多次调用 getSharedPreferences 几乎没有性能上的差别。
    • 获取 Sp 的时候就会通过一个线程将 xml 数据从磁盘加载到内存中。这个过程会加锁,加载完成后会设置 mLoaded 标志,并唤醒其他线程。

    三.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 方法 总结:
    • 通过 synchronized 进行线程安全保证
    • 在主线程进行获取,但是需要等加载的完成后才能进行读,所以get 方法可能造成主线程阻塞,从而导致 ANR 。
    • 加载完成后读的过程只涉及内存的读。

    四.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 总结
    • apply 没有返回值
    • apply 是在主线程将修改数据提交到内存, 然后再子线程(HandleThread)提交到磁盘
    • apply 会将 Runnble 添加到 QueueWork 中,如果主线程 Activity 暂停时或者 BroadcastReceiver 的 onReceive 方法调用后或者 service 的命令处理,就会在主线程执行 提交到硬盘的方法,这个过程就会造成主线程 ANR
    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 总结
    • commit 有返回值
    • commit 是在主线程将修改数据提交到内存, 然后再在主线程提交到磁盘
    • 用 commit 方法最保险。如果担心在主线程调用 commit 方法会出现 ANR,可以将所有的 commit 任务放到单线程池的线程里去执行。

    总结

    • Sp 主线程 getXX 方法会 ANR
    • Sp apply 方法会 ANR
    • Sp 主线程调用 commit 方法 ANR

    相关文章

      网友评论

          本文标题:SharedPreferences ANR 总结

          本文链接:https://www.haomeiwen.com/subject/eujyeqtx.html