Android SharedPreferences源码分析

这篇具有很好参考价值的文章主要介绍了Android SharedPreferences源码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Android SharedPreferences源码分析

概述

SharedPreferences 是 Android 平台上轻量级的 K-V 存储框架。

SharedPreferences 采用 XML 文件格式持久化键值对数据,文件的存储位置位于应用沙盒的内部存储 /data/data/<包名>/shared_prefs/ 位置,每个 XML 文件对应于一个 SharedPreferences 对象。

一个sp文件(XML文件) 对应一个 SharedPreferences 对象。

基本使用

SharedPreferences sp = context.getSharedPreferences("app", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("name", "小明")
        .putInt("age", 18)
        .putBoolean("sex", true)
        .apply();

String name = sp.getString("name", "");
int age = sp.getInt("age", 0);
boolean sex = sp.getBoolean("sex", false);

查看 /data/data/com.example.myapplication/shared_prefs/app.xml 文件:

Android SharedPreferences源码分析,# Android源码分析,android,SP源码分析

流程图

SP内存模型图

Android SharedPreferences源码分析,# Android源码分析,android,SP源码分析

SP初始化流程图

Android SharedPreferences源码分析,# Android源码分析,android,SP源码分析

读取数据流程图

Android SharedPreferences源码分析,# Android源码分析,android,SP源码分析

写入数据流程图

commit()

Android SharedPreferences源码分析,# Android源码分析,android,SP源码分析

apply()

Android SharedPreferences源码分析,# Android源码分析,android,SP源码分析

源码分析

获取SP对象

ContextImpl 类是 Context 抽象类的实现类。

// ContextImpl.java

class ContextImpl extends Context {
    // sp文件的根目录
    private File mPreferencesDir;
    // 缓存name和file对象的对应关系
    private ArrayMap<String, File> mSharedPrefsPaths;
    // 缓存包名、file对象和SharedPreferencesImpl对象的对应关系
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;

    // 通过name获取sp对象
    public SharedPreferences getSharedPreferences(String name, int mode) {      
        File file;
        synchronized (ContextImpl.class) {
            // mSharedPrefsPaths缓存对象为null则创建
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            // 从mSharedPrefsPaths缓存中获取file对象
            file = mSharedPrefsPaths.get(name);
            // 如果file对象为null,则创建file对象
            if (file == null) {
                // 通过路径获取file对象
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

    // 创建file对象
    public File getSharedPreferencesPath(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }

    // 获取sp文件的根目录
    private File getPreferencesDir() {
        synchronized (mSync) {
            if (mPreferencesDir == null) {
                mPreferencesDir = new File(getDataDir(), "shared_prefs");
            }
            return ensurePrivateDirExists(mPreferencesDir);
        }
    }

    // 通过file对象获取sp对象
    public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            // 从sSharedPrefsCache缓存获取ArrayMap
            final ArrayMap<File, SharedPreferencesImpl> cache = 							 													getSharedPreferencesCacheLocked();
            // 从缓存中获取sp对象
            sp = cache.get(file);
            if (sp == null) {
                // 如果sp对象为null,则创建并缓存
                checkMode(mode);     
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        return sp;
    }

    // 从sSharedPrefsCache缓存获取sp对象
    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        // sSharedPrefsCache缓存对象为null则创建
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }
        // 获取包名
        final String packageName = getPackageName();
        // sSharedPrefsCache通过包名获取ArrayMap
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        // packagePrefs为null则创建
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }
        return packagePrefs;
    }
}
  • 通过 name 获取对应的 file 对象,再通过 file 对象获取到 sp 对象。
  • mSharedPrefsPaths:缓存 name 和 file 对象的对应关系。
  • sSharedPrefsCache:这是一个二级映射表,第一级是缓存包名和 Map 对象,第二级是 Map 对象缓存 file 对象和 sp 对象的对应关系。

初始化

// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {
    // 目标文件
    private final File mFile;
    // 锁
    private final Object mLock = new Object();
    // 是否加载到内存中
    private boolean mLoaded = false;
    // 缓存加载的数据
    private Map<String, Object> mMap;

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false;
        mMap = null;
        mThrowable = null;
        // 开始加载文件
        startLoadFromDisk();
    }

    // 开启一个线程并加载文件
    private void startLoadFromDisk() {
        synchronized (mLock) {
            mLoaded = false;
        }
        // 开启线程
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                loadFromDisk();
            }
        }.start();
    }

    // 加载文件
    private void loadFromDisk() {
        synchronized (mLock) {
            if (mLoaded) {
                return;
            }
            // 如果有备份文件,则恢复备份文件
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
        }

        Map<String, Object> map = null;  
        if (mFile.canRead()) {
            // 读取sp文件
            BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
            // 解析sp的XML文件并转为Map
            map = (Map<String, Object>) XmlUtils.readMapXml(str);            
        }

        synchronized (mLock) {
            mLoaded = true;
            if (map != null) {
                // 使用新Map
                mMap = map;
            } else {
                mMap = new HashMap<>();
            }
            // 解析完文件后唤醒锁
            mLoaded = true;
            mLock.notifyAll();
        }
    }
}
  • 开启一个线程并读取sp文件。
  • 判断是否有备份文件,如果有则删除当前文件恢复备份文件。
  • 解析sp文件,并将 mLoad 设置为 true 表示已经读取到内存中。

读取数据

// 获取数据
public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        // 等待加载完成
        awaitLoadedLocked();
        // 从Map中读取数据
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

// 判断是否加载完成,如果没有加载完成就一直等待
private void awaitLoadedLocked() {
    while (!mLoaded) {
        try {
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
}
  • 使用 awaitLoadedLocked() 方法保证已经加载完成。

写入数据

虽然 ContextImpl 中使用了内存缓存,但是最终数据还是需要执行磁盘 IO 持久化到磁盘文件中。如果每一次 “变更操作” 都对应一次磁盘 “写回操作” 的话,不仅效率低下,而且没有必要。所以 SharedPreferences 会使用 “事务” 机制,将多次变更操作聚合为一个 “事务”,一次事务最多只会执行一次磁盘写回操作。
SharedPreferences 的事务操作由 Editor 接口实现。

// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {

    public Editor edit() {
        // 等待文件加载完成
        synchronized (mLock) {
            awaitLoadedLocked();
        }
        // 创建编辑器
        return new EditorImpl();
    }

    // 编辑器
    public final class EditorImpl implements Editor {
        // 锁对象
        private final Object mEditorLock = new Object();
        // 修改记录
        private final Map<String, Object> mModified = new HashMap<>();
        // 清除全部数据的标记
        private boolean mClear = false;

        // 存放String类型数据
        @Override
        public Editor putString(String key, @Nullable String value) {
            synchronized (mEditorLock) {
                mModified.put(key, value);
                return this;
            }
        }

        // 存放int类型数据
        @Override
        public Editor putInt(String key, int value) {
            synchronized (mEditorLock) {
                mModified.put(key, value);
                return this;
            }
        }

        // 删除数据
        @Override
        public Editor remove(String key) {
            synchronized (mEditorLock) {
                mModified.put(key, this);
                return this;
            }
        }

        // 清除所有数据
        @Override
        public Editor clear() {
            synchronized (mEditorLock) {
                mClear = true;
                return this;
            }
        }

        // 异步提交
        @Override
        public void apply() {
            ...
        }

        // 同步提交,并返回操作结果是否成功
        @Override
        public boolean commit() {
            ...
        }
    }   
}
MemoryCommitResult

MemoryCommitResult 是一个事务对象,收集需要写入磁盘的数据。

// MemoryCommitResult.java

private static class MemoryCommitResult {
    // 内存版本号
    final long memoryStateGeneration;
    // 写入磁盘的数据
    final Map<String, Object> mapToWriteToDisk;
    // 同步计数器  
    final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);

    @GuardedBy("mWritingToDiskLock")
    volatile boolean writeToDiskResult = false;
    boolean wasWritten = false;

    void setDiskWriteResult(boolean wasWritten, boolean result) {
        this.wasWritten = wasWritten;
        writeToDiskResult = result;
        // 唤醒等待锁
        writtenToDiskLatch.countDown();
    }
}
commitToMemory()
// SharedPreferencesImpl.java

final class SharedPreferencesImpl implements SharedPreferences {
    // 每次提交数据自增 1,事务结束自减 1
    private int mDiskWritesInFlight = 0;
    // 内存版本
    private long mCurrentMemoryStateGeneration;
    // 磁盘版本
    private long mDiskStateGeneration;
    // 修改记录
    private final Map<String, Object> mModified = new HashMap<>();
    // 清除全部数据的标记
    private boolean mClear = false;

    // 全量提交到内存
    private MemoryCommitResult commitToMemory() {
        long memoryStateGeneration;
        boolean keysCleared = false;
        List<String> keysModified = null;
        Set<OnSharedPreferenceChangeListener> listeners = null;
        Map<String, Object> mapToWriteToDisk;

        synchronized (SharedPreferencesImpl.this.mLock) {
            // 表示有多个写入操作
            if (mDiskWritesInFlight > 0) {
                mMap = new HashMap<String, Object>(mMap);
            }
            // 需要写入磁盘的数据
            mapToWriteToDisk = mMap; // 全量Map
            // 自增加1
            mDiskWritesInFlight++;

            synchronized (mEditorLock) {
                // 是否发生有效修改
                boolean changesMade = false;

                // 清除全部数据
                if (mClear) {
                    if (!mapToWriteToDisk.isEmpty()) {
                        changesMade = true;
                        mapToWriteToDisk.clear();
                    }
                    keysCleared = true;
                    mClear = false;
                }

                // 将内存中的mModified都数据整合到mapToWriteToDisk中
                for (Map.Entry<String, Object> e : mModified.entrySet()) {
                    String k = e.getKey();
                    Object v = e.getValue();
                    if (v == this || v == null) {
                        // 如果value是this,表示删除这个key
                        if (!mapToWriteToDisk.containsKey(k)) {
                            continue;
                        }
                        mapToWriteToDisk.remove(k);
                    } else {
                        // 修改key-value值
                        if (mapToWriteToDisk.containsKey(k)) {
                            Object existingValue = mapToWriteToDisk.get(k);
                            if (existingValue != null && existingValue.equals(v)) {
                                continue;
                            }
                        }
                        mapToWriteToDisk.put(k, v);
                    }

                    // 标记修改完成
                    changesMade = true;
                    // 记录发生修改的key
                    if (hasListeners) {
                        keysModified.add(k);
                    }
                }

                // 重置内存中的mModified
                mModified.clear();

                // 修改完成,自增加1
                if (changesMade) {
                    mCurrentMemoryStateGeneration++;
                }
                // 修改内存版本号
                memoryStateGeneration = mCurrentMemoryStateGeneration;
            }
        }
        return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
                                      listeners, mapToWriteToDisk);
    }
}
commit()
// SharedPreferencesImpl.EditorImpl#commit()

public boolean commit() {
    // 写入到内存,并获取事务对象
    MemoryCommitResult mcr = commitToMemory();
    // 写入到磁盘
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
    // 阻塞等待写入完成
    mcr.writtenToDiskLatch.await();       
    // 触发回调监听
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}
  • 调用 commitToMemory() 方法将数据提交到内存中,并返回 MemoryCommitResult 对象。
  • 调用 enqueueDiskWrite() 方法写入磁盘。
  • writtenToDiskLatch.await() 会阻塞等待写入完成。
  • notifyListeners() 触发回调监听。
apply()
// SharedPreferencesImpl.EditorImpl#apply()

public void apply() {
    // 写入到内存,并获取事务对象
    final MemoryCommitResult mcr = commitToMemory();
    // 创建awaitCommit任务
    final Runnable awaitCommit = new Runnable() {
        @Override
        public void run() {
            // 阻塞等待写入完成
            mcr.writtenToDiskLatch.await();
        }
    };
    // 将awaitCommit任务提交到QueuedWork的等待队列中
    QueuedWork.addFinisher(awaitCommit);
    // 创建postWriteRunnable任务,写入成功后执行
    Runnable postWriteRunnable = new Runnable() {
        @Override
        public void run() {
            awaitCommit.run();
            QueuedWork.removeFinisher(awaitCommit);
        }
    };
    // 写入到磁盘,触发postWriteRunnable任务
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    // 触发回调监听
    notifyListeners(mcr);
}
  • 调用 commitToMemory() 方法将数据提交到内存中,并返回 MemoryCommitResult 对象。
  • 创建 awaitCommit 任务放入 QueuedWork 的等待队列中,用于阻塞等待写入完成。
  • 调用 enqueueDiskWrite() 方法写入磁盘。
  • 写入后触发 postWriteRunnable 任务,完成后移除 awaitCommit 任务。
  • notifyListeners() 触发回调监听。
enqueueDiskWrite()
// SharedPreferencesImpl#enqueueDiskWrite()

// postWriteRunnable为null表示commit同步提交,不为null表示apply异步提交
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) {
                // 自减1
                mDiskWritesInFlight--;
            }
            if (postWriteRunnable != null) {
                postWriteRunnable.run();
            }
        }
    };

    // 如果是同步操作
    if (isFromSyncCommit) {
        // 判断mDiskWritesInFlight变量,如果存在并发写入则交给QueuedWork
        boolean wasEmpty = false;
        synchronized (mLock) {
            wasEmpty = mDiskWritesInFlight == 1;
        }
        // wasEmpty为true,表示没有并发写入,则直接执行写入任务
        if (wasEmpty) {
            writeToDiskRunnable.run();
            return;
        }
    }

    // 如果是异步操作,提交到QueuedWork执行
    QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
QueuedWork
public class QueuedWork {
    // 等待队列
    private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
    // 任务队列
    private static LinkedList<Runnable> sWork = new LinkedList<>();
    // apply时延迟执行时间
    private static final long DELAY = 100;

    public static void queue(Runnable work, boolean shouldDelay) {
        Handler handler = getHandler();

        synchronized (sLock) {
            sWork.add(work);

            if (shouldDelay && sCanDelay) {
                handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
            } else {
                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
            }
        }
    }
}
  • 用 apply() 方式提交时,消息会延迟100毫秒发送,避免频繁的磁盘写入操作。
  • 用 commit() 方式提交时,handler 会立即发送消息。
writeToFile()
// SharedPreferencesImpl#writeToFile()

private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
    // 判断旧文件是否存在	
    boolean fileExists = mFile.exists();

    if (fileExists) {
        // 是否执行写入操作
        boolean needsWrite = false;
        // 如果磁盘版本小于内存版本,执行磁盘写入操作
        if (mDiskStateGeneration < mcr.memoryStateGeneration) {
            if (isFromSyncCommit) {
                // 如果是同步执行,一定执行写入操作
                needsWrite = true;
            } else {
                // 如果是异步执行,只有最新的内存版本菜执行写入操作
                synchronized (mLock) {
                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
                        needsWrite = true;
                    }
                }
            }
        }

        // 无效的异步写回,直接结束
        if (!needsWrite) {
            mcr.setDiskWriteResult(false, true);
            return;
        }

        // 文件备份
        boolean backupFileExists = mBackupFile.exists();

        if (!backupFileExists) {
            // 如果没有备份文件,将旧文件改为备份文件
            if (!mFile.renameTo(mBackupFile)) {
                mcr.setDiskWriteResult(false, false);
                return;
            }
        } else {
            // 如果有备份文件,则删除旧文件
            mFile.delete();
        }
    }

    try {
        // 执行写入操作,写入到xml文件中
        FileOutputStream str = createFileOutputStream(mFile);     
        XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
        FileUtils.sync(str);
        str.close();
        ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);

        // 写入成功,直接删除备份文件
        mBackupFile.delete();
        // 同步磁盘版本号
        mDiskStateGeneration = mcr.memoryStateGeneration;
        // 写入成功
        mcr.setDiskWriteResult(true, true);

        return;
    } catch (XmlPullParserException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    } catch (IOException e) {
        Log.w(TAG, "writeToFile: Got exception:", e);
    }

    // 写入失败会执行这里
    if (mFile.exists()) {
        if (!mFile.delete()) {
            Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
        }
    }
    mcr.setDiskWriteResult(false, false);
}

主线程堵塞ANR

//QueuedWork.java

public static void waitToFinish() {
    ...
    // 执行所有的写入任务
    processPendingWork();
    ...
        
        // 执行所有的等待任务
        while (true) {
            Runnable finisher;

            synchronized (sLock) {
                finisher = sFinishers.poll();
            }

            if (finisher == null) {
                break;
            }

            finisher.run();
        }
}

private static void processPendingWork() {
    work = sWork;
    ....
     if (work.size() > 0) {
         for (Runnable w : work) {
             w.run();
         }
         ...  
     }
}

waitToFinish() 方法会将执行在 QueuedWork 中的任务队列和等待队列的任务。

// ActivityThread.java

public void handlePauseActivity(...) {
    performPauseActivity(r, finished, reason, pendingActions);
    if (r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }
}

private void handleStopService(IBinder token) {
    QueuedWork.waitToFinish();
    ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}

可以看到,在主线程的 Activity#onPause、Activity#onStop、Service#onStop、Service#onStartCommand 等生命周期状态变更时,会调用 QueudeWork.waitToFinish(),这些方法都执行在主线程中,一旦 waitToFinish() 执行超时就会ANR。

总结

SharedPreferences 是一个轻量级的 K-V 存储框架。从源码分析中,可以看到 SharedPreferences 在读写性能、可用性方面都有做一些优化,例如:锁细化、事务化、事务过滤、文件备份等。

建议:文章来源地址https://www.toymoban.com/news/detail-826983.html

  • 因为SP初始化时会加载全部sp文件,另外修改后提交属于全量提交,因此大文件尽可能的拆分多个文件,同时修改多个数据时尽量一次提交。
  • 因为 commit() 是同步操作,apply() 是异步操作,因此推荐使用 apply()。

到了这里,关于Android SharedPreferences源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android OkHttp源码分析--分发器

    OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中 的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。 OKHttp优点: 1、支持Http1、Http2、Quic以及WebSocket; 2、连接池复用底层TCP(Socket),减少请求

    2024年02月13日
    浏览(41)
  • Android Bluetooth | 蓝牙配对源码分析

    好厚米们,我又来了! 这次分享的是 蓝牙设备执行配对动作时Android源码的执行流程。 下面先来说下, 应用层是如何发起蓝牙配对的: ( ps:大多数业务逻辑,都是扫描到可用设备后,点击可用设备 - 发起配对。) 这里我直接略过点击可用设备的步骤哈,扫描到第一个可用

    2024年02月06日
    浏览(54)
  • Android RIL&IMS源码分析

    1、了解IMS相关知识体系 2、RILD 与 RILJ、IMS回调消息的机制         IMS全称是IP Multimedia Subsystem,中文意义为IP多媒体子系统。IMS是一种基于IP基础结构,能够融合数据、话音和移动等网络技术的系统。          IP = 基于IP 的传输,基于IP的会话控制,基于IP的业务实现

    2024年02月09日
    浏览(41)
  • Android JetPack深入分析LiveData源码

    Google Android开发者 LiveData概览 在了解 LiveData 源码之前,建议先了解 LifeCycle 相关知识,请参考:Android JetPack深入分析Lifecycle源码 定义 从定义中,我们可以大概知道: LiveData 是一个数据存储类,能够感知 Activity 、 Fragment 等组件的生命周期,并且支持对它存储的数据进行观察

    2024年02月12日
    浏览(44)
  • Android UiAutomator测试框架源码分析导读

    前言     竟然有人做这个框架的源码分析,你以为我疯了吧,还真有点,当初想提升能力,又不知道要干啥,就扎进去UiAutomator框架中,想看看自己能否看懂源码?开始看的时候,感受道吃力的感觉了,之后不敢说如履平地,反正可以快速看懂一个对象、一个方法,做起需求

    2024年02月16日
    浏览(37)
  • Android Compose 入门,深入底层源码分析

    我是跟着AS官网学习的,但是官方的教程写的不是很详细.官网链接 首先创建一个Compose项目,目录结构是这样: ui - theme - - Color.kt - - Theme.kt - - Type.kt MainActivity.kt 通过阅读源码,发现实际上还少了一个Shapes.kt,我手动添加了. 这个没什么好说的,官方的教程说的很明白了.这里简单贴一

    2024年04月10日
    浏览(53)
  • Android JetPack 深入分析ViewModel源码

    本文主要分析 ViewModel 相关源码,相关使用不再赘述,可参考Android ViewModel使用; ViewModel 概览 Google官方给的 ViewModel 定义如下: 定义主要提到两个关键点: 生命周期 上图是Google官网提供的 ViewModel生命周期图示 ,可以看到 ViewModel 的生命周期是从 onCreate 创建到 完成并销毁

    2024年02月11日
    浏览(45)
  • Android JetPack深入分析Lifecycle源码

    关键类介绍 Lifecycle 定义: 用于存储有关组件(如 activity 或 fragment)的生命周期状态的信息,并允许其他对象观察此状态。 它是一个 抽象类 ,具体实现类为 LifecycleRegistry ; 内部提供了 addObserver(LifecycleObserver observer) 添加观察者 、 removeObserver(LifecycleObserver observer)移除观察者

    2024年02月12日
    浏览(90)
  • android nv21 转 yuv420sp

    上面两个函数的目标都是将NV21格式的数据转换为YUV420P格式,但是它们在处理U和V分量的方式上有所不同。 在第一个函数 NV21toYUV420P_1 中,U和V分量的处理方式是这样的:对于U分量,它从NV21数据的Y分量之后的每个奇数位置取数据;对于V分量,它从NV21数据的Y分量之后的每个偶

    2024年02月11日
    浏览(42)
  • Android 计时器Chronometer 使用及源码分析(1),android音视频框架

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_textview_chronometer);//加载布局文件 initView(); } private void initView() { btn_start = findViewById(R.id.btn_start); btn_stop = findViewById(R.id.btn_stop); btn_reset = findViewById(R.id.btn_reset); chronome

    2024年04月14日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包