1. 程式人生 > >1.1 Activity(Android校招復習)

1.1 Activity(Android校招復習)

1 Activity是什麼?

Activity是Android中與使用者直接進行互動(滑動、觸控、點選等)的元件,也就是Android系統提供給使用者操作的UI元件。

2 Activity的生命週期

這裡寫圖片描述

2.1 Activity的4種狀態

  • running(執行狀態)
    Activity運行於“前臺任務棧”的棧頂,是與使用者直接進行互動的Activity。
  • paused
    Activity失去焦點,就是有其他Activity以透明、非全屏的狀態“覆蓋”於此Activity之上時。
  • stoped
    Activity處於任務棧內,儲存了所有執行時的資料,但無法與使用者互動。
  • killed
    Activity被釋放掉,不存在與記憶體之中。

2.2 Activity各生命週期方法的呼叫意義、作用

  • onCreate()
    表示Activity正在被建立。在這裡可以做一些初始化的工作。
  • onStart()
    表示Activity正在被啟動。已經可見,但不在前臺,無法互動。
  • onResume()
    表示Activity已經可見,並且出現在前臺可以互動。
  • onPause()
    表示Activity正在停止。在這裡可以做一些儲存資料、儲存使用者狀態(必須在此處儲存,因為onPause方法在Activity即將銷燬或將到後臺前必定執行)、停止動畫等工作,但不能太耗時
    ,因為必須onPause執行完成之後新的Activity才能Resume。
  • onStop()
    表示Activity即將停止。可以進行一些稍微重量級的回收工作,不能太耗時。在記憶體嚴重不足時將不會執行,因此儲存使用者狀態等操作最好在onPause()方法中進行
  • onRestart()
    表示Activity正在重新啟動。當前Activity從不可見重新變成可見狀態。
  • onDestory()
    表示Activity即將被銷燬。可以進行一些回收工作和最終的資源釋放。

2.3 Activity在不同場景下經歷的生命週期方法

常見情況

  • Activity正常啟動:
    onCreate()-> onStart()-> onResume()
    在正常情況下,一個Activity從啟動到結束會以如下順序經歷整個生命週期:
  • 當用戶按下back鍵之後(Activity正常銷燬):
    onPause()-> onStop()-> onDestory()
  • 當有一個透明、非全屏的Activity覆蓋在此Activity之上時:
    onPause()
  • 打開了另外一個Activity時(此Activity不處於前臺任務棧棧頂)
    onPause()-> onStop()
  • 使用者按下Home鍵時
    onPause()-> onStop()

特殊情況

  • 呼叫finish()方法後:
    • 在Activity的onCreate()中呼叫finish(),則執行的生命週期方法順序為:
      onCreate() -> onDestroy()
    • 在Activity的onStart()中呼叫finish(),則執行的生命週期方法順序為:
      onCreate() -> onStart() -> onStop() -> onDestroy()
    • 在Activity的onResume()或onPostResume()中呼叫finish(),則執行的生命週期方法順序為:
      onCreate() -> onStart() -> onResume() -> onPostResume() -> onPause() -> onStop() -> onDestroy()
    • 在Activity的onPause()、onStop()、onDestory()中呼叫finish(),則執行的什麼週期方法順序為:
      按照正常的生命週期順序執行。
  • 橫豎屏切換
    onPause()-> onSaveInstanceState()-> onStop()-> onDestroy()->onCreate()-> onStart()-> onRestoreInstanceState()-> onResume()
    PS:1.橫豎屏切換過程中,Activity進行被銷燬然後重建的過程,因此才有了生命週期的改變。
    2.在Activity因異常情況下終止時,系統會呼叫onSaveInstanceState()來儲存當前Activity的狀態。這個方法的呼叫是在onStop()之前,但它和onPause沒有既定的時序關係,該方法只在Activity被異常終止的情況下呼叫。
    3.異常終止的Activity被重建後,系統會呼叫onRestoreInstanceState(),並把Activity銷燬時onSaveInstanceState()方法所儲存的Bundle物件引數同時傳遞給onRestoreInstanceState()和onCreate()方法,因此,可通過onRestoreInstanceState()方法來恢復Activity的狀態,該方法的呼叫時機是在onStart之後
    4.onCreate()和onRestoreInstanceState()方法恢復Activity的狀態的區別:onRestoreInstanceState回撥則表明其中Bundle物件非空,不用加非空判斷。onCreate()需要非空判斷。建議使用onRestoreInstanceState()**。
    5.避免橫豎屏切換時,Activity的銷燬和重建,只需在AndroidManifest檔案中指定如下屬性即可:
    android:configChanges = “orientation| screenSize”
    避免重建後,Activity橫豎屏切換後會呼叫如下方法
@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

2.4 Android程序優先順序

前臺程序 > 可見程序 > 服務程序 > 後臺程序 > 空程序
前臺程序(running狀態):正在與使用者互動的Activity或與前臺Activity繫結的Service
可見程序(paused狀態):當Activity可見但不是處於前臺程序,也就是不可互動的程序。
服務程序:在後臺開啟的Service服務的程序
後臺程序(stoped狀態):如按下Home鍵後的當前Activity就處於後臺程序,此時後臺程序不會立即被kill掉,系統會根據當前的記憶體狀況來進行判斷是否kill。
空程序 :表示不活躍的元件,系統出於快取的目的而保留。

3 Android任務棧

任務棧是什麼?

  1. Task又稱為Android任務棧,是一個棧結構,具有後進先出的特性,用於存放我們的Activity,Android中用任務棧與啟動模式結合來達到節省記憶體資源的目的。

  2. 我們每次開啟一個新Activity或退出當前Activity都會在任務棧中新增或減少一個Activity,因此一個任務棧包含了一個Activity的集合,只有棧頂Activity才能與使用者互動,,Android系統通過Task有序地管理每個Activity,並決定哪個Activity與使用者互動。

  3. 我們退出應用程式時,必須把任務棧中所有的Activity清除出棧時,任務棧才 會被銷燬。當然任務棧也可以移動到後臺,並且保留了每一個Activity的狀態,可以有序的給使用者列出它們的任務,同時也不會丟失Activity的狀態資訊。

  4. 需要注意的是,一個App中可能不止一個任務棧,某些特殊情況下,單獨一個Actvity可以獨享一個任務棧。還有一點就是一個Task中的Actvity可以來自不同的App,同一個App的Activity也可能不在一個Task中。

4 Activity四種啟動模式

這裡寫圖片描述

4.1 Standard (標準模式)

說明: 假設沒有為Activity設定啟動模式的話,預設標準模式。無論這個例項是否存在,每次啟動一個Activity都會又一次建立一個新的例項入棧。

舉例:此時Activity 棧中以此有A、B、C三個Activity,此時C處於棧頂,啟動模式為Standard 模式。若在C Activity中加入點選事件,須要跳轉到還有一個同類型的C Activity。結果是還有一個C Activity進入棧中,成為棧頂。
這裡寫圖片描述

特殊情況:
如果在Service或Application中啟動一個Activity,其並沒有所謂的任務棧,可以使用標記位Flag來解決。解決辦法:為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,建立一個新棧。

絕大多數Activity。如果以這種方式啟動的Activity被跨程序呼叫,在5.0之前新啟動的Activity例項會放入傳送Intent的Task的棧的頂部,儘管它們屬於不同的程式,這似乎有點費解看起來也不是那麼合理,所以在5.0之後,上述情景會建立一個新的Task,新啟動的Activity就會放入剛建立的Task中,這樣就合理的多了。

4.2 SingleTop (棧頂複用模式)

說明:(分兩種處理情況)
1.須要建立的Activity已經處於棧頂時,此時會直接複用棧頂的Activity,不會再建立新的Activity。
2.若須要建立的Activity不處於棧頂,此時會又一次建立一個新的Activity入棧,同Standard模式一樣。
若情況一中棧頂的Activity被直接複用時,它的onCreate()、onStart()不會被系統呼叫,由於它並沒有發生改變。可是一個新的方法 onNewIntent()會被回撥(Activity被正常建立時不會回撥此方法)。

舉例:此時Activity 棧中以此有A、B、C三個Activity,此時C處於棧頂,啟動模式為SingleTop 模式。

情況一:在C Activity中加入點選事件,須要跳轉到還有一個同類型的C Activity。結果是直接複用棧頂的C Activity,並呼叫如下方法。

//由於不會重建一個Activity例項,則不會回撥其他生命週期方法。
@Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
    }

情況二:在C Activity中加入點選事件,須要跳轉到還有一個A Activity。結果是建立一個新的Activity入棧。成為棧頂。
這裡寫圖片描述

應用場景:
在通知欄點選收到的通知,然後需要啟動一個Activity,這個Activity就可以用SingleTop,否則每次點選都會新建一個Activity。

當然實際的開發過程中,測試妹紙沒準給你提過這樣的bug:某個場景下連續快速點選,啟動了兩個Activity。如果這個時候待啟動的Activity使用 SingleTop模式也是可以避免這個Bug的。

同Standard模式,如果是外部程式啟動SingleTop的Activity,在Android 5.0之前新建立的Activity會位於呼叫者的Task中,5.0及以後會放入新的Task中

4.3 SingleTask (棧內複用模式)

說明:若須要建立的Activity已經處於棧中時,此時不會建立新的Activity,而是將存在棧中的Activity上面的其他Activity所有銷燬,使它成為棧頂,並呼叫onNewintent()方法。

PS:該模式是一種單例模式,即一個棧內只有一個該Activity例項。該模式,可以通過在AndroidManifest檔案的Activity中指定該Activity需要載入到那個棧中,即SingleTask的Activity可以指定想要載入的目標棧。SingleTask和taskAffinity配合使用,指定開啟的Activity加入到哪個棧中。

<activity android:name=".Activity_A"
    android:launchMode="singleTask"
    android:taskAffinity="com.lvr.task"
    android:label="@string/app_name">
</activity>

PS:
1.關於taskAffinity的值:
每個Activity都有taskAffinity屬性,這個屬性指出了它希望進入的Task。如果一個Activity沒有顯式的指明該Activity的taskAffinity,那麼它的這個屬性就等於Application指明的taskAffinity,如果Application也沒有指明,那麼該taskAffinity的值就等於包名。

2.執行邏輯:
在這種模式下,如果Activity指定的棧不存在,則建立一個棧,並把建立的Activity壓入棧內。如果Activity指定的棧存在,如果其中沒有該Activity例項,則會建立Activity並壓入棧頂,如果其中有該Activity例項,則把該Activity例項之上的Activity殺死清除出棧,重用並讓該Activity例項處在棧頂,然後呼叫onNewIntent()方法。

**舉例:**此時Activity 棧中依次有A、B、C三個Activity。此時C處於棧頂,啟動模式為SingleTask 模式。

情況一:在C Activity中加入點選事件,須要跳轉到還有一個同類型的C Activity。結果是直接用棧頂的C Activity。

情況二:在C Activity中加入點選事件,須要跳轉到還有一個A Activity。結果是將A Activity上面的B、C所有銷燬,使A Activity成為棧頂。

這裡寫圖片描述

情況三:目標任務棧不存在,則建立一個任務棧,並壓入棧頂。

這裡寫圖片描述

應用場景:
應用於大多數App的主頁。對於大部分應用,當我們在主介面點選回退按鈕的時候都是退出應用,那麼當我們第一次進入主介面之後,主介面位於棧底,以後不管我們打開了多少個Activity,只要我們再次回到主介面,都應該使用將主介面Activity上所有的Activity移除的方式來讓主介面Activity處於棧頂,而不是往棧頂新加一個主介面Activity的例項。

通過這種方式能夠保證退出應用時所有的Activity都能報銷燬。在跨應用Intent傳遞時,如果系統中不存在singleTask Activity的例項,那麼將建立一個新的Task,然後建立SingleTask Activity的例項,將其放入新的Task中。

特殊情景:

特殊情景一:
現在我們假設有如下兩個Task棧,分別為前臺任務棧和後臺任務棧
這裡寫圖片描述

從圖中我們看出前臺任務棧分別為AB兩個Activity,後臺任務棧分別為CD兩個任務棧,而且其啟動模式均為singleTask,此時我們先啟動CD,然後再啟動AB,再有B啟動D,此時後臺任務棧便會被切換到前臺,而且這個時候整個***後退列表***就變成了ABCD,請注意我們這裡強調的是後退列表,而非棧合併。因此當用戶點選back鍵時,列表中的Activity會依次按DCBA順序出棧,如下圖所示:

這裡寫圖片描述

這裡我們通過兩個應用ActivityTask和ActivityTask2來測試重現這個現象。因為兩個是不同的應用所以啟動時所在的棧也是不同。我們先啟動ActivityTask2的應用,其ActivityC和ActivityD都是singleTask模式,然後再啟動應用ActivityTask,此時ActivityC和ActivityD所在任務棧會被退居後臺,而開啟的ActivityA和ActivityB會在前臺,而且都是預設模式。我們通過 adb shell dumpsys activity activities 命令檢視此時棧的情況:

這裡寫圖片描述

我們可以看到由兩個棧,分別為id=222且棧名為“com.cmcm.activitytask”的任務棧其包含ActivityA和ActivityB(下面簡稱AB,棧名一般預設和包名相同),另外一個任務棧,id=221,棧名為“com.cmcm.activitytask2”,其包含ActivityC和ActivityD(下面檢測CD)。現在我們通過ActivityB去啟動ActivityD,然後按back鍵回退。B呼叫D程式碼如下:

public class ActivityB extends Activity {
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        btn= (Button) findViewById(R.id.main);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_MAIN);
                intent.addCategory(Intent.CATEGORY_LAUNCHER);
                ComponentName cn = new ComponentName("com.cmcm.activitytask2", "com.cmcm.activitytask2.ActivityD");
                intent.setComponent(cn);
                startActivity(intent);
            }
        });
    }
}

執行結果如下:
這裡寫圖片描述

我們可以看到包含CD的任務棧被提前的,雖然CD隔開了,但是我們從id和棧名可以發現他們是同一個棧,而AB所在的棧則在CD所在棧的後面,所以此時我們按back回退時,退出順序是這樣的D->C->B->A,動態圖如下:

這裡寫圖片描述

到這裡我們就應該更加清晰的瞭解情景一的現象了。瞭解這點有什麼用呢,這可以使用我們更好地去管理我們的任務棧,而不會導致棧混亂是進入一些使用者本來就不需要介面,影響使用者體驗。

特殊情景二:

如果上面B不是請求啟動D而是請求啟動C,那麼又會是什麼情況呢?其實這個時候任務棧退出列表變成C->B->A,其實原因很簡單,singleTask模式的ActivityC切換到棧頂時會導致在他之上的棧內的Activity出棧。同樣我們還是使用上面的程式碼,把B啟動D改為B啟動C,那麼此時B未啟動C時任務棧的情況如下:

這裡寫圖片描述

我們仍然可以看到兩個任務棧,分別為id=242,棧名“com.cmcm.activitytask”的Task,包含ActivityA和ActivityB;id=241,棧名“com.cmcm.activitytask2”的Task,包含ActivityC和ActivityD。此時我們通過B啟動C後棧的情況變成如下情況 :

這裡寫圖片描述

因此,棧的退出列表就變成了C->B->A了,如下圖所示:
這裡寫圖片描述

動態圖如下:
這裡寫圖片描述

4.4 SingleInstance (單例模式)

說明 SingleInstance比較特殊,具有此模式的Activity僅僅能單獨位於一個任務棧中。這個經常使用於系統中的應用,比如Launch、鎖屏鍵的應用等等,整個系統中僅僅有一個!

舉例:比方 A Activity是該模式,啟動A後。系統會為它建立一個單獨的任務棧,由於棧內複用的特性,新的請求均不會建立新的Activity,除非這個獨特的任務棧被系統銷燬。

4.5 Activity啟動模式的使用方式

1.通過AndroidMenifest.xml檔案為Activity指定啟動模式,程式碼如下:

<activity android:name=".ActivityC" 
          android:launchMode="singleTask" />

2.通過在Intent中設定標誌位(addFlags方法)來為Activity指定啟動模式,示例程式碼如下:

Intent intent = new Intent();
intent.setClass(ActivityB.this,ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

那麼標誌位是是什麼呢?接下來我們就來了解一些常用的標誌位

1.3 Activity的Flags

**說明:**這裡我們主要介紹一下一些常用的Activity的Flag,因為Activity的Flag比較多,我們知道一些常用的就夠了,遇到比較特殊的還是查查官網文件吧。

  • Intent.FLAG_ACTIVITY_NEW_TASK

該標誌位表示使用一個新的Task來啟動一個Activity,相當於在清單檔案中給Activity指定“singleTask”啟動模式。通常我們在Service啟動Activity時,由於Service中並沒有Activity任務棧,所以必須使用該Flag來建立一個新的Task。我們來重現一下這個錯誤,建立一個Service服務,並在onCreate方法中啟動Activity,程式碼如下:

public class ServiceT extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent i =new Intent(getApplicationContext(),ActivityD.class);
        startActivity(i);
    }
}

啟動應用並啟動Service服務,後報錯如下:

這裡寫圖片描述

從異常資訊我們可以看出,提示我們新增Intent.FLAG_ACTIVITY_NEW_TASK標誌位,所以我們程式碼必須改成如下:

public class ServiceT extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public void onCreate() {
        super.onCreate();
        Intent i =new Intent(getApplicationContext(),ActivityD.class);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(i);
    }
}
  • Intent.FLAG_ACTIVITY_SINGLE_TOP

該標誌位表示使用singleTop模式來啟動一個Activity,與在清單檔案指定android:launchMode="singleTop"效果相同。

  • Intent.FLAG_ACTIVITY_CLEAR_TOP

該標誌位表示使用singleTask模式來啟動一個Activity,與在清單檔案指定android:launchMode="singleTask"效果相同。

  • Intent.FLAG_ACTIVITY_NO_HISTORY

使用該模式來啟動Activity,當該Activity啟動其他Activity後,該Activity就被銷燬了,不會保留在任務棧中。如A-B,B中以這種模式啟動C,C再啟動D,則任務棧只有ABD。

  • Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

使用該標識位啟動的Activity不新增到最近應用列表,也即我們從最近應用裡面檢視不到我們啟動的這個activity。與屬性android:excludeFromRecents="true"效果相同。

5 URL Scheme跳轉協議

5.1 什麼URL Scheme?

Scheme是Android中的一種頁面跳轉協議,是一種非常好的實現機制,通過定義自己的scheme協議,可以非常方便跳轉app中的各個頁面;通過scheme協議,伺服器可以定製化告訴App跳轉那個頁面,可以通過通知欄訊息定製化跳轉頁面,可以通過H5頁面跳轉頁面等。

5.2 URL Scheme的應用場景

客戶端應用可以向作業系統註冊一個 URL scheme,該 scheme 用於從瀏覽器或其他應用中啟動本應用。通過指定的 URL 欄位,可以讓應用在被調起後直接開啟某些特定頁面,比如商品詳情頁、活動詳情頁等等。也可以執行某些指定動作,如完成支付等。也可以在應用內通過 html 頁來直接呼叫顯示 app 內的某個頁面。綜上URL Scheme使用場景大致分以下幾種:

  • 伺服器下發跳轉路徑,客戶端根據伺服器下發跳轉路 徑跳轉相應的頁面
  • H5頁面點選錨點,根據錨點具體跳轉路徑APP端跳轉具體的頁面
  • APP端收到伺服器端下發的PUSH通知欄訊息,根據訊息的點選跳轉路徑跳轉相關頁面
  • APP根據URL跳轉到另外一個APP指定頁面

5.3 URL Scheme協議格式

完整的URL Scheme協議格式:

xl://goods:8888/goodsDetail?goodsId=10011002
通過上面的路徑 Scheme、Host、port、path、query全部包含,基本上平時使用路徑就是這樣子的。

  • xl代表該Scheme 協議名稱
  • goods代表Scheme作用於哪個地址域
  • goodsDetail代表Scheme指定的頁面
  • goodsId代表傳遞的引數
  • 8888代表該路徑的埠號

5.4 URL Scheme如何使用?

  1. 在AndroidManifest.xml中對標籤增加設定Scheme。
 <activity
            android:name=".GoodsDetailActivity"
            android:theme="@style/AppTheme">
            <!--要想在別的App上能成功調起App,必須新增intent過濾器-->
            <intent-filter>
                <!--協議部分,隨便設定-->
                <data android:scheme="xl" android:host="goods" android:path="/goodsDetail" android:port="8888"/>
                <!--下面這幾行也必須得設定-->
                <category android:name="android.intent.category.DEFAULT"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.BROWSABLE"/>
            </intent-filter>
        </activity>
  1. 獲取Scheme跳轉的引數
Uri uri = getIntent().getData();
if (uri != null) {
    // 完整的url資訊
    String url = uri.toString();
    Log.e(TAG, "url: " + uri);
    // scheme部分
    String scheme = uri.getScheme();
    Log.e(TAG, "scheme: " + scheme);
    // host部分
    String host = uri.getHost();
    Log.e(TAG, "host: " + host);
    //port部分
    int port = uri.getPort();
    Log.e(TAG, "host: " + port);
    // 訪問路勁
    String path = uri.getPath();
    Log.e(TAG, "path: " + path);
    List<String> pathSegments = uri.getPathSegments();
    // Query部分
    String query = uri.getQuery();
    Log.e(TAG, "query: " + query);
    //獲取指定引數值
    String goodsId = uri.getQueryParameter("goodsId");
    Log.e(TAG, "goodsId: " + goodsId);
}
  1. 呼叫方式
    在網頁中:
<a href="xl://goods:8888/goodsDetail?goodsId=10011002">開啟商品詳情</a>

在原生應用中:

Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
  startActivity(intent);
  1. 如何判斷一個URL Scheme是否有效
PackageManager packageManager = getPackageManager();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isValid = !activities.isEmpty();
if (isValid) {
    startActivity(intent);
}

注:以上內容是由自己從網際網路收集整理、自己寫、及看書、看視訊等總結出來的筆記,如果借鑑的內容需要標識出來請私信我。