首先說一下,今日頭條的面試主要分爲三輪到四輪,若是是旺季面三輪,首先是基礎面試,基本面試通常10個題左右,最近面試了一下今日頭條的移動Android資深工程師,記錄下。 第一面是北京的開發進行視頻面試,有理論和編程題組成。用的是在線編程工具,以下圖。html
//飽漢寫法
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
//懶漢寫法
private static final Singleton1 single = new Singleton1();
//靜態工廠方法
public static Singleton1 getInstance() {
return single;
}
複製代碼
2,請編程實現Java的生產者-消費者模型 看到這個有點懵逼,要是大學畢業的時候寫這個確定沒問題,這都工做多年,這也只能按照本身的思路寫了。這裏使用synchronized鎖以及wait notify實現一個比較簡單的。前端
import java.io.IOException;
public class WaitAndNotify
{
public static void main(String[] args) throws IOException
{
Person person = new Person();
new Thread(new Consumer("消費者一", person)).start();
new Thread(new Consumer("消費者二", person)).start();
new Thread(new Consumer("消費者三", person)).start();
new Thread(new Producer("生產者一", person)).start();
new Thread(new Producer("生產者一", person)).start();
new Thread(new Producer("生產者一", person)).start();
}
}
class Producer implements Runnable
{
private Person person;
private String producerName;
public Producer(String producerName, Person person)
{
this.producerName = producerName;
this.person = person;
}
@Override
public void run()
{
while (true)
{
try
{
person.produce();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable
{
private Person person;
private String consumerName;
public Consumer(String consumerName, Person person)
{
this.consumerName = consumerName;
this.person = person;
}
@Override
public void run()
{
try
{
person.consume();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
class Person
{
private int foodNum = 0;
private Object synObj = new Object();
private final int MAX_NUM = 5;
public void produce() throws InterruptedException
{
synchronized (synObj)
{
while (foodNum == 5)
{
System.out.println("box is full,size = " + foodNum);
synObj.wait();
}
foodNum++;
System.out.println("produce success foodNum = " + foodNum);
synObj.notifyAll();
}
}
public void consume() throws InterruptedException
{
synchronized (synObj)
{
while (foodNum == 0)
{
System.out.println("box is empty,size = " + foodNum);
synObj.wait();
}
foodNum--;
System.out.println("consume success foodNum = " + foodNum);
synObj.notifyAll();
}
}
}
複製代碼
關於更多的知識能夠https://zhuanlan.zhihu.com/p/20300609 3,HashMap的內部結構? 內部原理? 關於HashMap的問題,再也不詳述,這方面的資料也挺多,很少須要注意的是Java1.7和1.8版本HashMap內部結構的區別。 4,請簡述Android事件傳遞機制, ACTION_CANCEL事件什麼時候觸發? 關於第一個問題,不作任何解釋。 關於ACTION_CANCEL什麼時候被觸發,系統文檔有這麼一種使用場景:在設計設置頁面的滑動開關時,若是不監聽ACTION_CANCEL,在滑動到中間時,若是你手指上下移動,就是移動到開關控件以外,則此時會觸發ACTION_CANCEL,而不是ACTION_UP,形成開關的按鈕停頓在中間位置。 意思是當滑動的時候就會觸發,不知道你們搞沒搞過微信的長按錄音,有一種狀態是「鬆開手指,取消發送」,這時候就會觸發ACTION_CANCEL。java
5,Android的進程間通訊,Liunx操做系統的進程間通訊。 關於這個問題也是被問的不少,此處也不作解釋。android
6,JVM虛擬機內存結構,以及它們的做用。 這個問題也比較基礎,JVM的內存結構以下圖所示。c++
7,簡述Android的View繪製流程,Android的wrap_content是如何計算的。面試
8,有一個整形數組,包含正數和負數,而後要求把數組內的全部負數移至正數的左邊,且保證相對位置不變,要求時間複雜度爲O(n), 空間複雜度爲O(1)。例如,{10, -2, 5, 8, -4, 2, -3, 7, 12, -88, -23, 35}變化後是{-2, -4,-3, -88, -23,5, 8 ,10, 2, 7, 12, 35}。算法
要實現上面的效果有兩種方式: 第一種:兩個變量,一個用來記錄當前的遍歷點,一個用來記錄最左邊的負數在數組中的索引值。而後遍歷整個數組,遇到負數將其與負數後面的數進行交換,遍歷結束,便可實現負數在左,正數在右。spring
第二種:兩個變量記錄左右節點,兩邊分別開始遍歷。左邊的節點遇到負值繼續前進,遇到正值中止。右邊的節點正好相反。而後將左右節點的只進行交換,而後再開始遍歷直至左右節點相遇。這種方式的時間複雜度是O(n).空間複雜度爲O(1)編程
//方法1
public void setParted(int[] a){
int temp=0;
int border=-1;
for(int i=0;i<a.length;i++){
if(a[i]<0){
temp=a[i];
a[i]=a[border+1];
a[border+1]=temp;
border++;
}
}
for(int j=0;j<a.length;j++){
System.out.println(a[j]);
}
}
//方法2
public void setParted1(int[] a,int left,int right){
if(left>=right||left==a.length||right==0){
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
return ;
}
while(a[left]<0){
left++;
}
while(a[right]>=0){
right--;
}
if(left>=right||left==a.length||right==0){
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
return;
}
swap(a,left,right);
left++;
right--;
setParted1(a,left,right);
}
private void swap(int a[],int left,int right){
int temp=0;
temp=a[left];
a[left]=a[right];
a[right]=temp;
}
public static void main(String[] args) {
int a[]={1,2,-1,-5,-6,7,-7,-10};
new PartTest().setParted1(a,0,a.length-1);
}
複製代碼
顯然,第二種實現的難點比較高,不過只要此種知足條件。api
第二面 1,bundle的數據結構,如何存儲,既然有了Intent.putExtra,爲啥還要用bundle。
bundle的內部結構實際上是Map,傳遞的數據能夠是boolean、byte、int、long、float、double、string等基本類型或它們對應的數組,也能夠是對象或對象數組。當Bundle傳遞的是對象或對象數組時,必須實現Serializable 或Parcelable接口。
2,android的IPC通訊方式,是否使用過 這方面的資料比較多,也不方便闡述
3,Android的多點觸控如何傳遞 核心類
4,asynctask的原理 AsyncTask是對Thread和Handler的組合包裝。 https://blog.csdn.net/iispring/article/details/50670388 5,android 圖片加載框架有哪些,對比下區別 主要有4種:Android-Universal-Image-Loader、Picasso、Glide和Fresco Android-Universal-Image-Loader 優勢:支持下載進度監聽(ImageLoadingListener) * 可在View滾動中暫停圖片加載(PauseOnScrollListener) * 默認實現多種內存緩存算法(最大最早刪除,使用最少最早刪除,最近最少使用,先進先刪除,固然本身也能夠配置緩存算法) 缺點:2015年以後便再也不維護,該庫使用前須要進行配置。 Picasso 優勢:包較小(100k) * 取消不在視野範圍內圖片資源的加載 * 使用最少的內存完成複雜的圖片轉換 * 自動添加二級緩存 * 任務調度優先級處理 * 併發線程數根據網絡類型調整 * 圖片的本地緩存交給同爲Square出品的okhttp處理,控制圖片的過時時間。 缺點: 功能較爲簡單,自身沒法實現「本地緩存」功能。 Glide 優勢:多種圖片格式的緩存,適用於更多的內容表現形式(如Gif、WebP、縮略圖、Video) * 生命週期集成(根據Activity或者Fragment的生命週期管理圖片加載請求) * 高效處理Bitmap(bitmap的複用和主動回收,減小系統回收壓力) * 高效的緩存策略,靈活(Picasso只會緩存原始尺寸的圖片,Glide緩存的是多種規格),加載速度快且內存開銷小(默認Bitmap格式的不一樣,使得內存開銷是Picasso的一半)。 缺點:方法較多較複雜,由於至關於在Picasso上的改進,包較大(500k),影響不是很大。 Fresco 缺點:最大的優點在於5.0如下(最低2.3)的bitmap加載。在5.0如下系統,Fresco將圖片放到一個特別的內存區域(Ashmem區) * 大大減小OOM(在更底層的Native層對OOM進行處理,圖片將再也不佔用App的內存) * 適用於須要高性能加載大量圖片的場景。 缺點:包較大(2~3M) * 用法複雜 * 底層涉及c++領域
5,主線程中的Looper.loop()一直無限循環爲何不會形成ANR? ActivityThread.java 是主線程入口的類,ActivityThread.java 的main函數的內容以下。
Looper.prepareMainLooper();
...
//建立Looper和MessageQueue
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//輪詢器開始輪詢
Looper.loop();
複製代碼
而後再看Looper.loop()的源碼,能夠發現:
while (true) {
//取出消息隊列的消息,可能會阻塞
Message msg = queue.next(); // might block
…
//解析消息,分發消息
msg.target.dispatchMessage(msg);
…
}
複製代碼
顯然,ActivityThread的main方法主要就是作消息循環,一旦退出消息循環,那麼你的應用也就退出了。那麼這個死循環不會形成ANR異常呢? 說明:由於Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每個點擊觸摸或者說Activity的生命週期都是運行在 Looper.loop() 的控制之下,若是它中止了,應用也就中止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。也就說咱們的代碼其實就是在這個循環裏面去執行的,固然不會阻塞了。來看一下handleMessage的源碼:
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, 「>>> handling: 」 + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, 「activityStart」);
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, 「activityRestart」);
ActivityClientRecord r = (ActivityClientRecord) msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, 「activityPause」);
handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, 「activityPause」);
handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
}
複製代碼
能夠看見Activity的生命週期都是依靠主線程的Looper.loop,當收到不一樣Message時則採用相應措施。 若是某個消息處理時間過長,好比你在onCreate(),onResume()裏面處理耗時操做,那麼下一次的消息好比用戶的點擊事件不能處理了,整個循環就會產生卡頓,時間一長就成了ANR。
總結:Looer.loop()方法可能會引發主線程的阻塞,但只要它的消息循環沒有被阻塞,能一直處理事件就不會產生ANR異常。
除此以外,關於這個問題還有另一種問法:Android中爲何主線程不會由於Looper.loop()裏的死循環卡死? 回答這個問題,須要弄清Android系統中線程和進程的相互關係。 進程:每一個app運行時前首先建立一個進程,該進程是由Zygote fork出來的,用於承載App上運行的各類Activity/Service等組件。進程對於上層應用來講是徹底透明的,這也是google有意爲之,讓App程序都是運行在Android Runtime。大多數狀況一個App就運行在一個進程中,除非在AndroidManifest.xml中配置Android:process屬性,或經過native代碼fork進程。線程:線程對應用來講很是常見,好比每次new Thread().start都會建立一個新的線程。該線程與App所在進程之間資源共享,從Linux角度來講進程與線程除了是否共享資源外,並無本質的區別,都是一個task_struct結構體,在CPU看來進程或線程無非就是一段可執行的代碼,CPU採用CFS調度算法,保證每一個task都儘量公平的享有CPU時間片。
對於Looper.loop()爲何不會出現卡死的問題,能夠按照下面的思路: 對於線程既然是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,咱們是毫不但願會被運行一段時間,本身就退出,那麼如何保證能一直存活呢?簡單作法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder線程也是採用死循環的方法,經過循環方式不一樣與Binder驅動進行讀寫操做,固然並不是簡單地死循環,無消息時會休眠。但這裏可能又引起了另外一個問題,既然是死循環又如何去處理其餘事務呢?經過建立新線程的方式。真正會卡死主線程的操做是在回調方法onCreate/onStart/onResume等操做時間過長,會致使掉幀,甚至發生ANR,looper.loop自己不會致使應用卡死。
6,圖片框架的一些原理知識
7,其餘的一些Android的模塊化開發,熱更新,組件化等知識。
在Android面試的時候,常常會被問到一些Android開發中用到的一些開發框架,如常見的網絡請求框架Retrofit/OkHttp,組件通訊框架EventBus/Dagger2,異步編程RxJava/RxAndroid等。本文給你們整理下上面的幾個框架,以備面試用。
EventBus是一個Android發佈/訂閱事件總線,簡化了組件間的通訊,讓代碼更加簡介,可是若是濫用EventBus,也會讓代碼變得更加輔助。面試EventBus的時候通常會談到以下幾點: ####(1)EventBus是經過註解+反射來進行方法的獲取的
註解的使用:@Retention(RetentionPolicy.RUNTIME)表示此註解在運行期可知,不然使用CLASS或者SOURCE在運行期間會被丟棄。 經過反射來獲取類和方法:由於映射關係其實是類映射到全部此類的對象的方法上的,因此應該經過反射來獲取類以及被註解過的方法,而且將方法和對象保存爲一個調用實體。
####(2)使用ConcurrentHashMap來保存映射關係 調用實體的構建:調用實體中對於Object,也就是實際執行方法的對象不該該使用強引用而是應該使用弱引用,由於Map的static的,生命週期有可能長於被調用的對象,若是使用強引用就會出現內存泄漏的問題。
說明:併發編程實踐中,ConcurrentHashMap是一個常常被使用的數據結構,相比於Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在線程安全的基礎上提供了更好的寫併發能力,但同時下降了對讀一致性的要求。詳情能夠查看下面的文章: http://www.importnew.com/22007.html。
####(3)方法的執行 使用Dispatcher進行方法的分派,異步則使用線程池來處理,同步就直接執行,而UI線程則使用MainLooper建立一個Handler,投遞到主線程中去執行。
首先要明確EventBus中最核心的就是動態代理技術。
首先動態代理是區別於靜態代理的,代理模式中須要代理類和實際執行類同時實現一個相同的接口,而且在每一個接口定義的方法先後都要加入相同的代碼,這樣有可能不少方法代理類都須要重複。而動態代理就是將這個步驟放入運行時的過程,一個代理類只須要實現InvocationHandler接口中的invoke方法,當須要動態代理時只須要根據接口和一個實現了InvocationHandler的代理對象A生成一個最終的自動生成的代理對象A*。這樣最終的代理對象A*不管調用什麼方法,都會執行InvocationHandler的代理對象A的invoke函數,你就能夠在這個invoke函數中實現真正的代理邏輯。
動態代理的實現機制實際上就是使用Proxy.newProxyInstance函數爲動態代理對象A生成一個代理對象A的類的字節碼從而生成具體A對象過程,這個A*類具備幾個特色,一是它須要實現傳入的接口,第二就是全部接口的實現中都會調用A的invoke方法,而且傳入相應的調用實際方法(即接口中的方法)。
####Retrofit中的動態代理
Retrofit中使用了動態代理是不錯,可是並非爲了真正的代理才使用的,它只是爲了動態代理一個很是重要的功能,就是「攔截」功能。咱們知道動態代理中自動生成的A對象的全部方法執行都會調用實際代理類A中的invoke方法,再由咱們在invoke中實現真正代理的邏輯,實際上也就是A的全部方法都被A對象給攔截了。 而Retrofit的功能就是將代理變成像方法調用那麼簡單。
public interface ServiceApi {
@GET("/api/columns/{user} ")
Call<Author> getAuthor(@Path("user") String user)
}
複製代碼
再用這個retrofit對象建立一個ServiceApi對象,並經過getAuthor函數來調用函數。
ServiceApi api = retrofit.create(ServiceApi.class);
Call<Author> call = api.getAuthor("zhangsan");
複製代碼
也就是一個網絡調用你只須要在你建立的接口裏面經過註解進行設置,而後經過retrofit建立一個api而後調用,就能夠自動完成一個Okhttp的Call的建立。Retrofit的create()函數的代碼以下:
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
複製代碼
咱們能夠看出怎麼從接口類建立成一個API對象?就是使用了動態代理中的攔截技術,經過建立一個符合此接口的動態代理對象A*,那A呢?就是這其中建立的這個匿名類了,它在內部實現了invoke函數,這樣A*調用的就是A中的invoke函數,也就是被攔截了,實際運行invoke。而invoke就是根據調用的method的註解(,從而生成一個符合條件的Okhttp的Call對象,並進行真正的請求。
####Retrofit做用
Retrofit其實是爲了更方便的使用Okhttp,由於Okhttp的使用就是構建一個Call,而構建Call的大部分過程都是類似的,而Retrofit正是利用了代理機制帶咱們動態的建立Call,而Call的建立信息就來自於你的註解。
關於OkHttp3的內容你們能夠訪問下面的博客連接:OkHttp3源碼分析。該文章主要從如下幾個方面來說解OkHttps相關的內容: OkHttp3源碼分析[綜述] OkHttp3源碼分析[複用鏈接池] OkHttp3源碼分析[緩存策略] OkHttp3源碼分析[DiskLruCache] OkHttp3源碼分析[任務隊列]
Okhttp使用了一個線程池來進行異步網絡任務的真正執行,而對於任務的管理採用了任務隊列的模型來對任務執行進行相應的管理,有點相似服務器的反向代理模型。Okhttp使用分發器Dispatcher來維護一個正在運行任務隊列和一個等待隊列。若是當前併發任務數量小於64,就放入執行隊列中而且放入線程池中執行。而若是當前併發數量大於64就放入等待隊列中,在每次有任務執行完成以後就在finally塊中調用分發器的finish函數,在等待隊列中查看是否有空餘任務,若是有就進行入隊執行。Okhttp就是使用任務隊列的模型來進行任務的執行和調度的。
Http使用的TCP鏈接有長鏈接和短鏈接之分,對於訪問某個服務器的頻繁通訊,使用短鏈接勢必會形成在創建鏈接上大量的時間消耗;而長鏈接的長時間無用保持又會形成資源你的浪費。Okhttp底層是採用Socket創建流鏈接,而鏈接若是不手動close掉,就會形成內存泄漏,那咱們使用Okhttp時也沒有作close操做,實際上是Okhttp本身來進行鏈接池的維護的。在Okhttp中,它使用相似引用計數的方式來進行鏈接的管理,這裏的計數對象是StreamAllocation,它被反覆執行aquire與release操做,這兩個函數實際上是在改變Connection中的List<WeakReference<StreamAllocation>>
大小。List中Allocation的數量也就是物理socket被引用的計數(Refference Count),若是計數爲0的話,說明此鏈接沒有被使用,是空閒的,須要經過淘汰算法實現回收。
在鏈接池內部維護了一個線程池,這個線程池運行的cleanupRunnable其實是一個阻塞的runnable,內部有一個無限循環,在清理完成以後調用wait進行等待,等待的時間由cleanup的返回值決定,在等待時間到了以後再進行清理任務。相關代碼以下:
while (true) {
//執行清理並返回下場須要清理的時間
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
synchronized (ConnectionPool.this) {
try {
//在timeout內釋放鎖與時間片
ConnectionPool.this.wait(TimeUnit.NANOSECONDS.toMillis(waitNanos));
} catch (InterruptedException ignored) {
}
}
}
複製代碼
其中,Cleanup函數的執行過程以下:
說明:「併發」==(「空閒」+「活躍」)==5,而不是說併發鏈接就必定是活躍的鏈接。
如何標記空閒的鏈接呢?咱們前面也說了,若是一個鏈接身上的引用爲0,那麼就說明它是空閒的,那麼就要使用pruneAndGetAllocationCount來計算它身上的引用數,如同引用計數過程。 其實標記引用爲0的算法很簡單,就是遍歷它的List<Reference<StreamAllocation>>
,刪除全部已經爲null的弱引用,剩下的數量就是如今它的引用數量,pruneAndGetAllocationCount函數的源碼以下:
//相似於引用計數法,若是引用所有爲空,返回馬上清理
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
//虛引用列表
List<Reference<StreamAllocation>> references = connection.allocations;
//遍歷弱引用列表
for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i);
//若是正在被使用,跳過,接着循環
//是否置空是在上文`connectionBecameIdle`的`release`控制的
if (reference.get() != null) {
//很是明顯的引用計數
i++;
continue;
}
//不然移除引用
references.remove(i);
connection.noNewStreams = true;
//若是全部分配的流均沒了,標記爲已經距離如今空閒了5分鐘
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
return references.size();
}
複製代碼
從15年開始,前端掀起了一股異步編程的熱潮,在移動Android編程過程當中,常常會聽到觀察者與被觀察者等概念。
Observable的經過create函數建立一個觀察者對象。
public final static <T> Observable<T> create(OnSubscribe<T> f) {
return new Observable<T>(hook.onCreate(f));
}
複製代碼
Observable的構造函數以下:
protected Observable(OnSubscribe<T> f) {
this.onSubscribe = f;
}
複製代碼
建立了一個Observable咱們記爲Observable1,保存了傳入的OnSubscribe對象爲onSubscribe,這個很重要,後面會說到。
onSubscribe方法
public final Subscription subscribe(Subscriber<? super T> subscriber) {
return Observable.subscribe(subscriber, this);
}
private static <T> Subscription subscribe(Subscriber<? super T> subscriber, Observable<T> observable) {
...
subscriber.onStart();
onSubscribe.call(subscriber);
return hook.onSubscribeReturn(subscriber);
}
複製代碼
在RxJava中常常會數據轉換,如map函數,filtmap函數和lift函數。 lift函數
public <R> Observable<R> lift(Operator<? extends R, ? super T> operator) {
return Observable.create(new OnSubscribe<R>() {
@Override
public void call(Subscriber subscriber) {
Subscriber newSubscriber = operator.call(subscriber);
newSubscriber.onStart();
onSubscribe.call(newSubscriber);
}
});
}
複製代碼
咱們能夠看到這裏咱們又建立了一個新的Observable對象,咱們記爲Observable2,也就是說當咱們執行map時,實際上返回了一個新的Observable對象,咱們以後的subscribe函數實際上執行再咱們新建立的Observable2上,這時他調用的就是咱們新的call函數,也就是Observable2的call函數(加粗部分),咱們來看一下這個operator的call的實現。這裏call傳入的就是咱們的Subscriber1對象,也就是調用最終的subscribe的處理對象。
call函數
public Subscriber<? super T> call(final Subscriber<? super R> o) {
return new Subscriber<T>(o) {
@Override
public void onNext(T t) {
o.onNext(transformer.call(t));
}
};
}
複製代碼
這裏的transformer就是咱們在map調用是傳進去的func函數,也就是變換的具體過程。那看以後的onSubscribe.call(回到call中),這裏的onSubscribe是誰呢?就是咱們Observable1保存的onSubscribe對象,也就是咱們前面說很重要的那個對象。而這個o(又回來了)就是咱們的Subscriber1,這裏能夠看出,在調用了轉換函數以後咱們仍是調用了一開始的Subscriber1的onNext,最終事件通過轉換傳給了咱們的結果。
RxJava最好用的特色就是提供了方便的線程切換,但它的原理歸根結底仍是lift,使用subscribeOn()的原理就是建立一個新的Observable,把它的call過程開始的執行投遞到須要的線程中;而 observeOn() 則是把線程切換的邏輯放在本身建立的Subscriber中來執行。把對於最終的Subscriber1的執行過程投遞到須要的線程中來進行。
爲何subscribeOn()只有第一個有效? 由於它是從通知開始將後面的執行所有投遞到須要的線程來執行,可是以後的投遞會受到在它的上級的(可是執行在它以後)的影響,若是上面還有subscribeOn() ,又會投遞到不一樣的線程中去,這樣就不受到它的控制了。