Android SharedPreferences 분석

Android SharedPreferences 분석
인용:Shared Preferences 를 사용 하 는 과정 에서 Android 와 같은 경량급 저장 사상 은 사용 하기에 매우 편리 하 다 고 생각 합 니 다.그 이 유 를 알 고 있 는 생각 에 따라 Shared Preferences 소스 코드 를 읽 어 보 세 요.
입구 함수
public SharedPreferences getPreferences(int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }
public SharedPreferences getSharedPreferences(String name, int mode) {
    SharedPreferencesImpl sp;
    synchronized (sSharedPrefs) {
        sp = sSharedPrefs.get(name);
        if (sp == null) {
            File prefsFile = getSharedPrefsFile(name);
            sp = new SharedPreferencesImpl(prefsFile, mode);
            sSharedPrefs.put(name, sp);
            return sp;
           }
        }
        if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
                getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
            // If somebody else (some other process) changed the prefs
            // file behind our back, we reload it. This has been the
            // historical (if undocumented) behavior.
            sp.startReloadIfChangedUnexpectedly();
        }
        return sp;
    }

아주 간단 한 두 개의 입구 함수.
public SharedPreferences getPreferences(int mode)

Activity.class 클래스 에 정의
 public SharedPreferences getSharedPreferences(String name, int mode) 

ContextImpl.class 클래스 에 정의
이들 의 관 계 는 getPreferences 가 getShared Preferences 를 호출 하여 이 루어 집 니 다.
public SharedPreferences getPreferences(int mode) {
        return getSharedPreferences(getLocalClassName(), mode);
    }
public String getLocalClassName() {
        final String pkg = getPackageName();
        final String cls = mComponent.getClassName();
        int packageLen = pkg.length();
        if (!cls.startsWith(pkg) || cls.length() <= packageLen
                || cls.charAt(packageLen) != '.') {
            return cls;
        }
        return cls.substring(packageLen+1);
    }

getPreferences(int mode)함 수 는 getLocalClassName()을 호출 하여 클래스 이름 으로 전 달 됩 니 다.그러나 두 가지 차 이 는 바로 여기에 있 습 니 다.
getSharedPreferences 함수
getShared Preferences 함 수 는 Shared Preferences Impl 인 스 턴 스 를 되 돌려 줍 니 다.Shared Preferences 를 사용 한 사람 은 모두 알 고 있 을 것 입 니 다.저 희 는 Shared Preferences 인 스 턴 스 를 통 해 작 동 합 니 다.
SharedPreferences 임 플 계승 SharedPreferences
ContextImpl.class 에는 sShared Prefs 정적 변수 가 있 습 니 다.sShared Prefs 는 키 쌍 을 저장 합 니 다.getShared Preferences 함 수 를 호출 할 때마다 먼저 name 으로 sShared Prefs 에 저 장 된 Shared Preferences Impl 인 스 턴 스 가 있 는 지 확인 합 니 다.있 으 면 name 에 있 는 Shared Preferences Impl 인 스 턴 스 를 직접 되 돌려 줍 니 다.찾 지 못 하면 Shared Preferences Impl 인 스 턴 스 를 만 들 고 키 가 있 습 니 다.값 쌍 의 형식 을 sShared Prefs 에 삽입 합 니 다.나중에 name 을 통 해 Shared Preferences Impl 인 스 턴 스 를 가 져 올 수 있 도록 합 니 다.
  • getShared Preferences 함 수 를 호출 하여 전 달 된 name 은 색인 입 니 다.색인 을 통 해 Shared Preferences Impl 인 스 턴 스 를 되 돌려 줍 니 다
  • private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
                new HashMap<String, SharedPreferencesImpl>();
    public SharedPreferences getSharedPreferences(String name, int mode) {
            SharedPreferencesImpl sp;
            synchronized (sSharedPrefs) {
                //  name  SharedPreferencesImpl  
                sp = sSharedPrefs.get(name);
                if (sp == null) {
                    //  ,     .    name  SharedPreferencesImpl  
                    //getSharedPrefsFile()  Xml  File
                    File prefsFile = getSharedPrefsFile(name);
                    //  SharedPreferencesImpl  ,   ”/data/data/  /shared_prefs/”      XML  
                    sp = new SharedPreferencesImpl(prefsFile, mode);
                    //  SharedPreferencesImpl   sSharedPrefs,name    
                    sSharedPrefs.put(name, sp);
                    return sp;
                }
            }
    
            //     ,Context.MODE_MULTI_PROCESS getSharedPreferences      
            if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
                getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
                // If somebody else (some other process) changed the prefs
                // file behind our back, we reload it. This has been the
                // historical (if undocumented) behavior.
                sp.startReloadIfChangedUnexpectedly();
            }
            return sp;

    다음 세 가 지 는 이해 하기 쉽 습 니 다.getShared PrefsFile 은 Xml 파일 을 되 돌려 주 는 File 입 니 다.그래서 Shared Preferences 의 실현 은 Xml 파일 을 기반 으로 한 것 입 니 다.여기 서 알 수 있 습 니 다.
    4.567917.예 를 들 어 4.567918.
  • xml 파일 을 생 성 합 니 다
  • /data/data/com.dsliang.SharedPreferencesDemo/shared_prefs/ds.xml
  •  public File getSharedPrefsFile(String name) {
            return makeFilename(getPreferencesDir(), name + ".xml");
        }
    private File getPreferencesDir() {
            synchronized (mSync) {
                if (mPreferencesDir == null) {
                    mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
                }
                return mPreferencesDir;
            }
        }
        private File getDataDirFile() {
            if (mPackageInfo != null) {
                return mPackageInfo.getDataDirFile();
            }
            throw new RuntimeException("Not supported in system context");
        }

    SharedPreferences Impl 클래스
    Shared Preferences Impl 류 를 이해 하면 기본적으로 Shared Preferences 에 대해 알 수 있 습 니 다.
    구조 함수
    SharedPreferencesImpl(File file, int mode) {
            //Xml   File
            mFile = file;
            //Xml     (  )
            mBackupFile = makeBackupFile(file);
            //SharedPreferences  
            mMode = mode;
            //           (   sSharedPrefs)
            mLoaded = false;
            //SharedPreferences      ,               
            mMap = null;
            //             sSharedPrefs
            startLoadFromDisk();
        }
        private void startLoadFromDisk() {
            synchronized (this) {
                //          
                // getString/getBoolean            awaitLoadedLocked()     mLoaded.false - awaitLoadedLocked()      mLoaded true.
                //     Xml        ?       .    true              .
                mLoaded = false;
            }
            //       Xml    
            new Thread("SharedPreferencesImpl-load") {
                public void run() {
                    synchronized (SharedPreferencesImpl.this) {
                        loadFromDiskLocked();
                    }
                }
            }.start();
        }
    
        private void loadFromDiskLocked() {
            if (mLoaded) {
                return;
            }
            //    Xml    ,  Xml  .       .
            if (mBackupFile.exists()) {
                mFile.delete();
                mBackupFile.renameTo(mFile);
            }
    
            // Debugging
            if (mFile.exists() && !mFile.canRead()) {
                Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
            }
    
            Map map = null;
            FileStatus stat = new FileStatus();
            if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) {
                try {
                    BufferedInputStream str = new BufferedInputStream(
                            //  XmlPull  Xml  
                            new FileInputStream(mFile), 16*1024);
                    //      Map.         .        ,     .     Xml      Map    .
                    map = XmlUtils.readMapXml(str);
                    str.close();
                } catch (XmlPullParserException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } catch (FileNotFoundException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                } catch (IOException e) {
                    Log.w(TAG, "getSharedPreferences", e);
                }
            }
    
            //                    .
            mLoaded = true;
            if (map != null) {
                //mMap,SharedPreferencesImpl      Map   
                mMap = map;
                //      
                mStatTimestamp = stat.mtime;
                //    
                mStatSize = stat.size;
            } else {
                //           Map
                mMap = new HashMap<String, Object>();
            }
    
            //      
            notifyAll();
        }
    

    SharedPreferences Impl 클래스 분석,구조 함수 에 서 는 Xml 파일 을 읽 는 하위 스 레 드 를 만 들 고 맵 으로 해석 하여 Shared Preferences Impl 클래스 내부 에 저 장 된 mMap 을 읽 습 니 다.Xml 파일 을 읽 는 과정 에서 이 Shared Preferences Impl 인 스 턴 스 를 조작 하 는 것 이 막 힙 니 다.(awaitLoadedLocked()함수 로 인해)Shared Preferences 는 Xml 파일 의 모든 데 이 터 를 메모리 로 읽 습 니 다.
    데이터 읽 기
    Shared Preferences Impl.class 내부 에서 mMap 해시 표 는 Shared Preferences 키 를 저장 하 는 데 사 용 됩 니 다.
     private Map<String, Object> mMap; 

    getInt/getLong/getString 등.이 일련의 함 수 는 본질 적 으로 차이 가 별로 없다.바로 key 를 통 해 mMap 에서 조회 하고 책임 을 찾 지 못 하면 기본 값 으로 돌아 가 는 것 이다.
    public String getString(String key, String defValue) {
            synchronized (this) {
                awaitLoadedLocked();
                String v = (String)mMap.get(key);
                return v != null ? v : defValue;
            }
    
    public int getInt(String key, int defValue) {
            synchronized (this) {
                awaitLoadedLocked();
                Integer v = (Integer)mMap.get(key);
                return v != null ? v : defValue;
            }
        }
    
        public long getLong(String key, long defValue) {
            synchronized (this) {
                awaitLoadedLocked();
                Long v = (Long)mMap.get(key);
                return v != null ? v : defValue;
            }
        }
     private void awaitLoadedLocked() {
            if (!mLoaded) {
                // Raise an explicit StrictMode onReadFromDisk for this
                // thread, since the real read will be in a different
                // thread and otherwise ignored by StrictMode.
                BlockGuard.getThreadPolicy().onReadFromDisk();
            }
            while (!mLoaded) {
                try {
                    wait();
                } catch (InterruptedException unused) {
                }
            }
        }
    

    데이터 함 수 를 읽 는 것 은 주로 awaitLoadedLocked()함수 에 관심 을 가 져 야 합 니 다.파일 을 불 러 오지 않 으 면 작업 이 막 힐 수 있 습 니 다.
    데이터 수정-editor 클래스
    Shared Preferences Impl 류 의 edit()방법 을 호출 하여 Editor Impl 류 를 되 돌려 줍 니 다.Editor Impl 류 패 키 징 이 데이터 조작 에 대한 함수.putString/putBoolean 등 일련의 방법 을 되 돌려 줍 니 다.몇 가지 문 제 를 주의해 야 합 니 다.
  • putString/putBoolean 등 일련의 방법 으로 Editor Impl 류 내부 의 맵 을 조작 하 는 것 은 Shared Preferences Impl 류 의 맵 을 조작 하 는 것 이 아 닙 니 다.따라서 효력 을 발생 시 키 려 면 Editor Impl 류 의 commt()방법 을 호출 해 야 합 니 다
  • Editor Impl 류 의 clear(0 방법 은 Editor Impl 류 내부 의 Map 인 스 턴 스 를 비 우 는 것 입 니 다.다음 작업 은 Shared Preferences 의 정확 한 데 이 터 를 가 져 오지 않 습 니 다
  • edit().clear().commit();

    정확 한 자 세 는 다음 과 같다.
    edit().remove("Save")..remove("PassWord")commit();
        public Editor edit() {
            // TODO: remove the need to call awaitLoadedLocked() when
            // requesting an editor. will require some work on the
            // Editor, but then we should be able to do:
            //
            // context.getSharedPreferences(..).edit().putString(..).apply()
            //
            // ... all without blocking.
            synchronized (this) {
                awaitLoadedLocked();
            }
    
            return new EditorImpl();
        }

    마지막 으로 commt()방 법 원 코드 입 니 다.
        //  Commit()      
        private static class MemoryCommitResult {
            //        
            public boolean changesMade;  // any keys different?
            //Map  
            public List<String> keysModified;  // may be null
            //    ,    null
            public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
            public Map<?, ?> mapToWriteToDisk;
            public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
            //  
            public volatile boolean writeToDiskResult = false;
    
            public void setDiskWriteResult(boolean result) {
                writeToDiskResult = result;
                writtenToDiskLatch.countDown();
            }
        }
    
    
    // Returns true if any changes were made
            private MemoryCommitResult commitToMemory() {
                MemoryCommitResult mcr = new MemoryCommitResult();
                synchronized (SharedPreferencesImpl.this) {
                    // We optimistically don't make a deep copy until
                    // a memory commit comes in when we're already
                    // writing to disk.
                    //mDiskWritesInFlight        
                    if (mDiskWritesInFlight > 0) {
                        // We can't modify our mMap as a currently
                        // in-flight write owns it. Clone it before
                        // modifying it.
                        // noinspection unchecked
                        mMap = new HashMap<String, Object>(mMap);
                    }
                    //SharedPreferences Map   mcr.mapToWriteToDisk
                    mcr.mapToWriteToDisk = mMap;
                    mDiskWritesInFlight++;
    
                    //    
                    boolean hasListeners = mListeners.size() > 0;
                    if (hasListeners) {
                        mcr.keysModified = new ArrayList<String>();
                        mcr.listeners =
                                new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                    }
    
                    synchronized (this) {
                        //     ,  clear()    mClear=true.     clear()   put         .
                        if (mClear) {
                            if (!mMap.isEmpty()) {
                                mcr.changesMade = true;
                                mMap.clear();
                            }
                            mClear = false;
                        }
    
                        //  edit Map SharedPreferences Map
                        for (Map.Entry<String, Object> e : mModified.entrySet()) {
                            String k = e.getKey();
                            Object v = e.getValue();
                            //  romve  ,Value    this.     key    .      .                .
                            if (v == this) {  // magic value for a removal mutation
                                if (!mMap.containsKey(k)) {
                                    continue;
                                }
                                // SharedPreferences Map  
                                mMap.remove(k);
                            } else {
                                boolean isSame = false;
                                if (mMap.containsKey(k)) {
                                    Object existingValue = mMap.get(k);
                                    if (existingValue != null && existingValue.equals(v)) {
                                        continue;
                                    }
                                }
    
                                //     SharedPreferences Map
                                mMap.put(k, v);
                            }
    
                            //     ,       .    
                            mcr.changesMade = true;
                            if (hasListeners) {
                                mcr.keysModified.add(k);
                            }
                        }
    
                        //edit Map     .    .  SharedPreferences Map        
                        mModified.clear();
                    }
                }
                return mcr;
            }
    
        private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                      final Runnable postWriteRunnable) {
            final Runnable writeToDiskRunnable = new Runnable() {
                    public void run() {
                        synchronized (mWritingToDiskLock) {
                            writeToFile(mcr);
                        }
                        synchronized (SharedPreferencesImpl.this) {
                            mDiskWritesInFlight--;
                        }
                        if (postWriteRunnable != null) {
                            postWriteRunnable.run();
                        }
                    }
                };
    
            /.  commit()    ,  isFromSyncCommit true
            final boolean isFromSyncCommit = (postWriteRunnable == null);
    
            // Typical #commit() path with fewer allocations, doing a write on
            // the current thread.
            if (isFromSyncCommit) {
                boolean wasEmpty = false;
                synchronized (SharedPreferencesImpl.this) {
                    wasEmpty = mDiskWritesInFlight == 1;
                }
                if (wasEmpty) {
                    //  
                    writeToDiskRunnable.run();
                    return;
                }
            }
    
            QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
        }
    
        private void writeToFile(MemoryCommitResult mcr) {
            // Rename the current file so it may be used as a backup during the next read
            if (mFile.exists()) {
                if (!mcr.changesMade) {
                    // If the file already exists, but no changes were
                    // made to the underlying map, it's wasteful to
                    // re-write the file. Return as if we wrote it
                    // out.
                    mcr.setDiskWriteResult(true);
                    return;
                }
                if (!mBackupFile.exists()) {
                    if (!mFile.renameTo(mBackupFile)) {
                        Log.e(TAG, "Couldn't rename file " + mFile
                              + " to backup file " + mBackupFile);
                        mcr.setDiskWriteResult(false);
                        return;
                    }
                } else {
                    //    Xml    , mFile      .
                    mFile.delete();
                }
            }
    
            // Attempt to write the file, delete the backup and return true as atomically as
            // possible. If any exception occurs, delete the new file; next time we will restore
            // from the backup.
            try {
                FileOutputStream str = createFileOutputStream(mFile);
                if (str == null) {
                    mcr.setDiskWriteResult(false);
                    return;
                }
                //     
                XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
                //  
                FileUtils.sync(str);
                str.close();
    
                   //    ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
                FileStatus stat = new FileStatus();
                if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
                    synchronized (this) {
                        mStatTimestamp = stat.mtime;
                        mStatSize = stat.size;
                    }
                }
                // Writing was successful, delete the backup file if there is one.
                //   ,       
                mBackupFile.delete();
                mcr.setDiskWriteResult(true);
                return;
            } catch (XmlPullParserException e) {
                Log.w(TAG, "writeToFile: Got exception:", e);
            } catch (IOException e) {
                Log.w(TAG, "writeToFile: Got exception:", e);
            }
            // Clean up an unsuccessfully written file
            if (mFile.exists()) {
                if (!mFile.delete()) {
                    Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
                }
            }
            mcr.setDiskWriteResult(false);
        }
    }
    
    
    public boolean commit() {
                //    
                MemoryCommitResult mcr = commitToMemory();
                //        
                SharedPreferencesImpl.this.enqueueDiskWrite(
                    mcr, null /* sync write on this thread okay */);
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException e) {
                    return false;
                }
                notifyListeners(mcr);
                return mcr.writeToDiskResult;
            }
    
            private void notifyListeners(final MemoryCommitResult mcr) {
                if (mcr.listeners == null || mcr.keysModified == null ||
                    mcr.keysModified.size() == 0) {
                    return;
                }
                if (Looper.myLooper() == Looper.getMainLooper()) {
                    for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
                        final String key = mcr.keysModified.get(i);
                        for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
                            if (listener != null) {
                                listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
                            }
                        }
                    }
                } else {
                    // Run this function on the main thread.
                    ActivityThread.sMainThreadHandler.post(new Runnable() {
                            public void run() {
                                notifyListeners(mcr);
                            }
                        });
                }
            }
        }

    SharedPreferences 코드 요약
    Shared Preferences 는 본질 적 으로 Xml 파일 을 통 해 이 루어 집 니 다.사용 할 때 Xml 파일 을 맵 으로 해석 하여 메모리 에 저장 합 니 다.Xml 파일 의 모든 데 이 터 를 메모리 에 분석 한 다 는 것 을 알 아야 합 니 다.메모 리 를 사용 하 는 것 을 피 할 수 없습니다.그 다음 Shared Preferences 수정 으로 인해 전체 Xml 문 서 를 다시 쓸 수 있 습 니 다.

    좋은 웹페이지 즐겨찾기