SharedPreferences详解

  1. SharedPreferences

    SharedPreferences在文件中以键值对形式保存数据,适合保存少量数据。使用SharedPreferences可以通过下面两种方式:

    • 通过Context的getSharedPreferences()

    • 通过Activity的getSharedPreferences()

通过Context的方式是当前App都可以访问或者有相同user ID的其他App也可以访问,通过Activity是只能当前这个activity对象访问,并且这个SharedPreferences的文件名是activity的类名。
有几种操作模式:

Context.MODE_PRIVATE
Context.MODE_WORLD_READABLE
Context.MODE_WORLD_WRITEABLE
Context.MODE_MULTI_PROCESS
第一种是默认的,后面三个被废弃了,分别表示可以被其他的App读取和写入以及多进程中访问Preferences时设置会在修改时检查避免同时修改文件,有的提到了MODE_APPEND,这个是用在openFileOutput中的,文件存在会在文件后面追加而不是覆盖,Preferences没有这个标志,但实际上Preferences也是对文件的操作,只不过的相关操作中没有用到。

其中MODE_PRIVATE表示是只能被当前App访问或者被有相同user ID的App们访问,相同的user ID表示在manifest中设置android:sharedUserId=”com.my.app”,并且使用相同的签名打包,获取方法方法:
Context ctx = createPackageContext(“com.my.app.first”, CONTEXT_RESTRICTED);
prefs = context.getSharedPreferences(“PreferencesName”, Context.MODE_PRIVATE);
虽然可以用但是可能还是会有问题,B如果打算获取A的Preferences,有可能只能获取到B自己的,获取不到A的,必须在调用B自己的getSharedPreferences()之前先调用获取A的getSharedPreferences(),(具体参见How to read other app’s SharedPreferences (same user ID)?),

下面看一下具体的实现:
Context的实现类是ContextImpl,SharedPreferences的实现类是SharedPreferencesImpl,
ContextImpl中的getSharedPreferences():

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            if (sSharedPrefs == null) {
                sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
            }
            final String packageName = getPackageName();
            ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
            if (packagePrefs == null) {
                packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
                sSharedPrefs.put(packageName, packagePrefs);
            }

            // 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";
                }
            }
            sp = packagePrefs.get(name);
            if (sp == null) {
                File prefsFile = getSharedPrefsFile(name);
                sp = new SharedPreferencesImpl(prefsFile, mode);
                packagePrefs.put(name, 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;
    }

sSharedPrefs 是一个静态变量,是一个Map,保存包名和包名对应的SharedPreferences对象,不存在就创建,保证只有一个实例,其中SharedPreferences的名字可以为null,也就是getSharedPreferences(String name, int mode)中可以name==null。
SharedPreferencesImpl中

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        startLoadFromDisk();
    }

mFile 是保存Preferences中数据的file,mBackupFile 是mFile的备份,mMode 就是getSharedPreferences中的mode,mMap 是Preferences中的键值对,startLoadFromDisk是读取数据,

    private void startLoadFromDisk() {
        synchronized (this) {
            mLoaded = false;
        }
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                synchronized (SharedPreferencesImpl.this) {
                    loadFromDiskLocked();
                }
            }
        }.start();
    }

使用了线程锁,保证同时只有一个线程访问,然后子线程调用loadFromDiskLocked(),

    //部分删减
    private void loadFromDiskLocked() {
        //......
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
        //......
        try {
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16*1024);
                    map = XmlUtils.readMapXml(str);
                } 
                //.....
            }
        }
        mLoaded = true;
        if (map != null) {
            mMap = map;
            mStatTimestamp = stat.st_mtime;
            mStatSize = stat.st_size;
        } 
        //......
    }

其中数据从mBackupFile读取出来,赋值给mMap,是一次全部读取,所以Preferences不适合保存大量数据,否则效率很慢,而使用mBackupFile是为了在提交操作写mFile文件时读取的数据没问题,虽然数据可能是旧的,这也符合逻辑,如果采用异步提交,立即读取数据就有可能还是旧的。
继续往下看,看读取和写入操作:
读取

    public int getInt(String key, int defValue) {
        synchronized (this) {
            awaitLoadedLocked();
            Integer v = (Integer)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

以getInt为例,同样加了线程锁,保证同时只有一个线程访问Preferences,直接从mMap读取,为什么不是重新读文件然后获取就必须要讲讲提交数据了,这里猜测可能是在put数据或commit时赋值给了mMap,下面看看是不是。
提交操作都是封装在Editor类中,实现类是EditorImpl,以putInt为例:

        public Editor putInt(String key, int value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }

同样是加了同步锁,注意这里是保存到了mModified中,这样给mMap赋值可能就是在commit中,提交操作设计的方法有几个分别是apply,commit,commitToMemory,apply和commit都调用了commitToMemory,

        //部分删减
        private MemoryCommitResult commitToMemory() {
            synchronized (SharedPreferencesImpl.this) {
                mcr.mapToWriteToDisk = mMap;//提交写文件时将mcr.mapToWriteToDisk中保存的数据写入文件
                mDiskWritesInFlight++;
                synchronized (this) {
                    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 (!mMap.containsKey(k)) {
                                continue;
                            }
                            mMap.remove(k);
                        } else {
                            if (mMap.containsKey(k)) {
                                Object existingValue = mMap.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }
                            mMap.put(k, v);
                        }
                    }
                    mModified.clear();
                }
            }
            return mcr;
        }

这里面进行了赋值,在分别看看apply和commit,

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

            QueuedWork.add(awaitCommit);

            Runnable postWriteRunnable = new Runnable() {
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.remove(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);
        }
        public boolean commit() {
            MemoryCommitResult mcr = commitToMemory();
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            }
            notifyListeners(mcr);
            return mcr.writeToDiskResult;
        }

可以看出基本上就是SharedPreferencesImpl.this.enqueueDiskWrite的参数不一样,一个传了postWriteRunnable,一个是null,看一下enqueueDiskWrite:

    private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        final Runnable writeToDiskRunnable = new Runnable() {
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        writeToFile(mcr);
                    }
                    synchronized (SharedPreferencesImpl.this) {
                        mDiskWritesInFlight--;
                    }
                    if (postWriteRunnable != null) {
                        postWriteRunnable.run();
                    }
                }
            };
        final boolean isFromSyncCommit = (postWriteRunnable == null);
        // Typical #commit() path with fewer allocations, doing a write on
        // the current thread.
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (SharedPreferencesImpl.this) {
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                writeToDiskRunnable.run();
                return;
            }
        }
        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
    }

其中isFromSyncCommit这个变量控制,当postWriteRunnable参数不为空时为false,会执行QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);在子线程中执行写文件操作,否则就在当前线程(主线程)执行写操作,这样commit就是同步提交,apply是异步提交。还有writeToFile写文件:

    //部分删减
    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 (!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);//创建一个新文件
            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);//将mcr.mapToWriteToDisk中的数据写入
            FileUtils.sync(str);
            str.close();
            //这里用到了getSharedPreferences的mode
            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
            // Writing was successful, delete the backup file if there is one.
            mBackupFile.delete();//已经提交成功,需要删除备份文件,否则再读取数据可能就是从mBackupFile中读取
            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);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值