分类 android 下的文章

哎,最近被大佬问了一个关于Handler的问题,支支吾吾了半天,发现自己又是没有深入了解,乘着有动力,抓紧复习一波。
handler是Android方面的消息机制。

对于开发者来说(比如我),刚学习Android的时候,总是会遇到非UI线程无法操作view的情况。往往遇到这种时候,就需要用到handler去处理更新view。但其实handler的用途远远不止这些。

消息机制的四个点:Handler(处理者)、Message(消息体)、MessageQueue(消息队列)和Looper(一个循环的泵)。

  1. Handler 如何跨进程传递消息的呢
    比如当前有两个线程:A代表主线程,B代表子线程。B执行一个耗时任务,结束了将结果返回给A。这个过程是如何实现的呢?
private Handler mHandler = new Handler(){
       @Override
       public void handleMessage(Message msg) {
           switch (msg.what){
               case 1:
                   break;
               default:
                   break;
           }
           super.handleMessage(msg);
       }
   };
 new Thread(new Runnable() {
            @Override
            public void run() {
                //very 耗时的操作
                //····
                //···
                //··
                Message msg = mHandler.obtainMessage();
                msg.what = 1;
                msg.obj = "result";
                mHandler.sendMessage(msg);

            }
        }).start();

当创建一个Handler对象的时候,在activity中直接new Handler()创建的handler对象默认最终走到

  public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到Looper.myLooper();方法创建一个looper对象,这个looper对象查看方法可知

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

是通过ThreadLocal获取的,ThreadLoacl是什么呢?你可以理解成一个根据当前线程的容器,可以存储任何数据。但是这些数据存储提取的前提是,只在当前线程,别的线程可以吗?不行。

所以这里的sThreadLocal.get();是从哪里获取的呢?这里牵扯到主线程(UI)的问题。当一个activity创建的时候,ActivityThread默认会调用Looper.prepareMainLooper();和Looper.loop();方法,这两个方法默认已经将主线程的Looper和MessageQueue已经搭好了,并且保存在主线程的ThreadLocal中去了。(ThreadLocal具体可以看Android开发艺术探索,或者以后我填坑

最近支持项目的时候,发现一个问题,打开webview的时候,一直弹出自定义的页面,提示网页错误,未响应的问题。因为写了自定义webview加载超时也是显示这个页面,所以在处理bug时,不由的跑歪了方向,最后发现是标题500导致的错误。

本来这个问题,当初搭建webview的时候,在onReceivedError处理过一次,那时候傻傻的在onReceivedError中,以为能获取到相关的错误状态码。就简单的handler处理下,搞了错误的自定义界面。再实际的测试中,却惊喜的发现,实际上不能获取到错误。这是为啥呢。经过一番搜索,发现奥6.0不支持啊。然后查到blog作者提供两种通用的解决方案,一种实在title中获取到404,500等关键字,判断当前是错误了(我就是采取这种。。。说出来都是泪)。因为简单粗暴,且有效,但是未考虑实际开发过程中的错误。第二种就是每次请求时,去通过httpClient,okHttp等等网络框架去发起一次请求,获取其中的状态码。(但实际中 要考虑速度问题)这种方案一开始被我舍弃了。

于是乎,我是万万没有想到,有个webview页面的getTitle获取的标题叫做“500强xxxxxx”,瞬间爆炸,而且坑爹的是,因为采用混合开发,这一开始的标题不一定是最终标题,最终标题需要通过自定义js框架中获取的标题,最终显示的页面是没有“500强xxxx”标题。导致问题的。解决方案,在6.0设备是很容易解决的,因为官方修复了问题,但是4.4 5.0设备就有问题了,看到还有一个做法就是服务器端做处理,他去做页面找不到的问题的解决方案。

这里仅是记录下,遇到这个坑,简单粗暴解决后的遇到的问题经过,以次警示下自己。。。

参考文章:
Android webview处理404、500、断网、timeout页面的问题

我以前只知道项目代码集成这个模块,实现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方法里的异常,保证应用给用户好的体验。

因为人员变动,我也需要去解决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已经没有存在任何键值对了。否则,就会去找链表中的最老的那条键值对,删除它,并且减去相应的空间缓存大小。
接着循环,看是否满足跳出的条件,直到满足条件。






- 阅读剩余部分 -