(二)service的混合调用和AIDL的用法

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值