线程 Handler 用法及解析

Handler的理解、用法以及运行机制原理

一、Handler是什么?

Handler在我们android开发中是一项非常重要的机制,那Handler是什么呢?Handler是android提供用于更新UI的一套机制,也是消息处理机制。

Handler的主要作用有两个:

        (1).在新启动的线程中发送消息

                    (2).在主线程中获取,处理消息。

解释:(1) 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件, 进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。 主线程(UI线程)就是android程序从启动运行到最后的程序。

(2) 如果此时需要一个耗时的操作,例如: 联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示  "强制关闭"。

(3)这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,Android主线程是线程不安全的, 也就是说,更新UI只能在主线程中更新,子线程中操作是危险的。

(4)这个时候,Handler就出现了。,来解决这个复杂的问题 ,由于Handler运行在主线程中(UI线程中),  它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sendMessage()方法传弟)Message对象(里面包含数据)  , 把这些消息放入主线程队列中,配合主线程进行更新UI。

二、为什么要用Handler

如果我们不用Handler去发送消息,更新UI可以吗?

答案是不行的。 Android在设计的时候,就封装了一套消息创建,传递,处理机制,如果不遵循这样的机制,就没有办法更新UI信息的,就会抛出异常信息。

抛出异常的描述:不能在非UI线程中去更新UI

三、 Handler怎么用

 handler可以分发Message对象和Runnable对象到主线程中,每个Handler实例,都会绑定到创建他的线程中(一般是位于主程),它有两个作用:

        (1)合理调度安排消息和runnable对象,使它们在将来的某个点被执行.

        (2)安排一个动作在不同的线程中执行

Handler中开启线程和分发消息的一些方法:

      post(Runnable)直接开启Runnable线程

      postAtTime(Runnable,long)在指定的时间long,开始启动线程

      postDelayed(Runnable long)在延迟long时间后,启动Runnable线程

      sendEmptyMessage(int) 发送指定的消息,通过参数int来区分不同的消息

      sendMessage(Message)发送消息到UI线程中

      sendMessageAtTime(Message,long)     这个long代表的是系统时间,不推荐用

     sendMessageDelayed(Message,long)    此方法long代表调用后几秒后执行。

   sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新.

handler基本使用: 1)在主线程中,使用handler很简单,new一个Handler对象实现其handleMessage方法,在 handleMessage 中提供收到消息后相应的处理方法即可。(接收消息,并且更新UI)

                     2)在新启动的线程中发送消息

示例:

public class HandlerActivity extends AppCompatActivity {

    private Handler handler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            tv.setText("msg.arg1:"+msg.arg1+"--msg.arg2:"+msg.arg2);

        }

    };

    private TextView tv;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler);

        initView();

        new Thread(new Runnable() {

            @Override

            public void run() {

                Message msg = new Message();//实例化消息对象

                msg.arg1 = 99;//携带参数

                msg.arg2 = 100;//携带参数

                Object str = new Object();//实例化对象

                msg.obj = str; //携带参数为实体类对象

            }

        });

        }

    private void initView() {

        tv = (TextView) findViewById(R.id.tv);

    }

}

handler运行机制:



 Handler机制也可叫异步消息机制,它主要由4个部分组成:Message,Handler,MessageQueue,Looper,在上面图中我们已经有了大致印象,接下来我们对4个成员进行着重的了解:

1.Message

  Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。使用Message的arg1和arg2便可携带int数据,使用obj便可携带Object类型数据。

2.Handler

  Handler顾名思义就是处理者的意思,它只要用于在子线程发送消息对象Message,在UI线程处理消息对象Message,在子线程调用sendMessage方法发送消息对象Message,而发送的消息经过一系列地辗转之后最终会被传递到Handler的handleMessage方法中,最终在handleMessage方法中消息对象Message被处理。

3.MessageQueue

  MessageQueue就是消息队列的意思,它只要用于存放所有通过Handler发送过来的消息。这部分消息会一直存放于消息队列当中,等待被处理。每个线程中只会有一个MessageQueue对象,请牢记这句话。其实从字面上就可以看出,MessageQueue底层数据结构是队列,而且这个队列只存放Message对象。

4.Looper

  Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当MesssageQueue中存在一条消息,Looper就会将这条消息取出,并将它传递到Handler的handleMessage()方法中。每个线程只有一个Looper对象。

  了解了上述Handler机制的4个成员后,我们再来把思路理一遍:首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个Message对象,并且将消息的数据记录在这个消息对象Message的内部,比如arg1,arg2,obj等,然后通过前面的Handler实例对象调用sendMessge方法把这个Message实例对象发送出去,之后这个消息会被存放于MessageQueue中等待被处理,此时MessageQueue的管家Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler的handleMessage方法,最终前面提到的消息会被Looper从MessageQueue中取出来传递给handleMessage方法,最终得到处理。这就是Handler机制整个的工作流程。应该都差不多懂了吧,感觉我写的很接地气啊。

————————————————

版权声明:本文为CSDN博主「ttxs99989」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/ttxs99989/article/details/81814037


Handler用法及解析    解析 Handler用法及解析


.handler使用避免内存泄露

 1)handler怎么使用会产生内存泄露?

public class MainActivity extends AppCompatActivity {

    final Handler handler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

                ......

        }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //activity被执行时,被延迟的这个消息存于主线程消息队列中1分钟,

        //此消息包含handler引用,而handler由匿名内部类创建,持有activity引用,

        //activity便不能正常销毁,从而泄露

        handler.postDelayed(new Runnable() {

            @Override

            public void run() {

                ......

            }

        }, 1000 * 60);

    }

}

 2)如何避免handler的内存泄露?

public class MainActivity extends AppCompatActivity {

    //创建静态内部类

    private static class MyHandler extends Handler{

        //持有弱引用MainActivity,GC回收时会被回收掉.

        private final WeakReference<MainActivity> mAct;

        public MyHandler(MainActivity mainActivity){

            mAct =new WeakReference<MainActivity>(mainActivity);

        }

        @Override

        public void handleMessage(Message msg) {

            MainActivity mainAct=mAct.get();

            super.handleMessage(msg);

            if(mainAct!=null){

                //执行业务逻辑

            }

        }

    }

    private static final Runnable myRunnable = new Runnable() {

        @Override

        public void run() {

            //执行我们的业务逻辑

        }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        MyHandler myHandler=new MyHandler(this);

        //延迟5分钟后发送

        myHandler.postDelayed(myRunnable, 1000 * 60 * 5);

    }

}

 3)  雷区

a)Handler.post(Runnable)其实就是生成一个what为0的Message,调用

myHandler.removeMessages(0);

会使runnable任务从消息队列中清除。

详细解释:https://www.cnblogs.com/coding-way/p/5110125.html(转)

b) 子线程直接创建Handler,抛异常Can't create handler inside thread that has not called Looper.prepare()

原因是非主线程没有loop对象,所以要调用Looper.prepare()方法,而且如果主线程给子线程发送消息,还要调用一个Looper.loop()的方法(此方法保证消息队列中的消息被不停的拿出,并被处理)

class MyThread extends Thread{

        @Override

        public void run() {

            super.run();

            Looper.prepare();

            Handler handler = new Handler() {

                @Override

                public void handleMessage(Message msg) {

                    super.handleMessage(msg);

                    //处理消息

                }

            };

            Looper.loop();

        }

}

c)activity如被finish,但是handler刚好还在处理消息,如果需要用的资源已被释放,则会出现空指针异常。

所以在ondestory中去remove掉我们要处理的事件,还是有必要的。不想处理就直接try catch或者判空。

d)有时候你会发现removeCallbacks会失效,不能从消息队列中移除。

出现这情况是activity切入后台,再回到前台,此时的runnable由于被重定义,就会和原先的runnable并非同一个对象。所以这么做,加上static即可

static Handler handler = new Handler();

static Runnable myRunnable = new Runnable() {

        @Override

        public void run() {

            //执行我们的业务逻辑

        }

    };

这样,因为静态变量在内存中只有一个拷贝,保证runnable始终是同一个对象。

4.handlerThread

1)  handlerThread是什么?

(题外话:异步存在形式有thread,handlerThead,asyncTask,线程池,intentService)

handlerThread继承thread,不过内部比普通线程多了一个Looper

//内部Looper.prepare()

@Override

    public void run() {

        mTid = Process.myTid();

        Looper.prepare();

        synchronized (this) {

            mLooper = Looper.myLooper();

            notifyAll();

        }

        Process.setThreadPriority(mPriority);

        onLooperPrepared();

        Looper.loop();

        mTid = -1;

}

2)HandlerThread使用及销毁

public class MainActivity extends AppCompatActivity {

    private HandlerThread thread;

    static Handler mHandler;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //创建一个HandlerThread并启动它

        thread = new HandlerThread("MyHandlerThread");

        thread.start();

        //使用HandlerThread的looper对象创建Handler

        mHandler = new Handler(thread.getLooper(), new Handler.Callback() {

            @Override

            public boolean handleMessage(Message msg) {

                //这个方法是运行在 handler-thread 线程中的,可以执行耗时操作,因此不能更新ui,要注意

                if (msg.what == 0x1) {

                    try {

                        Thread.sleep(3000);

                        Log.e("测试: ", "执行了3s的耗时操作");

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    //这个方法是运行在 handler-thread 线程中的,可以执行耗时操作,因此不能更新ui,要注意

//                    ((Button) MainActivity.this.findViewById(R.id.button)).setText("hello");

                }

                return false;

            }

        });

        //停止handlerthread接收事件

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                thread.quit();

            }

        });

        //运行

        mHandler.sendEmptyMessage(0x1);

    }

}

上面demo中,只要调用了

mHandler.sendEmptyMessage(0x1);

就会开始执行任务

几个地方要注意:

a.handleMessage()可以做耗时操作,但是不能更新ui

b.如果不手动的调用HandlerThread.quit()或者HandlerThread..quitSafely()方法,HandlerThread会将持续的接收新的任务事件。

c.只有handleMessage()方法执行完,这轮的任务才算完成,HandlerThread才会去执行下一个任务。而且在此次执行时,即使手动的去调用quit()方法,HandlerThread的此次任务也不会停止。但是,会停止下轮任务的接收。

举例:

//耗时任务换成这个,点击按钮执行quit()方法,发现此次任务依旧执行

for (int i = 0; i < 99999999; i++) {

    Log.e("测试: ", "输出" +i);

}

d.HandlerThread的2种停止接收事件的方法。

第一个就是quit(),实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(带Delayed的)还是非延迟消息。

第二个就是quitSafely(),执行了MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

MessageQueue中源码:

void quit(boolean safe) {

        if (!mQuitAllowed) {

            throw new IllegalStateException("Main thread not allowed to quit.");

        }

        synchronized (this) {

            if (mQuitting) {

                return;

            }

            mQuitting = true;

            if (safe) {

                removeAllFutureMessagesLocked();

            } else {

                removeAllMessagesLocked();

            }

            // We can assume mPtr != 0 because mQuitting was previously false.

            nativeWake(mPtr);

        }

}

举例:

//quit方法后,即使发送新事件,也不会被接收

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

              thread.quit();

              //发送新事件

              mHandler.sendEmptyMessage(0x1);

        }

);

e.即使多次执行mHandler.sendEmptyMessage(0x1),任务队列中的任务依然只能一个一个的被处理。上一任务结束,开始执行下一个。

————————————————

版权声明:本文为CSDN博主「魔法少女 厄加特」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_37321098/article/details/81535449


多线程 - Thread原理及使用



 Java中创建线程有两种方式:继承Thread重写run()与实现Runnable()接口通过Thread构造。

继承Thread重写run()

创建一个TestThread继承自Thread,然后调用start()运行线程

TestThread testThread = new TestThread();

testThread.start();

public class TestThread extends Thread{

        @Override

        public void run() {

            super.run();

            Log.i("thread","TestThread");

        }

    }

打印的log为:TestThread

I/thread: TestThread

1

实现Runnable接口

定义一个类TestRunnable实现Runnable的run方法,通过Thread的构造函数传入一个TestRunnable对象,调用start()开启线程

Thread runnable = new Thread(new TestRunnable());

runnable.start();

public class TestRunnable implements Runnable{

      @Override

      public void run() {

          Log.i("thread","testRunnable");

      }

}

打印的log为:testRunnable

I/thread: testRunnable

1

继承Thread类与实现Runnable接口的原理和区别

通过上面的两种方式都可以实现多线程,那么将TestRunnable对象传给TestThread会是怎样的呢?代码走起

  TestThread testThread = new TestThread(new TestRunnable());

  testThread.start();

  public class TestThread extends Thread{

        public TestThread(Runnable target) {

            super(target);

        }

        @Override

        public void run() {

            super.run();

            Log.i("thread","TestThread");

        }

    }

    public class TestRunnable implements Runnable{

        @Override

        public void run() {

            Log.i("thread","testRunnable");

        }

    }


运行代码,发现log分别打印了TestThread和testRunnable

I/thread: TestThread

I/thread: testRunnable

1

2

而调用Thread的start()函数会创建一个新的线程以及调用线程的run()方法,而在TestThread的run()方法中,调用了super.run(),说明super.run()中调用了TestRunnable的run()方法,那看看Thread中构造函数传入Runnable对象是如何实现的。

public class Thread implements Runnable {

    public Thread(Runnable target) {

      init(null, target, "Thread-" + nextThreadNum(), 0);

    }

    private void init(ThreadGroup g, Runnable target, String name,

                      long stackSize, AccessControlContext acc) {

      /*省略其他的一些代码*/

        this.target = target;

        setPriority(priority);

        tid = nextThreadID();

    }

    @Override

    public void run() {

        if (target != null) {

            target.run();

        }

    }

}

可以发现Thread也是实现了Runnable接口,在构造函数时将传入的Runnable对象赋值给了target,而在实现的run()函数中调用了target.run(),即我们构造函数传入的Runnable对象。因此我们可以得到,通过继承Thread和实现Runnable本质是一样的,Thread是通过重写run()函数,而Runnable的方式间接通过Thread的target属性来调用run()函数,都依托于Thread,最终执行run()函数。

start()与run()的区别

通过上面的分析可以得知道Thread实现了Runnable接口,最终执行的也是run()函数,而我们在调用启动线程的时候是start()函数,那么他们有什么区别呢?首先看段代码

TestThread testThread = new TestThread();

testThread.start();

try {

      Thread.sleep(200);

} catch (InterruptedException ignored) {

}

Log.i("thread","TestThread start");

public class TestThread extends Thread{

        @Override

        public void run() {

            super.run();

            for (int i = 0; i < 10 ; i ++) {

                Log.i("thread","TestThread" + i);

                try {

                    Thread.sleep(200);

                } catch (InterruptedException ignored) {

                }

            }

        }

    }


在TestThread的run()打印10行log,然后在调用start()打印TestThread start,sleep 200毫秒方便看打印顺序,看看打印的结果如何

thread: TestThread0

thread: TestThread start

thread: TestThread1

thread: TestThread2

thread: TestThread3

thread: TestThread4

thread: TestThread5

thread: TestThread6

thread: TestThread7

thread: TestThread8

thread: TestThread9

因为TestThread是多线程执行的,因此TestThread start打印在TestThread0之后是正常的,既然start()间接调用了run(),那么我们直接调用run()会怎样呢?

        TestThread testThread = new TestThread();

        testThread.run();

        try {

            Thread.sleep(200);

        } catch (InterruptedException ignored) {

        }

        Log.i("thread","TestThread start");

输出的log如下

thread: TestThread0

thread: TestThread1

thread: TestThread2

thread: TestThread3

thread: TestThread4

thread: TestThread5

thread: TestThread6

thread: TestThread7

thread: TestThread8

thread: TestThread9

thread: TestThread start

发现TestThread是最后打印的,在执行完TestThread的run()方法之后才执行,变成了同步执行。

看看Thread的start()函数是怎么实现的

    /**

    * Starts the new Thread of execution. The <code>run()</code> method of

    * the receiver will be called by the receiver Thread itself (and not the

    * Thread calling <code>start()</code>).

    *

    * @throws IllegalThreadStateException - if this thread has already started.

    * @see Thread#run

    */

    public synchronized void start() {

        checkNotStarted();

        hasBeenStarted = true;

        nativeCreate(this, stackSize, daemon);

    }

    private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

在start()函数中调用了nativeCreate来创建一个线程,而start()的注释中也写明,创建一个新的线程,run()函数的代码在新的线程里面执行,而start()函数的线程在调用start()的线程里面执行。

根据上面的分析可以得到,直接调用run()函数没有创建新的线程,而是在调用的线程中执行,因此变成了同步执行;而调用start()函数,start()函数会在调用的线程中执行,还是属于同步执行,但是start()函数会创建一个新的线程来执行run()函数,因此我们通过继承Thread还是实现Runnable重写run()函数都会在新的线程执行,从而变成异步执行。

————————————————

版权声明:本文为CSDN博主「AdobeSolo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/adobesolo/article/details/77487764

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