
在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 源码实现所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)