1. 程式人生 > >Android 一個輕量級的自動恢復記憶體資料框架

Android 一個輕量級的自動恢復記憶體資料框架

license JCenter

引入

android 記憶體被回收是一個開發者的常見問題。當我們跳轉到一個二級介面,或者切換到後臺的時候,如果時間過長或者手機的記憶體不足,當我們再返回這個介面的時候,activity或fragment就會被記憶體回收。這時候雖然介面被重新執行了onCreate,但是很多變數的值卻已經被置空,這樣就導致了很多潛在的bug,已經很多空指標的問題。

其實這種問題需要解決的話也很簡單。大家知道,當Activity或者Fragment被記憶體回收後,我們再進入這個介面,它會自動重新進行onCreate操作,並且系統會幫助我們儲存一些值。但是系統只會儲存介面上的一些元素,比如textview中的文字,但是很多全域性變數仍然會被置空。
對於儲存這些變數,我們可以重寫onSaveInstanceState

這個方法,在onCreate中即可恢復資料。程式碼如下:
|

public int a;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initData();
    //記憶體回收,介面重新onCreate後,恢復資料
    if(savedInstanceState != null){
        a = savedInstanceState.getInt("A");
    }
}

private void initData() {
    ...
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    //儲存資料
    outState.putInt("A", a);
    super.onSaveInstanceState(outState);
}

通過這樣的操作,便可以解決記憶體回收後變數a的值變為初始值0的問題。

問題到這裡,似乎已經可以解決記憶體被回收的問題了。但是隨著專案的開發,一個Activity中的變數以及程式碼會變得非常多,這時候我們需要去儲存某個值就會使程式碼變得越來越凌亂,同時不斷重複的去寫outState.putXX已經savedInstanceState.getXX這樣的程式碼都是很重複的,一不小心還會去寫錯中間的key值。

於是我寫了這個很輕量級的框架,來解決這個問題。先給出引入這個框架後的程式碼寫法:

@NeedSave
public String test;
@NeedSave
private boolean b;
@NeedSave
public Boolean c;
@NeedSave
public ArrayList<String> t;
@NeedSave
public Integer i;
@NeedSave(isParcelable = true)
public ParcelableObject example;
@NeedSave
public SerializableObject example;
@NeedSave
public Float f1;
@NeedSave
public float f2;
@NeedSave
public char achar;
@NeedSave
public char achars[];
@NeedSave
public int sssss[];
@NeedSave
public Bundle bundle;
@NeedSave
public int a;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initData();
    SaveHelper.bind(this,savedInstanceState);
}

private void initData() {
    ...
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    SaveHelper.save(this,outState);
    super.onSaveInstanceState(outState);
}

這裡我特地寫了很多的變數,但是無論這個Activity中有多少變數,我在onCreate和onSaveInstanceState程式碼中都只要去各寫一行程式碼,同時給變數加一個標籤標記一個即可:

    @NeedSave
    SaveHelper.bind(this,savedInstanceState);
    SaveHelper.save(this,outState);

這樣就不會因為這種太多的重複的操作去導致程式碼邏輯的混亂,同時也避免了敲程式碼時因為key寫錯導致的錯誤。

效果展示

我們來看一下測試程式碼:

不進行資料儲存操作


很簡單,就是通過點選事情,去給變數“testString”賦值,然後再去模擬記憶體被回收的情況,看一下顯示的值是否是記憶體被回收前的。

呼叫框架程式碼後的記憶體恢復

加入框架程式碼:

加入程式碼之後的效果:

原理介紹

@NeedSave

這是一個註解,這個註解只能使用在全域性變數中,特別注意,被加上這個註解的變數必須是public,否則會不生效。
當前支援儲存的型別有:

    String
    boolean Boolean 
    ArrayList
    int int[] Integer
    Parcelable
    Serializable
    float Float
    char[] char
    Bundle 

注意,如果是Parcelable型別,需要特別在註解中加入   @NeedSave(isParcelable = true) 這樣標記

SaveHelper.bind(this,savedInstanceState);

這個方法其實是恢復資料的時候去呼叫的。

public static <T> void bind(T recover, Bundle savedInstanceState){
    if(savedInstanceState != null){
        ISaveInstanceStateHelper<T> saveInstanceStateHelper = findSaveHelper(recover);
        if(saveInstanceStateHelper != null){
            saveInstanceStateHelper.recover(savedInstanceState, recover);
        }
    }
}

savedInstanceState不會null的時候,說明就是需要記憶體恢復的時候,這時候就會去通過findSaveHelper方法找到一個實現類,然後去呼叫recover方法恢復資料。

SaveHelper.save(this,outState);

這是一個儲存資料的方法,注意的是,這個方法必須在super.onSaveInstanceState(outState);之前呼叫。

        public static <T> void save(T save, Bundle outState){
            ISaveInstanceStateHelper<T> saveInstanceStateHelper = findSaveHelper(save);
            if(saveInstanceStateHelper != null){
                saveInstanceStateHelper.save(outState, save);
            }
        }

它最終呼叫的是ISaveInstanceStateHelper實現類的save方法。

ISaveInstanceStateHelper實現類

這個類是一個介面,專門用來儲存和恢復資料用。這個類是不要我們自己寫的,在程式碼編譯的時候會自動生成模板程式碼。整個呼叫過程中也只有尋找ISaveInstanceStateHelper實現類的findSaveHelper這個方法呼叫了反射,其他時候不會去用到反射,而影響效率。
自動生成程式碼所在位置:

自動生成的程式碼如下:

public class MainActivity_SaveStateHelper implements ISaveInstanceStateHelper<MainActivity> {
  @Override
  public void save(Bundle outState, MainActivity save) {
    outState.putString("TEST",save.test);
    outState.putBoolean("C",save.c);
    outState.putSerializable("T",save.t);
    outState.putInt("I",save.i);
    outState.putParcelable("EXAMPLE",save.example);
    outState.putFloat("F1",save.f1);
    outState.putFloat("F2",save.f2);
    outState.putChar("ACHAR",save.achar);
    outState.putCharArray("ACHARS",save.achars);
    outState.putIntArray("SSSSS",save.sssss);
    outState.putIntArray("SASA",save.sasa);
    outState.putBundle("BUNDLE",save.bundle);
    outState.putInt("A",save.a);
  }

  @Override
  public void recover(Bundle savedInstanceState, MainActivity recover) {
    if(savedInstanceState != null) {
      recover.test = savedInstanceState.getString("TEST");
      recover.c = savedInstanceState.getBoolean("C");
      recover.t = (ArrayList<String>)savedInstanceState.getSerializable("T");
      recover.i = savedInstanceState.getInt("I");
      recover.example = savedInstanceState.getParcelable("EXAMPLE");
      recover.f1 = savedInstanceState.getFloat("F1");
      recover.f2 = savedInstanceState.getFloat("F2");
      recover.achar = savedInstanceState.getChar("ACHAR");
      recover.achars = savedInstanceState.getCharArray("ACHARS");
      recover.sssss = savedInstanceState.getIntArray("SSSSS");
      recover.sasa = savedInstanceState.getIntArray("SASA");
      recover.bundle = savedInstanceState.getBundle("BUNDLE");
      recover.a = savedInstanceState.getInt("A");
    }
  }
}

總結

看到這裡大家已經猜到其實這個框架的實現原理和BufferKnife是相同的。而bufferknife的原理很多文章都有,這裡就不過多介紹了。

github地址:https://github.com/JavaNoober/AutoSave

引入方式,在app的gradle中加入下面依賴即可:

compile 'com.noober:savehelper:1.0.0'
compile 'com.noober:savehelper-api:1.0.0'
annotationProcessor 'com.noober:processor:1.0.0'