Android经典场景设计
1、App图片缓存设计
设计一个ImageLoaderr,ImageLoader的工作原理是 这样的在显示图片的时候,这会先从内存中查找;如果没有就去本地查找,如果还没有就开一个新的线程去下载这个图片,下载成功会把图片同时缓存到内存和本地。
基于这个原理,我们可以在每次退出一个页面的时候,把ImageLoader内存中的缓存全部清除,这样就节省了大量的内存,反正下次再用到的时候就从本地再取出来,因为ImageLoader对图片是软引用的形式,所以内存中的图片在内存不足时就会被系统回收
ImageLoader的使用
ImageLoader由三大组件组成
- ImageLoaderConfiguration—对图片缓存进行总体配置包括内存缓存的大小、本地缓存的大小和位置,日志,下载策略等
- ImageLoader 我们一般使用displayImage来把URL对应的图片显示在ImageView上
- DisplayImageOptions在每个页面需要显示图片的地方,控制如何显示的细节,比如指定下载时的默认图,是否缓存到内存或者是本地。
(1)我们在Application中配置ImageLoader
public class YoungHeartApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CacheManager.getInstance().initCacheDir();
ImageLoaderConfiguration config =
new ImageLoaderConfiguration.Builder(
getApplicationContext())
.threadPriority(Thread.NORM_PRIORITY - 2)
.memoryCacheExtraOptions(480, 480)
.memoryCacheSize(2 * 1024 * 1024)
.denyCacheImageMultipleSizesInMemory()
.discCacheFileNameGenerator(new Md5FileNameGenerator())
.tasksProcessingOrder(QueueProcessingType.LIFO)
.memoryCache(new WeakMemoryCache()).build();
ImageLoader.getInstance().init(config);
}
}
(2)在使用ImageView加载图片的地方,配置当前页面的ImageLoader选项,有可能是Activity,也有可能是Adapter;
private DisplayImageOptions options;
public CinemaAdapter(ArrayList<CinemaBean> cinemaList,
AppBaseActivity context) {
this.cinemaList = cinemaList;
this.context = context;
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.ic_launcher)
.showImageForEmptyUri(R.drawable.ic_launcher)
.cacheInMemory()
.cacheOnDisc()
.build();
}
(3)在使用ImageView加载图片的地方,使用ImageLoader代码片段节选自上面的配置
imageLoader.displayImage(cinemaList.get(position)
.getCinemaPhotoUrl(), holder.imgPhoto);
2 、ImageLoader优化
虽然ImageLoader很强大,但一直把图片缓存在内存中,会导致内存占用过高,虽然对图片的引用是软引用,软引用在内存不够的时候会被GC,我们希望减少GC次数,所以需要手动清理ImageLoader中的缓存
我们在BaseActivity中的onDestroy方法中,执行Imageloader的clearMemoryCache,以确保每个页面都销毁
public abstract class AppBaseActivity extends BaseActivity {
protected boolean needCallback;
protected ProgressDialog dlg;
public ImageLoader imageLoader = ImageLoader.getInstance();
protected void onDestroy() {
//回收该页面缓存在内存的图片
imageLoader.clearMemoryCache();
super.onDestroy();
}
public abstract class AbstractRequestCallback implements RequestCallback {
public abstract void onSuccess(String content);
public void onFail(String errorMessage) {
dlg.dismiss();
new AlertDialog.Builder(AppBaseActivity.this).setTitle("出错啦")
.setMessage(errorMessage).setPositiveButton("确定", null)
.show();
}
public void onCookieExpired() {
dlg.dismiss();
new AlertDialog.Builder(AppBaseActivity.this)
.setTitle("出错啦")
.setMessage("Cookie过期,请重新登录")
.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
Intent intent = new Intent(AppBaseActivity.this,LoginActivity.class);
intent.putExtra(AppConstants.NeedCallback,true);
startActivity(intent);
}
}).show();
}
}
}
关于更多的ImageLoader配置参考下面的链接地址
http://blog.youkuaiyun.com/yueqinglkong/article/details/27660107
http://blog.youkuaiyun.com/vipzjyno1/article/details/23206387
http://blog.youkuaiyun.com/xiaanming/article/details/39057201
3、图片加载利器Fresco
Fresco的使用
- 在Application级别,对Fresco进行初始化
Fresco.initialize(getApplicationContenxt());
- Fresco是基于控件级别的,所以程序中显示网络图片需要把ImageView都替换为SimpleDraweeView
- Fresco也可以配置像Imageloader,使用ImagePipelineConfig来做这个事情,
Fresco核心技术分为三层
- 第一层:Bitmap缓存
在Android 5.0系统中考虑内存管理有了很大改进,所以Bitmap缓存位于java的堆(heap)中,
在android 4.0x和更底的系统,Bitmap缓存位于ashmem中,而不是位于Java的堆(heap),这意味着图片的创建和回收不会引发这多的GC,从而让App运动得更快,当App切换到后台时,Bitmap缓存会被清空 - 第二层:内存缓存
内存缓存存储了原始压缩格式,从内存中取出的图片,显示必须先解压,切换到后台时,内存缓存会清空 - 第三层:硬盘缓存
4、对网络流量进行优化
首先从接口层面进行优化:
- 从接口返回的数据,要使用gzip压缩,注意:大于1kb才进行压缩,否则得不偿失,
- json因为是xml格式的,数据量上看还是有一定的压缩空间的,在大数据时,可以使用ProtoBuffer,这种协议是二进制的,比json小很多
- 减少MobileApi调用 的次数
- 要建立取消网络请求的机制,一个页面如果没有请求完成,跳转到另外一个页面,取消之前的
5、图片策略优化
1、要确保下载的每张图,都符合ImageView控件的大小,
找最接近图片尺寸的办法 是面积法
s = (w1-a) * (w1-w) + (h1-h) * (h1-h)
w和h是实际的图片宽和高,w1和h1是事先规定的某个尺寸,s最小的那个
2、底流量模式
在请求服务接口的时候,我们可以在URL再增加一个参数quality,2G网络这个值是50%,3G这个值是70%,在列表页面的时候减少用户流量
3、极速模式
可以在设置里面进行设置是否在2G或者3G的时候进行加载图片
6、城市列表的设计
基于此,App的策略可以是这样的
1)本地仍然保存一份线上最新的城市列表数据(序列化后的)以及对应的版本号,我们要求每次发版本前做一次城市数据同步的事情。
2)每次进入到城市列表这个页面时,将本地城市列表数据对应的版本号version传入到接口中,根据返回的isMatch的值来判断是否版本号一致,如果一致,则直接从本地文件加载,如果不一致,就解析数据,把最新的列表数据和版本号序列保存到本地
3)如果网络加载失败从本地加载
4)在每次调用mobildeApi里,一定要开启gzip压缩
7、城市列表数据的增量更新机制
前面提到过当有数据更新时,version可以立即自增+1,
增量更新由增、删、改 3部分组成,我们可以在每笔数据中增加一个type,用来区分是c、d、m来进行操作
8、App与HTML5的交互
1)app操作Html5方法
// javascript代码
<script type="text/javascript">
function changeColor (color) {
document.body.style.backgroundColor = color;
}
</script>
// Android代码
wvAds.getSettings().setJavaScriptEnabled(true);
wvAds.loadUrl("file:///android_asset/104.html");
btnShowAlert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String color = "#00ee00";
wvAds.loadUrl("javascript: changeColor ('" + color + "');");
}
});
2)HTMl操作App
// HTML代码
<body>
<a onclick="baobao.callAndroidMethod(100,100,'ccc',true)">
CallAndroidMethod</a>
<a onclick="baobao.gotoAnyWhere('gotoNewsList:cityId=(int)12&cityName=北京')">
gotoAnyWhere</a>
</body>
// Android代码
wvAds.addJavascriptInterface(new JSInteface1(), "baobao");
class JSInteface1 {
public void callAndroidMethod(int a, float b, String c, boolean d) {
if (d) {
String strMessage = "-" + (a + 1) + "-" + (b + 1) + "-" + c
+ "-" + d;
new AlertDialog.Builder(MainActivity.this).setTitle("title")
.setMessage(strMessage).show();
}
}
public void gotoAnyWhere(String url) {
if (url != null) {
if (url.startsWith("gotoMovieDetail:")) {
String strMovieId = url.substring(24);
int movieId = Integer.valueOf(strMovieId);
Intent intent = new Intent(MainActivity.this,
MovieDetailActivity.class);
intent.putExtra("movieId", movieId);
startActivity(intent);
} else if (url.startsWith("gotoNewsList:")) {
//as above
} else if (url.startsWith("gotoPersonCenter")) {
Intent intent = new Intent(MainActivity.this,
PersonCenterActivity.class);
startActivity(intent);
} else if (url.startsWith("gotoUrl:")) {
String strUrl = url.substring(8);
wvAds.loadUrl(strUrl);
}
}
}
}
public void callAndroidMethod(int a, float b, String c, boolean d) {
if (d) {
String strMessage = "-" + (a + 1) + "-" + (b + 1) + "-" + c
+ "-" + d;
new AlertDialog.Builder(MainActivity.this).setTitle("title")
.setMessage(strMessage).show();
}
}
在小米3上,要在方法前加@JavascriptInterface,否则就不能触发javascript方法
9、App和HTML5之间定义跳转协议
根据上面的例子,运营团队就找到了App搞活动的解决方案,不发等待App每次发新版本才看到新的活动页面,而是每次做一个Html5的活动页面,然后通过mobileApi把这个HTML5页面的地址告诉App,然后这个App加载这个HTML5页面即可。
为此,HTML5和App约定好格式,例如:
gotoPersonCenter
gotoMovieDetail:movieId = 100
gotoNewsList:cityId=1&cityName=北京
gotoUrl:http://www.sina.com
然后就是上面的事例gotoAnyWhere(String url)
10、在App中内置 HTML5页面
根据经验什么时候需要内置HTML5页面也,一般当有些UI不太容易在App中使用原生语言实现时,比如画一个奇形怪状的表格,这是HTML5擅长的领域,只要调整好适配
事例讲解页面中显示一个表格,表格里面的内容是动态填充的
1)首先定义好两个HTML5文件,放在assets下,下面是静态页面的代码
<html>
<head>
</head>
<body>
<table>
<data1DefinedByBaobao>
</table>
</body>
</html>
再有一个数据模板data1_template.html,它负责提供表格中的一行的样式:
<tr>
<td>
<name>
</td>
<td>
<price>
</td>
</tr>
上面的这个<name>
和<price>
都是占位符 ,下面我们会用真实的数据来替换这些占位符
String template = getFromAssets("data1_template.html");
StringBuilder sbContent = new StringBuilder();
ArrayList<MovieInfo> movieList = organizeMovieList();
for (MovieInfo movie : movieList) {
String rowData;
rowData = template.replace("<name>", movie.getName());
rowData = rowData.replace("<price>", movie.getPrice());
sbContent.append(rowData);
}
String realData = getFromAssets("102.html");
realData = realData.replace("<data1DefinedByBaobao>",
sbContent.toString());
wvAds.loadData(realData, "text/html", "utf-8");
10、灵活切换Native 和HTML5页面的策略
对于经常需要改动的页面,我们会把它做成HTML5,在App中以WebView的形式加载,这样就避免页面每次修改,都要迭代更新
我们有一个更新灵活的方案,我们同时做两套页面,Native一套,HTML5一套,然后在App中设置一个变量,来判断页面将显示Native还是Html5,这个变量从接口中获取,我们要实现上面的这种形式的思路,大概如下
- 需要做一个后台,根据版本进行配置每个页面是使用Native还是HTML5页面
- 在App启动的时候,从接口获取每个页面是native还是HTML5
- 在App的代码层面,页面之间要实现松藕合,为此我们要设计一个导航器Navigator,由它来控制该跳转到native还是html5,最大的挑战是页面间参数传递,字典是一个比较好的形式
11 页面分发器
如果从html5页面跳转到Native页面,是不大可能传递复杂类型的实体,只能传递简单类型,所以,并不是每个native页面都可以替换为HTML5,接下来讨论的是,来自html5页面,传递简单类型的页面跳转请求,我们将其抽象为一个分发器,放到baseactivyt中。
将上面的gotoMovieDetail为例:
<a onclick = "baobao.goAnyWhere('gotoMoiveDetail:movieId=12')">gotoAnyWhere</a>
将上面的改写成
<a onclick = "baobao.goAnyWhere('com.example.youngheart.MovieDetailActivity,ios.movieDetailViewController:movieId=(int)123')">gotoAnyWhere</a>
上面分成3段,第一个是android要跳转activyt名称,二是ios跳转,三是传参数,key-value形式,下面我们取第一段反射为activity对象,取3段为参数
private String getAndroidPageName(String key) {
String pageName = null;
int pos = key.indexOf(",");
if (pos == -1) {
pageName = key;
} else {
pageName = key.substring(0, pos);
}
return pageName;
}
public void gotoAnyWhere2(String url) {
if (url == null)
return;
String pageName = getAndroidPageName(url);
if (pageName == null || pageName.trim() == "")
return;
Intent intent = new Intent();
int pos = url.indexOf(":");
if (pos > 0) {
String strParams = url.substring(pos);
String[] pairs = strParams.split("&");
for (String strKeyAndValue : pairs) {
String[] arr = strKeyAndValue.split("=");
String key = arr[0];
String value = arr[1];
if (value.startsWith("(int)")) {
intent.putExtra(key, Integer.valueOf(value.substring(5)));
} else if (value.startsWith("(Double)")) {
intent.putExtra(key, Double.valueOf(value.substring(8)));
} else {
intent.putExtra(key, value);
}
}
}
try {
intent.setClass(this, Class.forName(pageName));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
startActivity(intent);
}
我们要在前面加上类型(int)这样的约定,这样在解析时才不出错,
12、消灭全局变量
一些配置底的手机,在App切换到后台,闲置了一段时间后,再继续使用时,就会崩溃。在内存不足的时候,系统会回收一些闲置的资源,由于APP切换到后台,所以之前存放的全局变量很容易被回收,要想解决这个问题,就一定要使用序列化技术。
- 把数据作为Intent的参数传递
intent也不能传递过大的数据,也会发生崩溃。 - 把全局变量序列化到本地
下面演示GlobalsVariables变量
public class GlobalVariables implements Serializable, Cloneable {
/**
* @Fields: serialVersionUID
*/
private static final long serialVersionUID = 1L;
private static GlobalVariables instance;
private GlobalVariables() {
}
public static GlobalVariables getInstance() {
if (instance == null) {
Object object = Utils.restoreObject(
AppConstants.CACHEDIR + TAG);
if(object == null) { //App首次启动,文件不存在则新建之
object = new GlobalVariables();
Utils.saveObject(
AppConstants.CACHEDIR + TAG, object);
}
instance = (GlobalVariables)object;
}
return instance;
}
public final static String TAG = "GlobalVariables";
private UserBean user;
public UserBean getUser() {
return user;
}
public void setUser(UserBean user) {
this.user = user;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
// —————以下3个方法用于序列化————————
public GlobalVariables readResolve()
throws ObjectStreamException,
CloneNotSupportedException {
instance = (GlobalVariables) this.clone();
return instance;
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
}
public Object Clone() throws CloneNotSupportedException {
return super.clone();
}
public void reset() {
user = null;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
}
下面分析上面的代码:
- 首先这个一个单例,我们只能以如下方式来读写user数据
UserBean user = GlobalVariables.getInstance().getUser();
上面仅仅在声明中添加implements Seializable是不够的,因为序列化对象在每次反序列的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用,为了防止这个情况,需要在单例类中加入readResolve方法和readObject方法,并实现Cloneable接口。
- 看GlobalsVariables类的构建函数,不为空说明没有被回收,为空要么是本地文件不存在,还有全局变量被回收了,所以要在工具类util中加下两个方法restoreObject和saveObject两个方法。
public static final void saveObject(String path, Object saveObject) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
File f = new File(path);
try {
fos = new FileOutputStream(f);
oos = new ObjectOutputStream(fos);
oos.writeObject(saveObject);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static final Object restoreObject(String path) {
FileInputStream fis = null;
ObjectInputStream ois = null;
Object object = null;
File f = new File(path);
if (!f.exists()) {
return null;
}
try {
fis = new FileInputStream(f);
ois = new ObjectInputStream(fis);
object = ois.readObject();
return object;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (ois != null) {
ois.close();
}
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return object;
}
- 全局变量User变量,具有getUser()和setUser这两个方法,每一次调用setUser就会执行utils类的saveObject这个方法,如果User里面有一个实体,那么这个实现也要实现Serializable接口。
- 接下来我们看如何使用全局变量。
来源页
private void gotoLoginActivity() {
UserBean user = new UserBean();
user.setUserName("Jianqiang");
user.setCountry("Beijing");
user.setAge(32);
Intent intent = new Intent(LoginNew2Activity.this,
PersonCenterActivity.class);
GlobalVariables.getInstance().setUser(user);
startActivity(intent);
}
使用页
protected void initVariables() {
UserBean user = GlobalVariables.getInstance().getUser();
int age = user.getAge();
}
- 在App启动的时候,我们要清空存放本地文件的全局变量,因为这些全局变量的生命周期都应该随着App的关闭而消亡,但是我们来不及在App关闭的时候做,所以只好在app启动的时候第一件就是清队这些临时数据,为些需要在GlobalVariables这个全局变量类中增加一个reset方法,用于清空数据后,把空值强制保存到本地。
GlobalVariables.getInstance().reset();
public void reset() {
user = null;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
13、序列化的不好的地方
再次强调,把全局变量序列化本地,只是一种过渡解决方案,它有如下不好的地方
- 每次设置全局变量的值都要强制一次序列化,容易先成ANR,事例
public class GlobalVariables3 implements Serializable, Cloneable {
/**
* @Fields: serialVersionUID
*/
private static final long serialVersionUID = 1L;
private static GlobalVariables3 instance;
private GlobalVariables3() {
}
public static GlobalVariables3 getInstance() {
if (instance == null) {
Object object = Utils.restoreObject(AppConstants.CACHEDIR + TAG);
if(object == null) { //App第一次启动,文件不存在,则新建之
object = new GlobalVariables3();
Utils.saveObject(AppConstants.CACHEDIR + TAG, object);
}
instance = (GlobalVariables3)object;
}
return instance;
}
public final static String TAG = "GlobalVariables3";
private String userName;
private String nickName;
private String country;
public void reset() {
userName = null;
nickName = null;
country = null;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
// -----------以下3个方法用于序列化-----------------
public GlobalVariables3 readResolve() throws ObjectStreamException,
CloneNotSupportedException {
instance = (GlobalVariables3) this.clone();
return instance;
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
}
public Object Clone() throws CloneNotSupportedException {
return super.clone();
}
}
我们发现每次设置的时候,都要强制序列化本地一次,如果属性多了,序列化很多次,可以把所以属性设置完了再序列化一次
public class GlobalVariables4 implements Serializable, Cloneable {
/**
* @Fields: serialVersionUID
*/
private static final long serialVersionUID = 1L;
private static GlobalVariables4 instance;
private GlobalVariables4() {
}
public static GlobalVariables4 getInstance() {
if (instance == null) {
Object object = Utils.restoreObject(AppConstants.CACHEDIR + TAG);
if(object == null) { //App第一次启动,文件不存在,则新建之
object = new GlobalVariables4();
Utils.saveObject(AppConstants.CACHEDIR + TAG, object);
}
instance = (GlobalVariables4)object;
}
return instance;
}
public final static String TAG = "GlobalVariables3";
private String userName;
private String nickName;
private String country;
private HashMap<String, String> rules;
private String strCinema;
private String strPersons;
public void reset() {
userName = null;
nickName = null;
country = null;
rules = null;
strCinema = null;
strPersons = null;
guides = null;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
public String getUserName() {
return userName;
}
public void setUserName(String userName, boolean needSave) {
this.userName = userName;
if(needSave) {
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName, boolean needSave) {
this.nickName = nickName;
if(needSave) {
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
}
public String getCountry() {
return country;
}
public void setCountry(String country, boolean needSave) {
this.country = country;
if(needSave) {
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
}
public HashMap<String, String> getRules() {
return rules;
}
public void setRules(HashMap<String, String> rules) {
this.rules = rules;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
public JSONObject getCinema() {
if(strCinema == null)
return null;
try {
return new JSONObject(strCinema);
} catch (JSONException e) {
return null;
}
}
public void setCinema(JSONObject cinema) {
if(cinema == null) {
this.strCinema = null;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
return;
}
this.strCinema = cinema.toString();
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
public JSONArray getPersons() {
if(strPersons == null)
return null;
try {
return new JSONArray(strPersons);
} catch (JSONException e) {
return null;
}
}
public void setPersons(JSONArray persons) {
if(persons == null) {
this.strPersons = null;
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
return;
}
this.strPersons = persons.toString();
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
HashMap<String, Object> guides;
public HashMap<String, Object> getGuides() {
return guides;
}
public void setGuides(HashMap<String, Object> guides) {
if (guides == null) {
this.guides = new HashMap<String, Object>();
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
return;
}
this.guides = new HashMap<String, Object>();
Set set = guides.entrySet();
java.util.Iterator it = guides.entrySet().iterator();
while (it.hasNext()) {
java.util.Map.Entry entry = (java.util.Map.Entry) it.next();
Object value = entry.getValue();
String key = String.valueOf(entry.getKey());
this.guides.put(key, String.valueOf(value));
}
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
// -----------以下3个方法用于序列化-----------------
public GlobalVariables4 readResolve() throws ObjectStreamException,
CloneNotSupportedException {
instance = (GlobalVariables4) this.clone();
return instance;
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
}
public Object Clone() throws CloneNotSupportedException {
return super.clone();
}
public void save() {
Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
}
}
每次set后不做序列化,最后做序列化,这只是权宜之计,相当于补丁,是临时解决方案,
- 序列化的文件,会因为内存不够而丢失
因为会保存到/data/data/com.youngheart/cache/下面,内存不足会发生数据丢失的情况,保存SD卡不稳定,临时解决方案是每次使用完过后就要清空,减少体积 - Android并不是所有 的数据都支持序列化
可以所这些数据转换为json再保存,我们尽量不要使用序列化数据类型,包括JSONObject、JSONArray、HashMap<String、Object>、ArrayList<HashMap<String、Object>>
、
14、如果Activity也被销毁了呢
最好的解决方案是重新执行当前Activity的onCreate方法,这样做最安全、在onSaveInstanceState()、onRestoreInstanceState()
最好 做法是重新执行onCreate,因为页面太多不可能都保存
15、如何看待SharePreferences
SharePreference是全局变量序列化到本地的另一种形式、也可以存取任何支持序列化的数据类型
16、User是唯一例外的全局变量
依我看来,App中只有一个全局变量的存在是合理的,那就是User类,因为我们在任何地方都有可能用一User这个变量