1 基础认知
1.1 Service 是什么?
Service是一个应用组件, 它用来在后台完成一个时间跨度比较大的工作且没有关联任何界面。
1.2 Service所做工作举例
- 访问网络
- 播放音乐
- 文件IO操作
- 大量数据的数据库操作
- 其他
1.3 Service的特点
- Service在后台运行,不与用户直接交互
- 即使退出应用,服务也不会立即停止
- 在默认情况下,Service运行在应用程序进程的主线程中(也就是UI线程),如果需要在Service中处理一些如网络连接这种耗时任务,那么应该将这些任务放在工作线程中,避免阻塞用户界面
1.4 Service的分类
- Local Service(本地服务):Service对象与Serive的启动者在同个进程中运行, 两者的通信是进程内通信
- Remote Service(远程服务):Service对象与Service的启动者不在同一个进程中运行, 这时存在一个进程间通信的问题, Android专门为此设计了AIDL来实现进程间通信
2 启动与停止服务
2.1 启动方式
2.1.1 代码示例
布局文件代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/start_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="112dp"
android:layout_marginLeft="112dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="启动服务"
app:layout_constraintEnd_toStartOf="@+id/stop_service"
app:layout_constraintHorizontal_bias="0.018"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bind_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="绑定服务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.345"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/start_service" />
<Button
android:id="@+id/stop_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="停止服务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/unbind_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="解绑服务"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/bind_service"
app:layout_constraintTop_toBottomOf="@+id/stop_service" />
</androidx.constraintlayout.widget.ConstraintLayout>
清单文件代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicepractice">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService"/>
</application>
</manifest>
activity代码:
package com.example.servicepractice;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private Button startService;
private Button stopService;
private Button bindService;
private Button unbindService;
private boolean isBind;
private Intent intent;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected: ");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected: ");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(this, MyService.class);
initViews();
}
private void initViews() {
startService = findViewById(R.id.start_service);
stopService = findViewById(R.id.stop_service);
bindService = findViewById(R.id.bind_service);
unbindService = findViewById(R.id.unbind_service);
startService.setOnClickListener(onClickListener);
stopService.setOnClickListener(onClickListener);
bindService.setOnClickListener(onClickListener);
unbindService.setOnClickListener(onClickListener);
}
View.OnClickListener onClickListener = new View.OnClickListener() {
@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;
case R.id.bind_service:
if (!isBind) {
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
isBind = true;
Toast.makeText(MainActivity.this, "绑定成功",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this,
"已经绑定成功,不需要再次绑定",
Toast.LENGTH_SHORT).show();
}
break;
case R.id.unbind_service:
if (isBind) {
isBind = false;
Toast.makeText(MainActivity.this, "解绑完成",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "已经解绑,不需要再次解绑",
Toast.LENGTH_SHORT).show();
}
break;
}
}
};
@Override
protected void onDestroy() {
if (isBind) {
unbindService(serviceConnection);
isBind = false;
}
super.onDestroy();
}
}
service中的代码:
package com.example.servicepractice;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
public MyService() {
Log.e(TAG, "MyService: " );
}
@androidx.annotation.Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: ");
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.e(TAG, "onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy: ");
super.onDestroy();
}
class MyBinder extends Binder {
public MyBinder() {
Log.e(TAG, "MyBinder: ");
}
}
}
2.1.2 一般启动
- context.startService(Intent intent)
- context.stopService(Intent intent)
点击开始服务
MyService: MyService:
MyService: onCreate:
MyService: onStartCommand:
当service已经启动后再次调用startService(Intent intent),将不会再去创建service,而是调用onStartCommand()回调方法,也就是说Service的构造方法和onCreate()回调方法将不再调用。
onStartCommand:
点击停止服务
MyService: onDestroy:
2.1.3 绑定启动与解绑
- context.bindService(Intent intent, ServiceConnection connection)
- context.unbindService(ServiceConnection connection)
点击绑定服务 , 绑定服务过后再次调用bindService()不会调用任何回调方法
MyService: MyService:
MyService: onCreate:
MyService: onBind:
MyService: MyBinder:
MainActivity: onServiceConnected:
点击解绑服务
MyService: onUnbind:
MyService: onDestroy:
如果绑定过后没有解绑就销毁activity,则会报如下错误
E/ActivityThread: Activity com.example.servicepractice.MainActivity has leaked ServiceConnection com.example.servicepractice.MainActivity$1@3b1444 that was originally bound here
android.app.ServiceConnectionLeaked: Activity com.example.servicepractice.MainActivity has leaked ServiceConnection com.example.servicepractice.MainActivity$1@3b1444 that was originally bound here
at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:1610)
at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:1502)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1659)
at android.app.ContextImpl.bindService(ContextImpl.java:1612)
at android.content.ContextWrapper.bindService(ContextWrapper.java:698)
at com.example.servicepractice.MainActivity$2.onClick(MainActivity.java:67)
at android.view.View.performClick(View.java:6597)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2.1.3 Service的生命周期图
从上面这张图片可以看出,一个Service可以和多个组件绑定,并且只有当所有的组件都解绑之后才会调用onUnbind()。、
3 AIDL
3.1 基础认知
- 每个应用程序都运行在自己的独立进程中,并且可以启动另一个应用进程的服务,而且经常需要在不同的进程间传递数据对象。
- 在Android平台,一个进程不能直接访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。
- AIDL (Android Interface Definition Language) 用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。
- 如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
3.2 编写aidl注意事项
- 接口名和aidl文件名必须相同
- 接口和方法前不能加访问权限修饰符(public, private, protected等),也不能用final,static
- Aidl默认支持类型包括java基本类型和String,List,Map,CharSequence,使用这些类型时不需要import声明。对于List和Map中的元素类型必须是aidl支持的类型,如果使用自定义类型作为参数或者返回值,自定义类型必须实现Parcelable接口。
- 自定义类型和aidl生成的其他接口类型在aidl描述文件中,应该显示import,即便该类和aidl文件在同一个包中。
- 自定义类型的aidl文件名要和自定义类的名字相同,如Book.java,Book.aidl
- aidl文件和自定义实体类在服务端和客户端都要有一套一样的,包名也要一样
- 在aidl文件中所有非java基本类型参数必须加上in, out , inout标记,以指明参数是输入参数、输出参数还是输入输出参数。
- java 原始类型默认的标记为in,不能为其他标记。
3.3 远程服务端代码
清单文件代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.remoteservice">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".remote.MyRemoteService">
<intent-filter>
<action android:name="android.intent.action.test"/>
</intent-filter>
</service>
</application>
</manifest>
实体数据类:
package com.example.remoteservice.remote;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
public class Student implements Parcelable {
private int id;
private String name;
private int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
protected Student(Parcel in) {
id = in.readInt();
name = in.readString();
age = in.readInt();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
Log.e("TAG", "createFromParcel: ");
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
Log.e("TAG", "writeToParcel: ");
dest.writeInt(id);
dest.writeString(name);
dest.writeInt(age);
}
}
远程Service代码:
package com.example.remoteservice.remote;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyRemoteService extends Service {
@androidx.annotation.Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("TAG", "onBind()");
return new StudentService();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e("TAG", "onUnbind()");
return super.onUnbind(intent);
}
//处理Student相关的业务逻辑类
class StudentService extends IStudentService.Stub {
@Override
public Student getStudentById(int id) {
Log.e("TAG", "Service getStudentById() "+id);
return new Student(id, "Tom", 80);
}
}
}
Service aidl接口文件:
// IStudentService.aidl
package com.example.remoteservice.remote;
import com.example.remoteservice.remote.Student;
// Declare any non-default types here with import statements
interface IStudentService {
Student getStudentById(int id);
}
实体类的aidl文件:
// Student.aidl
package com.example.remoteservice.remote;
parcelable Student;
文件结构图:
3.4 客户端代码:
清单文件代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicepractice">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
</activity>
<activity android:name=".client.ClientActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService"/>
</application>
</manifest>
xml文件代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://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="bindRemoteService"
android:text="bind remote Service" />
<EditText
android:id="@+id/et_aidl_id"
android:layout_width="match_parent"
android:layout_height="50dp"
android:hint="学员ID"
android:text="3" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="invokeRemote"
android:text="调用远程服务端的方法" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unbindRemoteService"
android:text="unbind remote Service" />
</LinearLayout>
activity代码:
package com.example.servicepractice.client;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.example.remoteservice.remote.IStudentService;
import com.example.remoteservice.remote.Student;
import com.example.servicepractice.R;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class ClientActivity extends AppCompatActivity {
private EditText et_aidl_id;
private ServiceConnection conn;
private IStudentService studentService;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
et_aidl_id = findViewById(R.id.et_aidl_id);
}
public void bindRemoteService(View v) {
Intent intent = new Intent("android.intent.action.test");
intent.setPackage("com.example.remoteservice");
if (conn == null) {
conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e("TAG", "onServiceConnected()");
studentService = IStudentService.Stub.asInterface(service);
}
};
bindService(intent, conn, Context.BIND_AUTO_CREATE);
Toast.makeText(this, "绑定Service", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "已经绑定Service", Toast.LENGTH_SHORT).show();
}
}
public void invokeRemote(View v) throws RemoteException {
if (studentService != null) {
int id = Integer.parseInt(et_aidl_id.getText().toString());
Student student = studentService.getStudentById(id);
Toast.makeText(this, student.toString(), Toast.LENGTH_SHORT).show();
}
}
public void unbindRemoteService(View v) {
unbindRemoteService();
}
private void unbindRemoteService() {
if (conn != null) {
unbindService(conn);
conn = null;
studentService = null;
Toast.makeText(this, "解绑Service", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "还未绑定Service", Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
unbindRemoteService();
super.onDestroy();
}
}
客户端文件结构图:
3.5 运行效果截图
服务端日志:
TAG: onBind()
TAG: Service getStudentById() 3
TAG: writeToParcel:
客户端日志:
TAG: onServiceConnected()
TAG: createFromParcel:
4 案例:播放音乐
xml代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="播放"/>
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂停"/>
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止"/>
<Button
android:id="@+id/exit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="退出"/>
</LinearLayout>
activity代码:
package com.example.servicepractice;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class MusicAcitivity extends AppCompatActivity implements View.OnClickListener {
private Button btnPlay;
private Button btnPause;
private Button btnStop;
private Button btnExit;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_music);
initViews();
}
private void initViews() {
btnPlay = findViewById(R.id.play);
btnPause = findViewById(R.id.pause);
btnStop = findViewById(R.id.stop);
btnExit = findViewById(R.id.exit);
btnPlay.setOnClickListener(this);
btnPause.setOnClickListener(this);
btnStop.setOnClickListener(this);
btnExit.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(this, MusicService.class);
switch (v.getId()) {
case R.id.play:
intent.putExtra("action", "play");
break;
case R.id.pause:
intent.putExtra("action", "pause");
break;
case R.id.stop:
intent.putExtra("action", "stop");
break;
case R.id.exit:
intent.putExtra("action", "exit");
finish();
break;
}
startService(intent);
}
}
service代码:
package com.example.servicepractice;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
public class MusicService extends Service {
private MediaPlayer mediaPlayer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void playMedia() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.water_hander);
}
mediaPlayer.start();
}
private void pauseMedia() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
private void stopMedia() {
if (mediaPlayer != null) {
mediaPlayer.stop();//停止
mediaPlayer.reset();//重置
mediaPlayer.release();//释放资源
mediaPlayer = null;//使对象为空,否者重新播放会crash
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("TAG", "onStartCommand: ");
String action = intent.getStringExtra("action");
if (action.equals("play")) {
playMedia();
} else if (action.equals("pause")) {
pauseMedia();
} else if (action.equals("stop")) {
stopMedia();
} else if (action.equals("exit")) {
stopMedia();
stopSelf();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i("TAG", "onDestroy: ");
super.onDestroy();
}
}