service混合调用及AIDL用法
上一篇说了service的生命周期和两种调用方式,不熟悉的可以看上一篇 (一)生命周期和两种启动方式
日常开发中,还会比较常见的遇到一类需求,用户退出activity,仍然可以通过某种方式操作应用(调用service中的方法),比如播放音乐,用户退出界面后,要保证音乐继续播放,并且用户还可以控制播放下一首,暂停等。显然前面两种启动方式都无法满足需求。这里引入我们今天的主角,service的混合调用。下面以一个demo来说明下
1、Activity(Client端)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private String TAG = "hjw";
private IInterface iInterface;
public final static String ACTION_BUTTON = "com.notification.intent.action.ButtonClick";
public final static String INTENT_BUTTONID_TAG = "ButtonId";
public final static int BUTTON_PALY_ID = 1;
private final int REQUEST_CODE = 0xb01;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.start).setOnClickListener(this);
findViewById(R.id.bind).setOnClickListener(this);
findViewById(R.id.activity_do).setOnClickListener(this);
Log.d(TAG, "activit onCreate thread = " + Thread.currentThread().getId());
}
@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.start: {
Log.d(TAG, "to start service");
Intent intent = new Intent(this, MyService.class);
startService(intent);
break;
}
case R.id.bind: {
Log.d(TAG, "to bind service");
Intent intent = new Intent(this, MyService.class);
bindService(intent, new MyConnection(), BIND_AUTO_CREATE);
break;
}
case R.id.activity_do: {
Log.d(TAG, "actity to do service");
if (iInterface != null) {
iInterface.doSomething();
}
break;
}
default:
break;
}
}
@Override
protected void onStop() {
super.onStop();
generateNotificationAndSend();
}
private void generateNotificationAndSend() {
RemoteViews contentView = prepartRemoteView();
Notification.Builder builder = new Notification.Builder(this);
builder.setOngoing(false);
builder.setAutoCancel(false);
// 下面注释这段是发现的问题,android自8.0(26版本)开始,要求通知要指明channelID,否则不能显示,本例中的方案是使用25版本
// String channelID = "1";
// String channelName = "channel_name";
// NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
// builder.setChannelId(channelID);
builder.setSmallIcon(R.mipmap.ic_launcher); //需注意这个属性如果不设置,在某些机型上通知栏将不会显示
Notification notification = builder.build();
notification.defaults = Notification.DEFAULT_SOUND;
notification.flags = Notification.FLAG_AUTO_CANCEL;
//设置按钮
contentView.setImageViewResource(R.id.doSomething, R.mipmap.ic_launcher_round);
//设置点击的事件
Intent buttonIntent = new Intent(ACTION_BUTTON);
buttonIntent.putExtra(INTENT_BUTTONID_TAG, BUTTON_PALY_ID);
PendingIntent intent_paly = PendingIntent.getBroadcast(this, BUTTON_PALY_ID, buttonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
contentView.setOnClickPendingIntent(R.id.doSomething, intent_paly);
notification.contentView = contentView;
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, REQUEST_CODE,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.contentIntent = pi;
//构建NotificationManager,显示通知栏;
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
}
private RemoteViews prepartRemoteView() {
return new RemoteViews(getPackageName(), R.layout.notification_layout);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "activit onDestroy");
}
private class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//可以在activity没有退出时,使用binder对象,调用service中的方法
iInterface = (IInterface) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iInterface = null;
}
}
}
混合调用的核心就在2个点击事件上,一个start按钮,启动service,就是前面提到的第一种启动方式;接下来点击bind按钮,这里与前面说的bind的不同在于:这里的service,已经被start过了。
代码中比较复杂的就是发送通知那一段,由于自己对通知没有详细的学习过,这里就不误导别人了,等系统学习过了再专门来一篇关于通知的。并不是本文的重点,可以忽略。
activity布局代码就不贴出来了,非常的简单,就是2个按钮,一个start,一个bind。自定义通知的布局也是,就一个imageView,用来响应用户的点击事件
为什么不直接用button?不知道为啥,求大神解答
2、Service(Server端)
2.1暴露接口
public interface IInterface {
public void doSomething();
//you can expose the fuction here
}
2.2service
public class MyService extends Service {
private String TAG = "hjw";
public final static String ACTION_BUTTON = "com.notification.intent.action.ButtonClick";
public ButtonBroadcastReceiver receiver;
public final static String INTENT_BUTTONID_TAG = "ButtonId";
public final static int BUTTON_PALY_ID = 1;
@Override
public void onCreate() {
Log.d(TAG,"onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
//注册广播
receiver = new ButtonBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_BUTTON);
getApplicationContext().registerReceiver(receiver, intentFilter);
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
getApplicationContext().unregisterReceiver(receiver);
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
public void realDoSomething() {
Log.d(TAG, "realDoSomething thread = "+Thread.currentThread().getId());
}
private class MyBinder extends Binder implements IInterface {
@Override
public void doSomething() {
realDoSomething();
}
}
public class ButtonBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG,"onReceive");
String action = intent.getAction();
if (action.equals(ACTION_BUTTON)) {
//通过传递过来的ID判断按钮点击属性或者通过getResultCode()获得相应点击事件
int buttonId = intent.getIntExtra(INTENT_BUTTONID_TAG, 0);
switch (buttonId) {
case BUTTON_PALY_ID:
realDoSomething();
break;
default:
break;
}
}
}
}
}
使用方法就是,现在activity中执行start,再执行bind,然后点击home,让activity回调onStop,发送通知,点击通知中的按钮,执行doSomething方法,这个方法就是去调用service中的真正方法。
Line 2586: 01-20 15:57:43.141 D/hjw ( 5679): activit onCreate thread = 2
Line 2677: 01-20 15:57:48.570 D/hjw ( 5679): to start service
Line 2685: 01-20 15:57:48.615 D/hjw ( 5679): onCreate
Line 2686: 01-20 15:57:48.619 D/hjw ( 5679): onStartCommand
Line 2687: 01-20 15:57:50.140 D/hjw ( 5679): to bind service
Line 2690: 01-20 15:57:50.164 D/hjw ( 5679): onBind
Line 2691: 01-20 15:57:51.990 D/hjw ( 5679): actity to do service
Line 2693: 01-20 15:57:51.991 D/hjw ( 5679): realDoSomething thread = 2
Line 2765: 01-20 15:57:57.137 D/hjw ( 5679): onReceive
Line 2767: 01-20 15:57:57.137 D/hjw ( 5679): realDoSomething thread = 2
这样的话,可以在activity运行时,通过界面调用service中的方法,也可以在home退出前台时,通过Notification中的按钮调用service中的方法。
思考:如果用户点击back呢,显然例子中只有一个绑定者,如果activity销毁了,那么service必然会unBind,这样就没办法再调用service中的方法了。那么市面上的音乐应用是怎么做的呢,我认为,肯定是做了特殊,总之是不能让service执行unBind,否则就没办法调用方法了。比如对back处理,实际作用相当于home。
到此,service的混合调用就说完了,有些同学可能会发现,我定义的接口IInterface似乎没啥用,确实,在这个例子中显得多余了,写它的目的一个是符合程序的设计需求,方便拓展,把需要暴露的方法放在接口中。另一作用就是引入AIDL概念,AIDL就是进程间通信,至少有2个进程,如何通信呢,本例中,把Activity放在进程A中,把Service放在进程B中,显示不能直接调用方法了,那么怎么办呢,只要把本例中的IInterface改写成aidl文件,放在两个工程中即可。使用方法就是这么简单,原理后面另谋篇幅再做说明。补充AIDL两个坑
1:5.0以上,bindService使用的intent,必须指定包名
2:Android Studio自动生成的Service,要注意属性两个属性,enable和exported,需要都是true
最后再说一点,请注意log,我特意把ThreadId打了出来,目的就是说,Activity,Service,生命周期方法中都是运行在主线程,不能执行耗时操作。如果要执行耗时操作怎么办,开线程呗,除此之外,引出下一篇 IntentService