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

来源:互联网 发布:淘宝宝贝删除了怎么办 编辑:程序博客网 时间:2024/05/20 19:14

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核心技术分为三层

  1. 第一层:Bitmap缓存
    在Android 5.0系统中考虑内存管理有了很大改进,所以Bitmap缓存位于java的堆(heap)中,
    在android 4.0x和更底的系统,Bitmap缓存位于ashmem中,而不是位于Java的堆(heap),这意味着图片的创建和回收不会引发这多的GC,从而让App运动得更快,当App切换到后台时,Bitmap缓存会被清空
  2. 第二层:内存缓存
    内存缓存存储了原始压缩格式,从内存中取出的图片,显示必须先解压,切换到后台时,内存缓存会清空
  3. 第三层:硬盘缓存

4、对网络流量进行优化
首先从接口层面进行优化:

  1. 从接口返回的数据,要使用gzip压缩,注意:大于1kb才进行压缩,否则得不偿失,
  2. json因为是xml格式的,数据量上看还是有一定的压缩空间的,在大数据时,可以使用ProtoBuffer,这种协议是二进制的,比json小很多
  3. 减少MobileApi调用 的次数
  4. 要建立取消网络请求的机制,一个页面如果没有请求完成,跳转到另外一个页面,取消之前的

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约定好格式,例如:gotoPersonCentergotoMovieDetail:movieId = 100gotoNewsList: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,这个变量从接口中获取,我们要实现上面的这种形式的思路,大概如下
  1. 需要做一个后台,根据版本进行配置每个页面是使用Native还是HTML5页面
  2. 在App启动的时候,从接口获取每个页面是native还是HTML5
  3. 在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切换到后台,所以之前存放的全局变量很容易被回收,要想解决这个问题,就一定要使用序列化技术。

  1. 把数据作为Intent的参数传递
    intent也不能传递过大的数据,也会发生崩溃。
  2. 把全局变量序列化到本地
    下面演示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、序列化的不好的地方
再次强调,把全局变量序列化本地,只是一种过渡解决方案,它有如下不好的地方

  1. 每次设置全局变量的值都要强制一次序列化,容易先成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这个变量

0 0
原创粉丝点击