• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 安卓學習筆記之Handler

    標簽: android  多線程

    UI線程

    當系統啟動的時候,就會創建一個主線程(Main Thread),然后這個主線程向UI組件分發事件,主線程和UI的組件進行交互,故稱UI線程。


    線程安全

    Android的UI線程是不安全的。引用一下,百度百科的解釋

    線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。不會出現數據不一致或者數據污染。 線程不安全就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據
    from 百度百科

    既然這樣,Google給我們提供了更新ui界面的Handler類。


    Handler

    一個處理異步消息的類,創建一個子線程發送消息至主線程,在主線程更新相應的UI界面。比如在子線程進行長時間的網絡操作,然后更新UI界面上的TextView。
    使用的方法有兩種,

    1.post(runnable)&2.sendMessage(message)

    先演示一下,所謂不能直接在UI線程更新TextView的操作。在XML布局文件里創建一個TextView。

    package com.example.myapplication;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.widget.Button;
    import android.widget.TextView;
    
    /**
     * Created by Does on 2017/7/20.
     */
    
    public class HandlerExample extends Activity {
    
        private TextView tv_content;
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            tv_content= (TextView) findViewById(R.id.tv_content);
            new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                        tv_content.setText("更新UI");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

    寫好之后,運行之后,
    這里寫圖片描述

    Exception:只有創建了視圖層次結構的原始線程才能觸及它的視圖

    為了解決這種問題,我們使用Handler


    post(runnable)?

    這里模擬一個簡單的下載功能。XML文件里面添加兩個組件,一個Button和一個Textview

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private TextView tv_content;
        private Button btn_onclick;
        private Handler handler=new Handler();
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tv_content= (TextView) findViewById(R.id.tv_content);
            btn_onclick= (Button) findViewById(R.id.btn_onclick);
            btn_onclick.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            DownLoadThread downLoadThread=new DownLoadThread();
            downLoadThread.start();
        }
        class DownLoadThread extends  Thread{
                @Override
                public void run() {
                    try {
                        System.out.println("文件正在下載...");
                        Thread.sleep(2000);
                        System.out.println("文件下載成功");
                        Runnable runnable=new Runnable() {
                            @Override
                            public void run() {
                                System.out.println("當前線程的id是:  "+Thread.currentThread().getId());
                                MainActivity.this.tv_content.setText("textview已經改變");
                            }
                        };
                        handler.post(runnable);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
    
        }
    }

    運行效果如下,點擊Button之前
    這里寫圖片描述

    點擊Button之后
    這里寫圖片描述


    sendMessage(message)

    public class HandlerExample extends Activity {
    
        private TextView tv_content;
        private Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:
                        tv_content.setText("UI變化了");
                        break;
                    default:
                        break;
                }
            }
        };
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            tv_content= (TextView) findViewById(R.id.tv_content);
            new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                        Message message=new Message();
                        message.what=1;
                        handler.sendMessage(message);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

    效果和上面的是一樣的。

    這個明顯和上一個有些不同,多了幾樣的東西。Message(),sendMessage(),handleMessage()
    首先要子線程中,創建一個Handler的一個對象handler,然后執行handler.sendMessage(message),
    handler攜帶message的一個對象,message.what=1,給它一個標識,隨便取。在handleMessage()方法里,就會接受到傳過去的值,進而在handleMessage里進行UI的更新操作。


    Handler運行機制

    接下來我們說說內部機制的運行。主要有用到Looper,Handler,MessageQueu(消息隊列).從源碼入手,
    當我們手機一啟動的時候,系統默認主線程會先調用prepareMainLooper()方法。先執行prepare(false)

     public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                ...
                sMainLooper = myLooper();
            }
        }

    接著創建一個Looper對象,將ThreadLocal設置為線程安全的對象。

    private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }

    接著sThreadLocal向Looper類里傳過去quitAllowed,并在Looper()構造器里創建了一個MessageQueue的對象mQueue。

    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mRun = true;
            mThread = Thread.currentThread();
        }

    最后,再調用prepareMainLooper()中的myLooper(),取出線程安全的Looper對象。

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

    這是系統幫我們做的事情!!
    接著我們需要做一些事情更新UI的操作,我們在MainActivity方法中創建了一個Handler對象,

    public Handler() {
            this(null, false);
        }
    
        public Handler(Callback callback, boolean async) {
            ...
    
            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;
        }

    handler取出了系統為我們創建的Looper對象,并取出系統創建的mQueue對象。此時我們創建的handler就持有了系統剛創建的Looper對象和MessageQueue對象。
    **

    發送消息

    **
    當我們發送消息的時候,sendMessage

        sendMessage(Message msg)
    
        sendMessageDelayed(Message msg, long delayMillis)
    
        public final boolean sendMessageAtFrontOfQueue(Message msg) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, 0);
        }
    
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    首先是取出Looper對象中MessageQueue在enqueueMessage()方法中,handler攜帶我們要發送的Message,然后放入消息隊列MessageQueue中

    boolean enqueueMessage(Message msg, long when) {
            ...
    
            boolean needWake;
            synchronized (this) {
                ...
    
                msg.when = when;
                Message p = mMessages;
                if (p == null || when == 0 || when < p.when) {
    
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
    
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; 
                    prev.next = msg;
                }
            }
            ...
            return true;
        }

    循環取出

    接著Looper調用loop()方法

    public static void loop() {
            final Looper me = myLooper();
            ...
    
            final MessageQueue queue = me.mQueue;
    
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                Message msg = queue.next(); 
                ...
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
    
                msg.target.dispatchMessage(msg);
                ...
                final long newIdent = Binder.clearCallingIdentity();
                ...
    
                msg.recycle();
            }
        }

    myLooper()取出當前Looper對象
    me.mQueue拿到當前的MessageQueue對象
    queue.next();取出下一個消息
    如果消息存在 則調用消息的msg.target.dispatchMessage(msg);

    處理消息

    這就是我們重寫的方法,

     public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
    版權聲明:本文為qq_29157591原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/qq_29157591/article/details/75599147

    智能推薦

    安卓開發使用Thread、Handler實時更新UI學習筆記。

    最近開發安卓的時候常有需求要實時更新UI,然后發現很多都忘了,故做個學習筆記。 首先,假設需求:點擊按鈕后實時更新當前時間。 然后開工! 布局代碼: Java代碼: 完畢。...

    安卓Loop機制剖析之Looper,handler

    目錄 Looper是什么 簡單使用 從源碼了解loop原理 loop分析 myLooper() next() handler.dispatchMessage handler分析 消息入隊 同步屏障 總結 Looper是什么 用于為線程運行消息循環的類。默認情況下,線程沒有與之關聯的消息循環。要創建一個,在要運行循環的線程中調用 prepare(),然后調用loop()讓它處理消息,直到循環停止為止...

    安卓:數據存儲之SharedPreference——學習筆記

    Android提供了四種數據存儲的方式 SharedPreference:一種常用的數據存儲方式,其本質是基于XML文件存儲鍵值對數據,通常用來存儲一些簡單的配置信息。 SQLite:一個輕量級數據庫,支持基本SQL語法,是Android系統中常被采用的一種數據存儲方式。 ContentProvider:Android系統中能實現應用程序之間數據共享的一種存儲方式。 文件:即常說的文件存儲方法,常...

    安卓學習筆記(一)

    一、Android 起源 Android操作系統最初是由安迪·魯賓(Andy Rubin)開發出的,2005年被Google收購,并于2007年11月5日正式向外界展示了這款系統。 二、安卓發展史 三、安卓開發基本概念 四、安卓版本的發展 正式版發布之前有兩個測試的版本:分別是鐵臂阿童木和發條機器人 2008年由HTC代工發布了第一款安卓手機,谷歌公司發布安卓1.0,發展到現在的安卓...

    安卓學習筆記——URI

    一、URI:統一資源定位符,Universal Resource Identifier的簡稱 每一個ContentProvider都擁有一個公共的URI,這個URI用于表示ContentProvider提供的數據 二、URI的組成: 例: 三、URI 常用方法: 1、static Uri parse(String UriString):將一個字符串轉換為Uri 例如: 2、 List getPat...

    猜你喜歡

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    精品国产乱码久久久久久蜜桃不卡