Android面試筆記

Bozo發表於2019-04-16

廣播

註冊方式:

1、靜態註冊 ,在Manifest檔案的application節點中配置廣播接收者

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

2、動態註冊,通過Context物件的registerReceiver方法註冊廣播

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

區別:靜態註冊的為常駐型廣播,即使應用程式關閉了,如果又資訊廣播來,程式也會被系統呼叫執行。而動態註冊的廣播不是常駐型,廣播被取消註冊或者應用程式關閉後都不能接收

廣播的兩種型別:

1、有序廣播:按照優先順序,一級一級向下傳遞,接收者可以修改廣播資料,也可以終止廣播事件。

2、無序廣播:所有接收者都會接收事件,不能被攔截跟修改。

服務

啟動

1、使用ContextstartService方法啟動

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

2、使用ContextbindService方法啟動

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

停止

1、在外部使用stopService方法,如果使用bindService的方式啟動,則使用unbindService方法停止

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

onStartCommand方法的返回值

1、START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand方法後,服務被異常kill掉,系統不會自動重啟該服務

2、START_STICKY:如果Service程式被kill掉,保留Service的狀態為開始狀態,但不保留遞送的intent物件。隨後系統會嘗試重新建立Service,由於服務狀態為開始狀態,所以建立服務後一定會呼叫onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到Service,那麼引數Intent將為null。

3、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內無法結束

相關文章