細(xì)說Android消息機(jī)制

經(jīng)過這幾年的Android開發(fā)归园,慢慢積累了很多相關(guān)經(jīng)驗(yàn)停团,這里把一些我自認(rèn)為比較重要的亭枷,但網(wǎng)上介紹相對(duì)較少或者較淺的知識(shí)更加詳細(xì)的介紹下袭艺。大的層面,從這一篇開始會(huì)介紹三個(gè)方面

  • Android系統(tǒng)的消息機(jī)制
  • Android的Activity
  • Android的Fragment

會(huì)從更加抽象的層面來介紹。會(huì)有一定的閱讀門檻,但會(huì)較少的分析Android的源碼纳击,會(huì)重點(diǎn)分析原理,會(huì)把大致的脈絡(luò)介紹清楚袍镀。通過這個(gè)系列可能會(huì)幫你弄明白類似:

  • 為什么我上滑一個(gè)列表,列表滾動(dòng)還沒有停止的時(shí)候我突然下滑界面能馬上開始下滑冻晤。
  • Activity都有生命周期回調(diào)方法苇羡,這些方法是怎么被回調(diào)的?入口在哪兒鼻弧?
  • Fragment究竟是什么设江?
  • Fragment究竟是怎么被顯示出來的锦茁?
  • DialogFragment是什么?為什么DialogFragment能正確恢復(fù)叉存?
  • 臭名昭著的FragmentStateLoss是怎么來的码俩?

等等類似上面這些問題。會(huì)比較深入的從根本上把他們介紹清楚歼捏。

從顯示HelloWorld說起

介紹消息機(jī)制之前稿存,我們先看下面這個(gè)簡單的代碼。

public class Main {

    public static void main(String[] args) {
        System.out.println(">>>onCreate");

        displayHelloWorld();

        System.out.println("<<<onDestroy");
    }

    private static void displayHelloWorld() {
        System.out.println("HelloWorld");
    }
}

我們把上面的代碼來對(duì)應(yīng)到Android里瞳秽。假設(shè)在Android里點(diǎn)擊一個(gè)App后會(huì)執(zhí)行上面main方法瓣履。那么就是執(zhí)行:

System.out.println(">>>onCreate"); // 類似Activity的onCreate

然后執(zhí)行:

displayHelloWorld(); // 顯示Android的TextView,內(nèi)容:HelloWorld练俐。

最后執(zhí)行:

System.out.println("<<< onDestroy"); // 類似Activity的onDestroy 執(zhí)行這一行后界面將不再顯示

好了袖迎,代碼非常簡單,思考下這樣對(duì)應(yīng)后腺晾,我們能看到HelloWorld界面嗎燕锥?

displayHelloWorld();

被執(zhí)行完,我們能看到HelloWorld界面悯蝉,沒問題归形,但關(guān)鍵是緊接著又繼續(xù)執(zhí)行了:

System.out.println("<<<onDestory"); // 執(zhí)行這一行后界面將不再顯示

然后程序就退出了。在Android里就相當(dāng)于HelloWorld界面一閃而過鼻由,這肯定不是我們想要的結(jié)果连霉。從C入門的朋友還記得寫完HelloWorld后,命令提示符界面突然一閃而過嗎嗡靡?你還記得是怎么處理的嗎?
怎么辦窟感?sleep是否可以讨彼?

public class Main {

    public static void main(String[] args) {
        System.out.println(">>>onCreate");

        displayHelloWorld();

        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("<<<onDestroy");
    }

    private static void displayHelloWorld() {
        System.out.println("HelloWorld");
    }
}

改造成上面的代碼后,似乎是可以了柿祈,最后的onDestroy不會(huì)被執(zhí)行哈误,因?yàn)樵趕leep那里就停下來了。這代碼看著雖然很不舒服躏嚎,但顯示HelloWrold似乎是沒什么問題了蜜自。
但光顯示HelloWorld太單調(diào)了。假如需求變成了用戶從鍵盤輸什么就顯示什么怎么辦卢佣?


import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println(">>>onCreate");

        // display("HelloWorld");
        display(readInput());

        Thread.sleep(Integer.MAX_VALUE);
        System.out.println("<<<onDestroy");
    }

    private static void display(String s) {
        System.out.println(s);
    }

    private static String readInput() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
        br.close();
        return s;
    }
}

把display方法改造下重荠,把顯示內(nèi)容變成參數(shù)。然后增加一個(gè)讀取輸入的方法虚茶。試下會(huì)發(fā)現(xiàn)只能輸入一次不能不停的輸入戈鲁。再改造下:

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println(">>>onCreate");

        while (true) {
            display(readInput());
        }

        // Thread.sleep(Integer.MAX_VALUE);
        // System.out.println("<<<onDestroy");
    }

    private static void display(String s) {
        System.out.println(s);
    }

    private static String readInput() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = br.readLine();
        br.close();
        return s;
    }
}

我們用一個(gè)死循環(huán)(有沒有感覺到消息循環(huán)要來了仇参?還記得什么是軟中斷嗎?)婆殿,不停的讀取诈乒,這樣連sleep都不需要了。
到這里我們實(shí)現(xiàn)了一個(gè)Android界面能實(shí)時(shí)顯示鍵盤輸入的文字婆芦。
那假如我們需求又變化了怕磨,我們不光要顯示問題還做其他操作,比如運(yùn)算啊消约,顯示圖片啊等等肠鲫。這好辦我們把diaplay包裝成一個(gè)Runnable,你想干嘛干嘛荆陆。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

    private static Runnable sRunnable = new Runnable() {
        @Override
        public void run() {
            display(readInput());
        }
    };

    public static void main(String[] args) {
        System.out.println(">>>onCreate");

        while (true) {
            sRunnable.run();
        }
    }

    private static void display(String s) {
        System.out.println(s);
    }

    private static String readInput() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            String s = br.readLine();
            br.close();
            return s;
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

這樣修改sRunnable的run方法你想干嘛干嘛滩届。但問題又來了,我們不是只執(zhí)行一種操作被啼,我們希望能執(zhí)行操作帜消,我們希望被執(zhí)行的Runnable是個(gè)List,我們能隨意向List添加Runnable浓体。繼續(xù)改造:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Main {

    private static List<Runnable> sTaskQueue = new ArrayList<>();

    public static void main(String[] args) {
        System.out.println(">>>onCreate");

        sTaskQueue.add(new Runnable() {
            @Override
            public void run() {
                display(readInput());
            }
        });
        sTaskQueue.add(new Runnable() {
            @Override
            public void run() {
                System.out.println("Draw bitmap");
            }
        });
        sTaskQueue.add(new Runnable() {
            @Override
            public void run() {
                System.out.println("Open https://www.google.com");
            }
        });

        while (true) {
            for (Runnable runnable : sTaskQueue) {
                runnable.run();
            }
        }
    }

    private static void display(String s) {
        System.out.println(s);
    }

    private static String readInput() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            String s = br.readLine();
            br.close();
            return s;
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

這下好了泡挺,能執(zhí)行多種操作了,不光能顯示TextView還能繪制bitmap命浴,打開WebView娄猫。但運(yùn)行后發(fā)現(xiàn)還有問題,任務(wù)一直被重復(fù)執(zhí)行生闲,任務(wù)執(zhí)行后我們應(yīng)該把任務(wù)從TaskQueue從移除媳溺。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static Queue<Runnable> sTaskQueue = new LinkedList<>();

    public static void main(String[] args) {
        System.out.println(">>>onCreate");

        sTaskQueue.add(new Runnable() {
            @Override
            public void run() {
                display(readInput());
            }
        });
        sTaskQueue.add(new Runnable() {
            @Override
            public void run() {
                System.out.println("Draw bitmap");
            }
        });
        sTaskQueue.add(new Runnable() {
            @Override
            public void run() {
                System.out.println("Open https://www.google.com");
            }
        });

        while (!sTaskQueue.isEmpty()) {
            Runnable task = sTaskQueue.poll();
            task.run();
        }

        System.out.println("<<<onDestroy");
    }

    private static void display(String s) {
        System.out.println(s);
    }

    private static String readInput() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            String s = br.readLine();
            br.close();
            return s;
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

用Queue的數(shù)據(jù)結(jié)構(gòu),很方便避免了任務(wù)重復(fù)執(zhí)行碍讯。這里還有個(gè)問題悬蔽,我們肯定是希望任務(wù)能隨時(shí)被添加,而不應(yīng)該是像上面一樣被固定死了顯示字符捉兴,繪制bitmap蝎困,打開WebView。繼續(xù)改造:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {

    private static Queue<Runnable> sTaskQueue = new LinkedList<>();

    private static void addTask(Runnable runnable) {
        sTaskQueue.add(runnable);
    }

    public static void main(String[] args) throws Exception {
        System.out.println(">>>onCreate");

        addTask(() -> display(readInput()));
        addTask(() -> System.out.println("Draw bitmap"));
        addTask(() -> System.out.println("Open https://www.google.com"));

        startTaskThread();

        while (true) {
            Thread.sleep(1000);

            if (!sTaskQueue.isEmpty()) {
                Runnable task = sTaskQueue.poll();
                task.run();
            }
        }

        // System.out.println("<<<onDestroy");
    }

    private static void startTaskThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        long millis = (long) (2000 + Math.random() * 3000);
                        Thread.sleep(millis);

                        addTask(new Runnable() {
                            @Override
                            public void run() {
                                System.out.println("Random task: " + millis);
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    private static void display(String s) {
        System.out.println(s);
    }

    private static String readInput() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            String s = br.readLine();
            br.close();
            return s;
        } catch (IOException e) {
            e.printStackTrace();
            return e.getMessage();
        }
    }
}

添加了一個(gè)startTaskThread方法倍啥,用來運(yùn)行時(shí)創(chuàng)建任務(wù)禾乘。在Android里startTaskThread有點(diǎn)類似與點(diǎn)擊Button的事件∷渎疲可以是用戶滑動(dòng)了界面還是始藕,或者是任意時(shí)刻點(diǎn)擊Button等等任意操作。startTaskThread效果就是現(xiàn)在的代碼結(jié)構(gòu)能簡單的實(shí)現(xiàn)界面不退出,然后不停的執(zhí)行新來的任務(wù)鳄虱。好像基本實(shí)現(xiàn)了我們想要的效果弟塞。
按照這個(gè)模型,我們其實(shí)可以構(gòu)建出任意復(fù)雜的App拙已。只要我們把這些復(fù)雜的操作都包裝成Runnable添加到sTaskQueue即可决记。

上面的代碼其實(shí)就是Android的Handler Message MessageQueue Looper的模型。上面代碼加以改造倍踪,變成Android的Api就是這里的實(shí)現(xiàn)http://www.reibang.com/p/5b8d2f8d4f8d

大家對(duì)Handler的理解通常都是說跟主線程通信系宫,在主線程更新UI。事實(shí)上跟主線程通信只是很小的一個(gè)方面建车,Handler Message MessageQueue Looper更重要是實(shí)現(xiàn)了Android的消息循環(huán)扩借。如果你看了上面的Handler的二次實(shí)現(xiàn),而且自己也能獨(dú)立再寫一個(gè)缤至,那肯定會(huì)對(duì)Android的消息機(jī)制有個(gè)非常深刻的理解潮罪。

什么是消息機(jī)制

到了這里我們?cè)倏词裁聪C(jī)制(也可以叫消息循環(huán))應(yīng)該就很容易理解了。消息循環(huán)其實(shí)就是把一個(gè)線程需要執(zhí)行的任務(wù)分割成若干的消息领斥,然后線程一個(gè)個(gè)把這些任務(wù)包裝成的消息去循環(huán)往復(fù)的執(zhí)行嫉到,消息可以在任意時(shí)刻被添加。

為什么需要消息機(jī)制

如果你的軟件不是類似科學(xué)計(jì)算這樣的月洛,不需要隨時(shí)響應(yīng)外部事件的軟件的劃是不需要消息機(jī)制的何恶,但如果你的軟件是需要隨時(shí)響應(yīng)外界操作(比如觸摸操作,定時(shí)任務(wù))嚼黔,那么你就一定需要用到中斷(響應(yīng)觸摸操作就是消息機(jī)制實(shí)現(xiàn)的軟中斷细层,定時(shí)任務(wù)的定時(shí)由時(shí)鐘硬件中斷觸發(fā),然后被包裝成消息交給消息機(jī)制處理)唬涧,來響應(yīng)外界操作疫赎。這個(gè)中斷在軟件中表現(xiàn)其實(shí)就是消息機(jī)制。聽著似乎很繞碎节。舉個(gè)例子就非常清楚來捧搞。就比如Android中的觸摸操作。
當(dāng)用戶手指觸摸到一個(gè)Button區(qū)域的屏幕的時(shí)候钓株,觸摸屏硬件會(huì)通過驅(qū)動(dòng)把事件傳遞給操作系統(tǒng),操作系統(tǒng)把事件傳遞給當(dāng)前正在運(yùn)行的App陌僵,然后App的消息隊(duì)列會(huì)被插入一個(gè)包含觸摸信息的消息轴合,然后消息被實(shí)際執(zhí)行,這時(shí)候你用來響應(yīng)觸摸事件的代碼執(zhí)行時(shí)間應(yīng)該盡量少碗短,最好小于16ms(1000ms/60hz=16.666ms)受葛。那假如我響應(yīng)觸摸事件的行為是一個(gè)View從左到右移動(dòng)1600個(gè)像素,持續(xù)1600ms的動(dòng)畫怎么辦?1600ms明顯是遠(yuǎn)大于16ms的总滩。這時(shí)候動(dòng)畫應(yīng)該被分割成100個(gè)小消息纲堵,每個(gè)消息把View移動(dòng)16個(gè)像素,這背后會(huì)有很多需要處理的細(xì)節(jié)闰渔,不過Android自帶的動(dòng)畫框架可以輔助你非常簡單的實(shí)現(xiàn)這個(gè)過程席函。這樣就能實(shí)時(shí)響應(yīng)用戶的操作了。怎么響應(yīng)呢冈涧?比如現(xiàn)在是觸摸后的第160ms茂附,這時(shí)候我又點(diǎn)擊了一次Button,那這時(shí)候我希望的操作是取消動(dòng)畫督弓。實(shí)際會(huì)怎么被執(zhí)行呢营曼?
第160ms時(shí)候消息循環(huán)正在執(zhí)行之前的移動(dòng)動(dòng)畫,這時(shí)候已經(jīng)移動(dòng)了160個(gè)像素愚隧,這時(shí)候觸摸屏把觸摸事件傳遞給了操作系統(tǒng)蒂阱,操作系統(tǒng)把事件分發(fā)給App,App的消息隊(duì)列被插入一個(gè)新消息狂塘,然后消息被執(zhí)行录煤,執(zhí)行的實(shí)際操作就是animator.cancel()動(dòng)畫被取消。從用戶角度看屏幕實(shí)時(shí)響應(yīng)了用戶的操作請(qǐng)求睹耐。

我們?cè)賮硭伎枷录偃鐩]有消息機(jī)制辐赞,這個(gè)把View移動(dòng)1600像素,持續(xù)1600ms的動(dòng)畫會(huì)有什么問題硝训?如果沒有把這個(gè)持續(xù)1600ms的操作分割成很多小消息响委,會(huì)導(dǎo)致這1600ms內(nèi)無法響應(yīng)用戶操作。因?yàn)檫M(jìn)程需要1600ms把整個(gè)代碼處理完成才能繼續(xù)處理新代碼窖梁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赘风,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子纵刘,更是在濱河造成了極大的恐慌邀窃,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件假哎,死亡現(xiàn)場離奇詭異瞬捕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)舵抹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門肪虎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惧蛹,你說我怎么就攤上這事扇救⌒讨Γ” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵迅腔,是天一觀的道長装畅。 經(jīng)常有香客問我,道長沧烈,這世上最難降的妖魔是什么掠兄? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮掺出,結(jié)果婚禮上徽千,老公的妹妹穿的比我還像新娘。我一直安慰自己汤锨,他們只是感情好双抽,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闲礼,像睡著了一般牍汹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柬泽,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天慎菲,我揣著相機(jī)與錄音,去河邊找鬼锨并。 笑死露该,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的第煮。 我是一名探鬼主播解幼,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼包警!你這毒婦竟也來了撵摆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤害晦,失蹤者是張志新(化名)和其女友劉穎特铝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壹瘟,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲫剿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稻轨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灵莲。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖澄者,靈堂內(nèi)的尸體忽然破棺而出笆呆,到底是詐尸還是另有隱情,我是刑警寧澤粱挡,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布赠幕,位于F島的核電站,受9級(jí)特大地震影響询筏,放射性物質(zhì)發(fā)生泄漏榕堰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一嫌套、第九天 我趴在偏房一處隱蔽的房頂上張望逆屡。 院中可真熱鬧,春花似錦踱讨、人聲如沸魏蔗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莺治。三九已至,卻和暖如春帚稠,著一層夾襖步出監(jiān)牢的瞬間谣旁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工滋早, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榄审,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓杆麸,卻偏偏與公主長得像搁进,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子角溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容