經(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ù)處理新代碼窖梁。