Android基础笔记(十二)- 使用AIDL来进行跨进程通信

  • 绑定服务调用服务里方法的过程
  • 音乐盒小案例
  • 利用服务注册特殊广播接收者
  • 使用AIDL来进行跨进程通信

绑定服务调用服务里方法的过程

整个Activty绑定Service并调用其中方法的过程可以体现为下面的一张图,其中的核心是通过借助中间人IBinder来达到调用Service中方法的目的。。

接下来在明确一下调用过程的代码步骤:
①首先服务里有一个方法需要被调用
②定义一个中间人对象(继承Bidner类的内部类MyBinder
③在onBind方法中把我们自己定义的中间人返回MyBinder
④在Activity里面调用bindService()方法,获取我们的中间人对象
⑤间接利用中间人对象调用服务里面的方法。

音乐盒小案例

既然我们已经清楚了,在Activity如何调用Service中的方法,那么就练习一下吧。

在我们日常中,都使用过音乐APP,它们都可以在后台中继续播放音乐,那么我们也模仿一下,当然其中的播放、暂停、继续播放功能都用Log来代替。

另外,在之前的代码中,在ServiceConnectiononServiceConnected()方法中,每次都是把参数service,向下转型为Service内部的MyBinder对象,这样灵活性又不高、耦合性又强,所以采用接口的方式进行解耦。

整体而言还是非常简单的,我们来看下代码。

为了更方便的调用服务里的方法,我们抽取了一个接口:

public interface IService {
    // 调用服务中的play方法
    public void callPlay();
    // 调用服务中的pause方法
    public void callPause();
    // 调用服务中的replay方法
    public void callReplay();
}

在Service的内部,有着play、pause、replay等三个内部方法,创建一个私有的内部类,继承Binder并实现IService,然后在onBind()方法中,返回此对象。

public class MusicService extends Service {

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

    public void play() {
        System.out.println("播放音乐");
    }

    public void pause() {
        System.out.println("暂停播放");
    }

    public void replay() {
        System.out.println("继续播放音乐");
    }

    private class MyBinder extends Binder implements IService {
        @Override
        public void callPlay() {
            // 调用服务内部的方法
            play();
        }
        @Override
        public void callPause() {
            pause();
        }

        @Override
        public void callReplay() {
            replay();
        }
    }
}

在MainActivity中也是比较简单,我们直接在onCreate()开启服务并绑定服务,并通过按钮调用的相应IServiec接口中的方法。

public class MainActivity extends Activity {

    private ServiceConnection conn;
    private IService iService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent service = new Intent(this, MusicService.class);
        // 启动服务
        startService(service);

        // 绑定服务
        conn = new MyServiceConnection();
        bindService(service, conn, Context.BIND_AUTO_CREATE);

    }

    public void play(View v) {
        iService.callPlay();
    }

    public void pause(View v) {
        iService.callPause();
    }

    public void replay(View v) {
        iService.callReplay();
    }

    private class MyServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iService = (IService) service;
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
}

测试结果如下:

看到这里你可能就有疑问了,与之前没有什么太大的区别呀,唯一的区别就是使用了IService接口解除了Activity和Service通信的耦合。

不错,这就是关键点。这里要说明一个概念,远程服务和本地服务的区别。

在Android中,远程服务就是运行在其他应用(进程)里面的服务 ,而本地服务就是运行在自己应用(进程)里面的服务。它们都是运行在同一个手机里。

这就涉及到了IPC进程间通信了,Google已经为我们提供了解决进程间通信的方法,叫做AIDL,而其中就是使用类似IService的接口来达到目的的。更为详细的在下面讲述。

利用服务注册特殊广播接收者

是时候把服务和广播结合起来了,这样功能才会更强大。而就广播而言,这里用到了动态注册广播的方法,说白了就是通过代码注册一个广播。

我们可以使用registerReceiver()方法动态注册一个广播;另一个方面,什么才叫做特殊的广播接受者呢?一般来说,像电池电量低,屏幕锁屏等使用频率非常高的称为特殊广播接受者,它们只能够使用代码的方式进行注册。

那为什么这些广播需要在服务中进行动态注册呢? 试想一下,我们在普通应用中直接注册一个屏幕锁屏的广播接收者,当这个应用开启时肯定可以接收到屏幕锁屏的广播事件的。但是,当应用程序退出后,应用变成空进程,也就接收不到广播了。而在服务中动态注册屏幕锁屏事件,只要我们的Service不死,那么便一直可以接收到广播。

接下来就做一个在服务中动态注册锁屏广播事件的小案例。

还是先从界面开始,两个按钮,并在MainActivity中实现了对应的点击方法。

对应的代码也很简单:

public void start(View v) {
    Intent service = new Intent(this, ScreenService.class);
    startService(service);

}

public void stop(View v) {
    Intent service = new Intent(this, ScreenService.class);
    stopService(service);
}

接下来写一个接收广播的类,既然我们打算在服务中动态进行注册,那么便不需要再清单文件中声明了。

public class ScreenReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if ("android.intent.action.SCREEN_OFF".equals(action)) {
            System.out.println("接收到锁屏广播");
        }else if ("android.intent.action.SCREEN_ON".equals(action)) {
            System.out.println("接收到解锁广播");
        }
    }
}

接下来创建一个服务,并在服务中动态注册广播接收者。

public class ScreenService extends Service {

    private BroadcastReceiver receiver;

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

    @Override
    public void onCreate() {
        super.onCreate();

        // 接收广播的对象
        receiver = new ScreenReceiver();

        // 意图过滤器,添加关心的动作
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.SCREEN_OFF");
        filter.addAction("android.intent.action.SCREEN_ON");

        // 注册广播
        registerReceiver(receiver, filter);
        System.out.println("注册了屏幕锁屏的广播接收者");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 解除注册
        unregisterReceiver(receiver);
        System.out.println("反注册了屏幕锁屏的广播接收者");
    }
}

至此,整个小案例的操作就完毕了。看一下测试图:

使用AIDL来进行跨进程通信

在上面我们已经说过远程服务和本地服务的区别,那么如何才能让Activity与一个远程Service建立关联呢?这就要使用AIDL来进行跨进程通信了(IPC - Inter Process Communication 进程间通信)。

而AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。

说话可能太苍白,还是用一个示例来演示吧。我们平常都玩过手机斗地主,当我们买欢乐豆是,可能会用到支付宝的服务。

那么我们就用买欢乐豆使用支付宝支付,来作为我们的小案例吧。

既然是远程服务,也就是不同进程间通信,我们需要创建两个Android工程,一个工程名叫支付宝,另一个叫斗地主。支付宝是服务端,我们先从服务端开始。

在支付宝工程中,我们先像之前的例子一样,创建一个服务,写一个继承Binder的内部类,并使用onBind()方法,返回它的实例。然后再向服务中添加一个pay()的方法。既然我们调用服务内部的方法,为了解耦方便,我们再抽取出一个接口IService,里面有一个callPay方法。AliPayService代码如下:

public class AliPayService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        // 返回Binder实例
        return new MyBinder();
    }
    // 用于支付的方法
    public boolean pay(String name, String password, int money) {
        if ("biezhihua".equals(name) && "biezhihua".equals(password) && money > 4000) {
            return true;
        }
        return false;
    }
    // 内部类
    private class MyBinder extends Binder implements IService {
        @Override
        public boolean callPay(String name, String password, int money) {
            return pay(name, password, money);
        }
    }
}

抽取出来的IService接口代码如下:

public interface IService {
     public boolean callPay(String name, String password, int money);
}

步骤到这里,还和之前一点区别都没有。既然我们这个服务要提供给外部使用,就需要用到Google给我们提供的AIDL技术。接下来我们一步一步的将原始的本地服务,修改成可远程调用的服务。

先将我们IService.java文件修改为IService.aidl并将其中的权限修饰符去掉。代码如下:

package com.bzh.alipay;

interface IService {
    boolean callPay(String name, String password, int money);
}

修改后的效果如图所示,你会发现当我们修改完成后,eclipse帮我们自在的在gencom.bzh.alipay包下生成了一个IService.java文件。

我们打开看看,在第二行看到了这样一句话This file is auto-generated. DO NOT MODIFY.,它是自动生成的,不要去修改它。

我们已经把接口修改成AIDL文件了,那么我们的AliPayService类中的内部类MyBinder也要随之改变,改为继承Stub,这个Stub就是自动生成的IService.java中的一个内部类。

修改后的代码如下,区别也不是很大:

private class MyBinder extends Stub {
    @Override
    public boolean callPay(String name, String password, int money) throws RemoteException {
        return pay(name, password, money);
    }
}

最后,既然我们的服务需要提供给别人调用,那么便需要使用到隐式意图了,在清单文件中为服务配一个隐式意图,这样我们就可以在其他工程中触发这个服务了,代码如下:

<service android:name="com.bzh.alipay.AliPayService" >
     <intent-filter>
         <action android:name="com.bzh.pay" >
         </action>
     </intent-filter>
 </service>

接下来到斗地主工程中,为了使用远程服务,我们需要把AIDL文件和其所在包整个都拷贝过来,拷贝过来后,一会在gen中自动生成相应的代码。效果如下:

最后,我们需要在MainActivity中响应按钮,调用远程服务内部的方法了。其中,比较重要的两步分别是:

  1. 指定绑定服务时意图过滤器的动作为com.bzh.pay
  2. 在MyServiceConnection的onServiceConnected()方法中,使用IService.Stub.asInterface(service)语句将IBinder转换成AIDL接口,方便在pay()方法中调用远程服务的内部方法。

代码如下:

public class MainActivity extends Activity {
    ServiceConnection conn;
    private IService iService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 指定所绑定服务的隐式意图动作
        Intent service = new Intent();
        service.setAction("com.bzh.pay");

        conn = new MyServiceConnection();

        // 绑定服务
        bindService(service, conn, Context.BIND_AUTO_CREATE);
    }

    public void pay(View v) {
        try {
            boolean callPay = iService.callPay("biezhihua", "biezhihua", 6000);
            if (callPay) {
                Toast.makeText(getApplicationContext(), "支付成功", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(getApplicationContext(), "支付失败", Toast.LENGTH_SHORT).show();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iService = IService.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
}

测试图如下:

谢谢各位观赏!

时间: 2024-10-05 05:11:51

Android基础笔记(十二)- 使用AIDL来进行跨进程通信的相关文章

JavaSE基础笔记十二

第十一章 多线程 理解程序.进程.线程的概念 程序可以理解为静态的代码. 进程可以理解为执行中的程序. 线程可以理解为进程的近一步细分,程序的一条执行路径. 2.如何创建java程序的进程(重点) 方式一:继承于Thread类 ①创建一个继承于Thread的子类 ②重写Thread类的run()方法,方法内实现此子线程要完成的功能 ③创建一个子类的对象 ④调用线程的start():启动此线程,调用相应的run()方法 ⑤一个线程只能够执行一次start 方式二:实现Runnable接口 ①创建一

Oracle基础笔记十二

第十二章 用户控制权限 1.权限: 数据库安全性:系统安全性和数据安全性 系统权限: 对于数据库的权限 对象权限: 操作数据库对象的权限 2.系统权限 超过一百多种有效的权限 数据库管理员具有高级权限以完成管理任务,例如: 创建新用户 删除用户 删除表 备份表 2.1 DBA 使用 CREATE USER 语句创建用户 CREATE USER user IDENTIFIED BY   password; CREATE USER  scott IDENTIFIED BY   tiger; 2.2

Android学习笔记十二.深入理解LauncherActvity 之LauncherActivity、PreferenceActivity、PreferenceFragment

深入理解LauncherActvity 之LauncherActivity.PreferenceActivity.PreferenceFragment 从下图我们可以知道,LauncherActivity.PreferanceActivity均继承于ListActivity,其中LauncherActivity实现当列表界面列表项被点击时所对应的Acitvity被启动:PreferanceActivity实现一个程序参数设置.存储功能的Activity列表界面. 一.LauncherActivi

Android基础笔记(二)

测试的相关概念 Android中的单元测试 日志猫的介绍 登陆案例 把数据保存到SD卡上 Android中几个常用的目录 获取SD卡的大小及可用空间 文件权限的概念 SharedPreferences使用 Android官方推荐的生成XML的方式 Android官方推荐的解析XML的方式 测试的相关概念 好的程序不是开发出来的,是测试出来的. 根据是否知道源程序源码: 黑盒测试:不知源码,只是测试程序的功能 白盒测试:知道源码,根据源代码进行测试 根据测试的粒度:(模块的大小) 单元测试:uni

android 远程Service以及AIDL的跨进程通信

在Android中,Service是运行在主线程中的,如果在Service中处理一些耗时的操作,就会导致程序出现ANR. 但如果将本地的Service转换成一个远程的Service,就不会出现这样的问题了. 转换成远程Service非常简单,只需要在注册Service的时候将他的android:process的属性制定成 :remote就可以了. 重新运行项目,你会发现,不会出现ANR了. 为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Serv

Android中的跨进程通信方法实例及特点分析(一):AIDL Service

转载请注明出处:http://blog.csdn.net/bettarwang/article/details/40947481 最近有一个需求就是往程序中加入大数据的采集点,但是因为我们的Android程序包含两个进程,所以涉及到跨进程通信的问题.现将Android中的跨进程通信方式总结如下. Android中有4种跨进程通信方式,分别是利用AIDL Service.ContentProvider.Broadcast.Activity实现. 1.利用AIDL Service实现跨进程通信 这是

【朝花夕拾】跨进程通信,你只知道AIDL,就OUT了

一.前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/11774836.html],谢谢! 提起跨进程通信,大多数人首先会想到AIDL.我们知道,用AIDL来实现跨进程通信,需要在客户端和服务端都添加上aidl文件,并在服务端的Service中实现aidl对应的接口.如果还需要服务端给客户端发送信息,还需要再添加回调相关的aidl文件,以及使用RemoteCallbackList来辅助实现该功能.在我的另外一篇文章[朝花夕拾]Android性能

【转】 Pro Android学习笔记(二二):用户界面和控制(10):自定义Adapter

目录(?)[-] 设计Adapter的布局 代码部分 Activity的代码 MyAdapter的代码数据源和构造函数 MyAdapter的代码实现自定义的adapter MyAdapter的代码继续探讨BaseAdapter 我们可以同继承抽象类BaseAdapter来实现自己的Adapter,自己设置子View的UI,不同子View可以由不同的布局,并自己进行数据和子view中数据的对应关系.图是例子的呈现结果,我们有很多图标,对这些图标按一定大小进行缩放,然后布局在GridView中.这个

Swift学习笔记十二:下标脚本(subscript)

下标脚本就是对一个东西通过索引,快速取值的一种语法,例如数组的a[0].这就是一个下标脚本.通过索引0来快速取值.在Swift中,我们可以对类(Class).结构体(structure)和枚举(enumeration)中自己定义下标脚本的语法 一.常规定义 class Student{ var scores:Int[] = Array(count:5,repeatedValue:0) subscript(index:Int) -> Int{ get{ return scores[index];