Service服務
什麼是服務呢?
用俗話話應該是長期於後臺執行的程式,如果是官方一點,首先它是一個元件,用於執行長期執行的任務,並且與使用者沒有互動。
每一個服務都需要在配置檔案AndroidManifest.xml檔案裡進行生命,怎麼生命呢?
使用標籤,其實跟前面的activity,廣播接收者receiver一樣生命。
通過Context.startService()來開啟服務,通過Context.stop()來停止服務。當然啦,還有一種啟動形式就是通過Context.bindService()的方法。
為什麼要使用服務呢?
從上面的文字說,我們知道這個服務是用於執行長期後臺執行的操作。有些時候,我們沒有介面,但是程式仍然需要工作。比如說,我們播放音樂,在後臺播放音樂。比如說,我們下載任務,在後臺下載檔案。這些都是沒有介面 的後臺執行程式,這些都是用服務做的。
所以,服務就有它的用處了。
第二個原因是什麼呢?先給大家講幾個概念:
1、前臺程序:可以理解為是最頂部的,直接跟使用者互動的。比如說我們操作的Activity介面.
2、可見程序:可以見的,但是不操作的,比如說我們在一個Activity的頂部彈出一個Dialog,這個Dialog就是前臺程序,但是這個Activity則是可見程序。
3、服務程序:服務可以理解為是忙碌的後臺程序,雖然是在後臺,但是它很忙碌。
4、後臺程序:後臺程序就是退隱到後臺,不做事的程序。
5、空程序:空程序是不做事的,沒有任何東西在上面跑著,僅作快取作用。
假設,記憶體不夠用了,會先殺誰呢?
首先殺的是空程序,要是還不夠就殺後臺程序,要是還不夠,那麼就殺服務,但是服務被殺死以後,等記憶體夠用了,服務又會跑起來了。
所以:如果我們需要長期後臺操作的任務,使用Service就對了!其實Framework裡多數是服務。如果我們進行音樂播放,即使退到了後臺,也可以播放,我們使用服務完成吧!如果我們下載東西,退到後臺也能下載,那麼我們就使用服務吧!如果我們在不停地記錄日誌,那我們就用服務吧!
如果面試問到:服務用於執行耗時操作,這是對的嗎?
如時服務直接執行耗時操作,也會出現anr.
在這裡給大家補充一下anr的時長知識。首先ANR的意思是android no response,也就是無相應或者理解為操作超時。
在android系統中廣播的ANR時長為:
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
前臺廣播為10秒,後臺廣播為60秒。
按鍵操作的anr時長為:
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
按鈕事件的時長為5秒,常指的是Activity的操作。
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
服務的話,前臺服務為20秒超時,後臺服務為200秒超時。
以上。
如果在服務中直接做耗時操作,也是會出現ANR異常的。服務可以長期在後臺執行,所以你可以這麼做:如果要做耗時操作,比如說網路的訪問,資料庫的讀寫之類的,可以開執行緒去做。
服務的生命週期
前面我們知道了為什麼要使用服務,接下來我們就使用一下服務,並且學習一下它的生命週期。
我們要寫程式碼了:
首先,建立一個類,繼承Service,就像我們之前寫Activity要繼承自Activity,而廣播則繼承自BroadcastReceiver。程式碼如下:
package com.sunofbeaches.servicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
/**
* Created by TrillGates on 18/4/5.
* God bless my code!
*/
public class FirstService extends Service{
private static final String TAG = "FirstService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate...");
super.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() {
Log.d(TAG, "onDestroy...");
super.onDestroy();
}
}
接著,我們寫一個Activity去控制服務:
package com.sunofbeaches.servicedemo;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startService(View view) {
Log.d(TAG, "start service ... ");
startService(new Intent(this, FirstService.class));
}
public void stopService(View view) {
Log.d(TAG, "stop service....");
stopService(new Intent(this, FirstService.class));
}
}
註冊服務,四大元件都需要註冊。在配置檔案裡配置如下:
<service android:name=".FirstService"></service>
以前我們在學習Activity的時候,就知道顯示意圖和隱式意圖了。其實服務也是一樣的,啟動方式也有顯式和隱式的。
上面的Activity中我們直接使用顯式意圖來啟動服務。如果需要使用隱式意圖的方式去啟動,則需要配置一個意圖過濾。
執行結果:
可以看出,我們在startService之後,就執行了onCreate方法,接著是onStartCommand方法。當我們執行stopService的時候,就會走onDestroy方法了。
這就是服務最基本的生命週期了。其實onStart也是服務的生命週期,只是這個方法已經過時了。
當然啦,服務的生命週期並沒有這麼簡單,我們後面再詳細去探討吧!
繫結啟動服務
前面的開啟服務方式,有一個弊端。就是沒法進行通訊。所以我們接直來呢會學習另外一種啟動服務的方式–通過繫結服務的形式來啟動服務。
繫結服務,對應的停止服務則是解綁服務了!
好,我們看程式碼吧:
這是我們的第二個服務:
package com.sunofbeaches.servicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
/**
* Created by TrillGates on 18/4/18.
* God bless my code!
*/
public class SecondService extends Service {
private static final String TAG = "SecondService";
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return null;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.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() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
}
然後,在清單檔案裡註冊一下:
<service android:name=".SecondService"></service>
註冊完以後,我們編寫一個主介面,我們叫做:BindServiceActivity
package com.sunofbeaches.servicedemo;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
/**
* Created by TrillGates on 18/4/18.
* God bless my code!
*/
public class BindServiceActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
}
public void bindServiceClick(View view) {
//建立意圖物件
Intent intent = new Intent(this, SecondService.class);
//第一個是引數是意圖物件,第二個引數是回撥,第三個引數是標記,這個是自動建立的意,如果服務沒有start,那麼會自己建立。
//automatically create the service as long as the binding exists
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void unBindServiceClick(View view) {
//解綁服務
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
}
}
介面是這樣子的:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:onClick="bindServiceClick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="繫結服務"/>
<Button
android:onClick="unBindServiceClick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="解綁服務"/>
</LinearLayout>
這兩個按鈕的功能就是繫結服務和解綁服務,可以看上面的程式碼,其實非常簡單的!
我們第一個步驟就是先把服務繫結,啟動起來。第二步我們才去研究怎麼樣進行通訊。
把這個程式跑起來,是怎麼樣子的呢?
好的,到這裡,我們已經實現了通過繫結服務和啟動服務來啟動服務。並且通過解綁服務來停止服務。
接下來,我們看一下,Activity跟服務之間是怎麼通訊的。我們可以看到服務的生命週期有點不一樣了!它會執行onBind方法:
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return null;
}
這個方法裡頭呢,我們需要返回一個東西,也就是IBinder,假設我們現在這樣子,需要呼叫服務裡的一個方法,我們可以在裡面宣告 一個方法叫服務的內部方法!
我們把服務寫成這樣子:
package com.sunofbeaches.servicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
/**
* Created by TrillGates on 18/4/18.
* God bless my code!
*/
public class SecondService extends Service {
public class CommunicateBinder extends Binder{
void callInnerMethod(){
innerMethod();
}
}
private static final String TAG = "SecondService";
private void innerMethod() {
Log.d(TAG, "innerMethod was called...");
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return new CommunicateBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.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() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
}
有一個服務內部的類,繼承了binder類,其實binder類的話是實現了IBinder介面的:
所以我們在綁上的時候,就返回這個類給繫結服務的地方,我們看看繫結服務的地方是怎麼獲取到這個類的:
package com.sunofbeaches.servicedemo;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
/**
* Created by TrillGates on 18/4/18.
* God bless my code!
*/
public class BindServiceActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
}
public void bindServiceClick(View view) {
//建立意圖物件
Intent intent = new Intent(this, SecondService.class);
//第一個是引數是意圖物件,第二個引數是回撥,第三個引數是標記,這個是自動建立的意,如果服務沒有start,那麼會自己建立。
//automatically create the service as long as the binding exists
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
private SecondService.CommunicateBinder mCommunicateBinder;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service instanceof SecondService.CommunicateBinder) {
mCommunicateBinder = (SecondService.CommunicateBinder) service;
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void unBindServiceClick(View view) {
//解綁服務
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
}
public void callServiceMethod(View view) {
if (mCommunicateBinder != null) {
//呼叫服務內部的方法
mCommunicateBinder.callInnerMethod();
}
}
}
這裡面有一段程式碼是:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service instanceof SecondService.CommunicateBinder) {
mCommunicateBinder = (SecondService.CommunicateBinder) service;
}
}
我們就是這樣子拿到了繫結上以後傳回來的那個類,這樣子我們就可以呼叫服務裡的方法了:
public void callServiceMethod(View view) {
if (mCommunicateBinder != null) {
//呼叫服務內部的方法
mCommunicateBinder.callInnerMethod();
}
}
在UI上添加了一個按鈕,看一下我們測試的結果:
這樣子我們就可以控制服務了,假設說我們有一個服務在後臺跑著,用它來播放音樂的,因為我們音樂可以後臺播放呀,對吧!這個時間 ,我們需要控制音樂的播放和暫停了,就可以通過這種形式去控制音樂了。
這樣的程式碼還不夠完美,對於服務內部的方法,應該隱藏起來,而公共的東西進行抽取,所以,我們應該定義一個介面,把服務裡的:
public class CommunicateBinder extends Binder{
void callInnerMethod(){
innerMethod();
}
}
這個方法隱藏起來,那隱藏起來以後,外部怎麼能呼叫呢?當然是通過介面的形式來實現啦!
我們建立一個介面:
package com.sunofbeaches.servicedemo.interfaces;
/**
* Created by TrillGates on 18/4/20.
* God bless my code!
*/
public interface IServiceControl {
void callServiceInnerMethod();
}
接著,我們私有服務裡這個類,並且實現這個介面:
private class CommunicateBinder extends Binder implements IServiceControl{
@Override
public void callServiceInnerMethod() {
innerMethod();
}
}
這樣子,我們在服務連線上以後,就可以強轉成介面來呼叫了:
你看,已經沒法呼叫了,因為這個類已經私有了!
把程式碼改成這樣子:
package com.sunofbeaches.servicedemo;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import com.sunofbeaches.servicedemo.interfaces.IServiceControl;
import com.sunofbeaches.servicedemo.services.SecondService;
/**
* Created by TrillGates on 18/4/18.
* God bless my code!
*/
public class BindServiceActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
}
public void bindServiceClick(View view) {
//建立意圖物件
Intent intent = new Intent(this, SecondService.class);
//第一個是引數是意圖物件,第二個引數是回撥,第三個引數是標記,這個是自動建立的意,如果服務沒有start,那麼會自己建立。
//automatically create the service as long as the binding exists
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
private IServiceControl mCommunicateBinder;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service instanceof IServiceControl) {
mCommunicateBinder = (IServiceControl) service;
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void unBindServiceClick(View view) {
//解綁服務
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
}
public void callServiceMethod(View view) {
if (mCommunicateBinder != null) {
//呼叫服務內部的方法
mCommunicateBinder.callServiceInnerMethod();
}
}
}
這樣子的效果是一樣的,但是程式碼非常優雅!估計能理解這個的同學就可以體會到了介面有多麼有用了!
但是,以繫結的方式啟動的服務,在context銷燬的時候,必須解綁,否則會洩漏:
報錯內容:
[ 04-19 23:02:28.741 1509: 1597 D/ ]
HostConnection::get() New Host Connection established 0xa034adf0, tid 1597
Activity com.sunofbeaches.servicedemo.BindServiceActivity has leaked ServiceConnection com.sunofbeaches.servicedemo.BindServiceActivity$1@13ae5f6b that was originally bound here
android.app.ServiceConnectionLeaked: Activity com.sunofbeaches.servicedemo.BindServiceActivity has leaked ServiceConnection com.sunofbeaches.servicedemo.BindServiceActivity$1@13ae5f6b that was originally bound here
at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:1077)
at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:971)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1774)
at android.app.ContextImpl.bindService(ContextImpl.java:1757)
at android.content.ContextWrapper.bindService(ContextWrapper.java:539)
at com.sunofbeaches.servicedemo.BindServiceActivity.bindServiceClick(BindServiceActivity.java:27)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at android.view.View$1.onClick(View.java:4015)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
onUnbind
onDestroy
all threads took: 5.485ms
BindServiceActivity has leaked 這裡已經說了洩漏了!
bindService開啟的服務,在系統裡是看不到服務在執行的:
如果是通過startService的方式啟動的服務,則會在應用裡看到:
如果是通過bindService的方式來啟動,則不會有
總結一下繫結服務的特點(這些內容會在視訊裡給大家做實驗證明):
1、繫結服務在系統設定裡是沒有顯進服務正在跑著的;
2、如果onBind方法返回的是null,那麼onServiceConnected方法不會被呼叫;
3、繫結服務的生命週期跟Activity是不求同時生,但求同時死,Activity沒了,服務也要解綁;
4、服務在解除繫結以後會停止執行,執行unBind方法—>onDestroy方法;
5、繫結服務開啟的服務,只可以解綁一次,多次解綁會拋異常;
6、繫結的connection要跟解綁的connection要對應著,否則沒法解綁。
稍微總結一下,startService和bindService的區別,優點和缺點:
1、startService這個方法來啟動服務的話,是長期執行的,只有stopService才會停止服務。而bindService來啟動服務,不用的時候,需要呼叫unBindService,否則會導致context洩漏,所以bindService不是長期執行的。當context銷燬的時候,則會停止服務執行。
2、startService來啟動服務可以長期執行,但是不可以通訊,而bindService的方式來啟動服務則可以通訊,兩者都有優缺點,所以我們就有了混合起來使用的方法。
接下來,我們會學習服務的混合開啟方式,達到互補的效果。
混合啟動服務
在前面的基礎上,我們把開啟服務,停止服務,繫結服務,解綁服務,呼叫服務內部的方法結合起來。再看看它的生命週期,以及作用效果。
首先,我們先總結一下服務的生命週期:
》startService–>stopService
這個生命週期我們前面已經看到了:
onCreate—>onStartCommand—->onDestroy
》bindService–>unBindService
這個生命週期,我們也從前面的例子中看到了:
onCreate—->onBind—>onUnbind—->onDestroy
在這一小節之前,我們提到,兩種開啟服務各有各自的優點和缺點。startService的方法可以長期地在後臺執行,而bindService的方法則不可以長期於後臺執行;bindService啟動服務,可以跟服務進行通訊,但是startService啟動服務不可以跟服務進行通訊。
我們混合兩種開啟方式,比如說,我們先startService,再進行bindService,這樣子的話,服務可以長期於後臺執行,又可以跟服務進行通訊了。
我們還是以例子的形式來學習一下:
我們在前面的例子稍加修改,添加了開啟服務和停止服務,繫結服務和解綁服務不變,其實就是前面例子的結合體來的。
這個是介面:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startServiceClick"
android:text="開啟服務"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="stopServiceClick"
android:text="停止服務"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bindServiceClick"
android:text="繫結服務"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unBindServiceClick"
android:text="解綁服務"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="callServiceMethod"
android:text="呼叫服務內部方法"/>
</LinearLayout>
對應的,有點選事件,我們在activity裡的程式碼實現一下:
package com.sunofbeaches.servicedemo;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import com.sunofbeaches.servicedemo.interfaces.IServiceControl;
import com.sunofbeaches.servicedemo.services.SecondService;
/**
* Created by TrillGates on 18/4/18.
* God bless my code!
*/
public class BindServiceActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
}
public void bindServiceClick(View view) {
//建立意圖物件
Intent intent = new Intent(this, SecondService.class);
//第一個是引數是意圖物件,第二個引數是回撥,第三個引數是標記,這個是自動建立的意,如果服務沒有start,那麼會自己建立。
//automatically create the service as long as the binding exists
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
public void startServiceClick(View view) {
Intent intent = new Intent(this, SecondService.class);
startService(intent);
}
public void stopServiceClick(View view) {
Intent intent = new Intent(this, SecondService.class);
stopService(intent);
}
private IServiceControl mCommunicateBinder;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service instanceof IServiceControl) {
mCommunicateBinder = (IServiceControl) service;
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
public void unBindServiceClick(View view) {
//解綁服務
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
}
public void callServiceMethod(View view) {
if (mCommunicateBinder != null) {
//呼叫服務內部的方法
mCommunicateBinder.callServiceInnerMethod();
}
}
}
然後我們就是開始使用啦!
第一個實驗
》開啟服務—>繫結服務—->呼叫服務的方法——>解綁服務——>停止服務(不停止服務它會一直跑著)
開啟服務:
D/SecondService: onCreate D/SecondService: onStartCommand
繫結服務:
D/SecondService: onBind
呼叫服務的方法:
D/SecondService: innerMethod was called…
解綁服務:
D/SecondService: onUnbind
停止服務:
D/SecondService: onDestroy
從上面的操作記錄來看,我們在解綁服務的時候,它並沒有執行onDestroy方法,也就是說,服務還是在執行的。所以我們使用到了startService的好處:1、服務在後臺長期執行;
而我們也是能呼叫服務內部的方法的,所以我們使用到了bindService的好處:2、跟服務進行通訊
也就是說,只要startService的方式開啟服務,沒有stopService,服務一直在執行者。
第二個實驗:
我們去開啟服務(startService)—->繫結服務(bindService)—–>停止服務(stopService)
從上面的圖中,我們可以得出結論:通過startService,再去bindService,如果沒有解綁,那麼是停止不了服務的
實際開發中的模板流程:
第一步:startService–>這一步是為了服務可以長期後臺執行 第二步:bindService–>這一步的目錄是為了拿到服務的控制binder 第三步:呼叫服務裡的方法 第四步:unBindService—>解綁服務,否則無法停止服務的(但是服務,仍然在後臺執行) 第五步:在不需要服務的時候停止服務stopService
跨程序通訊AIDL
什麼是AIDL呢?可能一般的開發人員使用的場景不多,但是就我現在做開發,天天用到,因為我做的launcher要顯示所有第三方應用的附加介面,並且要進行通訊,所以就要用到AIDL了!
AIDL其實是:android interface definition language 安卓介面定義語言(其實這個使用得少,但是面試的時候會問一下吧!)
在例子開始之前,我們要了解一個概念:
IPC:inter process communication 程序間通訊。要知道什麼是程序間通訊,那就要從程序的角度去理解了。在window下的話,我們開啟一個應用,它就會給這個應用開一個程序。而在android裡也是一樣的,你看就知道了,在我們的ddms裡頭就有這麼多的程序:
我們可以看到這些程序:
程序之間是獨立的,而每個應用使用的記憶體也是獨立的。為什麼要獨立呢,如果不獨立的話就會存在安全問題呀!比如說,在你的應用裡有支付相關的內容,如果其他應用可以訪問得到的話,那麼就危險了!對吧!
程序之間是獨立的,那就有一個問題了!它們是怎麼進行通訊的呢?有這樣的場景呀,比如說:我們第三方應用要發起充值的業務,通過支付寶進行充值,呼叫支付寶簡單,但是支付寶怎麼把支付結果告訴我們的應用呢?對吧!
程序間的通訊,基本上在每個作業系統上都有這個概念。其實我們猜也能猜到是怎麼實現的!
我們可以劃出一個公共的記憶體空間,把要通訊的資料,通過這個空間來交流即可!而這個空間呢,就是binder了!
接下來的話,我們就會寫一個例子,怎麼樣來跨程序通訊!
這個例子大概是這樣子的:一個應用程式去呼叫另外一個應用裡的服務裡的方法。
如上所說,我們就是要建立兩個應用了!
但是要先理解兩個概念,本地服務和遠端服務。
本地服務是相對於自己來說的,在自己應用上的服務,其實就是本地服務。相對於自己來說,服務在別人的應用上,那麼就是遠端服務了。
圖中的服務相對於A來是遠端服務相對於B來說是本地服務。
接下來的這個例子,我們就是有兩個應用,一個是應用A,一個是應用B。在應用B裡頭有一個服務。然後呢,我們的應用A繫結應用B的服務,並且呼叫裡面的方法。
第一步,建立兩個應用:AIDLDemoA,和AIDLDemoB
第二步:在 AIDLDemoB的程式裡面建立一個服務:AppBService
package com.sunofbeaches.aidldemob;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
/**
* Created by TrillGates on 18/4/23.
* God bless my code!
*/
public class AppBService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
第三步,在配置檔案裡註冊這個服務:
<service android:name=".AppBService"
android:exported="true">
<intent-filter>
<action android:name="com.sunofbeaches.aidldemob.INNER_SERVICE"/>
</intent-filter>
</service>
這裡這個exported=”true”其實就是允許外部啟動這個服務,繫結這個服務。預設就是true的,如果改成false了,外部就沒法繫結/啟動了。
這個action是給第三方應用(應用A)繫結用的。
第四步,重點來了:我們要建立AIDL介面:
// IDemoBServiceControl.aidl
package com.sunofbeaches.aidldemob;
// Declare any non-default types here with import statements
interface IDemoBServiceControl {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
然後,就會建立這樣一個檔案了,對於AIDL的知識點挺多的,大家先學會怎麼使用,後面再學習更多的內容,比如說,你要知道怎麼要傳一個自定義的bean類,怎麼要相互控制,都是可以的。
這裡面的話,我們就使用A應用,呼叫B應用裡的服務的方法,並且把內容傳過去。
// IDemoBServiceControl.aidl
package com.sunofbeaches.aidldemob;
interface IDemoBServiceControl {
void callInnerService();
void innserServicePrint(in String printContent);
}
基本資料型別不需要導包,我們自己定義的才要導包,這個以後我們會學到的。
第五步,我們點選build裡的make一下,這樣子就會自動生成一個類,所以我們才可以new出來:
package com.sunofbeaches.aidldemob;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
/**
* Created by TrillGates on 18/4/23.
* God bless my code!
*/
public class AppBService extends Service {
private static final String TAG = "AppBService";
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "應用B的服務被繫結...");
return new IDemoBServiceControl.Stub() {
@Override
public void callInnerService() throws RemoteException {
innerMethod();
}
@Override
public void innerServicePrint(String printContent) throws RemoteException {
printMethod(printContent);
}
};
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "應用B的服務解綁了...");
return super.onUnbind(intent);
}
private void innerMethod() {
Log.d(TAG, "應用B的服務內部方法被呼叫了!...");
}
private void printMethod(String content) {
Log.d(TAG, "應用B的輸出方法被呼叫,傳過來的內容是 -- > " + content);
}
}
以上是我們服務修改成這樣子,IDemoBServiceControl.Stub()這個類是根據我們的aidl介面生成的,它繼承自己IBinder,所以我們可以直接new出來返回,當onBind的時候。
第五步:複製aidl的整個資料夾到應用A,檢視如下:
要注意的地方是:確保它們的包名是一樣的。如圖:
至於AIDL的原理,現在解釋可能大家還沒法理解。大家先按步驟去做,如果到這裡已經很吃力了,那就去看視訊好了。
然後,我們在應用A繫結服務的時候,就可以拿到這個介面的實現了,請看吧:
這是應用A的介面程式碼:
然後是主的Activity:
package com.sunofbeaches.aidldemoa;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import com.sunofbeaches.aidldemob.IDemoBServiceControl;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private IDemoBServiceControl mIDemoBServiceControl = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 這個方法用於繫結服務
*
* @param view
*/
public void remoteServiceBindClick(View view) {
Intent intent = new Intent();
//設定遠端服務B的包名
intent.setPackage("com.sunofbeaches.aidldemob");
//設定服務的action: com.sunofbeaches.aidldemob.INNER_SERVICE
intent.setAction("com.sunofbeaches.aidldemob.INNER_SERVICE");
//繫結服務,服務不能重複繫結
if (mIDemoBServiceControl == null) {
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//我們可以通過AIDL自動建立的類的一個方法,自動轉,不需要強轉了
mIDemoBServiceControl = IDemoBServiceControl.Stub.asInterface(service);
Log.d(TAG, "遠端服務綁定了...");
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIDemoBServiceControl = null;
Log.d(TAG, "遠端服務斷開綁定了...");
}
};
/**
* 這個方法用於解綁服務
*
* @param view
*/
public void unBindRemoteService(View view) {
unBindRemoteService();
}
/**
* 這個方法用於呼叫遠端服務裡的方法
*
* @param view
*/
public void callRemoteServiceInnerMethod(View view) {
if (mIDemoBServiceControl != null) {
try {
mIDemoBServiceControl.callInnerService();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
/**
* 這個方法用於呼叫遠端服務的方法,讓期輸出內容
*
* @param view
*/
public void callRemoteServicePrintText(View view) {
if (mIDemoBServiceControl != null) {
try {
mIDemoBServiceControl.innerServicePrint("陽光沙灘");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unBindRemoteService();
}
private void unBindRemoteService() {
if (mServiceConnection != null && mIDemoBServiceControl != null) {
unbindService(mServiceConnection);
mIDemoBServiceControl = null;
}
}
}
註釋已經寫清楚了,可以看註釋,如果有問題的話,可以到社群網站裡提問交流。
然後我們先跑起應用B,再跑起應用A。然後看應用B的Log,因為我們要看應用B的輸出內容:
從上面的結果,我們可以看到,我們應用A控制應用B了,已經做到了跨應用(程序)進行通訊了,後面的話我們以一個實際的使用例子來說一下吧