Android SharedPreferences 源码实现

Android SharedPreferences 源码实现,第1张

概述在Android中保存数据的方式之一就是使用SharedPreferences,因为会用到这个类,所以想分析下它的源码实现。1.获取SharedPreference实例。valsharedPreference=getSharedPreferences("main",Context.MODE_PRIVATE)sharedPreference.edit().putBoolean("init",true).

在AndroID中保存数据的方式之一就是使用 SharedPreferences , 因为会用到这个类,所以想分析下它的源码实现。

1. 获取SharedPreference实例。
val sharedPreference = getSharedPreferences("main", Context.MODE_PRIVATE)sharedPreference.edit().putBoolean("init", true).apply()

通常使用方式是通过 Context 获取一个 SharedPreferences 实例,下面看下具体的源码。

1.1 通过 ContextImpl 来获取SharedPreferencesImpl

class:: ContextImpl@OverrIDepublic 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.    if (mPackageInfo.getApplicationInfo().targetSdkVersion <            Build.VERSION_CODES.KITKAT) {        if (name == null) {            name = "null";        }    }    file file;    synchronized (ContextImpl.class) {        if (mSharedPrefsPaths == null) {            mSharedPrefsPaths = new ArrayMap<>();        }        file = mSharedPrefsPaths.get(name);        if (file == null) {            file = getSharedPreferencesPath(name);            mSharedPrefsPaths.put(name, file);        }    }    return getSharedPreferences(file, mode);}@OverrIDepublic file getSharedPreferencesPath(String name) {    return makefilename(getPreferencesDir(), name + ".xml");}
ContextImpl内部全局变量 mSharedPrefsPaths 是一个 ArrayMap<String, file> 的map容器,其中的key对应的是String类型的 name, value 对应的file是SharedPreferences保存数据的地方。SharedPreferences 中的文件是 xml 文件
@OverrIDepublic SharedPreferences getSharedPreferences(file file, int mode) {    SharedPreferencesImpl sp;    synchronized (ContextImpl.class) {        final ArrayMap<file, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();        sp = cache.get(file);        if (sp == null) {            checkMode(mode);            ......            sp = new SharedPreferencesImpl(file, mode);            cache.put(file, sp);            return sp;        }    }    if ((mode & Context.MODE_MulTI_PROCESS) != 0 ||        getApplicationInfo().targetSdkVersion < androID.os.Build.VERSION_CODES.HONEYCOMB) {        // If somebody else (some other process) changed the prefs        // file behind our back, we reload it.  This has been the        // historical (if undocumented) behavior.        sp.startReloadIfChangedUnexpectedly();    }    return sp;}@GuardedBy("ContextImpl.class")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;}
sSharedPrefsCache 是一个静态变量,ArrayMap类型,维护了 file 和 SharedPreferencesImpl 的对应关系。因为是静态变量,所以 SharedPreferencesImpl 创建后会一直缓存在 sSharedPrefsCache 这个ArrayMap中。如果 sSharedPrefsCache 没有这个file对应的 SharedPreferencesImpl 对象,则直接创建。

name -> file -> SharedPreferences实例。 上面的代码描述了一种转换关系,把name转换成file, 再从缓存中取 SharedPreferences 实例。这比用户传 file 的方式更简便。

1.2 SharedPreferencesImpl 构造方法实现

@UnsupportedAppUsageSharedPreferencesImpl(file file, int mode) {    mfile = file;    mBackupfile = makeBackupfile(file);    mMode = mode;    mloaded = false;    mMap = null;    mThrowable = null;    startLoadFromdisk();}

构造方法中的一些参数需要描述一下他们的作用:

mfile: 这个是SharedPreferences保存数据的地方,是一个xml类型的文件mloaded: 这个和mMap有关,startLoadFromdisk() 方法会读取 mfile 里面的内容并保存到 mMap 中,加载过程中 mloaded = false, 加载完之后 mloaded = true.
@UnsupportedAppUsageprivate voID startLoadFromdisk() {    synchronized (mlock) {        mloaded = false;    }    new Thread("SharedPreferencesImpl-load") {        public voID run() {            loadFromdisk();        }    }.start();}

可以看到,调用 startLoadFromdisk() 方法时会先设置 mloaded = false, 然后创建一个线程去读取 mfile 里面的内容。

private voID loadFromdisk() {        synchronized (mlock) {            if (mloaded) {                return;            }            if (mBackupfile.exists()) {                mfile.delete();                mBackupfile.renameTo(mfile);            }        }        // DeBUGging        if (mfile.exists() && !mfile.canRead()) {            Log.w(TAG, "Attempt to read preferences file " + mfile + " without permission");        }        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>) XmlUtils.readMapXml(str);                } catch (Exception e) {                    Log.w(TAG, "Cannot read " + mfile.get@R_419_4613@Path(), e);                } finally {                    IoUtils.closeQuIEtly(str);                }            }        } catch (ErrnoException e) {            // An errno exception means the stat @R_301_5138@. Treat as empty/non-existing by            // ignoring.        } catch (Throwable t) {            thrown = t;        }        synchronized (mlock) {            mloaded = true;            mThrowable = thrown;            // It's important that we always signal waiters, even if we'll make            // them fail with an exception. The try-finally is pretty wIDe, but            // better safe than sorry.            try {                if (thrown == null) {                    if (map != null) {                        mMap = map;                        mStatTimestamp = stat.st_mtim;                        mStatSize = stat.st_size;                    } else {                        mMap = new HashMap<>();                    }                }                // In case of a thrown exception, we retain the old map. That allows                // any open editors to commit and store updates.            } catch (Throwable t) {                mThrowable = t;            } finally {                mlock.notifyAll();            }        }    }
loadFromdisk() 方法会先判断 mloaded 是否为 true , 如果为 true 则直接返回,代表这个文件是已经加载完了的,因为SharedPreferences是线程共享的,所以存在已经被其他线程加载完的可能。通过XmlUtils加载文件流,创建一个 map 对象,加载完之后设置 mloaded = true. 并把这个 map 对象赋值给 mMap.

总结如下:

SharedPreferences 是一种永久存储数据的方式,通过 xml 文件,保存key-value的对应关系。创建SharedPreferencesImpl实例时会调用 startLoaddisk() 读取 mfile 中保存的数据,这个 *** 作是在子线程中实现的.mMap保存 mfile 中的数据,可以提高 SharedPreferences 获取key对应的value的速率。2. SharedPreferences 保存和读取数据
val sharedPreference = getSharedPreferences("main", Context.MODE_PRIVATE)sharedPreference.getBoolean("init", false)sharedPreference.edit().putBoolean("init", true).apply()

上面代码分别是 get 和 set 的使用方式现在看下源码实现

2.1 getXXX() 实现

@OverrIDepublic boolean getBoolean(String key, boolean defValue) {    synchronized (mlock) {        awaitLoadedLocked();        Boolean v = (Boolean)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 {            mlock.wait();        } catch (InterruptedException unused) {        }    }    if (mThrowable != null) {        throw new IllegalStateException(mThrowable);    }}

这里就说下 mloaded 为false的状态,这个状态下在构造方法中创建的线程还没有完成读取 mfile 的逻辑,这种情况下,mMap 还为 null, 所以是读取不到对应的 value 的。所以主线程会被阻塞,直到 SharedPreferencesImpl-load 线程完成加载逻辑。之后再从 mMap 中取 value.

2.2 setXXX() 实现

@OverrIDepublic Editor edit() {    // Todo: remove the need to call awaitLoadedLocked() when    // requesting an editor.  will require some work on the    // Editor, but then we should be able to do:    //    //      context.getSharedPreferences(..).edit().putString(..).apply()    //    // ... all without blocking.    synchronized (mlock) {        awaitLoadedLocked();    }    return new EditorImpl();}

第一步是调用 sharedPreferences.edit() 获取一个 Editor 对象,这个对象的实现类是 EditorImpl , 而且这个过程也要等 SharedPreferencesImpl-load 加载完。

@OverrIDepublic Editor putBoolean(String key, boolean value) {    synchronized (mEditorLock) {        mModifIEd.put(key, value);        return this;    }}@OverrIDepublic 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.addFinisher(awaitCommit);    Runnable postWriteRunnable = new Runnable() {            @OverrIDe            public voID run() {                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);}
putxxx() 方法会把新的一对 key-value 保存到 mModifIEd 中。commitToMemory() 会获取 SharedPreferencesImpl 中的mMap对象,对比 mModifIEd 中的key, 如果有 mMap 中没有的 key 或者 key相同但 value 不同的情况下,MemoryCommitResult 中的 memoryStateGeneration 就会加1,代表有变动。
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 wri    // the current thread.    if (isFromSyncCommit) {        boolean wasEmpty = false;        synchronized (mlock) {            wasEmpty = mdiskWritesInFlight == 1;        }        if (wasEmpty) {            writetodiskRunnable.run();            return;        }    }    QueueDWork.queue(writetodiskRunnable, !isFromSyncCommit);}
如果 postWriteRunnable == null. 那么 isFromSyncCommit = true, 此时就会在当前线程调用 writetodiskRunnable.run() 这是一个把 MemoryCommitResult 中的改动同步到 mfile 的过程,一个IO *** 作,当调用 commit() 时,postWriteRunnable = null.apply() 方法会传递一个 postWriteRunnable , 所以同步的 Runnable 就会交给 QueueWork 去处理。QueueWork 内部是一个 HandlerThread , 所有任务都会存放到一个 linkedList 中,然后在handleMessage()方法中循环的取任务执行。 总结

以上是内存溢出为你收集整理的Android SharedPreferences 源码实现全部内容,希望文章能够帮你解决Android SharedPreferences 源码实现所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/web/1033089.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-24
下一篇2022-05-24

发表评论

登录后才能评论

评论列表(0条)

    保存