1. 程式人生 > >Android面試題——Activity

Android面試題——Activity

記得2010年開始接觸Android(Android 1.5),使用Eclipse開發,學完JDK5新特性之後,工作中其實用的是Java6,甚至身邊都沒有幾個人用的是Android手機,當時HTC正火爆,一臺便宜的G3都要三千左右的樣子,對於剛畢業步入社會的童鞋來說絕對是奢侈品,開發全靠模擬器來做(苦逼臉)。

一、初步認識

學Android接觸最早的就是Activity了,四大元件中相對來說也是使用最頻繁的元件。畢竟絕大部分的App都是要和使用者互動的(極少數特殊的App,例如竊聽類的牛虻軟體,不需要介面,甚至桌面圖示也沒有)。Activity簡單理解就是和使用者互動的、可以看得到摸得著的介面嘍。其實Activity本身是沒有介面的

,所以Activity類建立了一個視窗,開發人員可以通過setContentView(View)介面把UI放到Activity建立的視窗上,當Activity指向全屏視窗時,也可以用其他方式實現:作為漂浮視窗(通過WindowIsFloating的主題集合),或者嵌入到其他的Activity(使用ActivityGroup)。Activity是單獨的,用於處理使用者操作。幾乎所有的Activity都要和使用者打交道,所以出去面試Android的職位必須會問到Activity相關的內容。

二、生命週期

去看Google官方的API文件,會看到提供的一張官方圖,如下:

2.1 常用的生命週期方法

1.onCreate()

當活動第一次啟動的時候,觸發該方法,可以在此時完成活動的初始化工作。 onCreate 方法有一個引數,該引數可以為空( null ),也可以是之前呼叫 onSaveInstanceState ()方法儲存的狀態資訊。

2.onStart()

該方法的觸發表示所屬活動將被展現給使用者,“使用者可見不可互動”的狀態。

3.onResume()

當一個活動和使用者發生互動的時候,觸發該方法(在Activity棧系統通過棧的方式管理這些Activity,即當前Activity在棧的最上端,執行完彈出棧,則回到上一個Activity)。

4.onPause()

當一個正在前臺執行的活動因為其他的活動需要前臺執行而轉入後臺執行的時候,觸發該方法。這時候需要將活動的狀態持久化、儲存起來,比如正在編輯的資料庫記錄等。

5.onStop()

當一個活動不再需要展示給使用者的時候,觸發該方法。如果記憶體緊張,系統會直接結束這個活動,而不會觸發 onStop 方法。 所以儲存狀態資訊是應該在onPause時做,而不是onStop時做。活動如果沒有在前臺執行,都將被停止或者Linux管理程序為了給新的活動預留足夠的儲存空間而隨時結束這些活動。因此對於開發者來說,在設計應用程式的時候,必須時刻牢記這一原則。在一些情況下,onPause方法或許是活動觸發的最後的方法,因此開發者需要在這個時候儲存需要儲存的資訊。

6.onRestart()

當處於停止狀態的活動需要再次展現給使用者的時候,觸發該方法。

7.onDestroy()

當活動銷燬的時候,觸發該方法。和 onStop 方法一樣,如果記憶體緊張,系統會直接結束這個活動而不會觸發該方法。

8.onSaveInstanceState()

系統呼叫該方法,允許活動儲存之前的狀態,比如說在一串字串中的游標所處的位置等。 通常情況下,開發者不需要重寫覆蓋該方法,在預設的實現中,已經提供了自動儲存活動所涉及到的使用者介面元件的所有狀態資訊。

2.2一些操作對應的生命週期流程

一個Activity的啟動順序:

onCreate()——>onStart()——>onResume()

當另一個Activity啟動時:

第一個Activity onPause()——>第二個Activity onCreate()——>onStart()——>onResume() ——>第一個Activity onStop()
當前Activity只有執行完onPause(),才會執行新Activity的onResume()。

當返回到第一個Activity時:

第二個Activity onPause() ——> 第一個Activity onRestart()——>onStart()——>onResume() ——>第二個Activity onStop()——>onDestroy()

一個Activity的銷燬順序:

(情況一)onPause()——>< Process Killed >
(情況二)onPause()——>onStop()——>< Process Killed >
(情況三)onPause()——>onStop()——>onDestroy()

使用者開啟新的Activity或者Home鍵切換到桌面:

onPause()——>onStop() ,如果新的Activity設定了透明主題,不會回撥onStop()

使用者按下Back鍵返回:

onPause()——>onStop()——>onDestroy()

Activity在螢幕旋轉時的生命週期:

android:configChanges 切橫屏 切豎屏 備註
不設定 1次 2次
orientation 1次 1次
orientation keyboardHidden 0次 0次 只執行onConfigurationChanged

生命週期小結

對於整個生命週期來說:

  • onCreate和onDestroy是一對,標誌著Activity的建立和銷燬,並且只可能呼叫一次。
  • 從Activity的是否可見來說,onStart和onStop是一對,隨著使用者的操作或者螢幕點亮熄滅,可能被呼叫多次。
  • 從Activity是否在前臺來說,onResume和onPause是一對,隨著使用者的操作或者螢幕點亮熄滅,可能被呼叫多次。

【補充:題外話——android中程序的優先順序?】
1 前臺程序 > 2 可見程序 > 3 服務程序 > 4 後臺程序 > 5 空程序

三、Activity之間的通訊

3.1使用Intent

在 Android 中,不同的 Activity 例項可能執行在一個程序中,也可能執行在不同的程序中。因此我們需要一種特別的機制幫助我們在 Activity 之間傳遞訊息。Android 中通過 Intent 物件來表示一條訊息,一個 Intent 物件不僅包含有這個訊息的目的地,還可以包含訊息的內容,這好比一封 Email,其中不僅應該包含收件地址,還可以包含具體的內容。對於一個 Intent 物件,訊息“目的地”是必須的,而內容則是可選項。

Intent負責對操作的動作、動作涉及資料、附加資料進行描述,Android則根據此Intent的描述,負責找到對應的元件,將 Intent傳遞給呼叫的元件,並完成元件的呼叫。因此,Intent在這裡起著一個媒體中介的作用,專門提供元件互相呼叫的相關資訊,實現呼叫者與被呼叫者之間的解耦。

在應用中,我們可以以兩種形式來使用Intent:

  • 直接Intent:指定了component屬性的Intent(呼叫setComponent(ComponentName)或者setClass(Context, Class)來指定)。通過指定具體的元件類,通知應用啟動對應的元件。

  • 間接Intent:沒有指定comonent屬性的Intent。這些Intent需要包含足夠的資訊,這樣系統才能根據這些資訊,在在所有的可用元件中,確定滿足此Intent的元件。

對於直接Intent,Android不需要去做解析,因為目標元件已經很明確。

Android需要解析的是那些間接Intent,通過解析,將 Intent對映給可以處理此Intent的Activity、IntentReceiver或Service。Intent解析機制主要是通過查詢已註冊在AndroidManifest.xml中的所有IntentFilter及其中定義的Intent,最終找到匹配的Intent。

這裡給出一個易道用車的面試題(2016.09)

說說以下兩種方式有何區別:
(1)intent.putExtra(“key”, “value”);
(2)Bundle b = new Bundle();
b.putString(“key”, “value”);
intent.putExtras(b);

也就是問:請說出intent.putExtra() 和 bundle.putExtra()的區別?

我們先看一下Intent物件相應函式的原始碼:

public Intent putExtra(String name, String value) {  
    if (mExtras == null) {  
        mExtras = new Bundle();  
    }  
    mExtras.putString(name, value);  
    return this;  
}  

可以知道,Intent的相關操作的實現是基於Bundle的,Bundle操作資料相對於Intent有哪些優勢呢?
舉個例子,如果我要從A介面跳轉到B介面和C介面,那麼我要寫寫兩個Intent,如果傳的資料相同,我兩個Intent都要新增,但是如果我用Bundle,直接一次性包裝就可以了。
再有,如果A介面要傳值給B介面,再從B介面傳值到C介面,你可以想象用Intent是多麼的麻煩了,但是用Bundle就很簡潔,而且還可以在B介面中新增新的資訊。

當然如果傳的資料非常之多而且很複雜,用這兩種方式顯然是不適合的,這時候我們可以使用可序列化的結構類,詳情參考之前的文章 Android中的Serializable和Parcelable

3.2使用SharedPreferences

3.3其他方式

Android 提供了包括 SharedPreferences 在內的很多種資料存貯方式,比如 SQLite,檔案等,程式設計師可以通過這些 API 實現 Activity 之間的資料交換。如果必要,我們還可以使用 IPC 方式。

四、Activity 的 Intent Filter

  Intent Filter 描述了一個元件願意接收什麼樣的 Intent 物件,Android 將其抽象為 android.content.IntentFilter 類。在 Android 的 AndroidManifest.xml 配置檔案中可以通過 節點為一個 Activity 指定其 Intent Filter,以便告訴系統該 Activity 可以響應什麼型別的 Intent。

  當使用 startActivity(intent) 來啟動另外一個 Activity 時,如果直接指定 intent 物件的 Component 屬性,那麼 Activity Manager 將試圖啟動其 Component 屬性指定的 Activity。否則 Android 將通過 Intent 的其它屬性從安裝在系統中的所有 Activity 中查詢與之最匹配的一個啟動,如果沒有找到合適的 Activity,應用程式會得到一個系統丟擲的異常。這個匹配的過程如下:
這裡寫圖片描述

五、Activity的棧式管理

  Android針對Activity的管理使用的是棧,就是說某一個時刻只有一個Activity處在棧頂,當這個Activity被銷燬後,下面的Activity才有可能浮到棧頂,或者有一個新的Activity被創建出來,則舊的Activity就被壓棧沉下去了。Activity是Android程式的表現層。程式的每一個顯示螢幕就是一個Activity。正在執行的Activity處在棧的最頂端,它是執行狀態的。
這裡寫圖片描述

當在程式中呼叫 Activity.finish()方法時,結果和使用者按下 BACK 鍵一樣:它告訴 Activity Manager該Activity例項可以被“回收”。隨後 Activity Manager 啟用處於棧第二層的 Activity ,把原 Activity 壓入到棧的第二層,從 Running 狀態轉到 Paused 狀態。

六、Activity的載入/啟動模式

standard、singleTop、singleTask、singleInstance(其中前兩個是一組、後兩個是一組),預設為standard

  • standard:(預設|標準)
    就是intent將傳送給新的例項,所以每次跳轉都會生成新的activity。

    我們平時直接建立的Activity都是這種模式的Activity,這種模式的Activity的特點是:只要你建立了Activity例項,一旦啟用該Activity,則會向任務棧中加入新建立的例項,退出Activity則會在任務棧中銷燬該例項。誰啟動了Activity,這個Activity就執行在啟動他的任務棧中。

    注:不要以ApplicationContext啟動,否則會報錯:
    AndroidRuntime: android.util.AndroidRuntimeException: Calling startActivity from outside of
    an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag .Is this really what you want? 這是因為standard模式的Activity預設會進入啟動它的Activity所屬的任務棧中,但是由於非Activity型別的Context(如ApplicationContext)沒有所謂的任務棧,所以出現了這個問題。解決方法就是為待啟動Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啟動的時候就會建立一個新的任務棧,這個時候待啟動的Activity實際是以singleTask模式啟動的。

  • singleTop:(棧頂複用)
    也是傳送新的例項,但不同standard的一點是,在請求的Activity正好位於棧頂時(配置成singleTop的Activity),不會構造新的例項。

    這種模式會考慮當前要啟用的Activity例項在任務棧中是否正處於棧頂,如果處於棧頂則無需重新建立新的例項,同時它的onNewIntent()方法會被回撥,會重用已存在的例項,需要注意的是,這個Activity的onCreate和onStart不會被系統呼叫;否則會在任務棧中建立新的例項。

    作用: 避免一個糟糕的使用者體驗,如果這個介面已經被開啟且在任務棧的棧頂,就不會重複開啟了

  • singleTask:(棧內複用)
    和後面的singleInstance都只建立一個例項,當intent到來,需要建立設定為singleTask的Activity的時候,系統會檢查棧裡面是否已經有該Activity的例項。如果有直接將intent傳送給它。

    這是一種單例項模式,如果任務棧中存在該模式的Activity例項,則把棧中該例項以上的Activity例項全部移除,呼叫該例項的onNewIntent()方法重用該Activity,使該例項處於棧頂位置,否則就重新建立一個新的Activity例項。可以用來退出整個應用。將主Activity設為SingTask模式,然後在要退出的Activity中轉到主Activity,然後重寫主Activity的onNewIntent函式,並在函式中加上一句finish。相當於條用 onNewIntent()+ClearTop。

    • 設定了”singleTask”啟動模式的Activity,它在啟動的時候,會先在系統中查詢屬性值affinity等於它的屬性值taskAffinity的任務存在;如果存在這樣的任務,它就會在這個任務中啟動,否則就會在新任務中啟動。因此,如果我們想要設定了”singleTask”啟動模式的Activity在新的任務中啟動,就要為它設定一個獨立的taskAffinity屬性值。
    • 如果設定了”singleTask”啟動模式的Activity不是在新的任務中啟動時,它會在已有的任務中檢視是否已經存在相應的Activity例項,如果存在,就會把位於這個Activity例項上面的Activity全部結束掉,即最終這個Activity例項會位於任務的堆疊頂端中。
  • singleInstance:(單例項)
    加強版的singleTask模式,除了具備singleTask的所有特性外,還加強了一點,具有此模式的Activity只能單獨位於一個任務棧中。當該模式Activity例項在任務棧中建立後,只要該例項還在任務棧中,即只要啟用的是該型別的Activity,都會通過呼叫例項的onNewIntent()方法重用該Activity,此時使用的都是同一個Activity例項,它都會處於任務棧的棧頂。此模式一般用於載入較慢的,比較耗效能且不需要每次都重新建立的Activity。

    onNewIntent()—->onResart()——>onStart()—–>onResume()

首先說明一下task這個概念,Task可以認為是一個棧,可放入多個Activity。比如啟動一個應用,那麼Android就建立了一個Task,然後啟動這個應用的入口Activity,那在它的介面上呼叫其他的Activity也只是在這個task裡面。那如果在多個task中共享一個Activity的話怎麼辦呢。舉個例來說,如果開啟一個導遊服務類的應用程式,裡面有個Activity是開啟GOOGLE地圖的,當按下home鍵退回到主選單又啟動GOOGLE地圖的應用時,顯示的就是剛才的地圖,實際上是同一個Activity,實際上這就引入了singleInstance。singleInstance模式就是將該Activity單獨放入一個棧中,這樣這個棧中只有這一個Activity,不同應用的intent都由這個Activity接收和展示,這樣就做到了共享。當然前提是這些應用都沒有被銷燬,所以剛才是按下的HOME鍵,如果按下了返回鍵,則無效。

七、Activity的跳轉

  • Activity跳轉,無返回結果
    這是最簡單的Activity跳轉方式。從一個Activity啟動另一個Activity,直接startActivity(new Intent(當前Activity.this, 下一Activity.class))。
  • Activity跳轉,返回資料/結果
    需要返回資料或結果的,則使用startActivityForResult (Intent intent, int requestCode),requestCode的值是自定義的,用於識別跳轉的目標Activity。跳轉的目標Activity所要做的就是返回資料/結果,setResult(int resultCode)只返回結果不帶資料,或者setResult(int resultCode, Intent data)兩者都返回!而接收返回的資料/結果的處理函式是onActivityResult(int requestCode, int resultCode, Intent data),這裡的requestCode就是startActivityForResult的requestCode,resultCode就是setResult裡面的resultCode,返回的資料在data裡面。

    ** 注意,在setResult後,要呼叫finish()銷燬當前的Activity,否則無法返回到原來的Activity,就無法執行原來Activity的onActivityResult函式,看到當前的Activity沒反應。

八、Activity之啟動方式

1.在“Home”下點選圖示,啟動應用程式的首個Activity。

我們稱之為主Activity,這是最常見的啟動方式,而且代表程式具備獨立的執行條件。通常會在Manifest中指定某個Activity的android.intent.category屬性為Launcher。 (實際上這也是隱式啟動Activity的其中一個)

2.在程式中啟動Activity。

應用(startActivity() 或者startActivityForResult())啟動特定的Activity,或者當程式需要處理一些特定的功能,而其他應用程式中已經具備了這種功能的Activity時,我們可以通過intent-filter來啟動Activity

3.強制程式僅以被其它程式呼叫的方式啟動(沒有直接的啟動入口):

在系統中有相當一部分的應用程式需要有嚴格的執行條件(Context),其無法以Stand alone的方式獨立運行於程序中。具備這種特性的Application大體上分為兩種:其一,帶有特定返回值的應用程式。parent出於當前操作需要,通過啟動其它Application來獲得某些特定的資源。例如:輸入法或者檔案資源選擇器等。另外一種情況,經常被呼叫來修改系統設定或者提供單一有特定目的的操作。這樣的Application無需在Home中提供快捷啟動方式,其僅僅被其它某些程式在特定的需求下來滿足需求。例如:更換系統鈴音程式。

4.在一個Application中包含有多個Mian Activities,並且各自具備有獨立的啟動入口:

很多開發者都習慣於預設的開發配置,一個Application僅僅包含一個獨立的應用。然後在特定的需求下可以打破這個限定,系統允許開發者將多個Main Activities應用捆綁在同一個Application中,而且這些Main Activities可以在Home中有獨立的啟動快捷方式。然而有非常重要的一點需要特別強調,這種方式並不提倡在任何情況下采用,一般當兩個應用程式需要呼叫相同的系統資源或者包含有大部分相同功能的應用時,才可以考慮採用這種方式來簡化使用者的安裝過程(另外一個角度來看,起到優化系統資源的目的)。技術上需要注意為不同的Main Activity定義不同的Task affinity。例如:Camera和Camcorder,它們共同使用攝像頭,而且同樣採用Gallery作為資源管理器,他們的Task Affinity分別設定為:”com.android.camera” 和 “com.android.videocamera”。

5.以Widget的方式體現Activity的應用價值:

嚴格意義上來看,這的確算得上是一種啟動方式,某些Application將部分常用的功能以Widget的形式在Home或者其它Application中被引用。

隱式啟動

(1) 根據Action和Category資訊來進行匹配

在Manifest.xml中註冊該Activity

<activity android:name=".TestActivity"  android:label="TestActivity">
    <intent-filter >
        <action android:name="cn.xiaoyao.test"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

在MainActivity.java裡啟動該Activity:

Intent intent = new Intent();
intent.setAction( "cn.xiaoyao.test");//設定intent的Action屬性值
intent.addCategory(Intent.CATEGORY_DEFAULT);//不加這行也行,因為這個值預設就是Intent.CATEGORY_DEFAULT
startActivity( intent );

(2) 根據Action和Data資訊進行匹配

在Manifest.xml中註冊該Activity

<activity android:name=".TestActivity"
    android:label="@string/testactivity">
    <intent-filter>
        <action android:name="cn.xiaoyao.test"></action>
        <category android:name="android.intent.category.DEFAULT"></category>
        <data android:scheme="xiaoyao"></data>
    </intent-filter>
 </activity>

如上註冊資訊,合法的Uri的寫法如下:

 //Uri uri = Uri.parse("xiaoyao");//不行
 //Uri uri = Uri.parse("xiaoyao://");//可以
 //Uri uri = Uri.parse("xiaoyao:");//可以
 //Uri uri = Uri.parse("xiaoyao://www.google.com/getDetails?id=123");//可以

在MainActivity.java裡啟動該Activity:

Intent intent = new Intent();
intent.setAction("cn.xiaoyao.test");//僅有data不能匹配,所以要設定Action屬性
intent.addCategory(Intent.CATEGORY_DEFAULT);//可去除
intent.setData(uri);//設定data屬性
startActivity(in);

(3) 根據action和data的mimeType屬性匹配

在Manifest.xml中註冊該Activity

<activity android:name=".TestActivity" android:label="@string/testactivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"></action>
        <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
        <category android:name="android.intent.category.DEFAULT"></category>
    </intent-filter>
</activity>

在MainActivity.java裡啟動該Activity:

Intent intent = new Intent();
intent .setAction("android.intent.action.VIEW");
intent .addCategory(Intent.CATEGORY_DEFAULT);//可去掉
intent.setType("vnd.android.cursor.dir/vnd.google.note");//要設定Data的MIMEType屬性
startActivity(intent );

注意:一個Activity中可以包括多個intent-filter屬性,intent-filter屬性中也可以包含多個data標籤對,只需要有一個滿足便可以匹配成功

如果匹配不到合適的Activity啟動時,會丟擲異常給使用者一個提示“應用程式意外停止”!