四大组件之 Service(服务)简析

作为 Android 四大组件之一的 Service,使用场景虽然没有 Activity 的 广泛,但是在特定的场景下,Service 却是不可或缺的,简单来说,Service 是一种可在后台执行长时间运行操作而不提供页面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍可继续运行。

启动服务

首先我们创建一个 MyService 继承 Service(抽象类),重写 onCreate() 方法、onStartCommand() 方法和 onDestroy() 方法,onBind() 方法在Service中是抽象方法,必须重写,如下所示

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind: ");
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }
}

然后在 Activity 中通过 startService() 方法就可以启动服务了,具体如下:

startService(new Intent(this,MyService.class));

相应地,启动服务后应该有停止服务方法:

stopService(new Intent(this,MyService.class));

当我们启动一个 Service 的时候,如果此时这个服务还未启动的时候,就会调用 Service 中的 onCreate() 和 onStartCommand() 方法,如果此时这个服务已经启动的时候就只会调用 onStartCommand() 方法。其实 Service 中还有一个弃用的 onStart() 方法,onStartCommand() 内部调用了 onStart() 方法,我们做一个了解即可,日常开发中有需要的话重写 onStartCommand() 即可,onStartCommand() 方法的返回值是 int 类型,不同的返回值具有不同的效果,具体后面会进行说明,Service 中并没有像Activity那样有 onStop() 回调方法。

绑定服务

绑定服务一般适用于和 Activity 通信,这里需要注意的是 Service 是运行在主线程中,如果我们执行耗时操作,需要开启工作线程去执行。绑定服务和启动服务一样得先有个 Service:

public class MyService extends Service {

    private static final String TAG = "MyService";
    
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate: ");
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind: ");
        return new MyBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

     public class MyBinder extends Binder {
        MyService getService() {
            return MyService.this;
        }
    }
}

这里有个 MyBinder 内部类,继承了 Binder ,然后在 onBind() 方法返回了MyBinder一个实例。

在 Activity 中绑定服务:

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected: ");
            //这里的IBinder涉及到Binder机制,有兴趣的同学可以去详细了解下
            //可以简单地理解为是 Service中 onBinder 返回的实例
            ((MyService.MyBinder) service).getService().startDownloadApk();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: ");
        }
    };

   bindService(new Intent(this,MyService.class),mConnection,BIND_AUTO_CREATE);

可以看到,我们首先创建了一个 ServiceConnection 匿名内部类,在 onServiceConnected() 方法中我们可以得到 MyBinder 的实例。bindService() 方法接收三个参数,第一个参数就是我们创建的 Intent 对象,第二个参数就是前面创建的ServiceConnection 的实例,第三个参数是一个标志位,一般都是 BIND_AUTO_CREATE,表示在Activity和Service建立关联后自动创建Service,即 MyService中的 onCreate() 方法得到执行。当我们调用 bindService() 方法进行绑定服务时,如果服务此前没有绑定过,则会执行 onBind() 方法,如果已经绑定过,则不会继续执行 onBind() 方法,这点需同每次 startService() 方法启动服务时都会调用onStartCommand() 作区分。此外,通过 bindService() 的绑定的Service的生命周期和调用绑定方法的 Context 生命周期一样,即如果 context 如果是Activity,当Activity 销毁的时候 Service 也会销毁,即使没有调用 unBindService() 方法,但是不建议这么做,因为会系统会抛出异常,虽然这个异常被系统捕捉了。而当 Context 是 Application 时,我们没及时的解绑 Service,则 Service 的生命周期就和 Application 一样长,这在开发中值得我们注意,使用不当会造成资源浪费、内存泄漏,影响应用性能。

解绑服务就很简单啦,直接在Activity中调用 unBindService() 方法。

unbindService(mConnection);

mConnection 就是我们之前创建的 ServiceConnection 啦。

我们可以同时将多个客户端一般是Activity绑定到服务。但是,系统会缓存 IBinder 服务通信通道。换言之,只有在第一个客户端绑定服务时,系统才会调用服务的 onBind() 方法来生成 IBinder。然后,系统会将同一 Binder 传递至绑定到相同服务的所有其他客户端,无需再次调用 onBinder() 方法,当最后一个客户端与服务的绑定时,系统会销毁服务,除非 startService() 方法也启动了该服务。

销毁Service

如前面所说,如果我们只是通过 startService() 方法启动服务,调用 stopService() 方法即可销毁 Service,可以看到打印日志 Service 调用了 onDestroy() 方法,当然也可以调用 Service 的 stopSelf() 方法停止服务。


同理,如果我们只是通过绑定服务 bindService() 方法创建服务,调用 unBindService() 方法即可销毁 Service:



可以看到,在调用 onUnbind() 方法之后紧接着调用 onDestroy() 方法。

这两种销毁方式很好理解,但如果我们既通过 startService() 启动服务,又通过 bindService() 绑定服务,如何才能销毁服务呢?答案是 stopService() 和 unBindService() 都要调用,Service 才能真正被销毁。

前台服务

Service 默认是在后台运行的,相对来说系统优先级还是比较低的,当系统出现内存不足时,就有可能会回收掉正在后台运行的 Service,这时我们可以通过开启前台服务解决这个问题。当然使用前台服务不仅仅是为了提高 Service 优先级的原因,更多时候是让用户感知服务的存在,达到对用户友好的目的,比如通过服务播放音乐的播放器,状态栏的通知可能表示正在播放的歌曲,并且允许用户通过启动 Activity 与音乐播放器进行交互。启动前台服务也很简单,主要是通过调用 startForeground() 方法,此方法有两个参数:唯一标识通知的整型数和用于状态栏的 Notification。此通知必须拥有 PRIORITY_LOW 或更高的优先级,代码如下:

public class ForegroundService extends Service {

    //Channel ID 必须保证唯一
    private static final String CHANNEL_ID = "me.tandeneck.blogdemo.service.foreground";
    private static final String CHANNEL_NAME = "foreground";
    private static final int ONGOING_NOTIFICATION_ID = 0x1;

    @Override
    public void onCreate() {
        super.onCreate();
        //点击通知跳转的Activity
        Intent notificationIntent = new Intent(this, NotificationActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("标题")
                .setContentText("内容")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true) // 用户触摸时,自动关闭
                .setOngoing(false)//设置处于运行状态
                .build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
            //向系统注册通知渠道,注册后不能改变重要性以及其他通知行为
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
        //提供给 startForeground() 的整型 ID 不得为 0。
        startForeground(ONGOING_NOTIFICATION_ID, notification);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }
}

如果应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE权限

点击开启前台服务按钮,服务就会以前台服务的模式启动了,并且在系统通知栏弹出一个通知栏图标,下拉可以看到通知的详细内容:


image.png

如果要从前台移除服务,可以调用 stopForeground() 方法,此方法采用布尔值,表示是否同时移除状态栏通知。此方法不会停止服务,注意同 stopService 方法进行区分,如果在服务仍运行于前台将其停止,则通知也会随之移除。

Activity 和 Service 通信

1. 通过Bidner的方式获取Service的引用,就是上述的绑定服务实现。关键步骤如下:
  • 在服务中,创建可执行某种操作的 Binder 实例。
  • 在 Binder中 返回当前的 Service 实例,该实例中包含 Activity 可调用的公共方法
  • 获取 Activity 提供的交互方法,甚至获取 Activity 实例(做好相应的内存管理即可)
  • 从 onBind() 回调方法返回此 Binder 实例
  • 在 Activity 中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定方法。
2.观察者模式

Service 作为被观察的对象,Activity 作为观察者,但是最终也需要通过绑定服务的方式,在Activity中的 onServiceConnected() 方法中得到 Service 对象, 本质上还是通过 Bidner 通信(回调接口同理),不过可以通过第三方库 EventBus 高度解耦实现。

3. 系统广播实现

广播的实现也是一种观察者模式,由于是系统内置的,又是四大组件之一,所以单独拎出来,实现也比较简单,代码如下:

Service 实现:
public class MsgService extends Service {

    private static final String TAG = "MsgService";

    private volatile boolean mCancel= false;
    private int mProgress = 0;
    private LocalBroadcastManager mLocalBroadcastManager;

    @Override
    public void onCreate() {
        super.onCreate();
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
        //创建服务后开启下载任务
        startDownload();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public void startDownload() {
        final Intent intent = new Intent(getString(R.string.receiver_action));
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!mCancel&& mProgress < 100) {
                    mProgress += 20;
                    intent.putExtra(ReceiverActivity.EXTRAS_PROGRESS, mProgress);
                    mLocalBroadcastManager.sendBroadcast(intent);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.d(TAG, "run: " + mProgress);
                }
                if (mProgress >= 100) {
                    intent.putExtra(ReceiverActivity.EXTRAS_FINISH, true);
                    mLocalBroadcastManager.sendBroadcast(intent);
                }
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //停止服务后取消掉下载任务
        mCancel= true;
    }
}
Activity实现:
public class ReceiverActivity extends BaseActivity {

    public static final String EXTRAS_PROGRESS = "progress";
    public static final String EXTRAS_FINISH = "finish";

    private MyReceiver mMyReceiver;
    private IntentFilter mIntentFilter;
    private TextView mProgressTv;
    private Button mStartBtn, mStopBtn;
    private LocalBroadcastManager mLocalBroadcastManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_receiver);
        mProgressTv = findViewById(R.id.progress_tv);
        mStartBtn = findViewById(R.id.start_btn);
        mStopBtn = findViewById(R.id.stop_btn);

        mStartBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //开启服务
                startService(new Intent(ReceiverActivity.this, MsgService.class));
            }
        });

        mStopBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //停止服务
                stopService(new Intent(ReceiverActivity.this, MsgService.class));
            }
        });

        //注册广播
        mIntentFilter = new IntentFilter(getString(R.string.receiver_action));
        mMyReceiver = new MyReceiver();
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
        mLocalBroadcastManager.registerReceiver(mMyReceiver, mIntentFilter);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //注销广播
        mLocalBroadcastManager.unregisterReceiver(mMyReceiver);
    }

    public class MyReceiver extends BroadcastReceiver {
        private static final String TAG = "MyReceiver";
        @Override
        public void onReceive(Context context, Intent intent) {
            int progress = intent.getIntExtra(EXTRAS_PROGRESS, 0);
            Log.d(TAG, "onReceive: "+progress);
            mProgressTv.setText("当前进度:" + progress);
            if (intent.getBooleanExtra(EXTRAS_FINISH, false)) {
                mProgressTv.setText("下载完成");
                stopService(new Intent(ReceiverActivity.this, MsgService.class));
            }
        }
    }
}

Service 保活

前面已经提到 Serivce 由于没有界面,虽然是四大组件之一,但是优先级相比Activity还是较低的,那么有什么办法可以提高 Service 的优先级呢?具体说来主要有以下几种方法:

1. 在 onStartCommand() 方法中,返回 START_STICKY

前面提到 onStartCommand() 的返回值是 int 类型,主要有以下几种值:

  • START_NOT_STICKY,当 Service 因为内存不足而被系统 kill 后,接下来即使系统内存足够可用的情况下,系统也不会尝试重新创建 Service,除非程序重新手动启动服务比如通过 startService() 方法。
  • START_STICKY,当 Service 因为内存不足而被系统杀死后,接下来系统内存够用的情况下,系统将会尝试重新创建此 Service ,创建成功后将会回调 onStartCommand() 方法,当其中的 intent 将会为 null,PendingIntent 除外。
  • START_REDELIVER_INTENT,与 START_STICKY 唯一不同的是,回到 onStartCommand() 方法时,其中的 intent 将会时非空,将是最后一次调用 startService() 方法中的 intent。
  • START_STICKY_COMPATIBLITY,顾名思义,是 START_STICKY 的兼容版本,但是在系统杀死服务后不一定保证调用 onStartCommand() 方法。
2.提高 Service 的优先级

在 AndroidManfiest.xml 文件中对于 intent-filter 可以通过 android:priority = "1000" 这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同样适用于广播。

3.提升 Service 进程的优先级,关于 Android 进程优先级

当系统内存紧张时,Android 系统会按照进程优先级进行进程的回收,我们可以通过启动前台服务来提高 Service 的优先级,具体方法可以参照前面。

4.在 onDestroy() 方法里重启 Service

当 Service 销毁时,即执行 onDestroy() 方法,可以发送一个自定义广播,当受到广播时,重新启动 Service。

结束语

以上就是对 Service的简单的解析啦,好久没码字了,累洗啦。限于个人的能力,如有错误之处请不吝赐教,感恩。--->点击查看完整源码<---

你可能感兴趣的:(四大组件之 Service(服务)简析)