1. 程式人生 > 實用技巧 >Service服務

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了,已經做到了跨應用(程序)進行通訊了,後面的話我們以一個實際的使用例子來說一下吧