SharedPreferencesによって引き起こされたANR問題の頭脳をまだ引っ掻いていますか?MMKVの統合と原則はここにあります!

幸せで傲慢な丑の新年をお祈りします!

序文

SharedPreferencesは、Googleが提供する軽量のストレージソリューションです。使いやすく、別のスレッドを開始せずにデータを直接保存できます。
ただし、SPによって引き起こされるANRの問題など、多くの問題も発生します。一般。
このため、MMKVなどのいくつかのSP代替ソリューションが後に登場しました

この記事の内容は主に次のとおりです
。1。
共有設定の問題
2.MMKVの基本的な使用法と紹介3.MMKVの原理

SharedPreferencesの問題

SPの効率は比較的低い

1.読み取りおよび書き込み方法:直接I / O
2.データ形式:xml
3.書き込み方法:完全更新

SPはxml形式を使用してデータを保存するため、各更新データは更新データを完全に置き換えることしかできません。
つまり、100個のデータがある場合、1個のデータのみを更新する場合は、すべてのデータをxml形式に変換する必要もあります。 、次にpass ioがファイルに書き込まれます。
これにより、SPの書き込み効率も比較的低くなります。

コミットによって引き起こされるANR

public boolean commit() {
    // 在当前线程将数据保存到mMap中
    MemoryCommitResult mcr = commitToMemory();
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
    try {
        // 如果是在singleThreadPool中执行写入操作,通过await()暂停主线程,直到写入操作完成。
        // commit的同步性就是通过这里完成的。
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
    }
    /*
     * 回调的时机:
     * 1\. commit是在内存和硬盘操作均结束时回调
     * 2\. apply是内存操作结束时就进行回调
     */
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

上記のように、
1.commitには、変更が正常に送信されたかどうかを示す戻り値があります
。2。コミットの送信は同期的であり、ディスク操作が成功するまで完了しません。

したがって、データ量が比較的多い場合、コミットを使用するとANRが発生する可能性があります

適用によって引き起こされるANR

コミットは同期的であり、SPは
、変更されたデータをメモリにアトミックに送信し、次に非同期的にハードウェアディスクに送信する非同期適用適用も提供します。コミットは、ハードウェアディスクに同期的に送信するため、複数の同時送信でコミットします。そのとき、処理中のコミットがディスクに保存されるのを待ってから操作するため、効率が低下します。applyはコンテンツにアトミックに送信されるだけですが、applyを呼び出す後の関数は、前のメモリデータを直接上書きします。これにより、効率がある程度向上します。

ただし、適用するとANRの問題が発生する可能性もあります

public void apply() {
    final long startTime = System.currentTimeMillis();

    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                mcr.writtenToDiskLatch.await(); // 等待
                ......
            }
        };
    // 将 awaitCommit 添加到队列 QueuedWork 中
    QueuedWork.addFinisher(awaitCommit);

    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                QueuedWork.removeFinisher(awaitCommit);
            }
        };
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}
  • 一つawaitCommitRunnableキューに追加タスクはQueuedWorkawaitCommit通話が可能になるawait()で待機する方法handleStopServicehandleStopActivityおよびので、この状態のライフサイクルは、裁判官として意志、タスクの待機を終了します
  • 書き込みタスクの1つpostWriteRunnableであるメソッドにより、書き込みタスクがキューに追加され、タスクの実行がスレッドに書き込まれます。RunnableenqueueDiskWrite

ライフサイクルに際非同期タスクのタイムリーな完了を確保するために、handleStopService()handlePauseActivity()handleStopActivity()通話が時にQueuedWork.waitToFinish()書き込みを待つことになるタスクが終了します

private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
        new ConcurrentLinkedQueue<Runnable>();

public static void waitToFinish() {
    Runnable toFinish;
    while ((toFinish = sPendingWorkFinishers.poll()) != null) {
        toFinish.run(); // 相当于调用 `mcr.writtenToDiskLatch.await()` 方法
    }
}
  • sPendingWorkFinishersあるConcurrentLinkedQueue例では、applyに追加タスク書き込む方法sPendingWorkFinishersキューは、シングルスレッドの書き込みタスク実行スレッドプールは、スレッドが言うことですスケジューリングプログラムによって制御されていない場合、スイッチングのライフサイクル、タスクの実行必ずしもステータスではありません
  • toFinish.run()メソッドを呼び出すのと同等のmcr.writtenToDiskLatch.await()メソッドは待機します
  • waitToFinish() このメソッドは、書き込みタスクの完了を待機するだけで、他には何もしません。書き込みタスクが多い場合は、順番に実行されます。ファイルが大きい場合、効率は非常に低くなります。 ANRが発生するのは当然のことです。

したがって、データ量が比較的多い場合は、applyANRも発生します

getXXX()はANRを引き起こします

書き込み操作だけでなく、すべてのgetXXX()メソッドが同期され、メインスレッドがgetメソッドを呼び出し、SPが読み込まれるのを待つ必要があります。また
getSharedPreferences()メソッドが最終的にSharedPreferencesImpl#startLoadFromDisk()スレッドの非同期データ読み取りメソッドを開くANR呼び出しにつながる可能性があります。

private final Object mLock = new Object();
private boolean mLoaded = false;
private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

ご覧のとおり、まだスキャンされていない大きなデータを読み取っているときに、スレッドの非同期読み取りデータを開いて、getXXX()メソッドを呼び出します

public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

private void awaitLoadedLocked() {
    ......
    while (!mLoaded) {
        try {
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    ......
}

メソッド内の同期メソッド呼び出しは、スレッドを開くwait()方法を待機しgetSharedPreferences()てデータを読み取り、最後に終了します。数KBのデータをより適切に読み取る場合、大きなファイルを読み取ると仮定すると、メインスレッドが障害を引き起こすことになります。

MMKVの使用

MMKVは、mmapメモリマッピングに基づくKey-Valueコンポーネントであり、基盤となるシリアル化/逆シリアル化は、高性能で安定性の高いprotobufを使用して実装されます。2015年半ばからWeChatで使用されており、そのパフォーマンスと安定性は長期にわたって検証されています。最近ではAndroid / macOS / Win32 / POSIXプラットフォームにも移植されており、一緒にオープンソース化されています。

MMKVの利点

1. MMKVは、シームレスに切り替えることができるSharedPreferencesインターフェイスを実装します。2。mmap
メモリマッピングファイルを介して、いつでも書き込むことができるメモリブロックが提供されます。アプリはデータを書き込むだけで、オペレーティングシステムが書き込みを担当します。メモリをファイルに戻します。クラッシュによるデータ損失が心配です。
3. MMKVデータのシリアル化はprotobufプロトコルを使用し、pbはパフォーマンスとスペース占有で優れたパフォーマンスを発揮します
。4。SPは完全更新、MMKVはインクリメンタル更新であり、パフォーマンス上の利点があります。

使用法の詳細については、ドキュメントgithub.com/Tencent/MMK ...を参照してください

MMKVの原則

MMKVの書き込み速度が速い理由

IO操作

SPの書き込みはIO操作に基づいていることがわかっています。IOを理解するには、ユーザースペースとカーネルスペースを理解する必要があります。
仮想メモリは、オペレーティングシステムによってユーザースペースとカーネルスペースの2つの部分に分けられます。ユーザースペースはユーザーです。プログラムコードの実行カーネルスペースは、カーネルコードが実行される場所です。安全のため、それらは分離されています。ユーザーのプログラムがクラッシュしても、カーネルは影響を受けません。画像

ファイル書き込みプロセス:
1。writeを呼び出して、書き込むデータの開始アドレスと長さをカーネルに通知します
。2。カーネルはデータをカーネルキャッシュ
にコピーします。3 オペレーティングシステムによって呼び出され、データをディスクにコピーします。書き込みを完了します。

MMAP

Linuxは、仮想メモリ領域をディスク上のオブジェクトに関連付けることにより、仮想メモリ領域の内容を初期化します。このプロセスは、メモリマッピングと呼ばれます。

ファイルに対してmmapを実行すると、プロセスの仮想メモリにアドレススペースが割り当てられ、マッピング関係が作成されます。
このようなマッピング関係を実装した後、ポインタを使用してこのメ​​モリの読み取りと書き込みを行うことができ、システムは対応するファイルディスクに自動的に書き戻します。

MMAPの利点

  • MMAPのファイルの読み取りおよび書き込み操作には、ディスクからユーザーのメインメモリへのデータコピープロセスが1つだけ必要です。これにより、データコピーの数が減り、ファイルの読み取りと書き込みの効率が向上します。
  • MMAPは論理メモリを使用してディスクファイルをマップします。オペレーティングメモリはオペレーティングファイルと同等であり、スレッドは必要ありません。MMAPの操作速度はオペレーティングメモリと同じです。
  • MMAPは、いつでも書き込むことができるメモリブロックを提供します。アプリはデータを書き込むだけです。メモリが不足している場合やプロセスの終了などが発生した場合、オペレーティングシステムがメモリをファイルに書き戻す責任があるため、必要はありません。クラッシュによるデータ損失を心配する。

MMAPの書き込み速度は基本的にメモリの書き込み速度と同じであり、SPよりもはるかに速いことがわかります。これがMMKVの書き込み速度が速い理由です。

MMKV書き込み方式

SPデータ構造

SPは、以下に示すように、XML形式を使用してデータを格納します

ただし、これにより、SPはデータを完全にのみ更新します。

MMKVデータ構造

MMKVのデータ構造は次のとおりです

MMKVはProtobufを使用してデータを保存し、冗長性の少ないデータとより多くのスペースを節約します。同時に、最後にデータを追加すると便利です。

書き込みメソッド

インクリメンタル書き込み
キーが繰り返されるかどうかに関係なく、データは前のデータに直接追加されます。これはより効率的であり、データを更新するために挿入する必要があるのは1つのデータだけです。

もちろん、これも問題を引き起こします。コンテンツを継続的に追加してファイルがどんどん大きくなっている場合はどうすればよいですか?
ファイルサイズが足りない場合は、この時点で完全に書き込む必要があります。データから重複キーを削除した後、ファイルサイズが書き込まれたデータのサイズと一致する場合は、完全な書き込みを直接更新できます。それ以外の場合は、拡張する必要があります。(拡張中に、頻繁なフル書き込みを防ぐために、拡張する各KVの平均サイズに従って将来必要になる可能性のあるファイルサイズを計算します)

MMKVの3つの利点

  • mmapはデータの損失を防ぎ、読み取りと書き込みの効率を向上させます。
  • データを合理化し、最小量のデータで最大の情報を表現し、データサイズを削減します。
  • 増分更新は、毎回相対的な増分で大量のデータが完全に書き込まれるのを防ぎます。

文末

私をフォローし、Androidの乾物を共有し、Androidのテクノロジーを交換してくれてありがとう。
記事に関する洞察や技術的な質問がある場合は、コメント領域にメッセージを残して話し合うことができます。私はあなたに誠実に答えます。
Androidアーキテクトシステムの高度な学習ルート、580,000語の学習ノート、無料の教育用ビデオ共有アドレス:私のGitHub
は、Bステーションに遊びに来てくれるすべての人を歓迎します。また、Androidアーキテクトの高度な技術的問題についてのさまざまなビデオ説明があります。あなたは早期の昇進を得て、あなたの給料を上げます。
駅Bの電車経由:https//space.bilibili.com/544650554

おすすめ

転載: blog.csdn.net/Androiddddd/article/details/113854016