Android面試筆記

廣播

註冊方式:

一、靜態註冊 ,在Manifest文件的application節點中配置廣播接收者java

<receiver android:name=".MyBroadCastReceiver">  
	<!-- android:priority屬性是設置此接收者的優先級(從-1000到1000) -->
	<intent-filter android:priority="20">
	<actionandroid:name="android.provider.Telephony.SMS_RECEIVED"/>  
	</intent-filter>  
</receiver>  
複製代碼

二、動態註冊,經過Context對象的registerReceiver方法註冊廣播android

//new出上邊定義好的BroadcastReceiver
MyBroadCastReceiver yBroadCastReceiver = new MyBroadCastReceiver();
//實例化過濾器並設置要過濾的廣播 
IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
//註冊廣播 
myContext.registerReceiver(smsBroadCastReceiver,intentFilter, 
             "android.permission.RECEIVE_SMS", null);
複製代碼

區別:靜態註冊的爲常駐型廣播,即便應用程序關閉了,若是又信息廣播來,程序也會被系統調用執行。而動態註冊的廣播不是常駐型,廣播被取消註冊或者應用程序關閉後都不能接收安全

廣播的兩種類型:

一、有序廣播:按照優先級,一級一級向下傳遞,接收者能夠修改廣播數據,也能夠終止廣播事件。app

二、無序廣播:全部接收者都會接收事件,不能被攔截跟修改。ide

服務

啓動

一、使用ContextstartService方法啓動函數

onCreate()--->onStartCommand()--->onDestroy()oop

二、使用ContextbindService方法啓動佈局

onCreate()--->onBind()--->onUnBind()--->onDestroy()this

中止

一、在外部使用stopService方法,若是使用bindService的方式啓動,則使用unbindService方法中止spa

二、在Service內部(onStartCommand方法內)使用stopSelf

onStartCommand方法的返回值

一、START_NOT_STICKY:「非粘性的」。使用這個返回值時,若是在執行完onStartCommand方法後,服務被異常kill掉,系統不會自動重啓該服務

二、START_STICKY:若是Service進程被kill掉,保留Service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試從新建立Service,因爲服務狀態爲開始狀態,因此建立服務後必定會調用onStartCommand(Intent,int,int)方法。若是在此期間沒有任何啓動命令被傳遞到Service,那麼參數Intent將爲null。

三、START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,系統會自動重啓該服務,並將Intent的值傳入。

IntentService

繼承於Service,啓動方式與Service的傳統啓動方式同樣,不一樣點在於內部有一個線程來處理耗時操做,當任務執行完成時服務會自動中止。

Activity的啓動模式

  • standard:標準模式,默認的啓動模式,不論是否已經存在實例都會生成新的實例

  • singleTop:棧頂複用模式,若是發現有對應Activity的實例正位於棧頂,則直接打開此頁面,再也不生成新的實例,同時onNewIntent方法會被執行,onCreateonStart方法都不會執行。不然跟standard模式同樣繼續生成新的實例。

  • singleTask:站內複用模式,若是棧內存在對應Activity的實例就會複用這個Activity,複用時會將它上面的Activity所有出棧,同時onNewIntent方法也會被執行。

  • singleInstance:單例模式,該模式具有singleTask模式的全部特性外,與它的區別就是,這種模式下的Activity會單獨佔用一個Task棧,具備全局惟一性。以singleInstance模式啓動的Activity在整個系統中是單例的,若是在啓動這樣的Activiyt時,已經存在了一個實例,那麼會把它所在的任務調度到前臺,重用這個實例。

Activity的啓動過程

app啓動的過程有兩種狀況,第一種是從桌面launcher上點擊相應的應用圖標,第二種是在activity中經過調用startActivity來啓動一個新的activity。

  1. Luncher.startActivitySafely()
public final class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, AllAppsView.Watcher {
                        
	......
        
	void startActivitySafely(Intent intent, Object tag) {
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		try {
			startActivity(intent);
		} catch (ActivityNotFoundException e) {
			......
		} catch (SecurityException e) {
			......
		}
	}
                        
    ......
        
}
複製代碼
  1. Activity.startActivity
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks {
 
	......
 
	@Override
	public void startActivity(Intent intent) {
		startActivityForResult(intent, -1);
	}
 
	......
 
}
複製代碼
  1. Activity.startActivityForResult
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks {
 
	......
 
	public void startActivityForResult(Intent intent, int requestCode) {
		if (mParent == null) {
			Instrumentation.ActivityResult ar =
				mInstrumentation.execStartActivity(
				this, mMainThread.getApplicationThread(), mToken, this,
				intent, requestCode);
			......
		} else {
			......
		}
 
 
	......
 
}
複製代碼
  1. Instrumentation.execStartActivity
public class Instrumentation {
 
	......
 
	public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
		IApplicationThread whoThread = (IApplicationThread) contextThread;
		if (mActivityMonitors != null) {
			......
		}
		try {
			int result = ActivityManagerNative.getDefault()
				.startActivity(whoThread, intent,
				intent.resolveTypeIfNeeded(who.getContentResolver()),
				null, 0, token, target != null ? target.mEmbeddedID : null,
				requestCode, false, false);
			......
		} catch (RemoteException e) {
		}
		return null;
	}
 
	......
 
}
複製代碼

這裏的ActivityManagerNative.getDefault返回ActivityManagerService的遠程接口,即ActivityManagerProxy接口

  1. ActivityManagerProxy.startActivity
class ActivityManagerProxy implements IActivityManager {
 
	......
 
	public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) throws RemoteException {
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		data.writeInterfaceToken(IActivityManager.descriptor);
		data.writeStrongBinder(caller != null ? caller.asBinder() : null);
		intent.writeToParcel(data, 0);
		data.writeString(resolvedType);
		data.writeTypedArray(grantedUriPermissions, 0);
		data.writeInt(grantedMode);
		data.writeStrongBinder(resultTo);
		data.writeString(resultWho);
		data.writeInt(requestCode);
		data.writeInt(onlyIfNeeded ? 1 : 0);
		data.writeInt(debug ? 1 : 0);
		mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
		reply.readException();
		int result = reply.readInt();
		reply.recycle();
		data.recycle();
		return result;
	}
 
	......
 
}
複製代碼
  1. ActivityManagerService.startActivity

Context

Context是一個抽象基類,翻譯爲上下文,也能夠理解爲環境,提供一些程序運行基礎信息。

Context有兩個子類,ContextWrapper是上下文功能的封裝類,而 ContextImpl 則是上下文功能的實現類。而 ContextWrapper 又有三個直接的子類, ContextThemeWrapperServiceApplication。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity,因此Activity和Service以及Application的Context是不同的,只有Activity須要主題,Service不須要主題。Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各類承擔着不一樣的做用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的,所以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是能夠通用的。不過有幾種場景比較特殊,好比啓動Activity,還有彈出Dialog。出於安全緣由的考慮,Android是不容許Activity或Dialog憑空出現的,一個Activity的啓動必需要創建在另外一個Activity的基礎之上,也就是以此造成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),所以在這種場景下,咱們只能使用Activity類型的Context,不然將會出錯。

Activity、Window、View三者之間的關係

  1. Activity 構造的時候會初始化一個Window( PhoneWindw )
  2. PhoneWindow 有一個 RootView ,這個RootView 是一個ViewGroup,是最初始的根視圖
  3. RootView 經過 addView 方法來一個個添加 View

View的繪製流程

View的繪製流程:onMeasure -> onLayout -> onDraw

第一步:onMeasure 測量視圖大小,從頂層父View到子View遞歸調用 measure 方法,measure 方法又回調 onMeasure方法。

第二步:onLayout 肯定View位置,進行頁面佈局。從頂層父View向子View遞歸調用 layout 方法的過程,即父View根據上一步 measure 獲得的佈局大小和佈局參數,將子View放在合適的位置上。

第三步:onDraw 繪製視圖。主要步驟爲①:繪製背景,②:繪製本身,③:繪製子View,④:繪製滾動條

View、ViewGroup事件分發

ViewGroup 包含 dispatchTouchEventonInterceptTouchEventonTouchEvent三個相關方法,View包含 dispatchTouchEventonTouchEvent兩個相關方法。

  1. Activity 接收到Touch事件時,將遍歷子View進行Down事件分發,分發的目的是爲了找到真正處理本次完整觸摸事件的View,這個View會在 onTouchEvent 返回true。
  2. 當某個子View返回true時,就終止事件分發,並同時在ViewGroup中記錄該View,接下來的move事件跟up事件都由該子View直接進行處理。
  3. 當ViewGroup全部子View都不捕獲Down事件時,將觸發ViewGroup自身的 onTouchEvent 事件。觸發的方式是調用 super.dispatchTouchEvent函數,即調用父View的dispatchTouchEvent方法。

Handler實現原理

Android的主線程不能進行耗時操做,子線程不能進行更新UI,因此就有了Handler,它的做用就是實現線程之間的通訊。

Handler整個流程中主要有四個對象:HandlerMessageMessageQueueLooper。經過將要傳遞的消息放在Message中,Handler經過 sendMessage 方法將消息放入 MessageQueue 中,Looper 對象會不斷的調用loop() 方法不斷從 MessageQueue 中取出 Message 交給 Handler進行處理。

Android內存泄露

  1. 內存泄漏跟內存溢出的區別:

    • 內存泄漏:指程序在申請內存後,沒法釋放已經申請的內存空間
    • 內存溢出:指程序在申請內存時,沒有足夠的內存空間供其使用
  2. 內存泄漏的緣由:

    • Handler引發的內存泄漏:

      將Handler聲明爲靜態內部類,就不會持有外部類的引用,其生命週期就跟外部類無關。若是Handler內部要使用Context,則可使用弱引用的方式。

    • 單例模式引發的內存泄漏:

      Context是ApplicationCotnext,ApplicationCotnext的生命週期與app一致,不會致使內存泄漏.

    • 非靜態內部類建立實例引發的:

      建立爲靜態實例

    • 非靜態匿名內部類引發的:

      將匿名內部類修改成靜態的

    • 註冊/反註冊未成對使用引發的內存泄漏

      註冊廣播接受器、EventBus等,記得解綁

    • 資源對象沒有關閉引發的內存泄漏

      在這些資源不使用的時候,記得調用相應的相似close()、destroy()、recycler()、release()等方法釋放

    • 集合對象沒有及時清理引發的內存泄漏

      一般會把一些對象裝入到集合中,當不使用的時候必定要記得及時清理集合,讓相關對象再也不被引用

  3. 內存泄漏檢測:LeakCanary

ANR

ANR全名"Application not responding",即應用無響應。產生的緣由:

  • 5s內沒法響應用戶輸入事件
  • 廣播在10s內沒法結束
  • Service在20s內沒法結束
相關文章
相關標籤/搜索