Service是Android四大組件中與Activity最相似的組件,它們都代表可執行的程序,Service與Activity的區別是:Service一直在后臺運行,它沒有用戶界面,所以絕不會到前臺運行。如果某個程序組件需要在運行時向用戶呈現某種界面,或者該程序需要與用戶交互,就需要使用Activity;否則就應該考慮使用Service了,最后通過使用Service實現定時更換壁紙。
定時更換壁紙效果圖:
Service簡介
Service是一個可長期在后臺運行的應用組件,并且不提供用戶界面。
- Service不是一個單獨的進程。
- Service不是一個線程。
創建、配置Service
開發Service分為兩步:
- 定義一個繼承Service的子類。
- 在AndroidManifest.xml文件中配置該Service。
補充:需要在AndroidManifest.xml中聲明的有:
- activity:活動
- activity-alias:活動別名
- service:服務
- provider:內容提供者
- receiver:廣播接收者
- meta-data:數據格式
- uses-library:第三方類庫
Service中定義了一系列生命周期方法:
- IBinder onBind(Intent intent):該方法是Service子類必須實現的方法。該方法返回一個IBinder對象,應用程序可通過該對象與Service組件通信。
- void onCreate():在Service第一次被創建后立即回調該方法。
- void onDestroy():在Service被關閉之前回調該方法。
- void onStartCommand(Intent intent, int flags, int startId):該方法的早期版本是onStart(Intent intent, int startId),每次客戶端調用startService(Intent)方法啟動Service時都會回調該方法。
- boolean onUnbind(Intent intent):當該Service上綁定的所有客戶端都斷開連接時將會回調該方法。
定義一個Service組件如下:
public class FirstService extends Service {
private static final String TAG = FirstService.class.getSimpleName();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
上面的Service只是重寫了Service組件的onCreate()、onStartCommand()、onDestroy()、onBind()等方法,重寫這些方法時只是打印了一個字符串。
在Android系統中運行Service有兩種方式:
- 通過Context的startService()方法:通過該方法啟動Service,訪問者與Service之間沒有關聯,即使訪問者退出了,Service也仍然運行。
- 通過Context的bindService()方法:通過該方法啟動Service,訪問者與Service綁定在一起,訪問者一旦退出,Service也就終止了。
啟動和停止Service——startService()方式啟動
使用Activity作為Service的訪問者,該Activity中包含兩個按鈕,一個用于啟動Service,一個用于關閉Service。代碼如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button mStartService;
private Button mStopService;
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStartService = (Button) findViewById(R.id.start_service);
mStopService = (Button) findViewById(R.id.stop_service);
intent = new Intent(MainActivity.this, FirstService.class);
mStartService.setOnClickListener(this);
mStopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
startService(intent);
break;
case R.id.stop_service:
stopService(intent);
break;
}
}
}
啟動、關閉Service十分簡單,調用Context的startService()、stopService()方法即可啟動、關閉Service。
注意:Android5.0開始,Google要求必須使用顯示的Intent啟動Service組件。
運行該程序,點擊啟動按鈕啟動Service,再點擊停止按鈕關閉Service,在Logcat面板可以看到如下輸出:
如果在不關閉Service的情況下,連續點擊三次啟動Service按鈕,程序會連續啟動三次Service,在Logcat面板可以看到如下輸出:
從上圖可以看出,每當Service被創建時會回調onCreate()方法,每次Service被啟動時都會回調onStartCommand()方法;多次啟動一個已有的Service不會再回調onCreate()方法,但每次啟動時都會回調onStartCommand()方法。
綁定本地Service并與之通信——bindService()方式啟動
如果Service和訪問者之間需要進行方法調用或者交換數據,則應該使用bindService()和unbindService()方法啟動、關閉Service。
Context的bindService()方法包含三個參數,分別如下:
- service:該參數通過Intent指定要啟動的Service。
- conn:該參數是一個ServiceConnection對象,該對象用于監聽訪問者與Service之間的連接情況。當訪問者與Service之間連接成功時回調該ServiceConnection對象的onServiceConnected(ComponentName name, IBinder service)方法;當Service所在的宿主進程由于異常中止或者其他原因終止,導致該Service與訪問者之間斷開連接時回調該ServiceConnection對象的onServiceDisconnected(ComponentName name)方法。
- onServiceConnected(ComponentName name, IBinder service)方法中的IBinder即可實現與被綁定Service之間的通信。
- flags:指定綁定時是否自動創建Service(如果Service還未創建)。該參數可指定為0(不自動創建)或者BIND_AUTO_CREATE(自動創建)。
實際開發時通常會采用繼承Binder(IBinder的實現類)的方式實現自己的IBinder對象。
下面程序示范了如何在Activity中綁定Service,并獲取Service的運行狀態。該程序的Service類需要真正實現onBind()方法,并讓該方法返回一個有效的IBinder對象。Service類代碼如下:
public class BindService extends Service {
private static final String TAG = BindService.class.getSimpleName();
private int count;
private boolean quit;
//定義onBinder方法所返回的對象
private MyBinder binder = new MyBinder();
//通過繼承Binder實現IBinder類
public class MyBinder extends Binder{
public int getCount(){
return count;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
//啟動一條線程,動態修改count狀態值
new Thread(){
@Override
public void run() {
while (!quit){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}.start();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return true;
}
@Override
public void onDestroy() {
super.onDestroy();
this.quit = true;
Log.d(TAG, "onDestroy");
}
}
Service類實現了onBind()方法,該方法返回一個可訪問該Service狀態數據(count)的IBinder對象,該對象將被傳給該Service的訪問者。
接下來定義一個Activity來綁定該Service,并在Activity中通過MyBinder對象訪問Service的內部狀態。該Activity包含三個按鈕,分別為綁定Service、解綁Service、獲取Service的運行狀態。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private Button mBindService;
private Button mUnbindService;
private Button mGetServiceState;
private Intent intent;
//保持所啟動的Service的IBinder對象
BindService.MyBinder binder;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
binder = (BindService.MyBinder) service;//①
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBindService = (Button) findViewById(R.id.bind_service);
mUnbindService = (Button) findViewById(R.id.unbind_service);
mGetServiceState = (Button) findViewById(R.id.get_service_state);
intent = new Intent(MainActivity.this, BindService.class);
mBindService.setOnClickListener(this);
mUnbindService.setOnClickListener(this);
mGetServiceState.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bind_service:
bindService(intent, conn, BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
unbindService(conn);
break;
case R.id.get_service_state:
Toast.makeText(MainActivity.this, "Service的count值為:" + binder.getCount(), Toast.LENGTH_SHORT).show();//②
break;
}
}
}
上面①號代碼用于在Activity與Service連接成功時獲取Service的onBind()方法返回的MyBinder對象,②號代碼可以通過MyBinder對象訪問Service的運行狀態。
點擊綁定Service按鈕,在Logcat面板可以看到如下輸出:
點擊獲取Service狀態按鈕,可以看到如下圖所示的輸出:
點擊解綁Service按鈕,在Logcat面板可以看到如下輸出:
如上圖所示,當程序調用unbindService()方法解除對某個Service的綁定時,系統會先調用該Service的onUnbind()方法,然后再回調onDestroy()方法。
與多次調用startService()方法啟動不同的是,多次調用bindService()方法并不會執行重復綁定。
Service的生命周期
隨著應用程序啟動Service方式不同,Service的生命周期也略有差異,如下圖:
如果應用程序通過startService()方法來啟動Service,Service的生命周期如上圖左半部分所示。如果應用程序通過bindService()方法來啟動Service,Service的生命周期如上圖右半部分所示。
當Activity調用bindService()綁定一個已通過startService()啟動的Service時,系統只是把Service內部IBinder對象傳給Activity,并不會把該Service生命周期完全綁定到該Activity,因而當Activity調用UNBindService()方法取消與該Service的綁定時,也只是切斷該Activity與Service之間的關聯,并不能停止該Service組件。要停止該Service組件,還需調用stopService()方法。
IntentService
首先看一下Service本身存在的兩個問題。
- Service不會專門啟動一個單獨的進程,Service與它所在應用位于同一個進程中。
- Service不是一個新的線程,因此不應該在Service中直接處理耗時的任務。
IntentService正好彌補了Service的不足:IntentService會使用隊列來管理請求Intent,每當客戶端代碼通過Intent請求啟動IntentService時,IntentService會將該Intent加入隊列,然后開啟一條新的worker線程來處理該Intent。對于異步的startService()請求,IntentService會按次序依次處理隊列中的Intent,該線程保證同一時刻只處理一個Intent。由于IntentService使用新的worker線程處理Intent請求,因此IntentService不會阻塞主線程,所以IntentService自己就可以處理耗時任務。
IntentService的特征:
- IntentService會創建單獨的worker線程來處理所有的Intent請求。
- IntentService會創建單獨的worker線程來處理onHandleIntent()方法實現的代碼,因此開發者無須處理多線程問題。
- 當所有請求處理完成后,IntentService會自動停止,因此開發者無須調用stopSelf()方法來停止該Service。
- 為Service的onBind()方法提供了默認實現,默認實現的onBind()方法返回null。
- 為Service的onStartCommand()方法提供了默認實現,該實現會將請求Intent添加到隊列中。
擴展IntentService實現Service無須重寫onBind()、onStartCommand()方法,只要重寫onHandleIntent()方法即可。
定時更換壁紙
通過AlarmManager周期性調用某個Service,從而讓系統實現定時更換壁紙的功能。
該程序界面有兩個按鈕,一個用于啟動定時更換壁紙,一個用于關閉定時更換壁紙,代碼如下:
public class MainActivity extends AppCompatActivity {
private Button mStart;
private Button mStop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStart = (Button) findViewById(R.id.start);
mStop = (Button) findViewById(R.id.stop);
//指定啟動ChangeService組件
Intent intent = new Intent(MainActivity.this, ChangeService.class);
//創建PendingIntent對象
final PendingIntent pi = PendingIntent.getService(MainActivity.this, 0, intent, 0);
mStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//獲取AlarmManager對象
AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
//設置每隔2秒執行pi所代表的組件一次
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, 2000, pi);
mStart.setEnabled(false);
mStop.setEnabled(true);
Toast.makeText(MainActivity.this, "壁紙定時更換啟動成功啦", Toast.LENGTH_SHORT).show();
}
});
mStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mStart.setEnabled(true);
mStop.setEnabled(false);
//獲取AlarmManager對象
AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
//取消對pi的調度
alarmManager.cancel(pi);
}
});
}
}
上面程序代碼指定程序每2秒執行一次pi所代表的組件。程序中pi代表了ChangeService組件,ChangeService代碼如下:
public class ChangeService extends Service {
//定義定時更換的壁紙資源
int[] wallpapers = new int[]{
R.drawable.author,
R.drawable.girl,
R.drawable.life
};
//定義系統的壁紙管理服務
WallpaperManager wallpaperManager;
//定義當前所顯示的壁紙
int current = 0;
@Override
public void onCreate() {
super.onCreate();
//初始化WallPaperManager
wallpaperManager = WallpaperManager.getInstance(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//如果到了最后一張,系統重新開始
if (current >= 3) {
current = 0;
}
try {
//改變壁紙
wallpaperManager.setResource(wallpapers[current++]);
} catch (IOException e) {
e.printStackTrace();
}
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
重寫Service的onStartCommand()方法,也就是每次啟動該Service時都會執行onStartCommand()方法中的代碼,更換壁紙的代碼就放在該方法中。
為了允許該程序改變壁紙,還需在AndroidManifest.xml中添加權限:
<uses-permission android:name="android.permission.SET_WALLPAPER" />
運行該程序,點擊開始,返回桌面即可看到系統壁紙每2秒更換一次,效果圖如下: