您的位置:首页 > 移动开发

App 研发录、架构设计、Crash分析和竞品技术分析------读书笔记(第三章)

2016-05-20 09:58 597 查看
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.csdn.net/yueqinglkong/article/details/27660107

http://blog.csdn.net/vipzjyno1/article/details/23206387

http://blog.csdn.net/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这个变量
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: