Read after me please:性能优化是一条漫长的路,走着走着就迷了路,一直走,一直迷…

小小简介
SharedPreference是Android系统中一种简单的、轻量级的文件存储,它是一种持久化的存储方式,以名称/值对(NVP)机制存放在xml中map根标签下,正如其名,它比较适合一些简单数据的存储,用于保存Int、long、boolean、String、Float、Set这些数据类型,可以在data/data/应用程序/shared_prefs的目录下可以查找到保存的xml文件。
基本使用方式
- 获取SharedPreference对象
SharedPreferences sp = context.getSharedPreferences(PREFERENCES_NAME,Context.MODE_PRIVATE);
- 获取 SharedPreferences.Editor
SharedPreferences.Editor edit = sp.edit();
- 存储数据
edit.putString(String key,String value);
edit.putStringSet(String key, Set<String> values);
edit.putLong(String key,long value);
edit.putFloat(String key,float value);
edit.putBoolean(String key,boolean value);
edit.putInt(String key,int value);
edit.commit();//同步写入,频繁读取会阻塞主线程,引起ANR
edit.apply();//异步写入,不关心结果,官方推荐,速度与性能较好
- 获取数据
sp.getString(String key,String value);
sp.getStringSet(String key, Set<String> values);
sp.getLong(String key,long value);
sp.getFloat(String key,float value);
sp.getBoolean(String key,boolean value);
sp.getInt(String key,int value);
这些基本使用方式应该不用啰嗦去介绍了,本文主要谈一些SP存储上的优化建议?,下图为自己在SP读写性能上的测试,仅供参考:
耗时统计
存储方式 | 数据条数 | 耗时/ms |
---|---|---|
多次commit | 1000 | 15052 |
一次性commit | 1000 | 24 |
多次apply | 1000 | 588 |
一次性apply | 1000 | 12 |
json | 1000 | 73 |
从耗时统计上看,在操作上,明显能看出频繁执行commit与apply操作是非常耗时的,那么卡顿的解决方案也呼之而出;在使用上,使用commit与apply的区别,虽然都是保存数据,但是在性能和耗时上也略显不同;在数据格式上,json文件与简单的KV数据性能上的差别。下面是结合这个测试结果给出的一些建议:
关于优化的建议
- 初始化上
1.建议在Application中初始化,重写attachBaseContext方法,SharedPreference的context传入
Application对象即可,最好使用单例,不必每次都获取Sp对象,减少开销。
2.如果项目中使用了MultiDex,存在分包,请在分包前即MultiDex.install()之前或者在multidex执行的这段时间初始化,因为这时cpu是利用不满的,我们没有办法充分利用CPU的原因,是因为如果我们在Multidex之前执行一些操作,我们很有可能因为这样一些操作的类或者是相关的类不在我们的主dex当中,在四点几的类中会直接崩溃,但是由于sharePreference不会产生这种崩溃,它是系统的类。 - 使用中
1.请不要使用SharedPreference存储大文件及存储大量的key和value,这样的话会造成界面卡顿或者ANR
比较占内存,记住它是简单存储,如果有类似的需求请考虑数据库、磁盘文件存储等等。
2.推荐使用apply进行存储,这也是官方推荐,当读入内存后,因为它是异步写入磁盘的,所以效率上会比commit好,如果你需要存储状态或者即存即用的话还是尽量使用commit。
3.请不要频繁使用apply与commit,如果存在这样的问题,请合并一次性apply与commit,可以参考封装一个map的结合,一次性提交,因为SharedPreference可能会存在IO瓶颈和锁性能差的问题。
4.尽量不要存放Json及html,数据少可以,无需担心,大量的话请放弃。
5.不要所有的数据都存在一个文件,不同类型的文件或者数据可以分开多个文件存储,避免使用一个大文件,这样可提高读取速度。
6.跨进程操作不要使用MULTI_PROCESS标志,而是使用contentprovide等进程间通信的方式。
7.如果你的项目对于存储性能要求非常高的情况,可以考虑放弃系统的SharedPreference存储,推荐你使用腾讯的高性能组件MMKV,目前超过7k+的star。

SP工具类
封装是基于AndroidUtilCode进行的部分改造,增加了提交Map的操作,方便一次性commit与apply,操作时请直接使用SpHelpUtils辅助类,需要的话直接复制+粘贴,减少跳转GitHub的时间,程序员就应该懒到极致。
public class SpHelpUtils {
/**
* 简单存储工具类
*/
private static SpUtils sDefaultSpUtils;
/**
* Set the default instance of {@link SpUtils}.
*
* @param spUtils The default instance of {@link SpUtils}.
*/
public static void setDefaultSpUtils(final SpUtils spUtils) {
sDefaultSpUtils = spUtils;
}
/**
* Put the string value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
*/
public static void put(@NonNull final String key, final String value) {
put(key, value, getDefaultSpUtils());
}
/**
* 提交map,一次性commit,减少频繁读写IO
*
* @param hashmap 存储集合
*/
public static void put(@NotNull final Map<String, Object> hashmap) {
put(hashmap, getDefaultSpUtils());
}
/**
* Put the string value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
* @param isCommit True to use {@link SharedPreferences.Editor#commit()},
* false to use {@link SharedPreferences.Editor#apply()}
*/
public static void put(@NonNull final String key, final String value, final boolean isCommit) {
put(key, value, isCommit, getDefaultSpUtils());
}
/**
* 提交map,一次性commit,减少频繁读写IO
*
* @param hashmap 存储集合
* @param isCommit True to use {@link SharedPreferences.Editor#commit()},
* false to use {@link SharedPreferences.Editor#apply()}
*/
public static void put(final Map<String, Object> hashmap, final boolean isCommit) {
put(hashmap, isCommit, getDefaultSpUtils());
}
/**
* Return the string value in sp.
*
* @param key The key of sp.
* @return the string value if sp exists or {@code ""} otherwise
*/
public static String getString(@NonNull final String key) {
return getString(key, getDefaultSpUtils());
}
/**
* Return the string value in sp.
*
* @param key The key of sp.
* @param defaultValue The default value if the sp doesn't exist.
* @return the string value if sp exists or {@code defaultValue} otherwise
*/
public static String getString(@NonNull final String key, final String defaultValue) {
return getString(key, defaultValue, getDefaultSpUtils());
}
/**
* Put the int value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
*/
public static void put(@NonNull final String key, final int value) {
put(key, value, getDefaultSpUtils());
}
/**
* Put the int value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
* @param isCommit True to use {@link SharedPreferences.Editor#commit()},
* false to use {@link SharedPreferences.Editor#apply()}
*/
public static void put(@NonNull final String key, final int value, final boolean isCommit) {
put(key, value, isCommit, getDefaultSpUtils());
}
/**
* Return the int value in sp.
*
* @param key The key of sp.
* @return the int value if sp exists or {@code -1} otherwise
*/
public static int getInt(@NonNull final String key) {
return getInt(key, getDefaultSpUtils());
}
/**
* Return the int value in sp.
*
* @param key The key of sp.
* @param defaultValue The default value if the sp doesn't exist.
* @return the int value if sp exists or {@code defaultValue} otherwise
*/
public static int getInt(@NonNull final String key, final int defaultValue) {
return getInt(key, defaultValue, getDefaultSpUtils());
}
/**
* Put the long value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
*/
public static void put(@NonNull final String key, final long value) {
put(key, value, getDefaultSpUtils());
}
/**
* Put the long value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
* @param isCommit True to use {@link SharedPreferences.Editor#commit()},
* false to use {@link SharedPreferences.Editor#apply()}
*/
public static void put(@NonNull final String key, final long value, final boolean isCommit) {
put(key, value, isCommit, getDefaultSpUtils());
}
/**
* Return the long value in sp.
*
* @param key The key of sp.
* @return the long value if sp exists or {@code -1} otherwise
*/
public static long getLong(@NonNull final String key) {
return getLong(key, getDefaultSpUtils());
}
/**
* Return the long value in sp.
*
* @param key The key of sp.
* @param defaultValue The default value if the sp doesn't exist.
* @return the long value if sp exists or {@code defaultValue} otherwise
*/
public static long getLong(@NonNull final String key, final long defaultValue) {
return getLong(key, defaultValue, getDefaultSpUtils());
}
/**
* Put the float value in sp.
*
* @param key The key of sp.
* @param value The value of sp.
*/
public static void put(@NonNull final String key, final float value) {
put(key, value, getDefaultSpUtils());
}
/**
* Put the float value in sp.
*
* @param key The k