我尽量不打错别字,用词准确,不造成阅读障碍。
最近在巩固Android知识点,突然感觉自己对Service的知识并没有形成一个知识网络,所以在此简单总结一下,复杂的东西一是不怎么会,不敢写出来丢人,二是写起来费劲,本文章可能并不适合小白,当然现在小白可能也很少了。
Service一般分为本地服务和远程服务,当然也可以按照其他方面分,但是刚开始接触还是分为这两种好理解。
首先是不管本地服务还是远程服务都必须知道的知识点。
你必须知道Service的生命周期,这是基础:
生命周期显示Service有两种启动方式,一种是直接startService(左边),一种是使用bindService(右边);
代码很简单,就从郭神那里拿过来了,自己为了验证几个问题也写了一遍。
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() executed");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
然后只要在Activity里面使用startService就好了:
Intent intent = new Intent(this, MyService.class);
startService(intent); //启动服务
/*****/
stopService(intent); //结束服务
最后要在AndroidManifest.xml里面注册Service:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
……
<service android:name="com.example.servicetest.MyService" >
service>
application>
但是这种方式启动后,除了事先在生命周期里面可以有一些操作外,之后与Serivice就取不到联系了,就是不能通信!所以一般不会这么写,一般都用第二种方式。
bindService的启动方式与startService的差别不大,首先要在MyService里面重写onBind方法,并创建Bind类,最后加上Bind类对象的引用:
public class MyService extends Service {
private MyBinder mBinder = new MyBinder();
public static final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() executed");
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
class MyBinder extends Binder {
public void startDownload() {
Log.d("TAG", "startDownload() executed");
// 执行具体的下载任务
}
}
}
接下来在Activity里面加上与Service的链接:
private MyService.MyBinder myBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyBinder) service;
myBinder.startDownload();
}
};
这样我们就可以在onServiceConnected里面获取Bind实例,并调用具体的public方法了;
接下来可以启动Service了:
在Activity里面调用:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE); //绑定服务
/******/
unbindService(connection); //解绑服务
BIND_AUTO_CREATE:表示自动创建服务;
startService方式启动的服务直接stopService就好了,即销毁服务,没有解除一说,因为没绑定啊。bindService方式启动的服务需要解绑:unbindService(connection); 然后才是销毁:stopService(intent); 没有解绑就直接销毁是不行的,一个Service必须在没有绑定任何Activity的时候才可以销毁,所以startService方式启动的Service可以直接stop,而bindService()不行。
Service一般在后台运行,比如心跳保活、音乐播放等等,但是,Service是运行在主线程的,所以不可以在Service里直接进行耗时操作,一般都会另开线程操作,那为什么要在主线程运行呢,因为可以与Activity关联,好控制啊。所以一般标准的Service这样写:
......
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行后台任务
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
class MyBinder extends Binder {
public void startDownload() {
new Thread(new Runnable() {
@Override
public void run() {
// 执行具体的下载任务
}
}).start();
}
}
Service是可以在前台运行的,因为后台Service优先级比较低,容易被回收。前台Service一般与Notification结合使用。
public class MyService extends Service {
public static final String TAG = "MyService";
private MyBinder mBinder = new MyBinder();
@Override
public void onCreate() {
super.onCreate();
//添加下列代码将后台Service变成前台Service
//构建"点击通知后打开MainActivity"的Intent对象
Intent notificationIntent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);
//新建Builer对象
Notification.Builder builer = new Notification.Builder(this);
builer.setContentTitle("前台服务通知的标题"); //设置通知的标题
builer.setContentText("前台服务通知的内容"); //设置通知的内容
builer.setSmallIcon(R.mipmap.ic_launcher); //设置通知的图标
builer.setContentIntent(pendingIntent); //设置点击通知后的操作
Notification notification = builer.getNotification(); //将Builder对象转变成普通的notification
startForeground(1, notification);//让Service变成前台Service,并在系统的状态栏显示出来
}
.........
}
如果通知栏方式比较老旧,请使用新的方式。
本地服务其实就是上面讲的方式,远程服务与本地服务不同的地方在于AndroidManifest.xml文件的设置:
<service
android:name="com.example.servicetest.MyService"
android:process=":remote" >
service>
远程服务已经是另一个进程了,所以在MyService的onCreate方法里面有耗时操作时当前进程是不会ANR的。
那么我们为什么一般不用远程服务呢?因为无法绑定,前面说了bindService()方法,远程服务时Service与Activity不在一个进程,所以无法绑定,那么岂不是不好控制了,不能通信了?所以远程服务尽量不要用,但是如果必须用怎么办?只能使用进程间通信技术(IPC)了,进程间通信有很多技术,在这里举例AIDL。
新建xxxx.aidl文件,右键java文件夹-new-AIDL,就会新建aidl文件夹,并有aidl文件,内容如下:
package com.example.servicetest;
interface MyAIDLService {
int plus(int a, int b);
String toUpperCase(String str);
}
之后保存并编译,然后在MyService中实现刚定义的aidl文件接口:
public class MyService extends Service {
......
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
MyAIDLService.Stub mBinder = new MyAIDLService.Stub() {
@Override
public String toUpperCase(String str) throws RemoteException {
if (str != null) {
return str.toUpperCase();
}
return null;
}
@Override
public int plus(int a, int b) throws RemoteException {
return a + b;
}
};
}
此时onBind返回的就是MyAIDLService.Stub的实现了。然后在Activity里面修改代码:
private MyAIDLService myAIDLService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAIDLService = MyAIDLService.Stub.asInterface(service);
try {
int result = myAIDLService.plus(3, 5);
String upperStr = myAIDLService.toUpperCase("hello world");
Log.d("TAG", "result is " + result);
Log.d("TAG", "upperStr is " + upperStr);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
onServiceConnected方法中调用MyAIDLService.Stub.asInterface()将service装换为MyAIDLService对象;
接下来bindService();时就可以绑定Service并运行方法了。但这只是在程序内部链接远程服务,真正情况下是外部程序链接远程服务;由于Android5.0后已经禁止隐式声明Intent来启动Service了,所以我们显示的方式,首先是AndroidManifest.xml:
<service
android:name="com.example.servicetest.MyService"
android:process=":remote" >
<intent-filter>
<action android:name="com.example.servicetest.MyAIDLService"/>
intent-filter>
service>
编译并运行程序。新建项目AndroidTestClient,将之前项目的aidl文件拷过来,注意加上包路径一起拷过来,最后就是在项目main目录下多了个aidl文件夹(与java平级),其下有导过来的包名的文件夹,再往下有aidl文件,然后打开新项目的MainActivity,加上:
private MyAIDLService myAIDLService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAIDLService = MyAIDLService.Stub.asInterface(service);
try {
int result = myAIDLService.plus(50, 50);
String upperStr = myAIDLService.toUpperCase("comes from ClientTest");
Log.d("TAG", "result is " + result);
Log.d("TAG", "upperStr is " + upperStr);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button bindService = (Button) findViewById(R.id.bind_service);
bindService.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.servicetest.MyAIDLService");
intent.setPackage("com.example.servicetest");
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
}
结果:
点击onBind按钮运行;不过还有一点需要说明的是,由于这是在不同的进程之间传递数据,Android对这类数据的格式支持是非常有限的,基本上只能传递Java的基本数据类型、字符串、List或Map等。
参考文章(感谢):
https://blog.csdn.net/guolin_blog/article/details/9797169