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

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约定好格式,例如:
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,这个变量从接口中获取,我们要实现上面的这种形式的思路,大概如下
  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这个变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值