經(jīng)過這幾年的Android開發(fā),慢慢積累了很多相關(guān)經(jīng)驗(yàn),這里把一些我自認(rèn)為比較重要的,但網(wǎng)上介紹相對較少或者較淺的知識更加詳細(xì)的介紹下。大的層面,從這一篇開始會介紹三個(gè)方面
- Android系統(tǒng)的消息機(jī)制
- Android的Activity
- Android的Fragment
會從更加抽象的層面來介紹。會有一定的閱讀門檻,但會較少的分析Android的源碼,會重點(diǎn)分析原理,會把大致的脈絡(luò)介紹清楚。通過這個(gè)系列可能會幫你弄明白類似:
- 為什么我上滑一個(gè)列表,列表滾動還沒有停止的時(shí)候我突然下滑界面能馬上開始下滑。
- Activity都有生命周期回調(diào)方法,這些方法是怎么被回調(diào)的?入口在哪兒?
- Fragment究竟是什么?
- Fragment究竟是怎么被顯示出來的?
- DialogFragment是什么?為什么DialogFragment能正確恢復(fù)?
- 臭名昭著的FragmentStateLoss是怎么來的?
等等類似上面這些問題。會比較深入的從根本上把他們介紹清楚。
從顯示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");
}
}
我們把上面的代碼來對應(yīng)到Android里。假設(shè)在Android里點(diǎn)擊一個(gè)App后會執(zhí)行上面main方法。那么就是執(zhí)行:
System.out.println(">>>onCreate"); // 類似Activity的onCreate
然后執(zhí)行:
displayHelloWorld(); // 顯示Android的TextView,內(nèi)容:HelloWorld。
最后執(zhí)行:
System.out.println("<<< onDestroy"); // 類似Activity的onDestroy 執(zhí)行這一行后界面將不再顯示
好了,代碼非常簡單,思考下這樣對應(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不會被執(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è)讀取輸入的方法。試下會發(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的事件??梢允怯脩艋瑒恿私缑孢€是,或者是任意時(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.itdecent.cn/p/5b8d2f8d4f8d
大家對Handler的理解通常都是說跟主線程通信,在主線程更新UI。事實(shí)上跟主線程通信只是很小的一個(gè)方面,Handler Message MessageQueue Looper更重要是實(shí)現(xiàn)了Android的消息循環(huán)。如果你看了上面的Handler的二次實(shí)現(xiàn),而且自己也能獨(dú)立再寫一個(gè),那肯定會對Android的消息機(jī)制有個(gè)非常深刻的理解。
什么是消息機(jī)制
到了這里我們再看什么消息機(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í)候,觸摸屏硬件會通過驅(qū)動把事件傳遞給操作系統(tǒng),操作系統(tǒng)把事件傳遞給當(dāng)前正在運(yùn)行的App,然后App的消息隊(duì)列會被插入一個(gè)包含觸摸信息的消息,然后消息被實(shí)際執(zhí)行,這時(shí)候你用來響應(yīng)觸摸事件的代碼執(zhí)行時(shí)間應(yīng)該盡量少,最好小于16ms(1000ms/60hz=16.666ms)。那假如我響應(yīng)觸摸事件的行為是一個(gè)View從左到右移動1600個(gè)像素,持續(xù)1600ms的動畫怎么辦?1600ms明顯是遠(yuǎn)大于16ms的。這時(shí)候動畫應(yīng)該被分割成100個(gè)小消息,每個(gè)消息把View移動16個(gè)像素,這背后會有很多需要處理的細(xì)節(jié),不過Android自帶的動畫框架可以輔助你非常簡單的實(shí)現(xiàn)這個(gè)過程。這樣就能實(shí)時(shí)響應(yīng)用戶的操作了。怎么響應(yīng)呢?比如現(xiàn)在是觸摸后的第160ms,這時(shí)候我又點(diǎn)擊了一次Button,那這時(shí)候我希望的操作是取消動畫。實(shí)際會怎么被執(zhí)行呢?
第160ms時(shí)候消息循環(huán)正在執(zhí)行之前的移動動畫,這時(shí)候已經(jīng)移動了160個(gè)像素,這時(shí)候觸摸屏把觸摸事件傳遞給了操作系統(tǒng),操作系統(tǒng)把事件分發(fā)給App,App的消息隊(duì)列被插入一個(gè)新消息,然后消息被執(zhí)行,執(zhí)行的實(shí)際操作就是animator.cancel()動畫被取消。從用戶角度看屏幕實(shí)時(shí)響應(yīng)了用戶的操作請求。
我們再來思考下假如沒有消息機(jī)制,這個(gè)把View移動1600像素,持續(xù)1600ms的動畫會有什么問題?如果沒有把這個(gè)持續(xù)1600ms的操作分割成很多小消息,會導(dǎo)致這1600ms內(nèi)無法響應(yīng)用戶操作。因?yàn)檫M(jìn)程需要1600ms把整個(gè)代碼處理完成才能繼續(xù)處理新代碼。