欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SharedPreference引發(fā)ANR原理詳解

 更新時(shí)間:2023年02月21日 15:22:48   作者:尹學(xué)姐  
這篇文章主要為大家介紹了SharedPreference引發(fā)ANR原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

日常開發(fā)中,使用過SharedPreference的同學(xué),肯定在監(jiān)控平臺(tái)上看到過和SharedPreference相關(guān)的ANR,而且量應(yīng)該不小。如果使用比較多或者經(jīng)常用sp存一些大數(shù)據(jù),如json等,相關(guān)的ANR經(jīng)常能排到前10。下面就從源碼的角度來看看,為什么SharedPreference容易產(chǎn)生ANR。

SharedPreference的用法,相信做過Android開發(fā)的同學(xué)都會(huì),所以這里就只簡單介紹一下,不詳細(xì)介紹了。

// 初始化一個(gè)sp
SharedPreferences sharedPreferences = context.getSharedPreferences("name_sp", MODE_PRIVATE);
// 修改key的值,有兩種方法:commit和apply
sharedPreferences.edit().putBoolean("key_test", true).commit();
sharedPreferences.edit().putBoolean("key_test", true).apply();
// 讀取一個(gè)key
sharedPreferences.getBoolean("key_test", false);

SharedPreference問題

SharedPreference的相關(guān)方法,除了commit外,一般的開發(fā)同學(xué)都會(huì)直接在主線程調(diào)用,認(rèn)為這樣不耗時(shí)。但其實(shí),SharedPreference的很多方法都是耗時(shí)的,直接在主線程調(diào)很可能會(huì)引起ANR的問題。另外,雖然apply方法的調(diào)用不耗時(shí),但是會(huì)引起生命周期相關(guān)的ANR問題。

下面就來從源碼的角度,看一下可能引起ANR的問題所在。

getSharedPreference(String name, int mode)

    @Override
    public SharedPreferences getSharedPreferences(String name, int mode) {
        File file;
        //  與sp相關(guān)的操作,都使用ContextImpl的類鎖
        synchronized (ContextImpl.class) {
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            // mSharedPrefsPaths是內(nèi)存緩存的文件路徑
            file = mSharedPrefsPaths.get(name);
            if (file == null) {
                // 此處獲取SharedPreferences的文件路徑,可能存在耗時(shí)
                file = getSharedPreferencesPath(name);
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

下面看下獲取文件路徑的方法:getSharedPreferencesPath(),這個(gè)方法可能存在耗時(shí)。

    public File getSharedPreferencesPath(String name) {
        // 創(chuàng)建一個(gè)sp的存儲(chǔ)文件
        return makeFilename(getPreferencesDir(), name + ".xml");
    }

調(diào)用getPreferencesDir()獲取sharedPrefs的根路徑

    private File getPreferencesDir() {
        // 所有和文件有關(guān)的操作,都會(huì)使用mSync鎖,可能出現(xiàn)與其他線程搶鎖的耗時(shí)
        synchronized (mSync) {
            if (mPreferencesDir == null) {
                mPreferencesDir = new File(getDataDir(), "shared_prefs");
            }
            // 這個(gè)方法,如果目錄不存在,會(huì)創(chuàng)建目錄,可能存在耗時(shí)
            return ensurePrivateDirExists(mPreferencesDir);
        }
    }

ensurePrivateDirExists():確保文件目錄存在

    private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
        if (!file.exists()) {
            final String path = file.getAbsolutePath();
            try {
                // 創(chuàng)建文件夾,會(huì)耗時(shí)
                Os.mkdir(path, mode);
                Os.chmod(path, mode);
            } catch (ErrnoException e) {
            }
        return file;
    }

再來看看getSharedPreferences生成SharedPreferenceImpl對(duì)象的流程。

    public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            // 獲取cache,先從cache中獲取SharedPreferenceImpl
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                // 如果沒有cache,則創(chuàng)建一個(gè)SharedPreferencesImpl,此處可能存在耗時(shí)
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        return sp;
    }

先來看下cache的原理

    private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        // sSharedPrefsCache是一個(gè)靜態(tài)變量,全局有效
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }
        // key:包名,value: ArrayMap<File, SharedPreferencesImpl> 
        final String packageName = getPackageName();
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        if (packagePrefs == null) {
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }
        return packagePrefs;
    }

再來看看SharedPreferenceImpl的構(gòu)造方法,看看SharedPreference是怎么初始化的。

    SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        // 設(shè)置是否load到內(nèi)存的標(biāo)志位為false
        mLoaded = false;
        startLoadFromDisk();
    }

startLoadFromDisk():開啟一個(gè)子線程,將sp中的內(nèi)容讀取到內(nèi)存中

    private void startLoadFromDisk() {
        // 改mLoaded標(biāo)志位時(shí),需要獲取mLock鎖
        synchronized (mLock) {
           // load之前先設(shè)置mLoaded標(biāo)志位為false
            mLoaded = false;
        }
        // 開啟一個(gè)線程,從文件中將sp中的內(nèi)容讀取到內(nèi)存中
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                // 在子線程load
                loadFromDisk();
            }
        }.start();
    }

loadFromDisk:真正讀取文件的地方

   private void loadFromDisk() {
        synchronized (mLock) {
            // 如果已經(jīng)load過了,直接return,不需要再重新load
            if (mLoaded) {
                return;
            }
            stat = Os.stat(mFile.getPath());
            if (mFile.canRead()) {
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16 * 1024);
                    // 讀取xml的內(nèi)容到map中
                    map = (Map<String, Object>) XmlUtils.readMapXml(str);
                } catch (Exception e) {
                    Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }
        synchronized (mLock) {
            // 設(shè)置mLoaded標(biāo)志位為true,表示已經(jīng)load完,通知所有在等待的線程
            mLoaded = true;
            mLock.notifyAll();
        }
    }

總結(jié):經(jīng)過上面的分析,getSharedPreferences主要的卡頓點(diǎn)在于,獲取PreferencesDir的時(shí)候,可能存在目錄尚未創(chuàng)建的情況。如果這個(gè)時(shí)候調(diào)用了創(chuàng)建目錄的方法,就會(huì)非常耗時(shí)。

getBoolean(String key, boolean defValue)

這個(gè)方法和所有獲取key的方法一樣,都可能存在耗時(shí)。

SharedPreferencesImpl的構(gòu)造方法,我們知道會(huì)開啟一個(gè)新的線程,將內(nèi)容從文件中讀取到緩存的map里,這個(gè)步驟我們叫l(wèi)oad。

    public boolean getBoolean(String key, boolean defValue) {
        synchronized (mLock) {
            // 需要等待,直到load成功
            awaitLoadedLocked();
            // 從緩存中取value
            Boolean v = (Boolean)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

主要耗時(shí)的方法,在awaitLoadedLocked里。

    private void awaitLoadedLocked() {
       // 只有當(dāng)mLoaded為true時(shí),才能跳出死循環(huán)
        while (!mLoaded) {
            try {
                // 調(diào)用wait后,會(huì)釋放mLock鎖,并且進(jìn)入等待池,等待load完之后的喚醒
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
    }

這個(gè)方法,調(diào)用了mLock.wait(),釋放了mLock的對(duì)象鎖,并且進(jìn)入等待池,直到load完被喚醒。

總結(jié):所以,getBoolean等獲取key的方法,會(huì)等待,直到sp的內(nèi)容從文件中copy到緩存map里。很可能存在耗時(shí)。

commit()

commit()方法,會(huì)進(jìn)行同步寫,一定存在耗時(shí),不能直接在主線程調(diào)用。

        public boolean commit() {
            // 開始排隊(duì)寫
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                // 等待同步寫的結(jié)果
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            } finally {
            }
            notifyListeners(mcr);
            return mcr.writeToDiskResult;
        }

apply()

大家都知道apply方法是異步寫,但是也可能造成ANR的問題。下面我們來看apply方法的源碼。

        public void apply() {
            // 先將更新寫入內(nèi)存緩存
            final MemoryCommitResult mcr = commitToMemory();
            // 創(chuàng)建一個(gè)awaitCommit的runnable,加入到QueuedWork中
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 等待寫入完成
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                    }
                };
            // 將awaitCommit加入到QueuedWork中
            QueuedWork.addFinisher(awaitCommit);
            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
            // 真正執(zhí)行sp持久化操作,異步執(zhí)行
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
            // 雖然還沒寫入文件,但是內(nèi)存緩存已經(jīng)更新了,而listener通常都持有相同的sharedPreference對(duì)象,所以可以使用內(nèi)存緩存中的數(shù)據(jù)
            notifyListeners(mcr);
        }

可以看到這里確實(shí)是在子線程進(jìn)行的寫入操作,但是為什么說apply也會(huì)引起ANR呢?

因?yàn)樵?code>Activity和Service的一些生命周期方法里,都會(huì)調(diào)用QueuedWork.waitToFinish()方法,這個(gè)方法會(huì)等待所有子線程寫入完成,才會(huì)繼續(xù)進(jìn)行。主線程等子線程,很容易產(chǎn)生ANR問題。

public static void waitToFinish() {
       Runnable toFinish;
       //等待所有的任務(wù)執(zhí)行完成
       while ((toFinish = sPendingWorkFinishers.poll()) != null) {
           toFinish.run();
       }
   }

Android 8.0 在這里做了一些優(yōu)化,但還是需要等寫入完成,無法完成解決ANR的問題。

總結(jié)

綜上所述,SharedPreference可能在以下幾種情況下產(chǎn)生卡頓,從而引起ANR:

  • 創(chuàng)建SharedPreference時(shí),調(diào)用getPreferenceDir,可能存在創(chuàng)建目錄的行為
  • getBoolean等方法,會(huì)等待直到SharedPreference將文件中的鍵值對(duì)全部讀取到緩存里,才會(huì)返回
  • commit方法直接同步寫,如果不小心在主線程調(diào)用,會(huì)引起卡頓
  • apply方法雖然是在異步線程寫入,但是由于ActivityService的生命周期會(huì)等待所有SharedPreference的寫入完成,所以可能引起卡頓和ANR問題

SharedPreference從設(shè)計(jì)之初,就是為了存儲(chǔ)少量key-value對(duì),而存在的。其本身的設(shè)計(jì),就存在很多缺陷。在存儲(chǔ)特別少量數(shù)據(jù)的時(shí)候,性能瓶頸還不顯著。但是現(xiàn)在很多開發(fā)同學(xué)在使用的時(shí)候,會(huì)往里面存一些大型的JSON字符串等,導(dǎo)致它的缺點(diǎn)被明顯暴露出來。建議在使用SharedPreference的時(shí)候,只用于存儲(chǔ)少量數(shù)據(jù),不要存大的字符串。

當(dāng)然,我們也有一些方法來統(tǒng)一優(yōu)化SharedPreference,減少ANR的發(fā)生,下一篇我們繼續(xù)講。

以上就是SharedPreference引發(fā)ANR原理詳解的詳細(xì)內(nèi)容,更多關(guān)于SharedPreference引發(fā)ANR的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android中ProgressBar用法簡單實(shí)例

    Android中ProgressBar用法簡單實(shí)例

    這篇文章主要介紹了Android中ProgressBar用法,以簡單實(shí)例形式分析了Android中ProgressBar進(jìn)度條控件的功能與布局相關(guān)技巧,需要的朋友可以參考下
    2016-01-01
  • Android中ShapeableImageView使用實(shí)例詳解(告別shape、三方庫)

    Android中ShapeableImageView使用實(shí)例詳解(告別shape、三方庫)

    之前Google推送了文章,Android?Material組件1.2.0里面就有ShapeableImageView,不用像以前再寫shape,下面這篇文章主要給大家介紹了關(guān)于Android中ShapeableImageView使用的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • Android原生繪圖工具Paint詳細(xì)

    Android原生繪圖工具Paint詳細(xì)

    這篇文章要給大家分享的是Android原生繪圖工具Paint,android中提供了類似的工具Canvas和Paint,分別對(duì)應(yīng)畫布和畫筆,本文就來介紹Androi中的Paint,感興趣的小伙伴一起來學(xué)習(xí)下面文章內(nèi)容
    2021-09-09
  • android 放大鏡ShapeDrawable妙用分享

    android 放大鏡ShapeDrawable妙用分享

    android上想實(shí)現(xiàn)局部放大的效果,比如畫面中加個(gè)放大鏡的效果,發(fā)現(xiàn)ShapeDrawable是一個(gè)最好的選擇。
    2013-06-06
  • Android自定義View實(shí)現(xiàn)內(nèi)存清理加速球效果

    Android自定義View實(shí)現(xiàn)內(nèi)存清理加速球效果

    這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)內(nèi)存清理加速球的效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • Android Native fdsan檢測工具介紹

    Android Native fdsan檢測工具介紹

    這篇文章主要為大家介紹了Android Native fdsan檢測工具介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • android中用xml文件實(shí)現(xiàn)帶邊框背景效果的方法

    android中用xml文件實(shí)現(xiàn)帶邊框背景效果的方法

    這篇文章主要給大家介紹了在android中xml文件實(shí)現(xiàn)帶邊框背景效果的方法,其實(shí)實(shí)現(xiàn)的功能不是很難,僅作記錄,幫助需要的朋友們做個(gè)參考,需要的朋友們下面來一起看看吧。
    2017-06-06
  • Android中EditText禁止輸入表情的實(shí)例代碼

    Android中EditText禁止輸入表情的實(shí)例代碼

    本篇文章主要介紹了Android中EditText禁止輸入表情的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08
  • Flutter開發(fā)通用頁面Loading組件示例詳解

    Flutter開發(fā)通用頁面Loading組件示例詳解

    這篇文章主要為大家介紹了Flutter開發(fā)通用頁面Loading組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Android OKHttp3攔截器的使用方法

    Android OKHttp3攔截器的使用方法

    本篇文章主要介紹了Android OKHttp3攔截器的使用方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03

最新評(píng)論