1. 程式人生 > >Android面試之Activity篇

Android面試之Activity篇

Activity知識體系圖

Activity是什麼?

  Activity實際上只是一個與使用者互動的介面而已。

1.Activity生命週期

1.1 Activity的4種狀態

  Active/Paused/Stopped/Killed

  Activie:當前Activity正處於執行狀態,指的是當前Activity獲取了焦點。

  Paused:當前Activity正處於暫停狀態,指的是當前Activity失去焦點,此時的Activity並沒有被銷燬,記憶體裡面的成員變數,狀態資訊等仍然存在,當然這個Activity也仍然可見,但是焦點卻不在它身上,比如被一個對話方塊形式的Activity獲取了焦點,或者被一個透明的Activity獲取了焦點,這都能導致當前的Activity處於paused狀態。

  Stopped:與paused狀態相似,stopped狀態的Activity是完全不可見的,但是記憶體裡面的成員變數,狀態資訊等仍然存在,但是也沒有被銷燬。

  Killed:已經被銷燬的Activity才處於killed狀態,它的記憶體裡面的成員變數,狀態資訊等都會被一併回收。

1.2 Activity的生命週期分析

正常情況下的生命週期:

  Activity啟動–>onCreate()–>onStart()–>onResume()

  點選home鍵回到桌面–>onPause()–>onStop()

  再次回到原Activity時–>onRestart()–>onStart()–>onResume()

  退出當前Activity時–>onPause()–>onStop()–>onDestroy()

詳細生命週期如下:

生命週期圖

1.啟動了一個Activity,通常是Intent來完成。啟動一個Activity首先要執行的回撥函式是onCreate(),通常在程式碼中你需要在此函式中繫結佈局,繫結控制元件,初始化資料等做一些初始化的工作。

2.即將執行Activity的onStart()函式,執行之後Activity已經可見,但是還沒有出現在前臺,無法與使用者進行互動。這個時候通常Activity已經在後臺準備好了,但是就差執行onResume()函數出現在前臺。

3.即將執行Activity的onResume()函式,執行之後Activity不止可見而且還會出現在前臺,可以與使用者進行互動啦。

4.由於Activity執行了onResume()函式,所以Activity出現在了前臺。也就是Activity處於執行狀態。

5.處於執行狀態的Activity即將執行onPause()函式,什麼情況下促使Activity執行onPause()方法呢?  [1]啟動了一個新的Activity  [2]返回上一個Activity  可以理解為當需要其他Activity,當前的Activity必須先把手頭的工作暫停下來,再來把當前的介面空間交給下一個需要介面的Activity,而onPause()方法可以看作是一個轉接工作的過程,因為螢幕空間只有那麼一個,每次只允許一個Activity出現在前臺進行工作。通常情況下onPause()函式不會被單獨執行,執行完onPause()方法後會繼續執行onStop()方法,執行完onStop()方法才真正意味著當前的Activity已經退出前臺,存在於後臺。

6.Activity即將執行onStop()函式,在“5”中已經說得很清楚了,當Activity要從前臺切換至後臺的時候會執行,比如:使用者點選了返回鍵,或者使用者切換至其他Activity等。

7.當前的Activity即將執行onDestory()函式,代表著這個Activity即將進入生命的終結點,這是Activity生命週期中的最後一次回撥生命週期,我們可以在onDestory()函式中,進行一些回收工作和資源的釋放工作,比如:廣播接收器的登出工作等。

8.執行完onDestory()方法的Activity接下來面對的是被GC回收,宣告生命終結。

9.很少情況下Activity才走“9”,網上一些關於對話方塊彈出後Activity會走“9”的說法,經過筆者驗證,在某個Activity內彈出對話方塊並沒有走“9”,所以網上大部分這樣說法的文章要麼是沒驗證,要麼直接轉載的,這個例子說明,實驗出真知,好了,不廢話了,那麼什麼情況下,Activity會走“9”呢?看看下面這位博主才是真的懂得“實驗出真知”的人:

10.當用戶在其他的Activity或者桌面回切到這個Activity時,這個Activity就會先去執行onRestart()函式,Restart有“重新開始”的意思,然後接下來執行onStart()函式,接著執行onResume()函式進入到執行狀態。

11.在“10”中講的很清楚了。

12.高優先順序的應用急需要記憶體,此時處於低優先順序的此應用就會被kill掉。

13.使用者返回原Activity。

下面來著重說明一下Activity每個生命週期函式: onCreate():  表示Activity正在被建立,這是Activity生命週期的第一個方法。通常我們程式設計師要在此函式中做初始化的工作,比如:繫結佈局,控制元件,初始化資料等。

onStart():  表示Activity正在被啟動,這時候的Activity已經被建立好了,完全過了準備階段,但是沒有出現在前臺,需要執行onResume()函式才可以進入到前臺與使用者進行互動。

onResume():  表示Activitiy已經可見了,並且Activity處於執行狀態,也就是Activity不止出現在了前臺,而且還可以讓使用者點選,滑動等等操作與它進行互動。

onPause():  表示Activity正在暫停,大多數情況下,Activity執行完onPause()函式後會繼續執行onStop()函式,造成這種函式呼叫的原因是當前的Activity啟動了另外一個Activity或者回切到上一個Activity。還有一種情況就是onPause()函式被單獨執行了,並沒有附帶執行onStop()方法,造成這種函式呼叫的原因很簡單,就是當前Activity裡啟動了類似於對話方塊的東東。

onStop():  表示Activity即將停止,我們程式設計師應該在此函式中做一些不那麼耗時的輕量級回收操作。

onRestart():  表示Activity正在重新啟動。一般情況下,一個存在於後臺不可見的Activity變為可見狀態,都會去執行onRestart()函式,然後會繼續執行onStart()函式,onResume()函數出現在前臺並且處於執行狀態。

onDestory():  表示Activity要被銷燬了。這是Activity生命中的最後一個階段,我們可以在onDestory()函式中做一些回收工作和資源釋放等,比如:廣播接收器的登出等。

異常情況下的生命週期:

什麼是異常情況呢? 情況1:資源相關的系統配置發生改變導致Activity被殺死並重新建立。

這裡寫圖片描述

可以從圖中看出當Activity發生意外的情況的時候,這裡的意外指的就是系統配置發生改變,Activity會被銷燬,其onPause,OnStop,onDestory函式均會被呼叫,同時由於Actiivty是在異常情況下終止的,系統會呼叫onSaveInstanceState來儲存當前Activity狀態。呼叫onSaveInstanceState的時機總會發生在onStop之前,至於會不會呼叫時機發生在onPause方法之前,那就說不定了,這個沒有固定的順序可言,正常情況下一般onSaveInstanceState不會被呼叫。當Activity被重新建立後,系統會呼叫onRestoreInstanceState,並且把Actiivty銷燬時onSaveInstanceState方法所儲存的Bundle物件作為引數傳遞給onRestoreInstanceState和onCreate方法。所以我們可以通過onRestoreInstanceState和onCreate方法來判斷Actiivty是否被重建了,如果被重建了,那麼我們就可以取出之前儲存的資料並恢復,從時序上來看,onRestoreInstanceState的呼叫時機發生在onStart之後。

同時,在onSaveInstanceState和onRestoreInstanceState方法中,系統自動為我們做了一定的恢復工作。當Activity在異常情況下需要重新建立時,系統會預設為我們儲存當前Activity的檢視結構。當Activity在異常情況下需要重新建立時,系統會預設為我們儲存當前Activity的檢視結構,並且在Activity重啟後為我們恢復這些資料,比如:文字框中使用者輸入的資料,ListView滾動的位置等,這些View相關的狀態系統都能夠預設為我們恢復。具體針對某一個特定的View系統 能為我們恢復哪些資料,我們可以檢視View的原始碼。和Activity一樣,每個View都有onSaveInstanceState和onRestoreInstanceState這兩個方法,看一下它們的具體實現,就能知道系統能夠自動為每個View恢復哪些資料。

關於儲存和恢復View層次結構,系統的工作流程是這樣的

首先Activity被意外終止時,Activity會呼叫onSaveInstanceState去儲存資料,然後Activity會委託Window去儲存資料,接著Window在委託它上面的頂級容器去儲存資料。頂級容器是一個ViewGroup,一般來說它很可能是DecorView。最後頂層容器再去一一通知它的子元素來儲存資料,這樣整個資料儲存過程就完成了。可以發現,這是一個典型的委託思想,上層委託下層,父容器去委託子元素去處理一件事情,這種思想在Android中有很多應用,比如:View的繪製過程,事件分發等都是採用類似的思想。至於資料恢復過程也是類似的,這樣就不再重複介紹了。

情況2:資源記憶體不足導致低優先順序的Activity被殺死。

首先,Activity有優先順序?你肯定懷疑,程式碼中都沒設定過啊!優先順序從何而來,其實這裡的Activity的優先順序是指一個Activity對於使用者的重要程度,比如:正在與使用者進行互動的Activity那肯定是最重要的。我們可以按照重要程度將Activity分為以下等級:

優先順序最高: 與使用者正在進行互動的Activity,即前臺Activity。

優先順序中等:可見但非前臺的Activity,比如:一個彈出對話方塊的Activity,可見但是非前臺執行。

優先順序最低:完全存在與後臺的Activity,比如:執行了onStop。

當記憶體嚴重不足時,系統就會按照上述優先順序去kill掉目前Activity所在的程序,並在後續通過onSaveInstanceState和onRestoreInstanceState來儲存和恢復資料。如果一個程序中沒有四大元件的執行,那麼這個程序將很快被系統殺死,因此,一些後臺工作不適合脫離四大元件獨立執行在後臺中,這樣程序更容易被殺死。比較好的方法就是將後臺工作放入Service中從而保證程序有一定的優先順序,這樣就不會輕易地被系統殺死。

總結: 上面分析了系統的資料儲存和恢復機制,我們知道,當系統配置發生改變之後,Activity會被重新建立,那麼有沒有辦法不重新建立呢?答案是有的,接下來我們就來分析這個問題。系統配置中有很多內容,如果某項內容發生了該變後,我們不想系統重新建立Activity可以給Activity指定configChanges屬性。比如我們不想讓Actiivty在螢幕旋轉的時候重新建立,就可以給configChanges屬性新增一些值,請繼續往下看。

1.3 一些特殊情況下的生命週期分析

1.3.1 Activity的橫豎屏切換

  與橫豎屏生命週期函式有關呼叫的屬性是"android:configChanges",關於它的屬性值設定影響如下:

  • orientation:消除橫豎屏的影響
  • keyboardHidden:消除鍵盤的影響
  • screenSize:消除螢幕大小的影響

  當我們設定Activity的android:configChanges屬性為orientation或者orientation|keyboardHidden或者不設定這個屬性的時候,它的生命週期會走如下流程:

   剛剛啟動Activity的時候:

   onCreate
   onStart
   onResume

   由豎屏切換到橫屏:

   onPause
   onSaveInstanceState //這裡可以用來橫豎屏切換的儲存資料
   onStop
   onDestroy
   onCreate
   onStart
   onRestoreInstanceState//這裡可以用來橫豎屏切換的恢復資料
   onResume

  橫屏切換到豎屏:

   onPause
   onSaveInstanceState
   onStop
   onDestroy
   onCreate
   onStart
   onRestoreInstanceState
   onResume

  當我們設定Activity的android:configChanges屬性為orientation|screenSize或者orientation|screenSize|keyboardHidden

   剛剛啟動Activity的時候:

   onCreate
   onStart
   onResume

   由豎屏切換到橫屏:
   
   什麼也沒有呼叫

   由橫屏切換到豎屏:

   什麼也沒有呼叫

  而且需要注意一點的是設定了orientation|screenSize屬性之後,在進行橫豎屏切換的時候呼叫的方法是onConfigurationChanged(),而不會回撥Activity的各個生命週期函式;

  當然在顯示中我們可以遮蔽掉橫豎屏的切換操作,這樣就不會出現切換的過程中Activity生命週期重新載入的情況了,具體做法是,在Activity中加入如下語句:

   android:screenOrientation="portrait" 始終以豎屏顯示 
   android:screenOrientation="landscape" 始終以橫屏顯示

  如果不想設定整個軟體遮蔽橫豎屏切換,只想設定遮蔽某個Activity的橫豎屏切換功能的話,只需要下面操作:

   Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);以豎屏顯示

   Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);以橫屏顯示

  最後提一點,當你橫豎屏切換的時候,如果走了銷燬Activity的流程,那麼需要儲存當前和恢復當前Activity的狀態的話,我們可以靈活運用onSaveInstanceState()方法和onRestoreInstanceState()方法。

1.3.2 什麼時候Activity單獨走onPause()不走onStop()?

  關於這個特殊情況,筆者在上面的生命週期圖解析的時候,貼了一個連結,這裡主要是檢驗你是否會了這個問題的答案,這裡筆者就不貼答案了,答案全在那個連結裡,你會了嗎?

1.3.3 什麼時候導致Activity的onDestory()不執行?

  當用戶後臺強殺應用程式時,當前返回棧僅有一個activity例項時,這時候,強殺,是會執行onDestroy方法的;當返回棧裡面存在多個Activity例項時,棧裡面的第一個沒有銷燬的activity執行會ondestroy方法,其他的不會執行;比如說:從mainactivity跳轉到activity-A(或者繼續從activity-A再跳轉到activity-B),這時候,從後臺強殺,只會執行mainactivity的onDestroy方法,activity-A(以及activity-B)的onDestroy方法都不會執行;

1.4 程序的優先順序

  前臺>可見>服務>後臺>空

  前臺:與當前使用者正在互動的Activity所在的程序。

  可見:Activity可見但是沒有在前臺所在的程序。

  服務:Activity在後臺開啟了Service服務所在的程序。

  後臺:Activity完全處於後臺所在的程序。

  空:沒有任何Activity存在的程序,優先順序也是最低的。

2.Android任務棧

  任務棧與Activity的啟動模式密不可分,它是用來儲存Activity例項的一種資料結構,Activity的跳轉以及回跳都與這個任務棧有關。詳情請看下面的Activity的啟動模式。

3.Activity的啟動模式

  Activity的啟動模式,你在初學期間一定很熟悉了吧!不管你是否熟悉還是不熟悉,跟隨筆者的思路把Activity的啟動模式整理一遍:

問題1:Activity為什麼需要啟動模式? 問題2:Activity的啟動模式有哪些?特性如何 問題3:如何給Activity選擇合適的啟動模式

問題1:Activity為什麼需要啟動模式?

  我們都知道啟動一個Activity後,這個Activity例項就會被放入任務棧中,當點選返回鍵的時候,位於任務棧頂層的Activity就會被清理出去,當任務棧中不存在任何Activity例項後,系統就回去回收這個任務棧,也就是程式退出了。這只是對任務棧的基本認識,深入學習,筆者會在之後文章中提到。那麼問題來了,既然每次啟動一個Activity就會把對應的要啟動的Activity的例項放入任務棧中,假如這個Activity會被頻繁啟動,那豈不是會生成很多這個Activity的例項嗎?對記憶體而言這可不是什麼好事,明明可以一個Activity例項就可以應付所有的啟動需求,為什麼要頻繁生成新的Activity例項呢?杜絕這種記憶體的浪費行為,所以Activity的啟動模式就被創造出來去解決上面所描述的問題。

問題2:Activity的啟動模式有哪些?特性如何

  Activity的啟動模式有4種,分別是:standard,singleTop,singleTask和singleInstance。下面一一作介紹:

1.系統預設的啟動模式:Standard   標準模式,這也是系統的預設模式。每次啟動一個Activity都會重新建立一個新的例項,不管這個例項是否存在。被建立的例項的生命週期符合典型情況下的Activity的生命週期。在這種模式下,誰啟動了這個Activity,那麼這個Activity就執行在啟動它的那個Activity的任務棧中。比如Activity A啟動了Activity B(B是標準模式),那麼B就會進入到A所在的任務棧中。有個注意的地方就是當我們用ApplicationContext 去啟動standard模式的Activity就會報錯,這是因為standard模式的Actiivty預設會進入啟動它的Activity所屬的任務棧中,但是由於非Activity型別的Context(如ApplicationContext)並沒有所謂的任務棧,所以這就會出現錯誤。解決這個問題的方法就是為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啟動的時候就會為它建立一個新的任務棧,這個時候啟動Activity實際上以singleTask模式啟動的,讀者可以自己仔細體會。

2.棧頂複用模式:SingleTop   在這種模式下,如果新的Activity已經位於任務棧的棧頂,那麼此Activity不會被重新建立,同時它的onNewIntent方法被回撥,通過此方法的引數我們可以取出當前請求的資訊。需要注意的是,這個Activity的onCreate,onStart不會被系統呼叫,因為它並沒有發生改變。如果新的Activity已經存在但不是位於棧頂,那麼新的Activity仍然會重新重建。舉個例子,假設目前棧內的情況為ABCD,其中ABCD為四個Activity,A位於棧低,D位於棧頂,這個時候假設要再次啟動D,如果D的啟動模式為singleTop,那麼棧內的情況依然為ABCD;如果D的啟動模式為standard,那麼由於D被重新建立,導致棧內的情況為ABCDD。

3.棧內複用模式:SingTask   這是一種單例例項模式,在這種模式下,只要Activity在一個棧中存在,那麼多次啟動此Activity都不會重新建立例項,和singleTop一樣,系統也會回撥其onNewIntent。具體一點,當一個具有singleTask模式的Activity請求啟動後,比如Activity A,系統首先尋找任務棧中是否已存在Activity A的例項,如果已經存在,那麼系統就會把A調到棧頂並呼叫它的onNewIntent方法,如果Activity A例項不存在,就建立A的例項並把A壓入棧中。舉幾個栗子:

  • 比如目前任務棧S1的情況為ABC,這個時候Activity D以singleTask模式請求啟動,其所需的任務棧為S2,由於S2和D的例項均不存在,所以系統會先建立任務棧S2,然後再建立D的例項並將其投入到S2任務棧中。
  • 另外一種情況是,假設D所需的任務棧為S1,其他情況如同上面的例子所示,那麼由於S1已經存在,所以系統會直接建立D的例項並將其投入到S1。
  • 如果D所需的任務棧為S1,並且當前任務棧S1的情況為ADBC,根據棧內複用的原則,此時D不會重新建立,系統會把D切換到棧頂並呼叫其onNewIntent方法,同時由於singleTask預設具有clearTop的效果,會導致棧內所有在D上面的Activity全部出棧,於是最終S1中的情況為AD。

  通過以上3個例子,你應該能比較清晰地理解singleTask的含義了。

4.單例項模式:SingleInstance   這是一種加強的singleTask模式,它除了具有singleTask模式所有的特性外,還加強了一點,那就是具有此種模式的Activity只能單獨位於一個任務棧中,換句話說,比如Activity A是singleInstance模式,當A啟動後,系統會為它建立一個新的任務棧,然後A獨自在這個新的任務棧中,由於棧內複用的特性,後續的請求均不會建立新的Activity,除非這個獨特的任務棧被系統銷燬了。

對於SingleInstance,面試時你有說明它的以下幾個特點:

(1)以singleInstance模式啟動的Activity具有全域性唯一性,即整個系統中只會存在一個這樣的例項。 (2)以singleInstance模式啟動的Activity在整個系統中是單例的,如果在啟動這樣的Activiyt時,已經存在了一個例項,那麼會把它所在的任務排程到前臺,重用這個例項。 (3)以singleInstance模式啟動的Activity具有獨佔性,即它會獨自佔用一個任務,被他開啟的任何activity都會執行在其他任務中。 (4)被singleInstance模式的Activity開啟的其他activity,能夠在新的任務中啟動,但不一定開啟新的任務,也可能在已有的一個任務中開啟。

換句話說,其實SingleInstance就是我們剛才分析的SingleTask中,分享Activity為棧底元素的情況。

  • 總結 上面介紹了4種啟動模式,這裡需要指出一種情況,我們假設目前有2個任務棧,前臺任務棧的情況為AB,而後臺任務棧的情況為CD,這裡假設CD的啟動模式均為singleTask。現在請求啟動D,那麼整個後臺任務棧都會被切換到前臺,這個時候整個後退列表變成了ABCD。當用戶按back鍵的時候,列表中的Activity會一一出棧,如下圖1所示:

注意: 前臺任務棧:就是指和使用者正在互動的應用程式所在的任務棧。 後臺任務棧:就是指處於後臺的應用程式所在的任務棧。

圖1 任務棧演示圖1

  如果不是請求的D而是請求的C,那麼情況就不一樣了,如下圖2所示:

圖2 任務棧演示圖2

  如何指定活動的啟動模式呢?在AndroidManifest.xml檔案當註冊活動的程式碼中去指定 比如:我要把MainActivity活動的啟動模式指定為singleInstance模式

設定Activity的啟動模式程式碼截圖

也可以在程式碼中指定:

Intent pack = new Inten(MCPersonalCenterActivity.this,MCGiftsCenterActivity.class);
pack.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(pack);

4.Activity元件之間的通訊

1.Activity->Activity

[1]Intent/Bundle 這種方式多用於Activity之間傳遞資料。示例程式碼如下:

    //首先建立一個Bundle物件
    Bundle bundle = new Bundle();
    bundle.putString("data_string","資料");
    bundle.putInt("data_int",10);
    bundle.putChar("da_char",'a');

    //然後建立一個Intent物件
    Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
    intent.putExtras(bundle);
    startActivity(intent);

[2]類靜態變數 在Activity內部定義靜態的變數,這種方式見於少量的資料通訊,如果資料過多,還是使用第一種方式。示例程式碼如下:

public class FirstActivity extends AppCompatActivity {

    //宣告為靜態
    static boolean isFlag = false;  

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        //首先建立一個Bundle物件
        Bundle bundle = new Bundle();
        bundle.putString("data_string","資料");
        bundle.putInt("data_int",10);
        bundle.putChar("da_char",'a');

        //然後建立一個Intent物件
        Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
        intent.putExtras(bundle);
        startActivity(intent);


    }

}

[3]全域性變數 建立一個類,裡面定義一批靜態變數,Activity之間通訊都可以訪問這個類裡面的靜態變數,這就是全域性變數。這種方式筆者就不給程式碼了。

2.Activity->Service [1]繫結服務的方式,利用ServiceConnection這個介面。

首先我們需要在要繫結的服務中宣告一個Binder類

public class MyService1 extends Service {

    public String data = "";

    public MyService1() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return new Binder();
    }



    public class Binder extends android.os.Binder{

        public void sendData(String data){

            MyService1.this.data = data;
        
        }

    }

}

然後我們讓Activity實現ServiceConnection這個介面,並且在onServiceConnected方法中獲取到Service提供給Activity的Binder例項物件,通過這個物件我們就可以與Service進行通訊可以通過上述程式碼的Binder類中的sendData()方法進行通訊。

public class ServiceBindActivity extends AppCompatActivity implements ServiceConnection,View.OnClickListener {

    private Button bt0,bt1,bt2;

    public MyService1.Binder binder = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service_bind);

        bt0 = findViewById(R.id.bt0);
        bt1 = findViewById(R.id.bt1);
        bt2 = findViewById(R.id.bt2);

        bt0.setOnClickListener(this);
        bt1.setOnClickListener(this);
        bt2.setOnClickListener(this);


    }

    @Override
    protected void onDestroy() {
       super.onDestroy();
       unbindService(this);
    }

    //這個是服務繫結的時候呼叫
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        binder = (MyService1.Binder) iBinder;
    }

    //這個是服務解綁的時候呼叫
    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }

    @Override
    public void onClick(View view) {

        switch (view.getId()){

            case R.id.bt0:

                //繫結服務
                Intent intent = new Intent(ServiceBindActivity.this,MyService1.class);
                bindService(intent,this, Context.BIND_AUTO_CREATE);

               break;

            case R.id.bt1:

                //通過binder物件來和Service進行通訊
                if(binder != null)
                binder.sendData("bt1");

                break;
            case R.id.bt2:

                //通過binder物件來和Service進行通訊
                if(binder != null)
                binder.sendData("bt2");

               break;

        }

    }
}

也不一定非要筆者這種寫法,你也可以有自己的寫法,但核心部分都一樣。

[2]Intent

這種方式很簡單,我們在啟動和停止Service時所呼叫的方法都需要傳入一個Intent例項物件,通過這個傳入的Intent物件,我們就可以與Service進行通訊。示例程式碼如下:

Activity程式碼是這樣的:

public class ServiceStartActivity extends AppCompatActivity implements View.OnClickListener {

    private Button bt0,bt1;

    private Intent intent ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service_start);

        intent = new Intent(this, MyService2.class);
    
        bt0 = findViewById(R.id.bt0);
        bt1 = findViewById(R.id.bt1);

        bt0.setOnClickListener(this);
        bt1.setOnClickListener(this);


    }

    @Override
    public void onClick(View view) {

        switch (view.getId()){

            case R.id.bt0:

                //開啟服務並且傳遞資料
                intent.putExtra("data_stirng","string資料");
                startActivity(intent);
            
                break;

            case R.id.bt1:

                //結束服務
                stopService(intent);
            
                break;

        }

    }
}

Service中的程式碼是這樣的:

public class MyService2 extends Service {

    public String data = "";

    public MyService2() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //得到Activity傳遞過來的資料
        data = intent.getStringExtra("data_string");
        return super.onStartCommand(intent, flags, startId);
    }
}

這種通訊方式的缺點顯而易見,那就是隻能傳遞少量的資料。

[3]CallBack + Handler,監聽服務的程序變化

Service中的程式碼:

public class MyService3 extends Service {

    //在Service中如果要進行耗時任務,可以通過CallBack介面提供的方法與Activity進行通訊
    public Callback callback;

    public MyService3() {
    }

    @Override
    public IBinder onBind(Intent intent) {
       // TODO: Return the communication channel to the service.
       return new Binder();
    }

    public void setCallBack(CallBack callBack){
        this.callback = callback;
    }

    public Callback getCallback() {
        return callback;
    }

    public interface CallBack{
        void onDataChange(String data);
    }

    public class Binder extends android.os.Binder{

        public MyService3 getMyService3(){
            return MyService3.this;
        }

    }

}

Activity中的程式碼:

public class ServiceBind2Activity extends AppCompatActivity implements ServiceConnection{



    public MyService3.Binder binder = null;

    private Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            Bundle bundle = msg.getData();
            String data_string = bundle.getString("data_string");

            //接下來就是更新ui

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service_bind2);
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

        binder = (MyService3.Binder) iBinder;
        binder.getMyService3().setCallBack(new MyService3.CallBack() {

            //此方法提供給MyService3在子執行緒中呼叫
            @Override
            public void onDataChange(String data) {
                Message message = new Message();
                Bundle bundle = new Bundle();
                bundle.putString("data_string","String資料");
                message.setData(bundle);
                //通過Handler進行非同步通訊,不過耗時操作放在MyService3中
                handler.sendMessage(message);
            }
        });

    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }


}

可能第一次看到這段程式碼的你很懵逼吧,其實很簡單,當ServiceBind2Activity去繫結服務MyService3的時候,那麼在Activity中的onServiceConnected()方法被呼叫,此時位於MySerivce3的CallBack介面引用被例項化,並且onDataChange()方法被實現,可以看到裡面是一段Handler通訊的程式碼,不錯,這個方法是為MyService3做耗時操作呼叫的,筆者沒有在MyService3中寫耗時操作的程式碼,不過說到這裡你應該明白了這種通訊方式的好處了吧,也印證了標題:監聽服務的程序變化。

3.Activity->Fragment [1]Bundle 在建立Fragment例項的時候,呼叫方法setArguments將一個Bundle物件傳遞給Fragment,然後在Fragment中先去判斷是否和當前Activity繫結上了,如果繫結上了,就可以拿出這個Bundle中的資料啦。示例程式碼如下:

在Activity中程式碼是這樣的:

    //首先建立一個Bundle物件
    Bundle bundle = new Bundle();
    bundle.putString("data_string","資料");
    bundle.putInt("data_int",10);
    bundle.putChar("da_char",'a');
    
    Fragment fragment = new MyFragment1();
    fragment.setArguments(bundle);

在MyFragment1中程式碼是這樣的:

if(isAdded()){//這裡判斷是否Fragment和Activity進行了繫結

   Bundle bundle = getArguments();
   String data_string = bundle.getString("data_string");
   String data_int = bundle.getInt("data_int");
   String data_char = bundle.getChar("data_char");


}

  對於這個isAdded()方法筆者還需要提出一點,為什麼要這麼寫呢?因為如果這個Fragment沒有和Activity繫結的話,那麼那個Bundle物件是無法從Activity傳遞給Fragment的,因此這種寫法是必須的。

[2]直接進行方法呼叫 在Activity裡通過Fragment的引用,可以直接呼叫Framgent中的定義的任何方法。示例程式碼如下:

    MyFragment1 myFragment1 = new MyFragment1();
    myFragment.toString("傳送的string資料");

5.scheme跳轉協議

  Android中的scheme是一種頁面內跳轉協議,通過自定義scheme協議,可以非常方便的跳轉到app中的各個頁面,通過scheme協議,伺服器可以定製化告訴app跳轉到哪個頁面,可以通過通知欄訊息定製化跳轉頁面,可以通過H5頁面跳轉到相應頁面等等。

6.原始碼解析startActivity幹了什麼?