1.使用前台服务
服务几乎都是在后台运行的, 一直以来它都是默默地做着辛苦的工作。 但是服务的系统优先级还是比较低的, 当系统出现内存不足的情况时, 就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。 前台服务和普通服务最大的区别就在于, 它会一直有一个正在运行的图标在系统的状态栏显示, 下拉状态栏后可以看到更加详细的信息, 非常类似于通知的效果。 当然有时候你也可能不仅仅是为了防止服务被回收掉才使用前台服务的, 有些项目由于特殊的需求会要求必须使用前台服务, 比如说墨迹天气, 它的服务在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息。
创建前台服务的代码:
package demo.xzy.qh.com.servicedemo;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.util.Log;
/**
* Function:创建前台服务
* Created by xuzhuyun on 2017/6/26.
*/
public class MyService extends Service {
private static final String TAG = "MyService";
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new Notification.Builder(this).setContentTitle("title")
.setContentText("this is detail text.")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setAutoCancel(false)
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return null;
}
}
可以看到, 这里只是修改了 onCreate()方法中的代码, 相信这部分的代码你会非常眼熟。没错!这就是我们在创建通知的方法。只不过这次在构建出 Notification 对象后并没有使用 NotificationManager 来将通知显示出来,而是调用了 startForeground()方法。这个方法接收两个参数,第一个参数是通知的 id,类似于 notify()方法的第一个参数,第二个参数则是构建出的 Notification 对象。调用 startForeground()方法后就会让 MyService 变成一个前台服务,并在系统状态栏显示出来。运行一下程序, 并点击 Start Service 或 Bind Service 按钮, MyService 就会以前台服务的模式启动了, 并且在系统状态栏会显示一个通知图标, 下拉状态栏后可以看到该通知的详细内容。
2.使用IntentService
服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现 ANR( Application NotResponding)的情况。所以这个时候就需要用到 Android 多线程编程的技术了, 我们应该在服务的每个具体的方法里开启一个子线程, 然后在这里去处理那些耗时的逻辑。 因此, 一个比较标准的服务就可以写成如下形式:
/**
* Function:
* Created by XZY on 2017/6/27 21:23
* Email:hgncxzy@qq.com
*/
public class MyService4 extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@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);
}
}
但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用 stopService()或者stopSelf()方法才能让服务停止下来。 所以, 如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:
package demo.xzy.qh.com.servicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
/**
* Function:
* Created by XZY on 2017/6/27 21:23
* Email:hgncxzy@qq.com
*/
public class MyService4 extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//处理具体逻辑
}
}).start();
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
}
虽说这种写法并不复杂, 但是总会有一些程序员忘记开启线程, 或者忘记调用 stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的服务, Android 专门提供了一个IntentService 类,这个类就很好地解决了前面所提到的两种尴尬,下面我们就来看一下它的用法。
package demo.xzy.qh.com.servicedemo;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
/**
* Function:学习使用IntentService:
* IntentService 的特性,这个服务在运行结束后应该是会自动停止的
* Created by xuzhuyun on 2017/6/26.
*/
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
/**
* 无参构造,调用父类有参构造方法
* Creates an IntentService. Invoked by your subclass's constructor.
*/
public MyIntentService() {
super("MyIntentService");
Log.i(TAG, "MyIntentService: ");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "onHandleIntent: Thread id is :" + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: executed.");
super.onDestroy();
}
}
这里首先是要提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现 onHandleIntent()这个抽象方法, 在这个方法中可以去处理一些具体的逻辑,而且不用担心 ANR 的问题,因为这个方法已经是在子线程中运行的了。这里为了证实一下, 我们在 onHandleIntent()方法中打印了当前线程的 id。 另外根据 IntentService 的特性,这个服务在运行结束后应该是会自动停止的, 所以我们又重写了 onDestroy()方法, 在这里也打印了一行日志,以证实服务是不是停止掉了。
这里的日志证明IntentService在运行完毕后自动结束了。
3.活动与服务进行通信
不知道你有没有发现, 虽然服务是在活动里启动的, 但在启动了服务之后, 活动与服务基本就没有什么关系了。 确实如此, 我们在活动里调用了 startService()方法来启动 MyService 这个服务, 然后 MyService 的 onCreate()和onStartCommand()方法就会得到执行。 之后服务会一直处于运行状态, 但具体运行的是什么逻辑, 活动就控制不了了。 这就类似于活动通知了服务一下: “你可以启动了!” 然后服务就去忙自己的事情了,但活动并不知道服务到底去做了什么事情,以及完成的如何。那么有没有什么办法能让活动和服务的关系更紧密一些呢?例如在活动中指挥服务去干什么,服务就去干什么。当然可以,这就需要借助我们刚刚忽略的 onBind()方法了。比如说目前我们希望在 MyService 里提供一个下载功能, 然后在活动中可以决定何时开始下载, 以及随时查看下载进度。 实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理,修改 MyService2中的代码,如下所示:
package demo.xzy.qh.com.servicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
/**
* Function:服务与活动间进行通信
* Created by xuzhuyun on 2017/6/26.
*/
public class MyService2 extends Service {
private static final String TAG = "MyService2";
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.i(TAG, "startDownload: executed. ");
}
public int getProgress() {
Log.i(TAG, "getProgress:executed. ");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: executed. ");
return mBinder;
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate: executed. ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: executed.");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: executed. ");
super.onDestroy();
}
}
可以看到,这里我们新建了一个 DownloadBinder 类,并让它继承自 Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。 当然这只是两个模拟方法, 并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。接着, 在 MyService2中创建了 DownloadBinder 的实例, 然后在 onBind()方法里返回了这个实例,这样 MyService2 中的工作就全部完成了。下面就要看一看, 在活动中如何去调用服务里的这些方法了。
package demo.xzy.qh.com.servicedemo;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
/**
* 1.测试前台服务
* 2.测试IntentService
* 3.活动与服务进行通信
*/
public class MainActivity extends Activity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private MyService2.DownloadBinder mDownloadBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.测试前台服务
Intent intent = new Intent(this, MyService.class);
startService(intent);
//2.测试IntentService
findViewById(R.id.start_intent_service).setOnClickListener(this);
//3.活动与服务进行通信
findViewById(R.id.activity_communicatte_with_service).setOnClickListener(this);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: executed. ");
mDownloadBinder = (MyService2.DownloadBinder) service;
mDownloadBinder.startDownload();
mDownloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected:executed. ");
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_intent_service:
Log.i(TAG, "onClick: 主线程id:"+Thread.currentThread().getId());
Intent intent = new Intent(this, MyIntentService.class);
startService(intent);
break;
case R.id.activity_communicatte_with_service:
Intent intent2 = new Intent(this, MyService2.class);
bindService(intent2, mServiceConnection, BIND_AUTO_CREATE);
break;
}
}
@Override
protected void onDestroy() {
Log.i(TAG, "onDestroy: executed. ");
unbindService(mServiceConnection);
super.onDestroy();
}
}
可以看到,这里我们首先创建了一个 ServiceConnection 的匿名类,在里面重写 了onServiceConnected()方法和 onServiceDisconnected()方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。在 onServiceConnected()方法中,我们又通过向下转型得到了 DownloadBinder 的实例, 有了这个实例, 活动和服务之间的关系就变得非常紧密了。现在我们可以在活动中根据具体的场景来调用 DownloadBinder 中的任何 public 方法,即实现了指挥服务干什么,服务就去干什么的功能。
这里仍然只是做了个简单的测试, 在onServiceConnected()方法中调用了 DownloadBinder 的 startDownload()和 getProgress()方法。
bindService()方法接收三个参数, 第一个参数就是刚刚构建出的 Intent 对象, 第二个参数是前面创建出的 ServiceConnection 的实例, 第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务。这会使得 MyService2 中的 onCreate()方法得到执行,但 onStartCommand()方法不会执行。然后如果我们想解除活动和服务之间的绑定该怎么办呢?调用一下 unbindService()方法就可以了,看onDestroy()方法里面的接触绑定即可。
点击按钮后,打印的日志如下:
表明,活动与服务间进行了通信,startDownload()和getProgress()方法均被调用.退出app,执行解绑操作,打印日志如下: