[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生命周期图解](http://upload-images.jianshu.io/upload_images/13971762-a9920d73aa10519a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
由上图我们可以很清晰的看到,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次停止(解除绑定)按钮**
![非绑定模式.png](https://upload-images.jianshu.io/upload_images/13971762-017b4c86f882c73b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
### 绑定模式
> **为验证多次点击效果,点击了5次绑定和2次解除绑定按钮,发现第二次点击解除绑定按钮出现提示**
![绑定模式.png](https://upload-images.jianshu.io/upload_images/13971762-f84e0c40657c73fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
---
# 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了,是不是很简单呢?下面我们来运行一下看看效果吧!**
![image.png](https://upload-images.jianshu.io/upload_images/13971762-6b9000b48ff64fa2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> 要注意的是,这个时候我们还是要在原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,点击并查看效果。**
![image.png](https://upload-images.jianshu.io/upload_images/13971762-f99d11d82222353b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
> ***需要注意的是,我们需要在绑定模式下才能修改数据,非绑定模式下我还不知道用什么方法可以修改数据,希望大神指教!***
# 5. IntentService的使用
# 6. 前台服务的使用