Handler使用的整理

介绍
  1. Message
    消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。
  2. Message Queue
    消息队列,用来存放通过Handler发布的消息,//按照先进先出执行(这句话是错的!)。
  3. Handler
    有俩用途:1、用于子线程与主线程之间的通讯 2、用于向子线程发出消息请求。Handler是Message的主要处理者,是Android提供的一套ui处理机制,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
  4. Looper
    循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理。
  5. 线程
    UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。
异常
  • Only the original thread that created a view hierarchy can touch its views.
    这个异常就是在子线程中进行更新ui操作抛出的,android的设计的时候,就封装了一套消息创建、传递、处理机制。比如我们不按要求在子线程去更新ui的话就会抛出以上异常。不过我们看一下下面的代码:
public class MyActivity extends Activity {
    private TextView tvText;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvText = (TextView) findViewById(R.id.main_tv);
        new Thread(new Runnable() {
            @Override
            public void run() {
                tvText.setText("OtherThread");
            }
        }).start();
    }
}

我们知道这段代码中在子线程进行了ui的操作,不过我们的代码是可以运行的,我们看一下定义异常的地方就知道了。

public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ………………………… 
      void checkThread() { 
      if (mThread != Thread.currentThread()) { 
            throw new CalledFromWrongThreadException( "Only the original        thread that created a view hierarchy can touch its views.");
       }
    } 
…………………… 
}

setText在源码中会调用checkforlayout方法然后invalidate。然后在checkThread里面判断thread和uiThread是否相等然后抛异常。
不能更新ui是因为ViewRootImpl的checkThread()的检查,而ViewRootImpl是在onResume()的方法中创建的,所以在onCreate中没有穿件ViewRootImpl,所以不抛异常,如果耗时的话才会抛出异常。比如我们在setText前面加上一个延迟。

  • 内存泄漏
@Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        mHandler.sendMessageDelayed(Message.obtain(), 10000);  
        finish();  
    }  

为什么会泄漏?
Handler 的生命周期与Activity 不一致;
引用 Activity 阻止了GC对Acivity的回收。

如何防止?
静态内部类,外部类
WeakReference

private static class MyHandler extends Handler{
}

然后

mHandler.removeCallbacks(mRunnable);  
//或者
mHandler.removeCallbacksAndMessages(null);
用法

-post

private Handler handler = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        
                    }
                }))
            }
        }).start();
这里直接使用的post进行处理,run回调下可以进行ui更新,更加轻便,postDelay还可以实现延迟效果。

-sendMessage

        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
这里使用了handlerMessage的回调,通过msg来传递信息,更加实用。
Message message = new Message();
handler.sendMessage(message);
Message msg = mHandler.obtainMessage();
msg.sendToTarget();

obtainmessage()是从消息池中拿来一个msg 不需要另开辟空间;
new需要重新申请,效率低,obtianmessage可以循环利用;
removeCallback 从队列中移除消息。

子线程间的通讯

在子线程中使用时需要手动添加代码loop,主线程其实也需要,只不过我们的android架构已经帮我们实现了。
handler与子线程的关联:

class Thread extends Thread{
      public Handler handler;
      @Override
      public void run(){
            Looper.prepare();
            handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                      super.handleMessage(msg);
                  }
             };
            Looper.loop();
      } 
}
private MyThread thread;
thread = new MyTread();
thread.start();
thread.handler.sendEmptyMessage();

handler在哪个线程定义,而looper特殊传入的话,则回调在哪个线程,所以在主线程的handlerMessage不要进行耗时处理。

handlerThread

在子线程定义thead

class Thread extends Thread{
      public Handler handler;
      public Looper looper;
      @Override
      public void run(){
            Looper.prepare();
            looper = Looper.myLooper();
            handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                      super.handleMessage(msg);
                  }
             };
            Looper.loop();
      } 
}

将loop传递进来:

Handler handler = new Handler(thread.looper){
       @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
       }
};
handler.sendEmptyMessage();

这里涉及到了一个多线程并发的问题,我们主线程中new Handler()中使用子线程的looper而子线程中looper还没有创建成功,而出现没有looper的问题。这时候需要使用handlerThread。handlerThread使用了wait来等待looper的创建。

thread = HandlerThread("handler thread");
thread.start();
handler = new Handler(thread.getlooper()){
      public void handlerMessage(android.os.Message msg){
      }
}
handler.sendEmptyMessage();

用handlerthread线程的话,可以等待looper实例创建好再发送。

子线程主线程通讯
Handler threadHandler;
Handler handler = new Handler(){
     @Override
      public void handleMessage(Message msg) {
             super.handleMessage(msg);
              threadHandler.sendMessageDelayed(msg1,1000);
      }
};
HandlerThread thread = new HandlerThread("handler thread");
thread.start();
threadHandler = new Handler(thread.getlooper()){
      @Override
      public void handleMessage(android.os.Message msg){
           handler.sendMessageDelayed(msg1,1000);
      }
}
更新ui的方式
  • handler.post
  • sendMessage
  • runOnUiThread
  • view.post
    这个很实用比如某些动画需要实现,而view没有初始化成功导致动画或者其他效果失效,这里可以使用这个方法。
WeakHandler

weakHandler是一个避免内存泄漏的handler库,使用方法和handler基本一致,但是我们要理解一下weakReference,就是弱引用,我们知道java中我们开发人员虽然不需要在意内存回收,可是在使用方面可以做到尽可量的优化,在我们使用弱引用的时候,我们的这个对象,如果他没有没其他对象引用,就会被gc,反着如果强引用的话就不会gc了。
这里我们拓展一下:

  • 强引用(StrongReference)
    只要引用存在,垃圾回收器永远不会回收,也是我们默认的引用。
  • 软引用(SoftReference)
    非必须引用,内存溢出之前进行回收。
  • 弱引用(WeakReference)
    谷歌更推荐这个使用,这个在垃圾搜索器搜索到这个对象时就会进行回收。
    软引用和弱引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
  • 虚引用(PhantomReference)
    虚引用用处不详..我理解为gc时候就会把虚引用一起回收了。
    总结为,强引用只要有引用就绝不回收,弱引用是oom前,软引用是垃圾回收的时候回收。

这里再介绍一个消息通知的框架 可以用于换肤等:

public class UIMessageCenter {

    private final static int UI_MSG_CHANFE_SKIN = 1;
    private static UIMessageCenter Intance = null;
    private List<WeakReference<IonMessage>> mOnMsgList = null;
    private WeakHandler mHanderUI = null;

    private UIMessageCenter() {
        mOnMsgList = new ArrayList<WeakReference<IonMessage>>();
        createHandler();
    }

    public static UIMessageCenter getIntance() {
        if (Intance == null) {
            Intance = new UIMessageCenter();
        }
        return Intance;
    }

    private void createHandler() {
        mHanderUI = new WeakHandler() {
            @Override
            public void handleMessage(Message msg) {

                if (mOnMsgList != null && mOnMsgList.size() > 0) {
                    for (int i = 0; i < mOnMsgList.size(); i++) {
                        WeakReference<IonMessage> fun = mOnMsgList.get(i);
                        if (fun != null) {
                            IonMessage ionMsg = fun.get();
                            if (ionMsg != null) {
                                HttpLog.d("handleMessage", "createHandler");
                                ionMsg.onMessage(msg);
                            }
                        }
                    }
                }
            }
        };
    }

    public void registerFun(IonMessage fun) {
        if (mOnMsgList != null && fun != null) {
            mOnMsgList.add(new WeakReference<IonMessage>(fun));
        }
    }

    public void removeFun(IonMessage fun) {
        if (mOnMsgList != null && fun != null) {
            mOnMsgList.remove(new WeakReference<IonMessage>(fun));
        }
    }

    /**
     * 通知换肤
     *
     * @param arg1
     * @param arg2
     * @param obj
     */
    public void notifyChangeSkin(int arg1, int arg2, Object obj) {
        if (mHanderUI != null) {
            mHanderUI.sendMessage(mHanderUI.obtainMessage(UI_MSG_CHANFE_SKIN, arg1, arg2, obj));
        }
    }
    
}
public interface IonMessage {
    void onMessage(Message msg);
}

使用方法:

class MyActivity extend Activity implements IonMessage{
      @Override
       public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          UIMessageCenter.getIntance().registerFun(this);
       }
      @Override
       public void onDestroy() {
          super.onDestroy();
          UIMessageCenter.getIntance().removeFun(this);
       }
      @Override
      public void onMessage(Message msg) {
        switch (msg.what) {
            case UIMessageCenter.UI_MSG_CHANFE_SKIN:
                changeSkin();
                break;
            default:
                break;
        }
    }
}

转载请注明出处:http://www.jianshu.com/p/e3248d737ced

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容