App 研发录、架构设计、Crash分析和竞品技术分析------读书笔记(第三章)
2016-05-20 09:58
691 查看
Android经典场景设计
1、App图片缓存设计
设计一个ImageLoaderr,ImageLoader的工作原理是 这样的在显示图片的时候,这会先从内存中查找;如果没有就去本地查找,如果还没有就开一个新的线程去下载这个图片,下载成功会把图片同时缓存到内存和本地。
ImageLoader的使用
ImageLoader由三大组件组成
ImageLoaderConfiguration—对图片缓存进行总体配置包括内存缓存的大小、本地缓存的大小和位置,日志,下载策略等
ImageLoader 我们一般使用displayImage来把URL对应的图片显示在ImageView上
DisplayImageOptions在每个页面需要显示图片的地方,控制如何显示的细节,比如指定下载时的默认图,是否缓存到内存或者是本地。
(1)我们在Application中配置ImageLoader
(2)在使用ImageView加载图片的地方,配置当前页面的ImageLoader选项,有可能是Activity,也有可能是Adapter;
(3)在使用ImageView加载图片的地方,使用ImageLoader代码片段节选自上面的配置
2 、ImageLoader优化
虽然ImageLoader很强大,但一直把图片缓存在内存中,会导致内存占用过高,虽然对图片的引用是软引用,软引用在内存不够的时候会被GC,我们希望减少GC次数,所以需要手动清理ImageLoader中的缓存
关于更多的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是基于控件级别的,所以程序中显示网络图片需要把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方法
2)HTMl操作App
在小米3上,要在方法前加@JavascriptInterface,否则就不能触发javascript方法
9、App和HTML5之间定义跳转协议
根据上面的例子,运营团队就找到了App搞活动的解决方案,不发等待App每次发新版本才看到新的活动页面,而是每次做一个Html5的活动页面,然后通过mobileApi把这个HTML5页面的地址告诉App,然后这个App加载这个HTML5页面即可。
10、在App中内置 HTML5页面
事例讲解页面中显示一个表格,表格里面的内容是动态填充的
1)首先定义好两个HTML5文件,放在assets下,下面是静态页面的代码
再有一个数据模板data1_template.html,它负责提供表格中的一行的样式:
上面的这个
10、灵活切换Native 和HTML5页面的策略
对于经常需要改动的页面,我们会把它做成HTML5,在App中以WebView的形式加载,这样就避免页面每次修改,都要迭代更新
需要做一个后台,根据版本进行配置每个页面是使用Native还是HTML5页面
在App启动的时候,从接口获取每个页面是native还是HTML5
在App的代码层面,页面之间要实现松藕合,为此我们要设计一个导航器Navigator,由它来控制该跳转到native还是html5,最大的挑战是页面间参数传递,字典是一个比较好的形式
11 页面分发器
如果从html5页面跳转到Native页面,是不大可能传递复杂类型的实体,只能传递简单类型,所以,并不是每个native页面都可以替换为HTML5,接下来讨论的是,来自html5页面,传递简单类型的页面跳转请求,我们将其抽象为一个分发器,放到baseactivyt中。
将上面的改写成
上面分成3段,第一个是android要跳转activyt名称,二是ios跳转,三是传参数,key-value形式,下面我们取第一段反射为activity对象,取3段为参数
我们要在前面加上类型(int)这样的约定,这样在解析时才不出错,
12、消灭全局变量
一些配置底的手机,在App切换到后台,闲置了一段时间后,再继续使用时,就会崩溃。在内存不足的时候,系统会回收一些闲置的资源,由于APP切换到后台,所以之前存放的全局变量很容易被回收,要想解决这个问题,就一定要使用序列化技术。
把数据作为Intent的参数传递
intent也不能传递过大的数据,也会发生崩溃。
把全局变量序列化到本地
下面演示GlobalsVariables变量
下面分析上面的代码:
首先这个一个单例,我们只能以如下方式来读写user数据
上面仅仅在声明中添加implements Seializable是不够的,因为序列化对象在每次反序列的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用,为了防止这个情况,需要在单例类中加入readResolve方法和readObject方法,并实现Cloneable接口。
看GlobalsVariables类的构建函数,不为空说明没有被回收,为空要么是本地文件不存在,还有全局变量被回收了,所以要在工具类util中加下两个方法restoreObject和saveObject两个方法。
全局变量User变量,具有getUser()和setUser这两个方法,每一次调用setUser就会执行utils类的saveObject这个方法,如果User里面有一个实体,那么这个实现也要实现Serializable接口。
接下来我们看如何使用全局变量。
来源页
使用页
在App启动的时候,我们要清空存放本地文件的全局变量,因为这些全局变量的生命周期都应该随着App的关闭而消亡,但是我们来不及在App关闭的时候做,所以只好在app启动的时候第一件就是清队这些临时数据,为些需要在GlobalVariables这个全局变量类中增加一个reset方法,用于清空数据后,把空值强制保存到本地。
13、序列化的不好的地方
再次强调,把全局变量序列化本地,只是一种过渡解决方案,它有如下不好的地方
每次设置全局变量的值都要强制一次序列化,容易先成ANR,事例
我们发现每次设置的时候,都要强制序列化本地一次,如果属性多了,序列化很多次,可以把所以属性设置完了再序列化一次
每次set后不做序列化,最后做序列化,这只是权宜之计,相当于补丁,是临时解决方案,
序列化的文件,会因为内存不够而丢失
因为会保存到/data/data/com.youngheart/cache/下面,内存不足会发生数据丢失的情况,保存SD卡不稳定,临时解决方案是每次使用完过后就要清空,减少体积
Android并不是所有 的数据都支持序列化
可以所这些数据转换为json再保存,我们尽量不要使用序列化数据类型,包括JSONObject、JSONArray、
14、如果Activity也被销毁了呢
最好的解决方案是重新执行当前Activity的onCreate方法,这样做最安全、在
15、如何看待SharePreferences
SharePreference是全局变量序列化到本地的另一种形式、也可以存取任何支持序列化的数据类型
16、User是唯一例外的全局变量
依我看来,App中只有一个全局变量的存在是合理的,那就是User类,因为我们在任何地方都有可能用一User这个变量
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这个变量
相关文章推荐
- unity 有限状态机使用
- Android studio 导入.9.png图片报错
- iOS 广告轮播注意点和定时器的三种实现方法
- Linq To SQL和Linq To Object的批量操作InsertAllOnSubmit介绍
- 易生活(一)-APP---回调函数在异步操作中的应用
- iOS 子视图隐藏tabBar
- iOS - UIWebView
- Android 混淆代码,使自己的apk更安全
- IOS-TableViewCell滑动删除
- 微信公众号自定义菜单操作步骤
- Android Scroll分析(三)——ViewDragHelper
- Android关于透明度对应表
- iOS中的点击事件
- vickate_iOS截屏保存功能
- Android程序结构--MVP模式
- android 之Fragment相关
- android 手机SD卡读写操作(以txt文本为例)实现步骤
- android 使用Activity类布局时怎样让图片居中
- Android IOS WebRTC 音视频开发总结(七一)-- H265/H264有何不同
- iOS开发之字体设置