Skip to content

Commit d72ee7d

Browse files
author
xiaoqi
committed
update 3.0.3
1 parent ac5330a commit d72ee7d

File tree

13 files changed

+539
-458
lines changed

13 files changed

+539
-458
lines changed

.idea/misc.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README-PRINCIPLE.md

+321
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
2+
# 原理介绍
3+
# 引入
4+
5+
android 内存被回收是一个开发者的常见问题。当我们**跳转到一个二级界面,或者切换到后台**的时候,如果时间过长或者手机的**内存不足**,当我们再返回这个界面的时候,activity或fragment就会被内存回收。这时候虽然界面被重新执行了onCreate,但是很多变量的值却已经被置空,这样就导致了很多潜在的bug,已经很多空指针的问题。
6+
7+
其实这种问题需要解决的话也很简单。大家知道,当Activity或者Fragment被内存回收后,我们再进入这个界面,它会自动重新进行onCreate操作,并且系统会帮助我们保存一些值。但是系统只会保存界面上的一些元素,比如textview中的文字,但是很多全局变量仍然会被置空。
8+
对于保存这些变量,我们可以重写**onSaveInstanceState**这个方法,在onCreate中即可恢复数据。代码如下:
9+
|
10+
11+
int a;
12+
@Override
13+
protected void onCreate(Bundle savedInstanceState) {
14+
super.onCreate(savedInstanceState);
15+
setContentView(R.layout.activity_main);
16+
initData();
17+
//内存回收,界面重新onCreate后,恢复数据
18+
if(savedInstanceState != null){
19+
a = savedInstanceState.getInt("A");
20+
}
21+
}
22+
23+
private void initData() {
24+
...
25+
}
26+
27+
@Override
28+
protected void onSaveInstanceState(Bundle outState) {
29+
//保存数据
30+
outState.putInt("A", a);
31+
super.onSaveInstanceState(outState);
32+
}
33+
34+
通过这样的操作,便可以解决内存回收后变量a的值变为初始值0的问题。
35+
36+
问题到这里,似乎已经可以解决内存被回收的问题了。但是随着项目的开发,一个Activity中的变量以及**代码会变得非常多**,这时候我们需要去保存某个值就会使代码变得越来越凌乱,同时不断重复的去写outState.putXX已经savedInstanceState.getXX这样的代码都是很重复的,一不小心还会去写错中间的key值。
37+
38+
于是我写了这个很轻量级的框架,来解决这个问题。先给出引入这个框架后的代码写法:
39+
40+
@NeedSave
41+
String test;
42+
@NeedSave
43+
protected boolean b;
44+
@NeedSave
45+
public Boolean c;
46+
@NeedSave
47+
public ArrayList<String> t;
48+
@NeedSave
49+
public Integer i;
50+
@NeedSave
51+
public ParcelableObject example;
52+
@NeedSave
53+
public SerializableObject example;
54+
@NeedSave
55+
public Float f1;
56+
@NeedSave
57+
public float f2;
58+
@NeedSave
59+
public char achar;
60+
@NeedSave
61+
public char achars[];
62+
@NeedSave
63+
public int sssss[];
64+
@NeedSave
65+
public Bundle bundle;
66+
@NeedSave
67+
public int a;
68+
69+
@Override
70+
protected void onCreate(Bundle savedInstanceState) {
71+
super.onCreate(savedInstanceState);
72+
setContentView(R.layout.activity_main);
73+
initData();
74+
SaveHelper.recover(this,savedInstanceState);
75+
}
76+
77+
private void initData() {
78+
//TODO
79+
}
80+
81+
@Override
82+
protected void onSaveInstanceState(Bundle outState) {
83+
SaveHelper.save(this,outState);
84+
super.onSaveInstanceState(outState);
85+
}
86+
87+
这里我特地写了很多的变量,但是无论这个Activity中有多少变量,我在onCreate和onSaveInstanceState代码中都只要去各写一行代码,同时给变量加一个标签标记一个即可:
88+
89+
@NeedSave
90+
SaveHelper.recover(this,savedInstanceState);
91+
SaveHelper.save(this,outState);
92+
93+
这样就不会因为这种太多的重复的操作去导致代码逻辑的混乱,同时也避免了敲代码时因为key写错导致的错误。
94+
95+
自定义view的变量保存示例:
96+
97+
public class CustomView extends View {
98+
99+
@NeedSave
100+
int a;
101+
102+
public CustomView(Context context) {
103+
super(context);
104+
}
105+
106+
public CustomView(Context context, @Nullable AttributeSet attrs) {
107+
super(context, attrs);
108+
}
109+
110+
111+
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
112+
super(context, attrs, defStyleAttr);
113+
}
114+
115+
116+
@Override
117+
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
118+
SaveHelper.save(this, container);
119+
super.dispatchSaveInstanceState(container);
120+
}
121+
122+
@Override
123+
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
124+
super.dispatchRestoreInstanceState(container);
125+
SaveHelper.recover(this, container);
126+
}
127+
}
128+
129+
130+
重写dispatchSaveInstanceState,dispatchRestoreInstanceState即可。
131+
注意SaveHelper.save(this, container)要在super.dispatchSaveInstanceState(container);**之前调用**
132+
# 效果展示
133+
我们来看一下测试代码:
134+
## 不进行数据保存操作
135+
136+
![](https://user-gold-cdn.xitu.io/2017/12/14/16053bf7ed7fb8f7?w=744&h=621&f=png&s=71616)
137+
很简单,就是通过点击事情,去给变量“testString”赋值,然后再去模拟内存被回收的情况,看一下显示的值是否是内存被回收前的。
138+
139+
![](https://user-gold-cdn.xitu.io/2017/12/14/16053ce960cd9ee3?w=416&h=585&f=gif&s=522365)
140+
141+
## 调用框架代码后的内存恢复
142+
加入框架代码:
143+
144+
![](https://user-gold-cdn.xitu.io/2017/12/14/16053c324e7de04c?w=696&h=787&f=png&s=95432)
145+
146+
加入代码之后的效果:
147+
148+
149+
![](https://user-gold-cdn.xitu.io/2017/12/14/16053cf18fe46d78?w=418&h=605&f=gif&s=450838)
150+
151+
152+
153+
154+
155+
# 原理介绍
156+
157+
## @NeedSave
158+
159+
这是一个注解,这个注解只能使用在全局变量中,特别注意,~~被加上这个注解的变量必须是**public**,否则会不生效~~
160+
1.0.1更新为只要非private即可。
161+
162+
~~当前支持保存的类型有:~~
163+
164+
String
165+
boolean Boolean
166+
ArrayList
167+
int int[] Integer
168+
Parcelable
169+
Serializable
170+
float Float
171+
char[] char
172+
Bundle
173+
174+
~~注意,如果是Parcelable类型,需要特别在注解中加入 @NeedSave(isParcelable = true) 这样标记~~
175+
**目前已经自动支持所有的类型,isParcelable已经弃用。**
176+
177+
## SaveHelper.recover(this,savedInstanceState);
178+
这个方法其实是恢复数据的时候去调用的。
179+
180+
public static <T> void recover(T recover, Bundle savedInstanceState){
181+
if(savedInstanceState != null){
182+
ISaveInstanceStateHelper<T> saveInstanceStateHelper = findSaveHelper(recover);
183+
if(saveInstanceStateHelper != null){
184+
saveInstanceStateHelper.recover(savedInstanceState, recover);
185+
}
186+
}
187+
}
188+
189+
savedInstanceState不会null的时候,说明就是需要内存恢复的时候,这时候就会去通过findSaveHelper方法找到一个实现类,然后去调用recover方法恢复数据。
190+
## SaveHelper.save(this,outState);
191+
这是一个保存数据的方法,**注意**的是,这个方法必须在super.onSaveInstanceState(outState);之前调用。
192+
193+
public static <T> void save(T save, Bundle outState){
194+
ISaveInstanceStateHelper<T> saveInstanceStateHelper = findSaveHelper(save);
195+
if(saveInstanceStateHelper != null){
196+
saveInstanceStateHelper.save(outState, save);
197+
}
198+
}
199+
200+
它最终调用的是ISaveInstanceStateHelper实现类的save方法。
201+
202+
## ISaveInstanceStateHelper实现类
203+
这个类是一个接口,专门用来保存和恢复数据用。这个类是不要我们自己写的,在代码编译的时候会**自动生成**模板代码。整个调用过程中也只有寻找ISaveInstanceStateHelper实现类的findSaveHelper这个方法调用了反射,其他时候不会去用到反射,而影响效率。
204+
自动生成代码所在位置:
205+
206+
![](https://user-gold-cdn.xitu.io/2017/12/9/1603a6d7496e99bf?w=465&h=275&f=png&s=15537)
207+
208+
自动生成的代码如下:
209+
210+
public class MainActivity_SaveStateHelper implements ISaveInstanceStateHelper<MainActivity> {
211+
@Override
212+
public void save(Bundle outState, MainActivity save) {
213+
outState.putString("TEST",save.test);
214+
outState.putBoolean("C",save.c);
215+
outState.putSerializable("T",save.t);
216+
outState.putInt("I",save.i);
217+
outState.putParcelable("EXAMPLE",save.example);
218+
outState.putFloat("F1",save.f1);
219+
outState.putFloat("F2",save.f2);
220+
outState.putChar("ACHAR",save.achar);
221+
outState.putCharArray("ACHARS",save.achars);
222+
outState.putIntArray("SSSSS",save.sssss);
223+
outState.putIntArray("SASA",save.sasa);
224+
outState.putBundle("BUNDLE",save.bundle);
225+
outState.putInt("A",save.a);
226+
}
227+
228+
@Override
229+
public void recover(Bundle savedInstanceState, MainActivity recover) {
230+
if(savedInstanceState != null) {
231+
recover.test = savedInstanceState.getString("TEST");
232+
recover.c = savedInstanceState.getBoolean("C");
233+
recover.t = (ArrayList<String>)savedInstanceState.getSerializable("T");
234+
recover.i = savedInstanceState.getInt("I");
235+
recover.example = savedInstanceState.getParcelable("EXAMPLE");
236+
recover.f1 = savedInstanceState.getFloat("F1");
237+
recover.f2 = savedInstanceState.getFloat("F2");
238+
recover.achar = savedInstanceState.getChar("ACHAR");
239+
recover.achars = savedInstanceState.getCharArray("ACHARS");
240+
recover.sssss = savedInstanceState.getIntArray("SSSSS");
241+
recover.sasa = savedInstanceState.getIntArray("SASA");
242+
recover.bundle = savedInstanceState.getBundle("BUNDLE");
243+
recover.a = savedInstanceState.getInt("A");
244+
}
245+
}
246+
}
247+
248+
# kotlin使用方法
249+
250+
如果要在kotlin使用,与在java中使用相同,直接加注解即可,但是不同之出在于:
251+
252+
1:如果是基本数据类型,需要多添加一个注解@JvmField
253+
254+
2:如果是其他数据类型,需要增加lateinit关键字或者添加一个注解@JvmField
255+
否则会报错"the modifier of the field must not be private, otherwise it won't work"。
256+
257+
示例:
258+
259+
260+
class KotlinActivity : AppCompatActivity() {
261+
262+
@NeedSave
263+
@JvmField
264+
var a :Int=3
265+
266+
@NeedSave
267+
lateinit var bundle: Bundle
268+
269+
override fun onCreate(savedInstanceState: Bundle?) {
270+
super.onCreate(savedInstanceState)
271+
setContentView(R.layout.activity_kotlin)
272+
SaveHelper.recover(this, savedInstanceState)
273+
Log.e("KotlinActivity", a.toString())
274+
275+
}
276+
277+
278+
override fun onSaveInstanceState(outState: Bundle?) {
279+
Log.e("KotlinActivity", "onSaveInstanceState")
280+
a = 2
281+
SaveHelper.save(this, outState)
282+
super.onSaveInstanceState(outState)
283+
}
284+
}
285+
286+
287+
# 总结
288+
看到这里大家已经猜到其实这个框架的实现原理和ButterKnife是相同的。而bufferknife的原理很多文章都有,这里就不过多介绍了。
289+
290+
# 更新
291+
292+
293+
## 2.0.0
294+
支持Bundle所有支持的的类型
295+
296+
## 2.1.0
297+
增加对PersistableBundle持久化数据的保存,用于手机关机重启后的数据恢复,使用方法如下:
298+
299+
300+
@NeedSave(isPersistable = true)
301+
PersistableBundle persistableBundle;
302+
303+
@NeedSave(isPersistable = true)
304+
int i;
305+
306+
@Override
307+
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
308+
super.onCreate(savedInstanceState, persistentState);
309+
SaveHelper.recover(this, savedInstanceState, persistentState);
310+
}
311+
312+
@Override
313+
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
314+
SaveHelper.save(this, outState, outPersistentState);
315+
super.onSaveInstanceState(outState, outPersistentState);
316+
}
317+
318+
319+
320+
github地址:[https://github.com/JavaNoober/AutoSave](https://github.com/JavaNoober/AutoSave)
321+

0 commit comments

Comments
 (0)