SharedPreferences使用性能优化

前言:SharedPreferences是开发中很常见的一个类,它的主要作用是持久化存储本地的一些轻量级数据,便于我们做一些简单的数据存储和逻辑判断,因为它简单和无结构化的特点,对于一般简单的业务场景来说,比数据库更加实用,本文主要说明一下在使用过程中的性能优化注意事项。

1、避免存储大量数据

SharedPreferences设计之初就是为了提供一个轻量级的数据存储方案,所以它不能和数据库相比,如果一个SharedPreferences存放的数据过多,在我们调用get*()的时候就会进行长时间的加载,会影响到主线程,所以尽量保证SharedPreferences中存放的数据不要太多,对于一些结构化的数据我们应该考虑是否使用数据库进行存放更为合适。

每次调用apply()或者commit()都是建立一个空文件,然后将所有的数据一次性写入,而不是增量写入。可以看看源码(SharedPreferences是个接口,具体实现由SharedPreferencesImpl实现类完成)

@Override
public boolean commit() {
    long startTime = 0;

    if (DEBUG) {
        startTime = System.currentTimeMillis();
    }

    MemoryCommitResult mcr = commitToMemory();

    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;
}

所以数据越大耗时就越长,就越有可能造成ANR。

几乎所有的App都会在Application中使用SharedPreferences做一些逻辑判断,所以推荐在Application中和MainActivity中使用的SharedPreferences是一个特有的,这个特有的SharedPreferences仅仅存放启动时必要的少量数据,因为初始化用到的数据不会太多,最开始SharedPreferences的缓存也是空的,更小的SharedPreferences可以让取值过程加快,也降低了App首次启动的内存消耗,提高启动速度和性能。

2、尽可能提前初始化

SharedPreferences在没有缓存的时候初始化是比较耗时的,一般写代码是这样的:

sp = context.getSharedPreferences("sp_dwf_user", Context.MODE_PRIVATE);
editor = sp.edit();
editor.putString(key, (String) obj).commit();

这种初始化后立马操作对象的方式是正常写法,但是也比较低效。为了尽可能在edit()之前就让SharedPreferences加载完成,可以将当前页面需要的SharedPreferences对象放在activity的onCreate()或者fragment的onCreate()中创建,这样它就会加载到内存中,建立好缓存,在我们需要用到的时候立马开始工作。

3、避免key过长

先来看看SharedPreferences中用到的数据结构:
1.ContextImpl中sSharedPrefsCache是一个ArrayMap:

class ContextImpl extends Context {
    ...
    /**
     * Map from package name, to preference name, to cached preferences.
     */
    @GuardedBy("ContextImpl.class")
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
	...
}

2.缓存SharedPreferencesImpl的cache是一个ArrayMap:

@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
    SharedPreferencesImpl sp;
    synchronized (ContextImpl.class) {
        final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
        sp = cache.get(file);
        ...
    }
}

3.SharedPreferencesImpl内部的mMap是HashMap:

if (thrown == null) {
    if (map != null) {
        mMap = map;
        mStatTimestamp = stat.st_mtim;
        mStatSize = stat.st_size;
    } else {
        mMap = new HashMap<>();
    }
}

为什么一会儿用ArrayMap,一会儿用HashMap呢?

从Android角度来说,当数据有下列特征时可以选择ArrayMap:
(1)数据量小(<1000)时,因为二分查找,所以速度更快。
(2)数据量小,无需考虑扩容的情况。
(3)运行期间不执行或较少执行remove操作。
(4)数据的插入操作十分低频。
(5)本身包含子map对象,即map中存map的结构。

一般App开发中用到的SharedPreferences个数很有限,一般人不会用超过20个,所以源码中用到了ArrayMap。而SharedPreferences内部存值用到了HashMap,因为HashMap的插入和查找效率都很高,并且系统也不知道开发者会存多少数据到SharedPreferences中,所以用HashMap能更好的扩容。

HashMap的数据结构本身就是比较耗内存的,甚至在没有数据的时候,初始化的时候已经分配了空间,所以会有基础的内存开销,而ArrayMap=0。

最后,如果SharedPreferences中存放的key过长,那么计算hashcode的时间就会加长,这肯定会影响效率,所以一般key不要太长,除非是为了某些情况取名冲突。

4、多次操作,批量提交

每次代码提交都是有性能代价的,官方推荐多次修改完成后,进行一次性提交。

一般情况下,SharedPreferences的效率并不会成为App性能的障碍,举个典型的例子,用户退出App时会保存用户的当前状态,我们可以将这种大量的相关性数据变成一个javabean对象,方便一次性存取。

5、缓存Editor对象

在SharedPreferencesImpl中,Editor对象的源码如下:

@Override
public 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();
}

也就是说每次调用edit()方法都会new出一个editor对象,这没有必要,如果我们当前类中有多处使用editor的地方,可以将editor变成类的成员变量,这样会减少内存波动。

6、不存放HTML和JSON

对于HTML和JSON数据,很多开发者都是直接toString()或者Json.toJsonString()后就放到SharedPreferences中,当然,SharedPreferences是可以存放string类型的数据,但是这样的数据存放在XML中会出现很多转义符号,在解析取出值的时候,如果系统碰到特殊符号就会进行特殊的处理,从而引发额外的字符串拼接以及函数调用。这类文件往往都很大,取值时SharedPreferences会将它直接缓存到内存中,进行静态持有直到App退出时才会被释放。

SharedPreferences虽然好用,但是需要思考这些对象是否可以直接存放在磁盘中,她们是否真的需要key-value关系。

7、拆分高频和低频操作

尽量避免写完之后立刻读值的操作,这种高频读写的操作放在一起就会出现因为锁而引起的开销。对于高频写操作的值与高频读操作的值,可以适当的拆分到不同的SharedPreferences中,可以减少一个文件中同步锁的竞争。当然这种情况也不常见,如果项目没有遇到性能瓶颈,可以忽略。

发布了87 篇原创文章 · 获赞 161 · 访问量 87万+

猜你喜欢

转载自blog.csdn.net/woshizisezise/article/details/103841574
今日推荐