android 入行也大半年了,一直都没有对基础知识做一个梳理,今后就对基础知识逐个总结,先从 Service 开始。
一、Servive 概念
我们一般把 Service 称做 "后台默默的劳动者"。现在的 智能手机应用程序都能在后台运行,这就是 得益于 Service 。在
android 开发中,Service 就是用来解决程序后台运行的解决方案,适合去执行一些不需要跟用户交互而且要求长期运行的任务。不依赖任何用户界面,即使程序切换到后台,或者打开新的应用程序。但是需要注意以下两点:
- Service 不是运行在一个独立的进程中,而是依赖于创建该Service的 应用程序进程,一旦该程序被 kill ,Service 也随之停止运行
- 不要以为 Service 是工作在后台,就理所当然的认为 Service 会自动开启线程。Service 默认是工作在 UI 线程的,一旦有耗时操作,必须开启新线程,防止 ANR 异常。
二、Service 的定义
使用 android studio 快速定义一个 MyService 继承自 Service.Service中 onBind() 为抽象方法,我们暂且先必须实现它,接着依次实现 onCreate() ,onStartCommand,onDestroy 生命周期方法。如下所示:
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
/**
* 跟其他组件绑定时使用
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Service 第一次创建时回调
*/
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: excuted");
}
/**
* 每次启动 Service 时回调
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: excuted");
return super.onStartCommand(intent, flags, startId);
}
/**
* 销毁 Service 时回调
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: excuted");
}
}
注意:身为 四大组件之一的 Service 也必须在 Manifest 里面注册,不然会报错,但是这里智能的 as 已经帮我们注册好啦~
<service
android:name=".app.service.MyService"
android:enabled="true"
android:exported="true">
</service>
- enabled表示是否启用该 服务
- exported表示是否允许除了当前应用程序之外的其他程序访问这个服务。
三、Service 的启动与停止
我们在 activity 中添加两个 Button ,用来触发 Service 的启动与停止。这里使用黄油刀直接注入。启动与停止的逻辑相当简单,一般我们组件间通信都是用 intent ,这里也不例外,startService(),stopService() 传入对应的 intent 即可。
@OnClick({R.id.start_service, R.id.stop_service})
public void onClick(View view) {
switch (view.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
//启动service
startService(startIntent);
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
//停止service
stopService(stopIntent);
break;
}
直接 run 程序跑到手机上,来看看 Service 具体的启动过程,我们事先已经在对应的生命周期方法中打好 log 了。现在点击 start 效果如下:
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
log 很清晰明了。当我们点击 start按钮启动 Service 时,系统回调了onCreate(),onStartCommand() 方法。这时候我们大胆猜测 Service 已经启动了,打开手机应用管理查看,如图
从此图明显看出 Service 确实启动成功了,同时也可以印证我们之前的观点:
** Service 不是运行在一个独立的进程中,而是依赖于创建该 Service 的 应用程序进程,一旦该程序被 kill ,Service 也随之停止运行**
这个时候我们再点击几次 starService 按钮,看看启动流程。
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
啊?当我再次点击三次 startService ,从 log 可以清晰的看出。这时候 onCreate() 已经不再回调了,而是重复回调了三次 onStartCommand()。 这时查看手机应用管理查看该 Service 也确实还在。我们抱着怀疑的态度继续做下实验。
点击 stopService 执行停止 Service 的逻辑。
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted
看看 log 知道系统回调了 onDestroy() 方法。此时我们看下手机,啊?MyService 已经没了。这样我们销毁 Service成功。我们继续~
点击 starService
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
看看 log 。纳尼?又从头开始回调一遍,再看看手机中运行程序,卧槽?MyService 又回来了。ok,结论来了~
- 当我们第一次启动 Service 时,Service 还不存在,系统会回调 onCreate() 创建Service ,同时回调 onStartCommand() 启动 Service。
- Service 一旦被创建,假如你没有 手动调用 stopService() 去销毁它,或者 kill 程序。它就一直会处于后台进程中,当我们再次启动时,系统不会再去创建,而是直接启动,即回调 onStartCommand() 方法。
四、Service 与 Activity 通信
之前的 Service 启动方式似乎不那么灵活,我们新建一个服务是要他给我们干活的,然而现在我们只能 start 、stop 去启动、停止 Servive 。start 完之后 Service 就在那边不停的干活。怎么干活、干了多少 、做得如何 我们不得而知。那么我们就有必要换一种方式来启动 Service 了。让我们能够拥有主动权。
假设一个场景:我们需要启动一个 Service 去执行下载功能,我们要控制什么时候下载,知道下载进度。
仔细看看 MyService 中的代码 、好像一开始就有一个回调方法在那边没用呢?哈哈,就从这个方法进去搞事情,修改 MyService 中的代码。
public class MyService extends Service {
private static final String TAG = "MyService";
private DownloadBinder mBinder = new DownloadBinder();
/**
* 跟其他组件绑定时使用
*
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
/**
* Binder---IBinder的实现类,绑定Service 的时候,返回实例给调用方使用
*/
public class DownloadBinder extends Binder {
public void startDownload() {
Log.e(TAG, "startDownload: excuted");
}
public int getProgress() {
Log.e(TAG, "getProgress: excuted");
return 0;
}
}
}
Binder 是 IBinder的实现类,绑定 Service 的时候,返回实例给调用方使用。我们自定义 Binder 类,在类中定义 public 方法,最终将 Binder 实例通过 onBind 返回给调用方,调用方只要有 Binder 实例就能随便调用它的公共方法。
那么 Activity (调用方) 需要拿到 Binder 实例。定义全局 DownloadBinder 。实例化
ServiceConnection 回调 onServiceConnected,onServiceDisconnected方法。仔细一看,onServiceConnected 携带 IBinder 参数。这个就是一旦我们绑定成功,Service 回传回来的实例,这时候通过向下转型成 DownloadBinder 即可。之后就可以随意控制方法调用啦~
private MyService.DownloadBinder mDownloadBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mDownloadBinder = ((MyService.DownloadBinder) service);
mDownloadBinder.startDownload();
mDownloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
修改布局 Button.点击 bindService 执行 bindService() 传入对应的 Intent,ServiceConnection实例,标志位(这里表示绑定之后自动创建Service)。
@OnClick({R.id.bind_service, R.id.unbind_service})
public void onClick(View view) {
switch (view.getId()) {
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, mConnection, BIND_AUTO_CREATE);//绑定服务
break;
case R.id.unbind_service:
unbindService(mConnection);//解除绑定
break;
}
}
运行效果如下:
05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted
我们看到创建了 Service ,但是不会回调 onStartCommand 方法了。同时执行了下载的方法。
此时点击 unBinderService 按钮,看下log
05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted
05-18 18:00:13.051 23371-23371/com.sunnada.jpushdemo E/MyService: onDestroy: excuted
此时回调 onDestroy() 方法 证明 Service 已经销毁。
注意:
- 我们一般在 activity 的 onDestroy() 生命周期函数中 unBinderServise 否则有内存泄漏的风险。
- 假如 使用 starService 启动了 Service ,并 通过 bindService 又绑定启动 Service 这时必须同时调用 stopService,unBindService 才会销毁 Service。
五、前台 Service
前面我们讲的 Service 都是默默的在后台运行的,系统优先级较低,在内存吃紧的情况下,有可能被系统回收,而前台 Service 会一直有一个正在运行的图标在系统的状态栏显示,类似一个通知。
修改 MyService 代码:
/*
* Service 第一次创建时回调
*/
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: excuted");
Intent intent = new Intent(this, LoginActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat
.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pendingIntent)
.build();
startForeground(1,notification);
}
这里使用 Builder 模式构造一个通知,最后调用 startForeground ,传入通知的 id ,Notification 实例化对象。让 MyService 变成一个前台服务,并且在状态栏显示。重新 run 程序,点击 BindService
over~下拉通知栏可以看到明显的通知。
六、IntentService
Service 默认是执行在主线程的,如果需要执行耗时操作,必须开启子线程,我们一般的做法是在 onStartCommand 中开启子线程去执行。
@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);
}
但是 Service 一旦启动就会一直在后台运行,不断的执行耗时操作,这是相当消耗资源的,在这种情况下,通常在执行完具体逻辑之后,添加 stopSelf() 让 Service 停止。但是这种做法有时候会让我们忘记开线程,或者忘记 stopSelf ,为了解决这种情况,Android 特地提供了 IntentService 。
新建一个MyIntentService 继承 IntentService,注意别忘了注册。
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.e(TAG, "onHandleIntent: " + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: excute");
}
}
一个空构造 调用父类有参构造,复写 onHandleIntent 抽象方法,直接就是子线程,可以直接执行耗时操作,而且 执行完之后
IntentService 会自动停止。下面修改 activity 按钮来测试看看。
run 程序,点击 starService,看 log
05-18 22:34:42.568 22842-24560/com.sunnada.jpushdemo E/MyIntentService: onHandleIntent: IntentService[MyIntentService]
05-18 22:34:42.569 22842-22842/com.sunnada.jpushdemo E/MyIntentService: onDestroy: excute
从 log 可以看出我们启动 IntentService 之后并没有调用 stop ,而
onDestroy 已经回调了,并且 onHandleIntent 确实是工作在子线程的。
over~由于篇幅的原因,Service 梳理就先到这了,后续会总结一些 Service 的实战应用。
声明:以上只是本人对于基础知识的梳理总结,部分参考自第一行代码第二版,如有不足之处,还望指出。
更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学