[TOC]
# 1.Service的創建
1. **手動創建**
- 新建Class文件,繼承Service
- 在AndroidMainfest中進行注冊
2. **使用AndroidStudio進行自動創建**
**AndroidMainfest.xml**
```java
<service
//Service所在的類
android:name=".MyService"
//Service是否可用
android:enabled="true"
//是否可以被其他應用訪問
android:exported="true"/>
```
---
# 2.Service的生命周期
## 先介紹一下Service的生命周期函數
* **OnCreate()**
當Service被創建時調用,在整個生命周期過程中只調用一次
* **OnStartCommand()**
當在Activity中調用startService()時被調用,一般用與Activity無關的Service,如播放音樂,下載等
* **OnBind()**
在Activity調用bindService()函數時調用
* **OnUnBind()**
在Activity調用unBindService()函數時調用
* **OnDestory()**
在Service銷毀時調用,調用該方法代表這本次生命周期結束,再次啟動該Service時,需調用OnCreate()方法
## Service生命周期的兩種模式

由上圖我們可以很清晰的看到,Service的生命周期分為兩種,**綁定模式**和**非綁定模式**,除此之外,還有經常可見的兩種模式的混合使用,只要我們把這兩種模式理解清楚了,相信混合模式也不在話下了
**非綁定模式:**
> onCreate() -> onStartCommand() -> onDestory()
通過Context的startService() 方法啟動一個Service,執行OnCreate()和OnStartCommand()方法,再次調用startService()則會再次執行OnStartCommand()方法,不管執行多少次startService(),只需要調用一次stopService()方法,Service就會調用OnDestory()方法,來結束本次服務;
**綁定模式:**
> onCreate() -> onBind() -> onUnBind() -> onDestory()
通過Context的bindService()方法綁定一個Service,執行OnCreate() 和 OnBind() 方法,再次調用bindService()無效,直到調用UnBindService() 方法,執行OnUnBind() 和 OnDestory() 方法,結束本次服務;
> > ==**注意:** 可能存在未調用onBindService()方法就調用OnUnBindService()或者調用一次onBindService()方法調用兩次OnUnBindService()方法,造成 *java.lang.IllegalArgumentException: Service not registered:xxx*的異常,可能導致程序崩潰==
> > > - [x] **解決方法**:在調用OnUnBindService()進行異常捕獲,對異常進行處理,最后給予提示信息
代碼示例:
**MyService.java**
```Java
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind ");
return null;
};
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
isRunning = false;
Log.i(TAG, "onDestroy: ");
}
}
```
**MainActivity.java**
```java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button startServiceBtn;
private Button stopServiceBtn;
private Button bindServiceBtn;
private Button unbindServiceBtn;
private Intent serviceIntent;
private static final String TAG = "MainActivity";
private ServiceConnection serviceConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent(this,MyService.class);
startServiceBtn=findViewById(R.id.start_service);
stopServiceBtn=findViewById(R.id.stop_service);
bindServiceBtn=findViewById(R.id.bind_service);
unbindServiceBtn=findViewById(R.id.unbind_service);
startServiceBtn.setOnClickListener(this);
stopServiceBtn.setOnClickListener(this);
bindServiceBtn.setOnClickListener(this);
unbindServiceBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
startService(serviceIntent);
break;
case R.id.stop_service:
stopService(serviceIntent);
break;
case R.id.bind_service:
bindService(serviceIntent, serviceConnection,BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
try {
unbindService(serviceConnection);
}catch (IllegalArgumentException e){
Log.i(TAG, "unBindService: "+e.getMessage());
Toast.makeText(this, "未綁定任何服務!", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}
```
**activity_main.xml**
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="啟動本地服務" />
<Button
android:id="@+id/stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止本地服務" />
<Button
android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="綁定本地服務" />
<Button
android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="解除綁定本地服務" />
</LinearLayout>
```
## 運行截圖
### 非綁定模式
> **為驗證多次點擊效果,點擊了5次啟動(綁定)和3次停止(解除綁定)按鈕**

### 綁定模式
> **為驗證多次點擊效果,點擊了5次綁定和2次解除綁定按鈕,發現第二次點擊解除綁定按鈕出現提示**

---
# 3. 跨應用啟動、綁定Service
> 相信在第一小節Service的創建中大家已經注意到了Service的一個屬性: android:exported="true",我在上面也給大家標明了注釋--是否可以被其他應用訪問,那么說明了Service是可以在不同的應用之間使用的,這也符合了我們所了解的,Android四大組件都是可以跨進程訪問的(在通常情況下,Android的一個應用就是一個進程),那么我們該如何操作呢?
**步驟如下:**
1. 創建一個新的Module--AnotherApp,將MainActivity.java和activity_main.xml拷貝一份
2. 對Intent的定義進行修改
```java
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.duoshilin.aidltestapplication", "com.duoshilin.aidltestapplication.MyService"));
```
> 下面是源碼對ComponentName(String pkg, String cls)的方法介紹
因為是跨應用的訪問Service,所有我們不能直接通過平常使用的方法去啟動的一個Service;
```java
/**
* Create a new component identifier.
*
* @param pkg The name of the package that the component exists in. Can
* not be null.
* @param cls The name of the class inside of <var>pkg</var> that
* implements the component. Can not be null.
*/
public ComponentName(String pkg, String cls) {
if (pkg == null) throw new NullPointerException("package name is null");
if (cls == null) throw new NullPointerException("class name is null");
mPackage = pkg;
mClass = cls;
}
```
> String pkg 指的是要訪問的Service所在的包名;
> String cls 指要訪問的Service的完整類名;
**這樣我們就可以跨應用去訪問一個Service了,是不是很簡單呢?下面我們來運行一下看看效果吧!**

> 要注意的是,這個時候我們還是要在原APP中去查看日志打印結果哈!
---
# 4. 跨進程的Service通信
> 在上一小節中,我們訪問到了其他應用中的Service,但是我們訪問到了之后應該干什么呢?當然是進行數據交互了,哈哈!不然我們為什么要去訪問他呢?(這里我就不說是跨應用了,專業點,還是跨進程吧!)
在這一小節,我們會涉及到一個新的知識點,AIDL(Android Interface Define Language 安卓接口定義語言),在AndroidStudio中我們可以創建一個AIDL文件。方法如下:在Android項目目錄結構的src/main文件夾,右擊 -> New -> Floder -> AIDL Floder,來創建一個AIDL文件夾,然后右擊該文件夾, New -> AIDL -> AIDL File, 即可創建一個AIDL文件。準備工作做的差不多了,下面開始進入今天的正題:
**1. 對MyService進行一些改進,為了方便理解,還是把整個文件放上吧**
```java
public class MyService extends Service {
private static final String TAG = "MyService";
//要訪問的數據,默認值為"default"
private String data="default";
//判斷該服務是否正在運行
private boolean isRunning = false;
//線程池,不了解的同學可以去了解一下,這個東西對Java來說也是挺重要的
private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1,1,1,TimeUnit.MINUTES,new SynchronousQueue<Runnable>());
//定義一個接口:當Service在運行期間每隔1s打印一下當前的數據
private Runnable task = new Runnable() {
@Override
public void run() {
while (isRunning){
Log.i(TAG, "當前數據: "+data);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void setData(String data) throws RemoteException {
MyService.this.data = data;
}
};
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
isRunning = true;
//啟動線程
poolExecutor.execute(task);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
isRunning = false;
Log.i(TAG, "onDestroy: ");
}
```
**2. 創建一個AIDL文件,內容如下:**
```java
interface IMyAidlInterface {
/**
* 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);
void setData(String data);
}
```
> 那些自動生成的內容我們暫時就先不看了,可以看到的是我增加了一個setData(String data) 接口,用來給Service的data變量設置值
**3. 增加新的控件,以便查看效果,重復代碼就不在這里貼了**
**activity_main.xml**
```java
<EditText
android:id="@+id/edit_data_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="這是另一個應用中的數據"/>
<Button
android:id="@+id/save_data_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存數據"/>
```
**MainActivity.java**
```java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
...
private Intent intent;
private IMyAidlInterface binder = null;
private EditText editData;
private Button savaDataBtn;
private ServiceConnection serviceConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
//新增語句,將service轉換為IMyAidlInterface的binder對象
binder=IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
...
intent = new Intent();
intent.setComponent(new ComponentName("com.duoshilin.aidltestapplication", "com.duoshilin.aidltestapplication.MyService"));
editData=findViewById(R.id.edit_data_input);
savaDataBtn=findViewById(R.id.save_data_btn);
savaDataBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
...
case R.id.save_data_btn:
try {
if(binder != null) {
binder.setData(editData.getText().toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
}
}
}
```
**4. 重新啟動兩個APP,點擊并查看效果。**

> ***需要注意的是,我們需要在綁定模式下才能修改數據,非綁定模式下我還不知道用什么方法可以修改數據,希望大神指教!***
# 5. IntentService的使用
# 6. 前臺服務的使用