2018年5月

建造者,开源框架中感觉应用非常常见的一种设计模式。常见的例如okhttp、retrofit,其中的配置都是通过Builder去添加的。
所以这是一种很方便的设计模式。
按照我们之前的模板设计方法举得例子。如果我作为一个消费者,通过模板方法去买外星人电脑,那么我会知道,这台电脑是如何组装的,是怎么拼接cpu,怎么拼接键盘,怎么拼接外壳。而且顺序也定死了。但是,如果是建造者模式的话,那么就会发现,其实我作为消费者,我不关心,如果制造的,我只是输入一些cpu型号,键盘样式,外壳材质就可以得到一台笔记本。我可以通过一个导演(这里指类似可以有限度组装笔记本的那种厂商---准系统)去实现制造一台笔记本(其实这里举例子不太好,应该相应的改为金拱门之类的好点,你作为顾客去点餐,不管是要巨无霸还是麦吉士,你可以再有限范围内挑选是否要黄瓜,是否要cheese等操作,而不需要知道汉堡如何制作出来,等着吃就是了。)那么建造者模式是如何实现的呢?

先来看看uml图
请输入图片描述

作为建造者模式,最重要的就是封装下层细节,有些繁琐的,无需着重实现的实现。
优点:封装性良好,无需关心每一个汉堡是如何生成的,最终生成相应的汉堡吃就完事了;建造者相对独立,容易扩展,需要啥汉堡流程,再添加一个就是了;便于控制细节风险(我的理解就是独立的一个建造模块,是可以自己把控的,不会对别的建造者产生问题和影响)。
使用场景:
产品类很复杂,但是调用顺序不同产生不同的东西,采取使用建造者模式比较适合。

他跟工厂模式有什么区别呢?建造者模式关注的是零件类型和装配工艺(顺序)。翻译过来就是说,工厂模式注重的还是产品本身,他所生产出来的产品都应该是类似的,好比工厂模式生产出来的笔记本,更加注重笔记本本身,有可能你是啥啥笔记本,但是你们功能没啥区别。建造者模式如果生产笔记本,那么他更加关心产品本身创造出来的过程。但是对于最终的消费者来说,他们获取其实没啥区别。

我以前只知道项目代码集成这个模块,实现UncaughtExceptionHandler就可以处理一些运行时错误,但是代码方面我并没有具体去看。现在有些需求需要重新更改下模块,于是掏出来复习下。

现在崩溃收集一般要么采用腾讯Bugly第三方平台管理,要么自建收集日志工具进行管理。腾讯bugly集成在此不在叙述,集成还是很方便的。自建的话,就是需要在捕获异常的时候,记录下来,上传服务器进行处理。

伪代码如下:

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
    private Context mContext;


    private  static CrashHandler mCrashHandler = new CrashHandler();
    private CrashHandler(){

    }

    public static CrashHandler getInstance(){
        return mCrashHandler;
    }

    public void init(Context context){
        uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();
    }

    /**
     * 这里就是未捕获的异常,进行统一处理
     * @param t
     * @param e
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {

        if (handleException(e)){
            //交给系统处理
            mCrashHandler.uncaughtException(t,e);
        }else {
            //自己处理异常
            
            //杀死进程
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(0);
        }


    }

    /**
     *先进行处理异常,return false表明未处理
     * @param throwable
     * @return
     */
    private boolean handleException(Throwable throwable){
        if (throwable == null){
            return false;
        }

        new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                    //提示错误信息
                Looper.loop();
            }
        }.start();
        saveExceptionToSD(throwable);//保存日志到sd卡
        postExceptionToWeb(throwable);//提交到自己服务器
        return true;
    }

    private void saveExceptionToSD(Throwable throwable){

    }

    private void postExceptionToWeb(Throwable throwable){

    }

网上封装好的代码太多了,相关具体可以去爬别人好的代码。但是核心思想都是去自己处理HnadlerException方法里的异常,保证应用给用户好的体验。

前几天谷歌开发者大会又开始大力推荐他的Flutter了。唉,作为一个懒惰的码畜。错过了RxJava,错过了kotlin,看到Flutter本想学学的,也被新创建的Drat语言伤透了心。但是谷歌爸爸自研新系统的消息愈演愈烈,没办法,还是稍微接触下先,准备学习吧。
首先安装环境就发现一个蛋疼的问题,Flutter有个中文网站,里面的中文教程windows安装的第一篇是0.2.8的环境,最新貌似变成0.3.2了,果然测试版开发就是快。这不是重点,重点是,git下载的话,你会后悔的,如果不想变换国内节点配置,直接下载官方的压缩包就是了。然后还是常规的加入环境变量。
碰到问题如下:

  1. 0.2.8环境不知道和3.1.2的android studio产生了什么奇妙的化学反应,在我这台笔记本死活卡在创建flutter应用中。换成0.3.2就好了。
  2. flutter doctor --android-licenses 在cmd中是正常的,且同意过了,但是在android studio的初始工程中,死活报错,我开始怀疑是环境变量Android sdk配错了,但是不是,这个bug找了半天,才在stackoverflow看到。android的sdk.dir莫名是上一个工程的sdk目录。切换就好了。
    运行成功截图如下。

请输入图片描述
改成符合标题的

请输入图片描述
本文只是记录下,运行flutter的hello world的坑。2333.学习等慢慢啃吧

因为人员变动,我也需要去解决Eclipse的相关问题。一个上古时代的工程在我电脑版本死活跑不起来。一起报Dx unsupported class file version 52.0 xxxxxx错误。
查看网上的解决方法,大部分都是因为原工程编译的android版本太低了,需要把1.8版本降低到1.7版本。

右键工程-->Properties-->Java Compiler -->选择你安装的jdk版本.

但是我试了,在我机子上无用,且同步Installed JREs里面匹配也是无用的。甚至换到1.6版本仍然无用。查询了相关的资料,发现都是老的,且基本都是这个方法,陷入绝望,想到4.4可以1.7jdk。但项目是android4.0.4版本。于是死马当活马医,在project.properties中更改 target=android-14 为 target=android-19 。成功运行。

参考资料Dx unsupported class file version 52.0

讲真,网上有很多讲关于这个的文章。我这篇只是梳理下,巩固下自己的知识。以前学习图片缓存框架的时候,接触过Lru,只知道这是一个帮助减少内存开销的方法。每次他确实是缓存的一种策略。在古老的手机只有512m的年代,手机内存是需要很节省点的(不像现在动辄就是6gb内存),虽说我看到这个策略出现的时间Android 3.1的年代,但是其中的原理是万变不离其宗的。

对了,本文基于老的LruCache研究的。新版的linkedHashMap方法有些不存在了,比如LinkedHashMap的eldest()方法。

LruCache需要分开理解。L-least r-recently u-used。结合起来就是 最近最少使用的缓存(算法)。
里面的原理主要就是靠一个linkedHashMap去维持一个栈。而这个栈不像Activity那种栈,先进后出这种。而是根据使用的情况和谁先进栈的情况综合考虑。文字描述可能有点头疼,一图流表示如下。
请输入图片描述

linkedHashMap是HashMap的子类,是一种双向链表,其构造方法的第二个参数是扩容用的(0.75标识当前容量快到0.75)第三个参数决定了是否是Lru顺序还是插入顺序。
这段不理解不要紧。你指看到创建linkedHashMap的时候,第三个参数是基于访问顺序,如果是true,就是Lru,false是不按照此顺序。

    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }


     public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

但是使用LinkedHashMap的时候,使用方法是跟hashMap是没啥区别的。只是他内部方法,帮你完成了他的特定的顺序结构罢了。

  1. get方法:
    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
        
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }
        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

hitCount代表命中次数 missCount代表丢失次数。
可以看到get()方法是有synchronized 锁住的,所以Lru是线程安全的,然后先从linkedHashMap中通过key去获取value,如果存在,命中次数+1;反之,丢失次数+1。并且create()一个新的null返回回去。

  1. put方法:
    public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }
        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }
        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }
        trimToSize(maxSize);
        return previous;
    }

这段代码意思是,如果不为null,put数加1,缓存空间数量+加上插入的缓存空间,并尝试往linkedHashMap插入数据,如果返回value不为null,则表示链表里面存在当前的key、value,缓存空间还原回未加之前。如果自己实现了entryRemoved方法,则会处理一遍。全部完成后,会去重新trimToSize()调整缓存大小。

有些人看源码会看到,哎哎哎,这个sizeOf返回的是1。怎么回事缓存空间大小呢?
因为safeSizeOf(key,value)-----> sizeOf(key,value),而sizeOf 是我们使用的时候会去重写的方法,所以会正确的返回value的大小。
  1. trimToSize方法:
    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                if (size <= maxSize) {
                    break;
                }
                //Map.Entry<K, V> toEvict = map.eldest();高版本找不到了,替换为下面的。
                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
                if (toEvict == null) {
                    break;
                }
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }
            entryRemoved(true, key, value, null);
        }
    }

这段表明trimTosize方法是个死循环,只有两个条件可以跳出。要么当前size比最大的缓存还大,要么就是链表的map已经没有存在任何键值对了。否则,就会去找链表中的最老的那条键值对,删除它,并且减去相应的空间缓存大小。
接着循环,看是否满足跳出的条件,直到满足条件。






- 阅读剩余部分 -