Android面試高頻知識點

持續更新、、、

一 Activity的生命週期 ?

Activity的生命週期 ,包括典型的生命週期和異常情況下的生命週期,以及singletop模式和simgletask模式的生命週期。

典型生命週期包括c s r p s d ,注意特定場景下的生命週期,比如打開新的activity 時, 舊activity 調用onPause -> onStop,新activity 調用c s r ,如果回到舊activity 則調用onRestart -> onStart -> onResume。

異常情況下的生命週期分析,比如屏幕旋轉時,Activity會被銷燬,其onPause、onStop、onDestroy均會被調用,同時在onStop前調用onSaveInstanceState方法保存數據,而新activity 調用c s r。

singletop模式和simgletask模式的生命週期,如果要啓動的Activity已經存在,則只調用onNewIntent方法,否則重新創建,調用 c s r。

 

二 Activity 的四種啓動模式?

1-標準模式,onCreate、onStart、onResume都會被調用。比如Activity A啓動了Activity B(B是標準模式),那麼B就會進入到A所在的任務棧中。

2-singleTop:棧頂複用模式。在這種模式下,如果新Activity已經位於任務棧的棧頂,那麼此Activity不會被重新創建,同時它的onNewIntent方法會被回調,否則重新創建。

3-singleTask,在這種模式下,只要Activity在一個棧中存在,那麼多次啓動此Activity都不會重新創建實例,系統也會回調其onNewIntent方法,否則重新創建。

4-singleInstance模式,單獨開啓一個任務棧。

 

 

三  activity四種啓動模式的應用場景?

1.standard模式, mainfest中沒有配置啓動模式就默認爲標準模式。

2.singleTop模式, 登錄頁面、WXPayEntryActivity、推送通知欄等。

3.singleTask模式,程序模塊邏輯入口:主頁面、WebView頁面、掃一掃頁面、電商中:購物界面,確認訂單界面,付款界面等。

4.singleInstance模式, 系統Launcher、鎖屏鍵、來電顯示等系統應用。

 

四 什麼是activity 的任務棧?

TaskAffinity屬性的值就是任務棧的名字,默認爲應用包名,如果爲某個activity指定TaskAffinity屬性,則表示開啓新的任務棧。當TaskAffinity和singleTask啓動模式配對使用的時候,待啓動的Activity會運行在名字和TaskAffinity相同的任務棧。

當TaskAffinity和allowTaskReparenting結合的時候,會導致任務棧的轉移。比如現在有2個應用A和B, 應用A啓動了應用B的Activity C,然後按Home鍵回到桌面,然後再單擊應用B的桌面圖標,這個時候並不是啓動了應用B的主Activity,而是重新顯示了已經被應用A啓動的Activity C,或者說,C從A的任務棧轉移到了B的任務棧中。

 

五 Service的兩種狀態?

啓動狀態:當應用組件(如 Activity)通過調用 startService() 啓動服務時,服務即處於“啓動”狀態。一旦啓動,服務即可在後臺無限期運行,即使啓動服務的組件已被銷燬也不受影響,除非手動調用才能停止服務, 服務不會將結果返回給調用方,無法交互。

綁定狀態:當應用組件通過調用 bindService() 綁定到服務時,服務即處於“綁定”狀態。綁定服務允許組件與服務進行交互、發送請求、獲取結果,甚至是可以進程間通信 (IPC) 。多個組件可以同時綁定到該服務,但全部取消綁定後,該服務即會自動銷燬。

注意,如果服務將執行任何耗時事件,則應在服務內創建新線程來完成這項工作,簡而言之,Service裏面的耗時操作應該另起線程執行。

 

六 Service的兩種狀態互相可以轉換嗎?

1. 先綁定服務後啓動服務,那麼綁定服務將會轉爲啓動服務運行,這時如果之前綁定的宿主(Activity)被銷燬了,也不會影響服務的運行,直到收到調用停止服務或者內存不足時纔會銷燬該服務。

2.先啓動服務後綁定服務並不會轉爲綁定服務,但是還是會與宿主綁定,只是即使宿主解除綁定後,服務依然按啓動服務的生命週期在後臺運行,直到調用了stopService()或是服務本身調用了stopSelf()方法抑或內存不足時纔會銷燬服務。這種先啓動,後綁定服務,用於音樂播放器控制歌曲。

 

七 Service生命週期

第一次調用startService方法時,onCreate方法、onStartCommand方法將依次被調用,而多次調用startService時,只有onStartCommand方法被調用,最後我們調用stopService方法停止服務時onDestory方法被回調,這就是啓動狀態下Service的執行週期。

è¿éåå¾çæè¿°

 

八 如何保證服務不被殺死?

1.因內存資源不足而殺死Service
這種情況比較容易處理,可將onStartCommand() 方法的返回值設爲 START_STICKY或START_REDELIVER_INTENT ,該值表示服務在內存資源緊張時被殺死後,在內存資源足夠時再恢復。

2.也可將Service設置爲前臺服務,這樣就有比較高的優先級,在內存資源緊張時也不會被殺掉。

3.可以在 onDestory() 中發送廣播重新啓動,這樣殺死服務後會立即啓動。當然onDestory() 方法在某些情況下不會執行。

4.我們可開啓兩個服務,相互監聽,相互啓動。服務A監聽B的廣播來啓動B,服務B監聽A的廣播來啓動A。

 

九 IntentService作用及原理?

IntentService繼承自Service,用於在後臺執行耗時的異步任務,當任務完成後會自動停止。注意這裏是自動停止。它擁有較高的優先級,不易被系統殺死(繼承自Service的緣故),因此比較適合執行一些高優先級的異步任務。它的實現原理是HandlerThread和Handler,而HandlerThread是自帶Looper的的Thread。自帶Looper的的Thread有什麼好處呢? 當開啓子線程時,如果想在裏面創建自己的Handler,是會報錯的,因爲該子線程無法獲取到Looper。所以如果想在子線程裏面創建自己的Handler,需要自己調用Looper.prepare()方法獲取Looper,然後調用Looper.loop進行消息循環。而HandlerThread正好幫我們完成了這些工作,這就是HandlerThread的好處。

 

十 BroadcastReciever作用,使用場景,實現原理?

作用:

1.不同的程序之間的數據傳輸與共享,比如說攔截系統短信,攔截騷擾電話等。

2.起到了一個通知的作用,比如在service中要通知主程序,更新主程序的UI等

使用場景:

1.同一app內部的同一組件內的通信

2.同一app內部的不同組件之間的通信

3.同一app具有多個進程的不同組件之間的通信

4.不同app之間的組件之間通信

5.Android系統在特定情況下與App之間的通信

原理:

1.廣播接收者通過Binder機制向AMS(Activity Manager Service)註冊

2.廣播發送者通過binder機制向AMS發送廣播

3.AMS根據相應條件(IntentFilter/Permission等)在已註冊列表中,查找合適的廣播接收者,將廣播發送到BroadcastReceiver(一般情況下是Activity)相應的消息循環隊列中

4.消息循環執行拿到此廣播,回調BroadcastReceiver中的onReceive()方法

靜態註冊與動態註冊的區別:

1.註冊的地方不同

2.動態註冊時,當activity退出,就接收不到廣播了,靜態註冊即使app退出,仍然能接收到廣播。但是自Android3.1開始,對於系統廣播,如果App進程已經退出,將不能接收到廣播,對於自定義的廣播,可以通過覆寫flag爲FLAG_INCLUDE_STOPPED_PACKAGES,使得靜態註冊的BroadcastReceiver,即使所在App進程已經退出,也能接收到廣播。

BroadcastReceiver注意事項:

在onResume()註冊、onPause()註銷,因爲onPause()在App死亡前一定會被執行,從而保證廣播在App死亡前一定會被註銷,從而防止內存泄露。不在onCreate() & onDestory() 或 onStart() & onStop()註冊、註銷是因爲:
當系統因爲內存不足要回收Activity佔用的資源時,Activity在執行完onPause()方法後就會被銷燬,有些生命週期方法onStop(),onDestory()就不會執行,從而導致內存泄露。

 

十一 Fragment 聲明週期,其與Activity之間如何傳值?

生命週期爲:

1.Activity向Fragment傳值:

在Activity中創建對象fragment,通過調用fragment.setArguments()傳遞到fragment中,然後在Fragment中通過調用getArguments()得到bundle對象,就能得到裏面的值。

2.Fragment向Activity傳值:

第一種,在Activity中調用getFragmentManager()得到fragmentManager,,調用findFragmentByTag(tag)或者通過findFragmentById(id)。

第二種,通過回調的方式,定義一個接口(可以在Fragment類中定義),接口中有一個空的方法,在fragment中需要的時候調用接口的方法,值可以作爲參數放在這個方法中,然後讓Activity實現這個接口,必然會重寫這個方法,這樣值就傳到了Activity中。

3.Fragment與Fragment之間如何傳值:

第一種:通過findFragmentByTag得到另一個的Fragment的對象,這樣就可以調用另一個的方法了。
第二種:通過接口回調的方式。
第三種:通過setArgumentsgetArguments的方式。

FragmentPagerAdapter與FragmentStatePagerAdapter的區別?

FragmentPagerAdapter適用於Fragment頁面少的情況,FragmentStatePagerAdapter適用於Fragment頁面多的情況(頁面多時回收內存)。

 

十二 Binder機制

Android爲什麼採用Binder做爲IPC機制?

1.性能更強:Binder 只需要一次數據拷貝,性能上僅次於共享內存,而Socket/管道/消息隊列需要2次拷貝。

2.更穩當:共享內存雖然無需拷貝,但是控制負責,難以使用。從穩定性的角度講,Binder 機制是優於內存共享的(基於 C/S 架構)。

3.更安全傳統的 IPC 接收方無法獲得對方可靠的進程用戶ID/進程ID(UID/PID),從而無法鑑別對方身份。Android 爲每個安裝好的 APP 分配了自己的 UID,故而進程的 UID 是鑑別進程身份的重要標誌。傳統的 IPC 只能由用戶在數據包中填入 UID/PID,但這樣不可靠,容易被惡意程序利用。

Linux 下的傳統 IPC 通信原理?

通常的做法是消息發送方調用 copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中,然後內核程序調用 copy_to_user() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區,如下圖:

這種傳統的 IPC 通信方式有兩個問題:

1.性能低下,一內存緩存區 --> 內核緩存區 --> 內存緩存區,需要 2 次數據拷貝。

2.接收數據的緩存區由數據接收進程提供,但是接收進程並不知道需要多大的空間來存放將要傳遞過來的數據,因此只能開闢儘可能大的內存空間或者先調用 API 接收消息頭來獲取消息體的大小,這兩種做法不是浪費空間就是浪費時間。

動態內核可加載模塊機制?

傳統的 IPC 機制如管道、Socket 都是內核的一部分,因此通過內核支持來實現進程間通信自然是沒問題的。但是 Binder 並不是 Linux 系統內核的一部分,那怎麼辦呢?這就得益於 Linux 的動態內核可加載模塊(Loadable Kernel Module,LKM)的機制;模塊可以被單獨編譯,但是不能獨立運行。它在運行時被鏈接到內核作爲內核的一部分運行。這樣,Android 系統就可以通過動態添加一個內核模塊運行在內核空間,用戶進程之間通過這個內核模塊作爲橋樑來實現通信。而在 Android 系統中,這個內核模塊就叫 Binder 驅動(Binder Dirver)。

內存映射?

 知道了上面的LKM機制,Android 系統中用戶進程之間是如何通過這個內核模塊(Binder 驅動)來實現通信的呢?這就不得不提到 Linux 下的另一個概念:內存映射。內存映射通過 mmap() 來實現。內存映射簡單的講就是將用戶空間的內存區域映射到內核空間。映射關係建立後,用戶對這塊內存區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間。

Binder IPC通信過程如下:

十三 View滑動的幾種方式?

通過三種方式可以實現View的滑動:第一種是通過View本身提供的scrollTo/scrollBy方法來實現滑動;第二種是動畫中的一種,view動畫,也叫補間動畫,另外屬性動畫也可以;第三種是通過改變View的LayoutParams使得View重新佈局從而實現滑動。

scrollTo/scrollBy:操作簡單,適合對View內容的滑動;

 動畫:操作簡單,主要適用於沒有交互的View和實現複雜的動畫效果;

改變佈局參數:操作稍微複雜,適用於有交互的View。

彈性滑動的原理及實現?

它們都有一個共同思想:將一次大的滑動分成若干次小的滑動並在一個時間段內完成,彈性滑動的具體實現方式有很多,比如通過Scroller、Handler#postDelayed以及Thread#sleep等。

 

十四 View的事件分發機制?

點擊事件達到頂級View(一般是一個ViewGroup)以後,會調用ViewGroup的dispatchTouchEvent方法,然後的邏輯是這樣的:如果頂級ViewGroup攔截事件即onInterceptTouchEvent返回true,則事件由ViewGroup處理,這時如果ViewGroup的mOnTouchListener被設置,則onTouch會被調用,否則onTouchEvent會被調用。也就是說,如果都提供的話,onTouch會屏蔽掉onTouchEvent。在onTouchEvent中,如果設置了mOnClickListener,則onClick會被調用。如果頂級ViewGroup不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子View,這時子View的dispatchTouchEvent會被調用。到此爲止,事件已經從頂級View傳遞給了下一層View,接下來的傳遞過程和頂級View是一致的,如此循環,完成整個事件的分發。

處理滑動衝突的場景及解決方法?

如上面的滑動衝突場景,外部方向和內部方向不一致,先根據滑動距離判斷是水平滑動還是豎直滑動,然後有兩種攔截法,外部攔截法和內部攔截法。

外部攔截法:

所謂外部攔截法是指點擊事情都先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要此事件就不攔截,外部攔截法需要重寫父容器的onInterceptTouchEvent方法,在內部做相應的攔截即可。
 

public boolean onInterceptTouchEvent(MotionEvent event) {
            boolean intercepted = false;
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercepted = false;//這裏不能攔截,否則後續點擊事件都被攔截。
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (父容器需要當前點擊事件) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;//默認不攔截
                break;
            }
            default:
                break;
              }
              mLastXIntercept = x;
              mLastYIntercept = y;
              return intercepted;
  }

內部攔截法:

內部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交由父容器進行處理,這種方法和Android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起來較外部攔截法稍顯複雜。它的僞代碼如下,我們需要重寫子元素的dispatchTouchEvent方法:
 

public boolean dispatchTouchEvent(MotionEvent event) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                parent.requestDisallowInterceptTouchEvent(true);//父容器不允許攔截
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastX;
                    int deltaY = y - mLastY;
                    if (父容器需要此類點擊事件)) {
                        parent.requestDisallowInterceptTouchEvent(false);//父容器允許攔截
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    break;
                }
                default:
                    break;
                }

                mLastX = x;
                mLastY = y;
                return super.dispatchTouchEvent(event);
        }

除了子元素需要做處理以外,父元素也要默認攔截除了ACTION_DOWN以外的其他事件,這樣當子元素調用parent.requestDisal-lowInterceptTouchEvent(false)方法時,父元素才能繼續攔截所需的事件。
 

public boolean onInterceptTouchEvent(MotionEvent event) {
            int action = event.getAction();
            if (action == MotionEvent.ACTION_DOWN) {
                return false;
              } else {
                  return true;
              }
    }

 

十五 View的工作流程,measure過程、layout過程、draw過程?

MeasureSpec有三種模式:EXACTLY對應佈局文件中控件寬高的固定值或Match_Parent;AT_MOST對應佈局文件中控件寬高的Wrap_Content;UNSPECIFIED一般用於系統內部。子元素的MeasureSpec與父容器的MeasureSpec和子元素本身的LayoutParams有關,此外還和View的margin及padding有關。

當View採用固定寬/高,不管父容器的MeasureSpec是什麼,View的MeasureSpec都是精確模式。當View的寬/高是match_parent時,如果父容器是精準模式,那麼View也是精準模式並且其大小是父容器的剩餘空間;如果父容器是最大模式,那麼View也是最大模式並且其大小不會超過父容器的剩餘空間。當View的寬/高是wrap_content時,不管父容器的模式是精準還是最大化,View的模式總是最大化並且大小不能超過父容器的剩餘空間。

爲什麼獲取View的寬高會爲0?怎麼解決?

如果在Activity的回調裏面獲取View的寬高會爲0,比如在onCreate,onStart,onResume裏面是獲取不到view寬高的。原因是因爲View的繪製有ViewrootImp來完成的,而ViewrootImp是在onResume方法回調之後創建的,所以在onResume之前無法獲取。解決辦法是在onWindowFocusChanged回調裏面獲取,還可以在view.post(runnable)裏面和ViewTreeObserver的回調裏面獲取。

 

十六 自定義View?

直接繼承View重寫onDraw方法

這種方法主要用於實現一些不規則的效果,一般需要重寫onDraw方法。採用這種方式需要自己支持wrap_content,並且padding也需要自己處理。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
                  
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                  
	int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
                  
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
             
     int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
                  
	int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 
	//需要考慮wrap_content的情況                 
	if (widthSpecMode == MeasureSpec.AT_MOST
 && heightSpecMode ==
                  MeasureSpec.AT_MOST) {
                   
 setMeasuredDimension(200, 200);
                  
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
                    		 	
setMeasuredDimension(200, heightSpecSize);
                  
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
                    		
setMeasuredDimension(widthSpecSize, 200);
                  
}
              }

              
@Override
              
protected void onDraw(Canvas canvas) {
                  
	super.onDraw(canvas);
  
	//需要把padding考慮進來                
	final int paddingLeft = getPaddingLeft();
                  
	final int paddingRight = getPaddingLeft();
                  
	final int paddingTop = getPaddingLeft();
                  
	final int paddingBottom = getPaddingLeft();
                  
	int width = getWidth() - paddingLeft - paddingRight;
                  
	int height = getHeight() - paddingTop - paddingBottom;
                  
	int radius = Math.min(width, height) / 2;
                  
	canvas.drawCircle(paddingLeft + width / 2, 
paddingTop + height / 2,
   radius, mPaint);
              
}
          
}

如果直接繼承特定的View,比如TextView,則不需要考慮wrap_content,padding。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章