SharedPreferense原理和缺陷分析

本文深入探讨了Android的SharedPreferences实现原理,包括其初始化、读写数据过程,以及存在的问题。SharedPreferences以XML文件形式存储数据,使用时会从磁盘读取并缓存到内存。读操作会阻塞调用线程直至文件读取完毕,可能存在UI流畅度影响。写操作通过Editor异步或同步保存,commit是同步写入,apply是异步。文件更新全量覆盖,可能导致高成本。同时,SP具有线程安全但不支持跨进程,频繁使用可能导致ANR。为优化,建议减少单个SP中的数据量,合理使用commit和apply,以及注意跨进程场景下的数据共享问题。
摘要由CSDN通过智能技术生成

SharedPreferense 实现原理

简介

SharedPreferences是Android提供给我们的用于存储轻量级K-V数据的持久化方案。以XML文件的形式存储在/data/data/packageName/的shared_prefs文件夹

它提供了 putString()、putString(Set)、putInt()、putLong()、putFloat()、putBoolean() 六种数据类型。(注意没有Double)

使用示例

//根据文件名,获取SharedPreferences对象;mode一般都使用MODE_PRIVATE,只能由该App访问
SharedPreferences sp = context.getSharedPreferences("setting", Context.MODE_PRIVATE)
//根据key,获取指定值
Boolean needInitChannels = sp.getBoolean("isDebug", false)
//获取Editor编辑对象,用于编辑SharedPreferences
SharedPreferences.Editor editor = sp.edit()
editor.putBoolean("isDebug",true)
editor.putLong("isLong",1000)
//同步提交到SharedPreferences文件,获取是否同步成功的结果
Boolean res = editor.commit()
//异步提交到SharedPreferences文件
editor.apply()

当我们第一次访问一个名为"setting"的SharedPreferences文件,系统会在应用数据目录下(/data/data/packageName/)的shared_prefs文件夹下,创建一个同名的setting.xml文件。

存储的xml文件格式如下:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <long name="isLong" value="1000" />
    <boolean name="isDebug" value="true" />
   
    <!-- <float name="isFloat" value="1.5" />
    <string name="isString">Android</string>
    <int name="isInt" value="1" />
    <set name="isStringSet">
        <string>element 1</string>
        <string>element 2</string>
        <string>element 3</string>
    </set> -->
</map>
原理分析
初始化

我们在使用SP之前会先通过context.getSharedPreferences()获取SP的实例对象, context的实现类是ContextImpl, 看下ContextImpl的getSharedPreferences实现:

public SharedPreferences getSharedPreferences(String name, int mode) {
        if (mPackageInfo.getApplicationInfo().targetSdkVersion <
                Build.VERSION_CODES.KITKAT) {
            if (name == null) {
                name = "null";   //name为null,则文件命名为null.xml
            }
        }

        File file;
        synchronized (ContextImpl.class) { //加锁同步
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            file = mSharedPrefsPaths.get(name);
            if (file == null) {
                file = getSharedPreferencesPath(name);
                //mSharedPrefsPaths缓存文件名和文件映射
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

这里有一个重要的参数mSharedPrefsPaths

private ArrayMap<String, File> mSharedPrefsPaths;

它是一个ArrayMap,缓存了文件名和文件对象的映射。初始化获取时会先从缓存里获取对应的文件对象,没有再去创建文件并缓存。

接着通过getSharedPreferences(file, mode)获取SharedPreferences对象:

@Override
    public 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);
        // ... new一个实例
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
       //.....
        return sp;
    }

同样可以看到,这里对SharedPreferences的实例对象SharedPreferencesImpl也进行了缓存。

getSharedPreferences获取缓存:

 @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时ContextImpl的静态变量,缓存了packageName-ArrayMap<File, SharedPreferencesImpl>

private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

也就是说sSharedPrefsCache缓存了同一个应用包名的ArrayMap<File, SharedPreferencesImpl>集合,一个文件对应一个SharedPreferencesImpl对象。

也就是说,一个name会对应一个SharedPreferences的File实例,而一个File会对应一个SharedPreferencesImpl实例。并且对File实例和SharedPreferencesImpl实例对象都进行了缓存

首次使用 getSharedPreferences 时,内存中不存在 SP 以及 SP Map 缓存,需要创建 SP 并添加到 ContextImpl 的静态成员变量(sSharedPrefs)中。

sp = new SharedPreferencesImpl(file, mode);

SharedPreferencesImpl构造方法

 SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        mThrowable = null;
        startLoadFromDisk();
    }
static File makeBackupFile(File prefsFile) {
        return new File(prefsFile.getPath() + ".bak");
    }

makeBackupFile 用来定义备份文件,命名为 “xml同名.bak”, 该文件在写入磁盘时会用到,用来备份文件,在写入失败异常的情况下,下次使用从备份文件恢复,这样就只需丢弃写入失败的数据,而之前的数据还能恢复。

 @UnsupportedAppUsage
    private void startLoadFromDisk() {
        synchronized (mLock) {
            mLoaded = false;
        }
        //开启异步线程从磁盘读取文件,加锁防止多线程并发操作
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                loadFromDisk();
            }
        }.start();
    }

    private void loadFromDisk() {
        synchronized (mLock) {  //加锁
            if (mLoaded) { //已经加载过
                return;
            }
            //备份文件存在,说明上次写入失败,直接从备份文件恢复到mFile
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(m
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是实现该功能的代码: 1. 在注册页面的布局文件中添加姓名和年龄的输入框,以及确认按钮: ```xml <EditText android:id="@+id/et_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入姓名"/> <EditText android:id="@+id/et_age" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="number" android:hint="请输入年龄"/> <Button android:id="@+id/btn_confirm" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="确认"/> ``` 2. 在注册页面的Activity中,获取输入框信息,存入SharedPreferense,并在确认按钮的点击事件中进行比较: ```java public class RegisterActivity extends AppCompatActivity { private EditText etName; private EditText etAge; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_register); etName = findViewById(R.id.et_name); etAge = findViewById(R.id.et_age); Button btnConfirm = findViewById(R.id.btn_confirm); btnConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 获取姓名和年龄 String name = etName.getText().toString(); int age = Integer.parseInt(etAge.getText().toString()); // 存入SharedPreferense SharedPreferences sp = getSharedPreferences("user_info", MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("name", name); editor.putInt("age", age); editor.apply(); // 从SharedPreferense获取信息并进行比较 String savedName = sp.getString("name", ""); int savedAge = sp.getInt("age", 0); if (name.equals(savedName) && age == savedAge) { Toast.makeText(RegisterActivity.this, "注册成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(RegisterActivity.this, "注册失败", Toast.LENGTH_SHORT).show(); } } }); } } ``` 在上述代码中,我们通过SharedPreferences来存储用户的姓名和年龄信息,并在确认按钮的点击事件中,从SharedPreferences中获取保存的信息并进行比较,以判断用户输入的信息是否正确。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vinson武

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值