1. 程式人生 > >android常見面試題與我自己的回答 (二)

android常見面試題與我自己的回答 (二)

1,android:process

解決訪問SharedPreferences,不在同一程序

private SharedPreferencesDB(Context cxt) {
		this.context = cxt;
		Context context;
		try {
			context = cxt.createPackageContext(cxt.getPackageName(), Context.CONTEXT_IGNORE_SECURITY);
			prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);//可讀可寫
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}

	}

如果該屬性指定名稱以“:”開頭,則一個新的專屬於該應用的程序將會被建立。

如果該程序名以小寫字母開頭,則為該activity提供許可權以讓其在一個全域性的程序中執行。這樣會允許多個應用的不同元件共用一個程序,以便節省資源。

2,applicationContext與context的區別

ApplicationContext,它的生命週期和我們的單例物件一致。

Context和Application Context的區別是很大的,也就是說,他們的應用場景(你也可以認為是能力)是不同的,並非所有Activity為Context的場景,Application Context都能搞定。

大家注意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是為什麼說是NO呢?下面一個一個解釋:

數字1:啟動Activity在這些類中是可以的,但是需要建立一個新的task。一般情況不推薦。

數字2:在這些類中去layout inflate是合法的,但是會使用系統預設的主題樣式,如果你自定義了某些樣式可能不會被使用。

數字3:在receiver為null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)

注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用於使用。

和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。

3、notifyDataSetChanged與notifyDataSetInvalidated

notifyDataSetInvalidated:更新資料並把列表元件移到頂部

notifyDataSetChanged:容易使列表閃動

notifyDataSetInvalidated(),會重繪控制元件(還原到初始狀態)
notifyDataSetChanged(),重繪當前可見區域

public void notifyDataSetChanged ():該方法內部實現了在每個觀察者上面呼叫onChanged事件。每當發現數據集有改變的情況,或者讀取到資料的新狀態時,就會呼叫此方法。
 
public void notifyDataSetInvalidated ():該方法內部實現了在每個觀察者上面呼叫onInvalidated事件。每當發現數據集監控有改變的情況,比如該資料集不再有效,就會呼叫此方法。

注:必須在UI主執行緒中呼叫執行

4,android4.0前與後對於flash的處理

4.0需要硬體加速

mainfest.xml設定android:hardwareAccelerated="true"

5,防止睡眠

<uses-permission android:name="android.permission.WAKE_LOCK" />

private void acquireWakeLock() {
		if (wakeLock == null) {
			PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
			wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, this.getClass().getCanonicalName());
			wakeLock.acquire();
		}

	}

private void releaseWakeLock() {
		if (wakeLock != null && wakeLock.isHeld()) {
			wakeLock.release();
			wakeLock = null;
		}

	}

6,電話狀態監聽

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(new MobliePhoneStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
private class MobliePhoneStateListener extends PhoneStateListener {

		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			if (currentMusic == null)
				return;
			MediaPlayer mPlayer = playerMap.get(currentMusic.getPlayUrl());
			switch (state) {
			case TelephonyManager.CALL_STATE_IDLE: /* 無任何狀態時 */
				break;
			case TelephonyManager.CALL_STATE_OFFHOOK: /* 接起電話時 */
				break;
			case TelephonyManager.CALL_STATE_RINGING: /* 電話進來時 */
				break;
			default:
				break;

			}

		}

	}


7,監聽耳機插拔
IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
		this.registerReceiver(new AudioBroadCastReceiver(), filter);

public class AudioBroadCastReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {/*耳機被拔出*/
				
			}
		}
	}

8,監控apk安裝

<receiver
            android:enabled="true"
            android:name="com.mobcent.ad.android.ui.activity.receiver.AdBootReceiver" >
            <intent-filter >
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <data android:scheme="package" />
            </intent-filter>
        </receiver>

@Override
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();
		String packageName = intent.getData().getSchemeSpecificPart();
		adService = new AdServiceImpl(context);
		if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {//新增
			
		}else if(Intent.ACTION_PACKAGE_REMOVED.equals(action)){//移除
			
		}else if(Intent.ACTION_PACKAGE_INSTALL.equals(action)){//安裝
			
		}

	}

9,Android本身的api並未宣告會丟擲異常,則其在執行時有無可能丟擲runtime異常,你遇到過嗎?諾有的話會導致什麼問題?如何解決?

會,比如nullpointerException。我遇到過,比如textview.setText()時,textview沒有初始化。會導致程式無法正常執行出現forceclose。開啟控制檯檢視logcat資訊找出異常資訊並修改程式。

10,android系統會自動派發各種事件,事件觸發到對應的派發順序是

答案1:EventHub-keyInputQueue-windowManagerService-ViewRoot

答案2:windowsManager-phoneWindow-activity\

11,lauchmode及應用場景

standard,建立一個新的Activity。 singleTop,棧頂不是該型別的Activity,建立一個新的Activity。否則,onNewIntent。
singleTask,回退棧中沒有該型別的Activity,建立Activity,否則,onNewIntent+ClearTop。
singleInstance,回退棧中,只有這一個Activity,沒有其他Activity。 singleTop適合接收通知啟動的內容顯示頁面。 例如,某個新聞客戶端的新聞內容頁面,如果收到10個新聞推送,每次都開啟一個新聞內容頁面是很煩人的。 singleTask適合作為程式入口點。 例如瀏覽器的主介面。不管從多少個應用啟動瀏覽器,只會啟動主介面一次,其餘情況都會走onNewIntent,並且會清空主介面上面的其他頁面。 singleInstance適合需要與程式分離開的頁面。 例如鬧鈴提醒,將鬧鈴提醒與鬧鈴設定分離。 singleInstance不要用於中間頁面,如果用於中間頁面,跳轉會有問題,比如:A -> B (singleInstance) -> C,完全退出後,在此啟動,首先開啟的是B。

12,談談對android的理解

android應用開發一般來說由四大塊構成
activity
intent
provider
broadcastreciver
從這種結構上來看,android系統是提供了從顯示層到資料層到訊息機制的一整套的應用開發方案,而且是一種比較先進的解決方案。
從寫android程式碼的過程中,我感覺到android project是一種典型的MVC結構,非常類似於主要用於WEB開發的J2EE架構,xml佈局檔案是view相當於JSP頁面;activity和intent起到了controller的作用;provider對資料層做了良好的封裝,而且provider把資料管理的範疇從資料庫泛化到了資料的概念,不光管理資料記錄,只要是資料檔案(圖片、視訊、聲音檔案、所有其他的一切的file)都納入管理,且提供了資料共享的機制,這是比較出彩的地方;broadcastreceiver提供了一種良好的訊息機制,使得一個應用不再是一個資訊孤島,而是和其他的應用、服務等構成了資訊網路,從而極大的豐富了應用的開發空間,給了應用開發者極大的想象創造的可能。

13,如何保證service在後臺不被kill

擁有service的程序具有較高的優先順序

官方文件告訴我們,Android系統會盡量保持擁有service的程序執行,只要在該service已經被啟動(start)或者客戶端連線(bindService)到它。當記憶體不足時,需要保持,擁有service的程序具有較高的優先順序。

1. 如果service正在呼叫onCreate,onStartCommand或者onDestory方法,那麼用於當前service的程序則變為前臺程序以避免被killed。
2. 如果當前service已經被啟動(start),擁有它的程序則比那些使用者可見的程序優先順序低一些,但是比那些不可見的程序更重要,這就意味著service一般不會被killed.
3. 如果客戶端已經連線到service (bindService),那麼擁有Service的程序則擁有最高的優先順序,可以認為service是可見的。
4. 如果service可以使用startForeground(int, Notification)方法來將service設定為前臺狀態,那麼系統就認為是對使用者可見的,並不會在記憶體不足時killed。
5. 如果有其他的應用元件作為Service,Activity等執行在相同的程序中,那麼將會增加該程序的重要性。


onStartCommand方法,返回START_STICKY

StartCommond幾個常量引數簡介:

1、START_STICKY

在執行onStartCommand後service程序被kill後,那將保留在開始狀態,但是不保留那些傳入的intent。不久後service就會再次嘗試重新建立,因為保留在開始狀態,在建立     service後將保證呼叫onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent。

2、START_NOT_STICKY

在執行onStartCommand後service程序被kill後,並且沒有新的intent傳遞給它。Service將移出開始狀態,並且直到新的明顯的方法(startService)呼叫才重新建立。因為如果沒有傳遞任何未決定的intent那麼service是不會啟動,也就是期間onstartCommand不會接收到任何null的intent。

3、START_REDELIVER_INTENT

在執行onStartCommand後service程序被kill後,系統將會再次啟動service,並傳入最後一個intent給onstartCommand。直到呼叫stopSelf(int)才停止傳遞intent。如果在被kill後還有未處理好的intent,那被kill後服務還是會自動啟動。因此onstartCommand不會接收到任何null的intent
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		flags = START_STICKY;
		return super.onStartCommand(intent, flags, startId);
	}
【結論】 手動返回START_STICKY,親測當service因記憶體不足被kill,當記憶體又有的時候,service又被重新建立,比較不錯,但是不能保證任何情況下都被重建,比如程序被幹掉了....

提升service優先順序

在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority = "1000"這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時適用於廣播。

        <service
            android:name="com.dbjtech.acbxt.waiqin.UploadService"
            android:enabled="true" >
            <intent-filter android:priority="1000" >
                <action android:name="com.dbjtech.myservice" />
            </intent-filter>
        </service>

【結論】目前看來,priority這個屬性貌似只適用於broadcast,對於Service來說可能無效

提升service程序優先順序

Android中的程序是託管的,當系統程序空間緊張的時候,會依照優先順序自動進行程序的回收。Android將程序分為6個等級,它們按優先順序順序由高到低依次是:

   1.前臺程序( FOREGROUND_APP)
   2.可視程序(VISIBLE_APP )
   3. 次要服務程序(SECONDARY_SERVER )
   4.後臺程序 (HIDDEN_APP)
   5.內容供應節點(CONTENT_PROVIDER)
   6.空程序(EMPTY_APP)

當service執行在低記憶體的環境時,將會kill掉一些存在的程序。因此程序的優先順序將會很重要,可以使用startForeground將service放到前臺狀態。這樣在低記憶體時被kill的機率會低一些。

在onStartCommand方法內新增如下程式碼:

		 Notification notification = new Notification(R.drawable.ic_launcher,
		 getString(R.string.app_name), System.currentTimeMillis());
		
		 PendingIntent pendingintent = PendingIntent.getActivity(this, 0,
		 new Intent(this, AppMain.class), 0);
		 notification.setLatestEventInfo(this, "uploadservice", "請保持程式在後臺執行",
		 pendingintent);
		<span style="color:#ff0000;"> startForeground(0x111, notification);</span>

注意在onDestroy裡還需要stopForeground(true)

【結論】如果在極度極度低記憶體的壓力下,該service還是會被kill掉,並且不一定會restart

onDestroy方法裡重啟service

service +broadcast  方式,就是當service走ondestory的時候,傳送一個自定義的廣播,當收到廣播的時候,重新啟動service;

【結論】當使用類似口口管家等第三方應用或是在setting裡-應用-強制停止時,APP程序可能就直接被幹掉了,onDestroy方法都進不來,所以還是無法保證~.~

Application加上Persistent屬性

看Android的文件知道,當程序長期不活動,或系統需要資源時,會自動清理門戶,殺死一些Service,和不可見的Activity等所在的程序。但是如果某個程序不想被殺死(如資料快取程序,或狀態監控程序,或遠端服務程序),可以這麼做:

    <application
        android:name="com.test.Application"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
       <span style="color:#ff0000;"> android:persistent="true"</span>
        android:theme="@style/AppTheme" >
    </application>

【結論】據說這個屬性不能亂設定,不過設定後,的確發現優先順序提高不少,或許是相當於系統級的程序,但是還是無法保證存活

監聽系統廣播判斷Service狀態

通過系統的一些廣播,比如:手機重啟、介面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的Service是否還存活,別忘記加許可權啊。

 <receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.intent.action.PACKAGE_RESTARTED" />
                <action android:name="com.dbjtech.waiqin.destroy" />
            </intent-filter>
        </receiver>
BroadcastReceiver中:      
	@Override
	public void onReceive(Context context, Intent intent) {
		if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
			System.out.println("手機開機了....");
			startUploadService(context);
		}
		if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
				startUploadService(context);
		}
	}
【結論】這也能算是一種措施,不過感覺監聽多了會導致Service很混亂,帶來諸多不便

14,Broadcast生命週期

Broadcast的生命週期只有一個回撥方法:void onReceive(Context curContext,Intent broadcastMsg)。當broadcast訊息到達接收者時,Android會呼叫他的onReceive()方法,並且傳遞包含這個資訊的intent物件。broadcast接收者在執行這個方法時,被認為是活動的。當onReceive()方法返回時,它停止的活動狀態。

一個活動的廣播接受者程序是不能被殺死的,但是當他所消耗的記憶體被別的程序需要時,一個非活動狀態的程序可以被系統隨時殺死。 這帶來一個問題,相應一個廣播訊息是非常耗時的,因此,很多事情需要在一個獨立的執行緒中執行,而不是在主執行緒裡。如果onReceive()方法啟動一個執行緒,那麼整個程序包括剛啟動的新執行緒,是非活動狀態的,(除非程序裡其他應用程式元件有活動的),所以有被系統銷燬的危險。這個問題的解決方法是在onReceive()方法裡啟動一個服務然後處理一些事情,所以系統會知道在這個程序裡仍然有處於活動狀態的任務需要被處理。

Android作業系統嘗試儘可能長時間的保持應用的程序,但當可用記憶體很低時最終要移走一部分程序。怎樣確定那些程式可以執行,那些要被銷燬,Android讓每一個程序在一個重要級的基礎上執行,重要級低的程序最有可能被淘汰,一共有五級,下面這個列表就是按照重要性排列的: l        第一級 前臺程序顯示的是使用者此時需要處理和顯示的。下列的條件有任何一個成立,這個程序都被認為是在前臺執行的。 與使用者正發生互動的。 它控制一個與使用者互動的必須的基本的服務。 有一個正在呼叫生命週期的回撥函式的Service(如onCreate()、onStar()、onDestroy()) 它有一個正在執行onReceive()方法的廣播接收物件。 只有少數的前臺程序可以在任何給定的時間內執行,銷燬他們是系統萬不得已的、最後的選擇——當記憶體不夠系統繼續執行下去時。通常,在這一點上,裝置已經達到了記憶體分頁狀態,所以殺掉一些前臺程序來保證能夠響應使用者的需求。 l        第二級 一個可用的程序沒有任何前臺元件,但它仍然可以影響到使用者的介面。下面兩種情況發生時,可以稱該程序為可用程序。 它是一個非前臺的activity,但對使用者仍然可用,(onPause()方法已經被呼叫)。這是可能發生的,例如:前臺的activity是一個允許上一個activity可見的對話方塊,即當前activity半透明,能看到前一個activity的介面。 它是一個服務於可用activity的服務。 l        第三級 一個服務程序是一個通過呼叫startService()方法啟動的服務,並且不屬於前兩種情況。儘管服務程序沒有直接被使用者看到,但他們確實是使用者所關心的,比如後臺播放音樂或網路下載資料。所以系統保證他們的執行,直到不能保證所有的前臺可見程式都正常執行時才會終止他們。 l        第四級 一個後臺程序就是一個非當前正在執行的activity(activity的onStop()方法已經被呼叫),他們不會對使用者體驗造成直接的影響,當沒有足夠記憶體來執行前臺可見程式時,他們將會被終止。通常,後臺程序會有很多個在執行,所以他們維護一個LRU最近使用程式列表來保證經常執行的activity能最後一個被終止。如果一個activity正確的實現了生命週期的方法,並且儲存它當前狀態,殺死這些程序將不會影響到使用者體驗。 l        第五級 一個空執行緒沒有執行任何可用應用程式組,保留他們的唯一原因是為了設立一個快取機制,來加快元件啟動的時間。系統經常殺死這些記憶體來平衡系統的整個系統的資源,程序快取和基本核心快取之間的資源。 Android把程序裡優先順序最高的activity或服務,作為這個程序的優先順序。例如,一個程序擁有一個服務和一個可見的activity,那麼這個程序將會被定義為可見程序,而不是服務程序。 此外,如果別的程序依賴某一個程序的話,那麼被依賴的程序會提高優先順序。一個程序服務於另一個程序,那麼提供服務的程序不會低於獲得服務的程序。例如,如果程序A的一個內容提供商服務於程序B的一個客戶端,或者程序A的一個Service被程序B的一個元件繫結,那麼程序A至少擁有和程序B一樣的優先順序,或者更高。 操作來啟動一個服務,而不是啟動一個執行緒--尤其是這個操作可能會拖垮這個activity。例如後臺播放音樂的同時,通過照相機向伺服器傳送一張照片。啟動一個服務會保證這個操作至少執行在service 程序的優先順序下,無論這個activity發生了什麼,廣播接收者應該作為一個空服務而不是簡單的把耗時的操作單獨放在一個執行緒裡。

15,Invalidate和postInvalidate的區別 

android中實現view的更新有兩組方法,一組是invalidate,另一組是postInvalidate,其中前者是在UI執行緒自身中使用,而後者在非UI執行緒中使用。 
    Android提供了Invalidate方法實現介面重新整理,但是Invalidate不能直接線上程中呼叫,因為他是違背了單執行緒模型:Android UI操作並不是執行緒安全的,並且這些操作必須在UI執行緒中呼叫。 
Android程式中可以使用的介面重新整理方法有兩種,分別是利用Handler和利用postInvalidate()來實現線上程中重新整理介面。

16,setContentView的原理

首先初始化mDecor,即DecorView為FrameLayout的子類。就是我們整個視窗的根檢視了。

然後,根據theme中的屬性值,選擇合適的佈局,通過infalter.inflater放入到我們的mDecor中。

在這些佈局中,一般會包含ActionBar,Title,和一個id為content的FrameLayout。

最後,我們在Activity中設定的佈局,會通過infalter.inflater壓入到我們的id為content的FrameLayout中去。

17,ViewGroup事件分發

MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent 

在View上觸發事件,最先捕獲到事件的為View所在的ViewGroup,然後才會到View自身

26行:進行判斷:if(disallowIntercept || !onInterceptTouchEvent(ev))

兩種可能會進入IF程式碼段

1、當前不允許攔截,即disallowIntercept =true,

2、當前允許攔截但是不攔截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;

注:disallowIntercept 可以通過viewGroup.requestDisallowInterceptTouchEvent(boolean);進行設定,後面會詳細說;而onInterceptTouchEvent(ev)可以進行復寫。

41行:進行判斷當前的x,y座標是否落在子View身上,如果在,47行,執行child.dispatchTouchEvent(ev),就進入了View的dispatchTouchEvent程式碼中了

正常情況下,即我們上例整個程式碼的流程我們已經走完了:

1、ACTION_DOWN中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則找到包含當前x,y座標的子View,賦值給mMotionTarget,然後呼叫mMotionTarget.dispatchTouchEvent

2、ACTION_MOVE中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則直接呼叫mMotionTarget.dispatchTouchEvent(ev)

3、ACTION_UP中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則直接呼叫mMotionTarget.dispatchTouchEvent(ev)

當然了在分發之前都會修改下座標系統,把當前的x,y分別減去child.left 和 child.top ,然後傳給child;

1、如果ViewGroup找到了能夠處理該事件的View,則直接交給子View處理,自己的onTouchEvent不會被觸發;

2、可以通過複寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給自己處理,則會執行自己對應的onTouchEvent方法

3、子View可以通過呼叫getParent().requestDisallowInterceptTouchEvent(true);  阻止ViewGroup對其MOVE或者UP事件進行攔截;

18,View事件分發機制

不管是DOWN,MOVE,UP都會按照下面的順序執行:

1、dispatchTouchEvent

2、 setOnTouchListener的onTouch

3、onTouchEvent

直接看13行:首先判斷mOnTouchListener不為null,並且view是enable的狀態,然後 mOnTouchListener.onTouch(this, event)返回true,這三個條件如果都滿足,直接return true ; 也就是下面的onTouchEvent(event)不會被執行了;

1、整個View的事件轉發流程是:

View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent

在dispatchTouchEvent中會進行OnTouchListener的判斷,如果OnTouchListener不為null且返回true,則表示事件被消費,onTouchEvent不會被執行;否則執行onTouchEvent。

2、onTouchEvent中的DOWN,MOVE,UP

DOWN時:

a、首先設定標誌為PREPRESSED,設定mHasPerformedLongPress=false ;然後發出一個115ms後的mPendingCheckForTap;

b、如果115ms內沒有觸發UP,則將標誌置為PRESSED,清除PREPRESSED標誌,同時發出一個延時為500-115ms的,檢測長按任務訊息;

c、如果500ms內(從DOWN觸發開始算),則會觸發LongClickListener:

此時如果LongClickListener不為null,則會執行回撥,同時如果LongClickListener.onClick返回true,才把mHasPerformedLongPress設定為true;否則mHasPerformedLongPress依然為false;

MOVE時:

主要就是檢測使用者是否劃出控制元件,如果劃出了:

115ms內,直接移除mPendingCheckForTap;

115ms後,則將標誌中的PRESSED去除,同時移除長按的檢查:removeLongPressCallback();

UP時:

a、如果115ms內,觸發UP,此時標誌為PREPRESSED,則執行UnsetPressedState,setPressed(false);會把setPress轉發下去,可以在View中複寫dispatchSetPressed方法接收;

b、如果是115ms-500ms間,即長按還未發生,則首先移除長按檢測,執行onClick回撥;

c、如果是500ms以後,那麼有兩種情況:

i.設定了onLongClickListener,且onLongClickListener.onClick返回true,則點選事件OnClick事件無法觸發;

ii.沒有設定onLongClickListener或者onLongClickListener.onClick返回false,則點選事件OnClick事件依然可以觸發;

d、最後執行mUnsetPressedState.run(),將setPressed傳遞下去,然後將PRESSED標識去除;

19,AsyncTask原理

18行:設定當前AsyncTask的狀態為RUNNING,上面的switch也可以看出,每個非同步任務在完成前只能執行一次。
20行:執行了onPreExecute(),當前依然在UI執行緒,所以我們可以在其中做一些準備工作。
22行:將我們傳入的引數賦值給了mWorker.mParams ,mWorker為一個Callable的子類,且在內部的call()方法中,呼叫了doInBackground(mParams),然後得到的返回值作為postResult的引數進行執行;postResult中通過sHandler傳送訊息,最終sHandler的handleMessage中完成onPostExecute的呼叫。
23行:exec.execute(mFuture),mFuture為真正的執行任務的單元,將mWorker進行封裝,然後由sDefaultExecutor交給執行緒池進行執行。

20,LayoutInflate原理

Inflate(resId , null ) 只建立temp ,返回temp

Inflate(resId , parent, false )建立temp,然後執行temp.setLayoutParams(params);返回temp

Inflate(resId , parent, true ) 建立temp,然後執行root.addView(temp, params);最後返回root

由上面已經能夠解釋:

Inflate(resId , null )不能正確處理寬和高是因為:layout_width,layout_height是相對了父級設定的,必須與父級的LayoutParams一致。而此temp的getLayoutParams為null

Inflate(resId , parent,false ) 可以正確處理,因為temp.setLayoutParams(params);這個params正是root.generateLayoutParams(attrs);得到的。

Inflate(resId , parent,true )不僅能夠正確的處理,而且已經把resId這個view加入到了parent,並且返回的是parent,和以上兩者返回值有絕對的區別

21,說說View的重新整理機制

在Android的佈局體系中,父View負責重新整理、佈局顯示子View;而當子View需要重新整理時,則是通知父View來完成。

子View呼叫invalidate時,首先找到自己父View(View的成員變數mParent記錄自己的父View),然後將AttachInfo中儲存的資訊告訴父View重新整理自己。

 View的父子關係的建立分為兩種情況:

1) View加入ViewGroup中

2)DecorView註冊給WindowManagerImpl時,產生一個ViewRoot作為其父View

AttachInfo是在View第一次attach到Window時,ViewRoot傳給自己的子View的。這個AttachInfo之後,會順著佈局體系一直傳遞到最底層的View。

並且在新的View被加入ViewGroup時,也會將該AttachInfo傳給加入的View

在invalidate中,呼叫父View的invalidateChild,這是一個從底向上回溯的過程,每一層的父View都將自己的顯示區域與傳入的重新整理Rect做交集

這個向上回溯的過程直到ViewRoot那裡結束,由ViewRoot對這個最終的重新整理區域做重新整理scheduleTraversals();

在Android的佈局體系中,父View負責重新整理、佈局顯示子View;而當子View需要重新整理時,則是通知父View來完成。這種處理邏輯在View的程式碼中明確的表現出來:

public void invalidate() {
    final ViewParent p = mParent;
    final AttachInfo ai = mAttachInfo;
    if (p != null && ai != null) {
        final Rect r = ai.mTmpInvalRect;

        // 設定重新整理區域為自己的尺寸 
        r.set(0, 0, mRight - mLeft, mBottom - mTop);
        p.invalidateChild(this, r);
    }
}

子View呼叫invalidate時,首先找到自己父View(View的成員變數mParent記錄自己的父View),然後將AttachInfo中儲存的資訊告訴父View重新整理自己。

View的父子關係的建立分為兩種情況:

1) View加入ViewGroup中

private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) {

        .....

            // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }

       .....

}

2)DecorView註冊給WindowManagerImpl時,產生一個ViewRoot作為其父View。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView){

    .....

    view.assignParent(this);

    ....

}

AttachInfo是在View第一次attach到Window時,ViewRoot傳給自己的子View的。這個AttachInfo之後,會順著佈局體系一直傳遞到最底層的View。

View.java

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;

    .....
}

ViewGroup.java

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    super.dispatchAttachedToWindow(info, visibility);

    for (int i = 0; i < count; i++) {
        children[i].dispatchAttachedToWindow(info, visibility);
    }
}

並且在新的View被加入ViewGroup時,也會將該AttachInfo傳給加入的View

ViewGroup.java

private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) {

    child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));

}

到這裡明白了mParent與AttachInfo代表的意義,可以繼續重新整理過程的分析。

在invalidate中,呼叫父View的invalidateChild,這是一個從第向上回溯的過程,每一層的父View都將自己的顯示區域與傳入的重新整理Rect做交集。

public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;

    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        final int[] location = attachInfo.mInvalidateChildLocation;

        // 需要重新整理的子View的位置 
        location[CHILD_LEFT_INDEX] = child.mLeft;
        location[CHILD_TOP_INDEX] = child.mTop;

        // If the child is drawing an animation, we want to copy this flag onto
        // ourselves and the parent to make sure the invalidate request goes through
        final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;

        // Check whether the child that requests the invalidate is fully opaque
        final boolean isOpaque = child.isOpaque() && !drawAnimation && child.getAnimation() != null;

        // Mark the child as dirty, using the appropriate flag
        // Make sure we do not set both flags at the same time
        final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;

        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }

            if (drawAnimation) {
                if (view != null) {
                        view.mPrivateFlags |= DRAW_ANIMATION;
                } else if (parent instanceof ViewRoot) {
                        ((ViewRoot) parent).mIsAnimating = true;
                }
            }

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
            if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
                view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
            }

            parent = parent.invalidateChildInParent(location, dirty);
        } while (parent != null);
    }
}

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & DRAWN) == DRAWN) {
        if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                        FLAG_OPTIMIZE_INVALIDATE) {

            // 根據父View的位置,偏移重新整理區域 
            dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY);

            final int left = mLeft;
            final int top = mTop;

            //計算實際可重新整理區域 
            if (dirty.intersect(0, 0, mRight - left, mBottom - top) ||
                        (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
                mPrivateFlags &= ~DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
                return mParent;
            }
        } else {
            mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;

            location[CHILD_LEFT_INDEX] = mLeft;
            location[CHILD_TOP_INDEX] = mTop;

           dirty.set(0, 0, mRight - location[CHILD_LEFT_INDEX],
                        mBottom - location[CHILD_TOP_INDEX]);

                return mParent;
            }
        }

        return null;
}

這個向上回溯的過程直到ViewRoot那裡結束,由ViewRoot對這個最終的重新整理區域做重新整理。

ViewRoot.java

public void invalidateChild(View child, Rect dirty) {

    scheduleTraversals();

}


22,ClassLoader委託機制

Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的關係如下:

Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。

但是這並不是繼承關係,只是語義上的定義,基本上,每一個ClassLoader實現,都有一個Parent ClassLoader。

 可以通過ClassLoader的getParent方法得到當前ClassLoader的parent。Bootstrap ClassLoader比較特殊,因為它不是java class所以Extension ClassLoader的getParent方法返回的是NULL。

由於classloader 載入類用的是全盤負責委託機制。所謂全盤負責,即是當一個classloader載入一個Class的時候,這個Class所依賴的和引用的所有 Class也由這個classloader負責載入,除非是顯式的使用另外一個classloader載入。

23,Suface與SurfaceView

簡單地說Surface對應了一塊螢幕緩衝區,每個window對應一個Surface,任何View都是畫在Surface上的,傳統的view共享一塊螢幕緩衝區,所有的繪製必須在UI執行緒中進行

什麼是SurfaceView? 說SurfaceView是一個View也許不夠嚴謹,然而從定義中 public class SurfaceView extends View {...}顯示SurfaceView確實是派生自View,但是SurfaceView卻有著自己的Surface 每個SurfaceView建立的時候都會建立一個MyWindow,new MyWindow(this)中的this正是SurfaceView自身,因此將SurfaceView和window繫結在一起,而前面提到過每個window對應一個Surface,所以SurfaceView也就內嵌了一個自己的Surface,可以認為SurfaceView是來控制Surface的位置和尺寸。大家都知道,傳統View及其派生類的更新只能在UI執行緒,然而UI執行緒還同時處理其他互動邏輯,這就無法保證view更新的速度和幀率了,而SurfaceView可以用獨立的執行緒來進行繪製,因此可以提供更高的幀率,例如遊戲,攝像頭取景等場景就比較適合用SurfaceView來實現。

什麼是SurfaceHolder.Callback?
SurfaceHolder.Callback主要是當底層的Surface被建立、銷燬或者改變時提供回撥通知,由於繪製必須在surface被建立後才能進行,因此SurfaceHolder.Callback中的surfaceCreated 和surfaceDestroyed 就成了繪圖處理程式碼的邊界。SurfaceHolder,可以把它當成Surface的容器和控制器,用來操縱Surface。處理它的Canvas上畫的效果和動畫,控制表面,大小,畫素等。

24,Invalidate()原理

invalidate()函式的主要作用是請求View樹進行重繪,該函式可以由應用程式呼叫,或者由系統函式間接呼叫,例如setEnable(), setSelected(), setVisiblity()都會間接呼叫到invalidate()來請求View樹重繪,更新View樹的顯示。

注:requestLayout()和requestFocus()函式也會引起檢視重繪

下面我們來具體進行分析invalidate(true)函式的執行流程:

            1、首先呼叫skipInvalidate(),該函式主要判斷該View是否不需要重繪,如果不許要重繪則直接返回,不需要重繪的條件是該View不可見並且未進行動畫

            2、接下來的if語句是來進一步判斷View是否需要繪製,其中表達式 (mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)的意思指的是如果View需要重繪並且其大小不為0,其餘幾個本人也未完全理解,還望高手指點~~如果需要重繪,則處理相關標誌位

            3、對於開啟硬體加速的應用程式,則呼叫父檢視的invalidateChild函式繪製整個區域,否則只繪製dirty區域(r變數所指的區域),這是一個向上回溯的過程,每一層的父View都將自己的顯示區域與傳入的重新整理Rect做交集。

接下來看invalidateChild()的 實現過程:

大概流程如下,我們主要關注dirty區域不是null(非硬體加速)的情況:

            1、判斷子檢視是否是不透明的(不透明的條件是isOpaque()返回true,檢視未進行動畫以及child.getAnimation() == null),並將判斷結果儲存到變數isOpaque中,如果不透明則將變數opaqueFlag設定為DIRTY_OPAQUE,否則設定為DIRTY。

            2、定義location儲存子檢視的左上角座標

            3、如果子檢視正在動畫,那麼父檢視也要新增動畫標誌,如果父檢視是ViewGroup,那麼給mPrivateFlags新增DRAW_ANIMATION標識,如果父檢視是ViewRoot,則給其內部變數mIsAnimating賦值為true

            4、設定dirty標識,如果子檢視是不透明的,則父檢視設定為DIRTY_OPAQUE,否則設定為DIRTY

            5、呼叫parent.invalidateChildInparent(),這裡的parent有可能是ViewGroup,也有可能是ViewRoot(最後一次while迴圈),首先來看ViewGroup, ViewGroup中該函式的主要作用是對dirty區域進行計算

      以上過程的主體是一個do{}while{}迴圈,不斷的將子檢視的dirty區域與父檢視做運算來確定最終要重繪的dirty區域,最終迴圈到ViewRoot(ViewRoot的parent為null)為止,並將dirty區域儲存到ViewRoot的mDirty變數中

具體分析如下:            1、判斷此次呼叫是否在UI執行緒中進行

            2、將dirty的座標位置轉換為ViewRoot的螢幕顯示區域

            3、更新mDirty變數,並呼叫scheduleTraversals發起重繪請求

      至此一次invalidate()就結束了

      總結:invalidate主要給需要重繪的檢視新增DIRTY標記,並通過和父檢視的矩形運算求得真正需要繪製的區域,並儲存在ViewRoot中的mDirty變數中,最後呼叫scheduleTraversals發起重繪請求,scheduleTraversals會發送一個非同步訊息,最終呼叫performTraversals()執行重繪,performTraversals()的具體過程以後再分析。


25,Android 2D動畫框架實現原理

RootView 只有一個孩子就是 DecorView,這裡整個 View Tree 都是 DecorView 的子 View,它們是從 android1.5/frameworks/base/core/res/res/layout/screen_title.xml 這個 layout 檔案 infalte 出來的,感興趣的讀者可以參看 frameworks\policies\base\phone\com\android\internal\policy\Imp\PhoneWindow.java 中 generateLayout 函式部分的程式碼。我們可以修改佈局檔案和程式碼來做一些比較 cool 的事情,如象 Windows 的縮小 / 關閉按鈕等。標題視窗以下部分的 FrameLayou 就是為了讓程式設計師通過 setContentView 來設定使用者需要的視窗內容。因為整個 View 的佈局就是一棵樹,所以繪製的時候也是按照樹形結構遍歷來讓每個 View 進行繪製。ViewRoot.java 中的 draw 函式準備好 Canvas 後會呼叫 mView.draw(canvas),其中 mView 就是呼叫 ViewRoot.setView 時設定的 DecorView。然後看一下 View.java 中的 draw 函式:

遞迴的繪製整個視窗需要按順序執行以下幾個步驟:

  1. 繪製背景;
  2. 如果需要,儲存畫布(canvas)的層為淡入或淡出做準備;
  3. 繪製 View 本身的內容,通過呼叫 View.onDraw(canvas) 函式實現,通過這個我們應該能看出來 onDraw 函式過載的重要性,onDraw 函式中繪製線條 / 圓 / 文字等功能會呼叫 Canvas 中對應的功能。下面我們會 drawLine 函式為例進行說明;
  4. 繪製自己的孩子(通常也是一個 view 系統),通過 dispatchDraw(canvas) 實現,參看 ViewGroup.Java 中的程式碼可知,dispatchDraw->drawChild->child.draw(canvas) 這樣的呼叫過程被用來保證每個子 View 的 draw 函式都被呼叫,通過這種遞迴呼叫從而讓整個 View 樹中的所有 View 的內容都得到繪製。在呼叫每個子 View 的 draw 函式之前,需要繪製的 View 的繪製位置是在 Canvas 通過 translate 函式呼叫來進行切換的,視窗中的所有 View 是共用一個 Canvas 物件
  5. 如果需要,繪製淡入淡出相關的內容並恢復儲存的畫布所在的層(layer)
  6. 繪製修飾的內容(例如滾動條),這個可知要實現滾動條效果並不需要 ScrollView,可以在 View 中完成的,不過有一些小技巧,具體實現可以參看我們的 TextViewExample 示例程式碼

當一個 ChildView 要重畫時,它會呼叫其成員函式 invalidate() 函式將通知其 ParentView 這個 ChildView 要重畫,這個過程一直向上遍歷到 ViewRoot,當 ViewRoot 收到這個通知後就會呼叫上面提到的 ViewRoot 中的 draw 函式從而完成繪製。View::onDraw() 有一個畫布引數 Canvas, 畫布顧名思義就是畫東西的地方,Android 會為每一個 View 設定好畫布,View 就可以呼叫 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去畫內容。每一個 ChildView 的畫布是由其 ParentView 設定的,ParentView 根據 ChildView 在其內部的佈局來調整 Canvas,其中畫布的屬性之一就是定義和 ChildView 相關的座標系,預設是橫軸為 X 軸,從左至右,值逐漸增大,豎軸為 Y 軸,從上至下,值逐漸增大 , 見下圖 :

Android 動畫就是通過 ParentView 來不斷調整 ChildView 的畫布座標系來實現的,下面以平移動畫來做示例,見下圖 4,假設在動畫開始時 ChildView 在 ParentView 中的初始位置在 (100,200) 處,這時 ParentView 會根據這個座標來設定 ChildView 的畫布,在 ParentView 的 dispatchDraw 中它發現 ChildView 有一個平移動畫,而且當前的平移位置是 (100, 200),於是它通過呼叫畫布的函式 traslate(100, 200) 來告訴 ChildView 在這個位置開始畫,這就是動畫的第一幀。如果 ParentView 發現 ChildView 有動畫,就會不斷的呼叫 invalidate() 這個函式,這樣就會導致自己會不斷的重畫,就會不斷的呼叫 dispatchDraw 這個函式,這樣就產生了動畫的後續幀,當再次進入 dispatchDraw 時,ParentView 根據平移動畫產生出第二幀的平移位置 (500, 200),然後繼續執行上述操作,然後產生第三幀,第四幀,直到動畫播完。

以上是以平移動畫為例子來說明動畫的產生過程,這其中又涉及到兩個重要的型別,Animation 和 Transformation,這兩個類是實現動畫的主要的類,Animation 中主要定義了動畫的一些屬性比如開始時間、持續時間、是否重複播放等,這個類主要有兩個重要的函式:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 會根據動畫的屬性來產生一系列的差值點,然後將這些差值點傳給 applyTransformation,這個函式將根據這些點來生成不同的 Transformation,Transformation 中包含一個矩陣和 alpha 值,矩陣是用來做平移、旋轉和縮放動畫的,而 alpha 值是用來做 alpha 動畫的(簡單理解的話,alpha 動畫相當於不斷變換透明度或顏色來實現動畫),以上面的平移矩陣為例子,當呼叫 dispatchDraw 時會呼叫 getTransformation 來得到當前的 Transformation,這個 Transformation 中的矩陣如下

所以具體的動畫只需要過載 applyTransformation 這個函式即可,類層次圖如下:

使用者可以定義自己的動畫類,只需要繼承 Animation 類,然後過載 applyTransformation 這個函式。對動畫來說其行為主要靠差值點來決定的,比如,我們想開始動畫是逐漸加快的或者逐漸變慢的,或者先快後慢的,或者是勻速的,這些功能的實現主要是靠差值函式來實現的,Android 提供了 一個 Interpolator 的基類,你要實現什麼樣的速度可以過載其函式 getInterpolation,在 Animation 的 getTransformation 中生成差值點時,會用到這個函式。

從上面的動畫機制的分析可知某一個 View 的動畫的繪製並不是由他自己完成的而是由它的父 view 完成,所有我們要注意上面 TextView 旋轉一週的動畫示例程式中動畫的效果並不是由 TextView 來繪製的,而是由它的父 View 來做的。findViewById(R.id.TextView01).startAnimation(anim) 這個程式碼其實是給這個 TextView 設定了一個 animation,而不是進行實際的動畫繪製,程式碼如下 :

public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidate(); }

26,對多型的理解

多型的定義:指允許不同類的物件對同一訊息做出響應。即同一訊息可以根據傳送物件的不同而採用多種不同的行為方式。(傳送訊息就是函式呼叫)

多型存在的三個必要條件
一、要有繼承;
二、要有重寫;
三、父類引用指向子類物件。

27,介面與抽象類的區別

  1. Java介面和Java抽象類最大的一個區別,就在於Java抽象類可以提供某些方法的部分實現,而Java介面不可以(就是interface中只能定義方法,而不能有方法的實現,而在abstract class中則可以既有方法的具體實現,又有沒有具體實現的抽象方法),這大概就是Java抽象類唯一的優點吧,但這個優點非常有用。如果向一個抽象類里加入一個新的具體方法時,那麼它所有的子類都一下子都得到了這個新方法,而Java介面做不到這一點,如果向一個Java接口裡加入一個 新方法,所有實現這個介面的類就無法成功通過編譯了,因為你必須讓每一個類都再實現這個方法才行,這顯然是Java介面的缺點這個在我的另外一篇部落格mapreduce 新舊API 區別中有提到類似的問題,在新的mapreduce api中更傾向於使用抽象類,而不是介面,因為這更容易擴充套件。原因就是上面劃線部分所說的。
  2. 一個抽象類的實現只能由這個抽象類的子類給出,也就是說,這個實現處在抽象類所定義出的繼承的等級結構中,而由於Java語言的單繼承性,所以抽象類作為型別定義工具的效能大打折扣。在這一點上,Java介面的優勢就出來了,任何一個實現了一個Java介面所規定的方法的類都可以具有這個介面的型別,而一個類可以實現任意多個Java介面,從而這個類就有了多種型別。(使用抽象類,那麼繼承這個抽象類的子類型別就比較單一,因為子類只能單繼承抽象類;而子類能夠同時實現多個介面,因為型別就比較多。介面和抽象類都可以定義物件,但是隻能用他們的具體實現類來進行例項化。)
  3. 從第2點不難看出,Java介面是定義混合型別的理想工具,混合類表明一個類不僅僅具有某個主型別的行為,而且具有其他的次要行為。
  4. 結合1、2點中抽象類和Java介面的各自優勢,具精典的設計模式就出來了:宣告型別的工作仍然由Java介面承擔,但是同時給出一個Java 抽象類,且實現了這個介面,而其他同屬於這個抽象型別的具體類可以選擇實現這個Java介面,也可以選擇繼承這個抽象類,也就是說在層次結構中,Java 介面在最上面,然後緊跟著抽象類,這下兩個的最大優點都能發揮到極至了。這個模式就是“預設適配模式”。在Java語言API中用了這種模式,而且全都遵循一定的命名規範:Abstract +介面名。(A extends AbstractB implements interfaceC,那麼A即可以選擇實現(@Override)介面interfaceC中的方法,也可以選擇不實現;A即可以選擇實現(@Override)抽象類AbstractB中的方法,也可以選擇不實現)

28,GLSurFaceView特性的是

1> 管理一個surface,這個surface就是一塊特殊的記憶體,能直接排版到android的檢視view上。

                2> 管理一個EGL display,它能讓opengl把內容渲染到上述的surface上。                 3> 使用者自定義渲染器(render)。                 4> 讓渲染器在獨立的執行緒裡運作,和UI執行緒分離。                 5> 支援按需渲染(on-demand)和連續渲染(continuous)。                 6> 一些可選工具,如除錯。