1. 程式人生 > >Android Service淺析(上)

Android Service淺析(上)

什麼是Service?

Service作為構建應用的四大元件之一,它用來執行長時間的後臺操作且不使用使用者介面,比如網路事務處理、檔案I/O的讀寫、音樂的播放。但是你仔細考慮這個定義可能會產生一個疑問:在Java中並沒有提供所謂的Service元件,一樣要完成這些功能,為什麼不直接在其它元件,如Activity中直接新增這些後臺程式碼呢?
這就要提到Service的其它的特性:
 1. 服務可以由其它元件啟動,如Activity,在該活動銷燬或者應用關閉的情況,服務依舊可以在後臺執行。而如果在Activity開啟一個子執行緒來處理這些後臺程式碼,那麼在該活動銷燬後,所在程序變為空程序,系統在記憶體不足時會優先終止該程序,該程序中所有子執行緒就會被終止。假如你在一個app中下載檔案,在關閉該app時候,如果下載任務關閉,你可能不希望的這樣,更多是在手機狀態列下開啟一個獨立的下載任務,即使在該app關閉後,下載工作依舊繼續;再以音樂播放器為例,你會發現通過back鍵退出應用時,音樂播放並沒有停止,也就是說並不是真正的關閉程式,而這正是Service元件在後臺執行的效果,雖然這種方式很容易實現一些隱蔽性的工作,但是通過良好的程式碼設計,能夠很好的避免這些問題,而且實際開發中它確實有很多應用場景。

 2. Android的應用框架鼓勵共享應用的功能及資料,而元件是實現該目標的重要載體,服務作為一個元件,可以被其它元件啟動或者是相互通訊,甚至是跨應用元件之間的通訊;而如果是在Activity中定義子執行緒的方式,其它元件很難獲取到它的例項並且控制它,特別是在建立該子執行緒的元件被銷燬的情況,無法再獲取它的例項。而Service卻可以做到,比如音樂播放器就首先開啟一個後臺播放的功能,然後其它元件可以繫結到該例項,並且與之通訊,實現播放功能的控制。

當然你可能會想既然Service有怎麼多的優點,那麼以後所有多程序的工作都通過Service元件去處理,這有點矯枉過正,你需要自己去判斷需要執行的工作是否需要獨立在後臺執行或實現元件通訊,如果只需要在該元件執行期間才進行,就可以在該元件中使用多執行緒,這樣該元件銷燬時下載中止或者停止音樂播放。

Service的誤區.

服務在其託管程序的主執行緒中執行,它既不建立自己的執行緒,也不在單獨的程序中執行(除非另行指定)。 這意味著,如果服務將執行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或聯網),則應在服務內建立新執行緒來完成這項工作。通過使用單獨的執行緒,可以降低發生“應用無響應”(ANR) 錯誤的風險,而應用的主執行緒仍可繼續專注於執行使用者與 Activity 之間的互動。

Service分為兩種形式:

  • 啟動服務
    當應用元件(如 Activity)通過呼叫 startService() 啟動服務時,服務即處於“啟動”狀態。一旦啟動,服務即可在後臺無限期執行,即使啟動服務的元件已被銷燬也不受影響。 已啟動的服務通常是執行單一操作,而且不會將結果返回給呼叫方。
  • 繫結服務
    當應用元件通過呼叫 bindService() 繫結到服務時,服務即處於“繫結”狀態。繫結服務提供了一個客戶端-伺服器介面,允許元件與服務進行互動、傳送請求、獲取結果,甚至是利用程序間通訊 (IPC) 跨程序執行這些操作。 僅當與另一個應用元件繫結時,繫結服務才會執行。 多個元件可以同時繫結到該服務,但全部取消繫結後,該服務即會被銷燬。

Service的基本用法

  1. 建立啟動服務
    和Activity類似,要建立服務,必須建立 Service 的子類(或使用它的一個現有子類)。在實現中,您需要重寫一些回撥方法,包括onCreate()、onStartCommand()、onDestroy(),程式碼示例如下:
package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;


public class MyService extends Service {

    public static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate() executed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy() executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

在回撥方法中添加了一條列印語句,另外它們都呼叫了超類實現,這其實不是硬性規定,不同於Activity,繼承Service可以不呼叫超類實現,因為以上程式碼是Android Studio自動生成的,程式碼也可以如下編寫:

package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;


public class MyService extends Service {

    public static final String TAG = "MyService";

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate() executed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() executed");
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy() executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

其實你檢視Service類的原始碼,發現onCreate()和onDestroy()中沒有任何執行程式碼,所以super.onCreate()和super.onDestroy()是可有可無的,而onStartCommmand()方法需要返回一個整型數值,Service類中提供了幾個常量,分別表示了不同含義,後面具體介紹,所以你可以呼叫父類的方法返回一個常量值,也可以自己直接返回一個常量,比如上面程式碼的中Service.START_STICKY。而onBind()雖然不是啟動服務時的回撥方法,但是它是一個抽象方法(Service是一個抽象類),所以必須提供方法體的實現,如果沒有具體實現,可以返回null。

方法介紹:

  • onCreate()
    首次建立服務時,系統將呼叫此方法來執行一次性設定程式(在呼叫 onStartCommand() 或 onBind() 之前)。如果服務已在執行,則不會呼叫此方法。

  • onStartCommand()
    當另一個元件(如 Activity)通過呼叫 startService() 請求啟動服務時,系統將呼叫此方法。一旦執行此方法,服務即會啟動並可在後臺無限期執行。 如果您實現此方法,則在服務工作完成後,需要由您通過呼叫 stopSelf() 或 stopService() 來停止服務。

  • onBind()
    當另一個元件想通過呼叫 bindService() 與服務繫結(例如執行 RPC)時,系統將呼叫此方法。在此方法的實現中,您必須通過返回 IBinder 提供一個介面,供客戶端用來與服務進行通訊。請務必實現此方法,但如果您並不希望允許繫結,則應返回 null。

  • onDestroy()
    當服務不再使用且將被銷燬時,系統將呼叫此方法。服務應該實現此方法來清理所有資源,如執行緒、註冊的偵聽器、接收器等。 這是服務接收的最後一個呼叫。

接著在AndroidManifest.xml中宣告該服務元件,程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MyService"></service>
    </application>

</manifest>

啟動服務:您可以通過將 Intent(指定要啟動的服務)傳遞給 startService(),從 Activity 或其他應用元件啟動服務。Android 系統呼叫服務的 onStartCommand() 方法,並向其傳遞 Intent。
開啟佈局檔案activity_main.xml,新增兩個按鈕分別於啟動服務、停止服務,程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
    android:id="@+id/start"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start MyService" />

<Button
    android:id="@+id/stop"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Stop MyService" />

</LinearLayout>

在活動檔案MainActivity.class中分別為兩個按鈕註冊監聽事件,分別執行啟動服務和停止服務操作,程式碼如下:

package com.example.myapplication;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

    public static final String TAG = "MainActivity";
    private Button start;
    private Button stop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,MyService.class);
                startService(intent);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,MyService.class);
                stopService(intent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy() executed");
    }
}

執行程式,點選Start MyService按鈕,可以看到LogCat的列印日誌如下:
這裡寫圖片描述
它會執行onCreate()、onStartCommand()方法,如果再多次點選Start MyService按鈕,結果如下:
這裡寫圖片描述
它不會再執行onCreate(),因為通過第一次的啟動,服務已經處於執行狀態,所以以後每次執行startService()都只回調onStartCommand()方法。

停止服務:啟動服務必須管理自己的生命週期。也就是說,除非系統必須回收記憶體資源,否則系統不會停止或銷燬服務,而且服務在 onStartCommand() 返回後會繼續執行。因此,服務必須通過呼叫 stopSelf() 自行停止執行,或者由另一個元件通過呼叫 stopService() 來停止它。這裡我們通過點選Stop MyService按鈕來停止服務,Logcat的列印日誌如下:
這裡寫圖片描述

執行了onDestory()表示服務銷燬。接著我們測試在關閉MainActivity活動時,服務是否會銷燬:首先再次點選Start MyService按鈕啟動服務,按back鍵退出MainActivity活動,檢視Logcat的列印日誌如下:
這裡寫圖片描述
你可能會看到“onDestroy() executed”字樣,但是你要注意到前面的TAG是“MainActivity”,而不是“MyService”,它表示MainActivity已經被銷燬了(這部分程式碼在上面MainActivity.class末尾部分),但是MyService並沒有執行onDestroy()方法,也就表示它可以在啟動它的活動被銷燬的情況下繼續執行,所以我們應該主動停止服務。

但是在實際開發過程中,我們不會編寫上面的程式碼,因為上面的服務是在主執行緒(也就是所謂的UI執行緒)中執行,而服務往往需要執行長時間的操作,這會導致主執行緒的阻塞,應用出現ANR(應用無響應)錯誤,測試程式碼如下:
在MainActivity.class的onCreate()方法中新增一行程式碼,列印當前執行緒的id:

Log.d(TAG, "MainActivity Thread id is " + Thread.currentThread().getId());

在MyService.class的onCreate()方法也新增一行程式碼,列印當前執行緒的id:

Log.d(TAG, "MyService Thread id is " + Thread.currentThread().getId());

重新執行程式,並點選Start MyService按鈕,Logcat列印日誌如下:
這裡寫圖片描述
結果表示它們在同一個執行緒。

在MyService的onStartCommand()新增一行,讓當前執行緒睡20S,程式碼如下:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        try {
            Thread.sleep(20 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return START_STICKY;
    }

重複以上操作,結果如下:
這裡寫圖片描述
頁面出現ANR錯誤,所以一般都需要在onStartCommand中開啟子執行緒來處理,程式碼格式如下:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                /*
                 * 實際的業務操作,比如上傳下載等
                 */
                stopSelf();//主動關閉服務,而不是通過stopService()的方式
            }
        }).start();

        return START_STICKY;
    }

但是你可能會提出的一個疑問:既然這類程式碼的編寫都有固定的格式,能不能提供一個類,封裝這些基本操作,減少編寫的重複性,Android很好的考慮到這一點,它提供一個IntentService類:這是 Service 的子類,它使用工作執行緒逐一處理所有啟動請求。如果不要求服務同時處理多個請求,這是最好的選擇。 只需實現 onHandleIntent() 方法即可,該方法會接收每個啟動請求的 Intent,使你能夠執行後臺工作。它封裝瞭如下操作:

  • 提供 onStartCommand() 的預設實現。
  • 提供 onBind() 的預設實現(返回 null)。
  • 建立工作佇列,通過onStartCommand()方法接收到所有客戶端傳送的Intent物件。
  • 建立單獨的工作執行緒來執行onHandleIntent()方法,並處理工作佇列逐一發送的Intent物件,所以開發者無須處理多執行緒問題。
  • 在處理完所有啟動請求後停止服務,因此開發者永遠不必呼叫 stopSelf()。

綜上所述,您只需實現 onHandleIntent() 來完成客戶端提供的工作即可。(不過,您還需要為服務提供小型建構函式。),程式碼示例如下:

package com.example.myapplication;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;


public class MyIntentService extends IntentService {

    public static final String TAG = "MyIntentService";

    //必須提供一個建構函式,呼叫父類IntentService(String)構造方法,該字串用來標識建立的執行緒名稱
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        //列印執行緒的id
        Log.d(TAG, "MyIntentService Thread id is " + Thread.currentThread().getId());
        try {
            Thread.sleep(20*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d(TAG, "Finished");

    }
}

按照上面普通Service的操作方式,包括註冊服務、啟動服務,結果如下:
這裡寫圖片描述
程式沒有出現ANR錯誤,因為它預設建立新的執行緒來執行onHandleIntent()方法。
需要說明的是,這與上面普通Service開啟多執行緒的方式還是有所區別,它是通過非同步訊息機制來處理每一個Intent請求,同一時刻只能有一個Intent請求會被處理;而在上面普通Service中,則每次Intent請求都會建立一個執行緒來處理,是同時處理多個請求,但是這種多執行緒情況處理可能比較危險,具體可以檢視Service原始碼和Google API Guide。

onStartCommand()的返回值
onStartCommand() 方法必須返回整型數。整型數是一個值,用於描述系統應該如何在服務終止的情況下繼續執行服務,從 onStartCommand() 返回的值必須是以下常量之一:

  • START_NOT_STICKY
    如果系統在 onStartCommand() 返回後終止服務,則除非有掛起 Intent 要傳遞,否則系統不會重建服務。這是最安全的選項,可以避免在不必要時以及應用能夠輕鬆重啟所有未完成的作業時執行服務。

  • START_STICKY
    如果系統在 onStartCommand() 返回後終止服務,則會重建服務並呼叫 onStartCommand(),但絕對不會重新傳遞最後一個 Intent。相反,除非有掛起 Intent 要啟動服務(在這種情況下,將傳遞這些 Intent ),否則系統會通過空 Intent 呼叫 onStartCommand()。這適用於不執行命令、但無限期執行並等待作業的媒體播放器(或類似服務)。

  • START_REDELIVER_INTENT
    如果系統在 onStartCommand() 返回後終止服務,則會重建服務,並通過傳遞給服務的最後一個 Intent 呼叫 onStartCommand()。任何掛起 Intent 均依次傳遞。這適用於主動執行應該立即恢復的作業(例如下載檔案)的服務。

上面的內容來於官網的摘錄,但是關於裡面的終止服務是什麼意思?首先它不是指正常stopService()和stopSelf()方法來中止服務。而是一些非正常的方式終止服務,比如服務有可能在記憶體過低的情況被Android系統強制停止並供有使用者焦點的Activity使用,因為它跟使用者的聯絡更加直接和密切,所以它的優先順序是最高的。而服務之間的優先順序分別是:前臺服務>繫結服務>啟動服務,前臺服務幾乎永不終止,關於前臺服務和繫結服務後面介紹。但是終止的服務,一旦資源變得再次可用,系統就有可能會重啟服務,而是否啟動及啟動後的動作就取決於onStartCommand() 返回的值。而有什麼方式可以測試這種行為呢,我發現在Overview Screen,即檢視最近開啟程式的介面,如下圖:
這裡寫圖片描述
通過關閉該任務介面時,可以觸發以上行為。
關於MainActivity的程式碼如下:

package com.example.myapplication;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

    public static final String TAG = "MainActivity";
    private Button start;
    private Button stop;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);

        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,MyService.class);
                //通過intent傳遞一個字串資料
                intent.putExtra("data","This is data");
                startService(intent);
            }
        });

        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,MyService.class);
                stopService(intent);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy() executed");
    }
}

相對於前面的程式碼只添加了一行程式碼,通過Intent的putExtra()方法來傳遞一個字串資訊。

而MyService的程式碼如下:

package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;


public class MyService extends Service {

    public static final String TAG = "MyService";

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate() executed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand() executed");
        //讀取intent中的資料
        String data = intent.getStringExtra("data");
        Log.d(TAG, data);
        //返回值設定為START_NOT_STICKY
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy() executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

在MyService中新增兩行程式碼,讀取資訊,並打印出來。

啟動程式,點選Start MyService按鈕,然後開啟Overview Screen,通過右上角的“x”關閉該應用的頁面,LogCat的列印日誌如下:
這裡寫圖片描述
這似乎沒有任何特別的地方,實際也上確實如此,但是你如果把MyService中onStartCommand()的返回值改為“START_STICKY“,重複以上操作,LogCat的列印日誌如下:
這裡寫圖片描述
並且手機彈出如下頁面:
這裡寫圖片描述
把值該為”START_REDELIVER_INTENT“,重複以上操作,注意:在Overview Screen中關閉該應用頁面後需要等待一會,LogCat的列印日誌如下:
這裡寫圖片描述
我想你已經發現問題了:在第二張截圖和第四張截圖中,MyService又執行了onCreate()和onStartCommand()方法,這表示MyService服務又重新建立了,而從第一張截圖中可知它並沒有重啟,如果你再對照上面的關於不同返回值的含義就能清楚其中的道理。另外,通過模擬器或者是手機你可以檢視應用的程序是否關閉:
這裡寫圖片描述

個人理解:首先在Overview Screen中關閉應用的頁面,會關閉應用所屬的程序,從而導致寄宿在該程序上的服務也會被關閉,但是Android系統記憶體足夠,所以會根據onStartCommand的返回值來判斷是否需要重啟Service,如為START_NOT_STICKY,在沒有掛起Intent要傳遞的情況,它是不會重建服務的,所謂的掛起,是指如果由佇列來接收和分發Intent時,佇列還存在沒有分發的Intent時,服務就被系統終止了。而上面的程式碼只發送了一個Intent物件,且已經處理結束,所以它並不會重建服務。而值為START_STICKY和START_REDELIVER_INTENT,則事先不管是否存在掛起的Intent,都會重建服務,它們的區別就是在沒有掛起的Intent時,前者會執行onStartCommand()方法,但把空Intent傳遞給方法,後者會把最後一次的Intent傳遞給方法。如有誤,希望大神指點。

啟動服務的反饋方式
服務可以在後臺默默的執行而不打擾使用者,但是你既可以把它看作優點也可以看作缺點,很多手機使用者都會以為關閉應用就是關閉了一切,如果讓他了解到服務的機制,以後會對安裝一個應用顯得很謹慎。而很多軟體正是利用了服務的特點,在後臺偷偷開啟了很多使用者難以察覺的程式,比如與伺服器建立永久連線,實時檢測軟體版本,一旦版本更新,推送通知給使用者。

雖然服務的執行不需要使用者介面,但是有時候我們為了服務在記憶體不足的情況下,不被系統終止或者主動讓使用者意識到服務的存在,會讓服務成為前臺服務。前臺服務必須為狀態列提供通知,狀態列位於“正在進行”標題下方,這意味著除非服務停止或從前臺刪除,否則不能清除通知。就比如常見的音樂播放、天氣預報、檔案下載,它們通過狀態列區域,以通知的形式讓使用者時刻感受到該程式在執行,而且相對於開啟應用,通過狀態列來開啟應用似乎更加便捷,最重要的是你並不希望音樂突然停止播放。

除了這種重量級的方式,我們可能會需要在後臺服務進行到某個節點,傳送資訊給使用者,這是可以使用 Toast 通知或狀態列通知來通知使用者所發生的事件。
Toast 通知是指出現在當前視窗的表面、片刻隨即消失不見的訊息,而狀態列通知則在狀態列提供內含訊息的圖示,使用者可以選擇該圖示來採取操作(例如啟動 Activity)。
通常,當某些後臺工作已經完成(例如檔案下載完成)且使用者現在可以對其進行操作時,狀態列通知是最佳方法。 當用戶從展開檢視中選定通知時,通知即可啟動 Activity(例如檢視已下載的檔案)。

前臺服務與通知的區別就是:一個是實時性的,另一個是臨時性,雖然它們在外形來說,是完全一樣的,都是通知的形式,但是前臺服務所關聯的通知,它的生命週期是與服務休慼與共的,而通知發出後,就是任人宰割的單身漢。

第三種情況就是當一個活動啟動服務後,服務是無法直接返回資料給活動的,如果需要在服務進行了某個操作後,能夠反應到活動介面上怎麼去實現,這就需要通過廣播機制來實現了,可以在服務中傳送一個廣播,在活動中動態註冊一個廣播接收器,接受到服務傳送的廣播即對使用者介面進行更新。

下面分別以程式碼的形式,演示這三種反饋方式:

1、 通知的方式.
在MyService中修該的程式碼如下

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stopSelf();
            }
        }).start();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Download is complete", Toast.LENGTH_SHORT).show();
        Log.d(TAG, "onDestroy() executed");
    }
   @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stopSelf();
            }
        }).start();
        return START_STICKY;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onDestroy() {
        //獲取通知管理器物件,它屬於系統服務
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        //通過構建器來建立通知物件,構建器原理見Effective Java第2條.
        Notification.Builder builder = new Notification.Builder(this);
        builder.setTicker("有新訊息");
        builder.setContentTitle("下載通知");
        builder.setContentText("下載任務已完成,請檢視!");
        builder.setSmallIcon(R.mipmap.ic_launcher);
        //把聲音、震動、閃光設定為預設
        builder.setDefaults(Notification.DEFAULT_ALL);
        //通過pendingIntent設定通知的開啟動作
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        builder.setContentIntent(pi);
        //設定開啟通知後,通知自動消失
        builder.setAutoCancel(true);
        //建立通知物件
        Notification notification = builder.build();
        //通過系統通知服務傳送自定義通知
        manager.notify(1, notification);

        Log.d(TAG, "onDestroy() executed");
    }

執行程式,點選Start MyService按鈕,系統在2s後,分別以Toast和通知的形式傳送資訊給手機。
這裡寫圖片描述
這裡寫圖片描述
2、前臺服務

   @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onCreate() {
        //通過構建器來建立通知物件,構建器原理見Effective Java第2條.
        Notification.Builder builder = new Notification.Builder(this);
        builder.setTicker("有新訊息");
        builder.setContentTitle("下載通知");
        builder.setContentText("下載任務已完成,請檢視!");
        builder.setSmallIcon(R.mipmap.ic_launcher);
        //把聲音、震動、閃光設定為預設
        builder.setDefaults(Notification.DEFAULT_ALL);
        //通過pendingIntent設定通知的開啟動作
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        builder.setContentIntent(pi);
        //建立通知物件
        Notification notification = builder.build();
        startForeground(1,notification);

    }

在onCreate()方法中,首先建立通知物件,但是與傳送通知不同,前臺服務程式碼中並沒有獲取系統通知管理服務,而是通過最後一個方法startForeground(id,notification)來把當前服務物件與通知繫結到一起,它與通知的最大區別就是在服務終止後,系統會關閉該通知,而通知則是獨立的,沒有關聯性的。
這裡寫圖片描述
3、廣播的方式
MyService程式碼如下:

package com.example.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;


public class MyService extends Service {

    public static final String UPDATE_ACTION = "com.example.myapplication.action.UPDATE_ACTION";

    @Override
    public void onCreate() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stopSelf();
            }
        }).start();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Intent intent = new Intent();
        intent.setAction(UPDATE_ACTION);
        intent.putExtra("isCompleted",true);
        //傳送廣播
        sendBroadcast(intent);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

activity_main.xml程式碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start MyService"/>

    <TextView
        android:id="@+id/text_view"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

MainActivity程式碼如下:

package com.example.myapplication;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    private Button start;
    private TextView textView;
    private MyReceiver receiver;
    private IntentFilter filter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,MyService.class);
                startService(intent);
            }
        });
        textView = (TextView) findViewById(R.id.text_view);

        //動態註冊廣播接收器
        receiver = new MyReceiver();
        filter = new IntentFilter();
        filter.addAction(MyService.UPDATE_ACTION);
        registerReceiver(receiver, filter);

    }
    //廣播接收器必須要定義在Activity內部:利用內部類可以訪問外部類屬性和方法的特性。
    class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            boolean isCompleted = intent.getBooleanExtra("isCompleted",false);
            if(isCompleted){
                textView.setText("下載已完成!");
            }
        }
    }

    @Override
    protected void onDestroy() {
        //在活動關閉時,登出廣播接收器
        unregisterReceiver(receiver);
        super.onDestroy();
    }
}

執行程式,點選Start MyService按鈕,大約過了5S以後,介面下方空白處顯示“下載已完成”,如下圖:
這裡寫圖片描述

相關推薦

Android Service淺析

什麼是Service? Service作為構建應用的四大元件之一,它用來執行長時間的後臺操作且不使用使用者介面,比如網路事務處理、檔案I/O的讀寫、音樂的播放。但是你仔細考慮這個定義可能會產生一個疑問:在Java中並沒有提供所謂的Service元件,一樣要完成

Android Service總結

Service是android中在後臺執行,不提供使用者介面的一個元件。 Service可以有2種形式:啟動的(Started)Service和繫結的(Bound)Service。 啟動的Service是由一個元件(比如Activity)呼叫startService建立的,

Android Service 服務—— Service

                一、 Service簡介Service是android 系統中的四大元件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的級別差不多,但不能自己執行只能後臺執行,並且可以和其他元件進行互動。ser

Activity生命週期的回撥,你應該知道得更多!--Android原始碼剖析

private class ApplicationThread extends ApplicationThreadNative { //... public final void schedulePauseActivity(IBinder token, boolean finished,

SRWebSocket原始碼淺析

一. 前言: WebSocket協議是基於TCP的一種新的網路協議。它實現了瀏覽器與伺服器全雙工(full-duplex)通訊——可以通俗的解釋為伺服器主動傳送資訊給客戶端。 區別於MQTT、XMPP等聊天的應用層協議,它是一個傳輸通訊協議。它有著自己一套連

android IPC通訊-sharedUserId&&Messenger

  看了一本書,上面有一章講解了IPC(Inter-Process Communication,程序間通訊)通訊,決定結合以前的一篇部落格android 兩個應用之間的通訊與呼叫和自己的理解來好好整理總結一下這塊的知識,由於內容較多,這部分會分上中下三篇部落格來

Android 屬性動畫Property Animation 全然解析

顏色 valid 全部 加速度 ext target ng- 點擊 save 轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/380674751、概述Android提供了幾種動畫類型:View Anima

2018年Android面試題含答案

密碼學 進程的地址空間 變量 細節 一段時間 設備驅動 橋梁 異常 graph 這些面試題是我在今年年初換工作的時候整理,沒有重點。包括java基礎,數據結構,網絡,Android相關等等。適合中高級工程師。由於內容過多,將會分為上下兩部分。希望能夠幫到一些朋友,如果幫助到

讀書筆記--Android Gradle權威指南

說了 聲明 通過命令 google robot 結構 信息 我只 cati 最近看了一本書《Android Gradle 權威指南》,對於 Gradle 理解又更深了,但不想過段時間就又忘光了,所以打算寫一篇讀書筆記,將書中一些我個人覺得蠻有用的點記錄、總結一下。 前言 首

Android插件化的兼容性Android O的適配

cto load 註意 android系統 自己 攔截 str oca 接口 首先聲明,《Android插件化開發指南》這本書所介紹的Android底層是基於Android6.0(API level 23)的,而本書介紹的各種插件化解決方案,以及配套的70多個例

Android Fragment 真正的完全解析

watermark 展示 near 主界面 ddt comm 講解 超級 pro 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/lmj623565791/article/details/37970961 轉載請標明出處:

Android Hook框架adbi原始碼淺析

二、libbase 其實上面載入完SO庫後,hook的功能我們完全可以自己在動態庫中實現。而adbi作者為了方便我們使用,編寫了一個通用的hook框架工具即libbase庫。libbase依然在解決兩個問題:1.獲取要hook的目標函式地址;2.給函式打二進位制補丁即inline hook。 關於獲取ho

Android Hook框架adbi原始碼淺析

adbi(The Android Dynamic Binary Instrumentation Toolkit)是一個Android平臺通用hook框架,基於動態庫注入與inline hook技術實現。該框架由兩個主要模組構成,1.hijack負責將動態庫注入到目標程序;2.libbase提供動態庫本身,它實

Pro Android學習筆記一五五 感測器5 磁場感測器和方位

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android學習筆記三四 再談Intent-一些知識

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android原生下載基本邏輯+斷點續傳

零、前言 1.今天帶來的是Android原生下載的上篇,主要核心是斷點續傳,多執行緒下載將會在下篇介紹 2.本例使用了Activity,Service,BroadcastReceiver三個元件 3.本例使用了兩個執行緒:LinkURLThread做一些初始工作,DownLoadThread進行核心下

android影象處理系列之四--給圖片新增邊框

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android 屬性動畫Property Animation 完全解析 【轉】

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/38067475 1、概述 Android提供了幾種動畫型別:View Animation 、Drawable Animation 、Property Anima

Android O Settings原始碼流程分析搜尋欄篇

Android O Settings  靜態介面篇 介面渲染篇 資料載入篇之一級選單 資料載入篇之二級選單 資料載入篇之獲取及修改預設設定屬性值 搜尋欄篇 Settings 搜尋欄 上篇——介面 中篇——實現原理 下篇—

Android安全/開發基礎--10--圖形介面UI和碎片Fragment

10-1、View理論 View的事件體系 View是Android中所有控制元件的基類。ViewGroup是控制元件組,內部包含了許多控制元件。 view的四個位置屬性:top、left、right、bottom MotionEvent:手指接觸屏幕後產生的