草庐IT

Android系统编程入门系列之服务Service中的进程间通信

星城之路 2023-03-28 原文

上篇文章以线程间的通信方式Handler类结尾,服务Service还支持的进程间通信,又是具体怎么实现的呢?这就要用到加载服务一文中提到的AIDL语言规范了。

AIDL是 Android Interface Definition Language 的缩写,即Android接口定义语言,使用其定义的规范编程,可实现Android系统上不同进程间的通信。官网ADIL概述中以服务端和客户端通信为例做了大致讲述。与线程间的通信类似,不同进程间的通信也是分为通信消息内容、消息发送方、消息接收方三个部分的,其中的消息内容也就是AIDL支持的数据类型;而由于服务已经位于当前应用程序所在的默认进程,所以服务中所实现的接口内容即对接收消息的处理,也就是消息接收方;相对的,其他需要发送消息的进程都可以看做是客户端进程,只要绑定服务之后都可以调用服务的接口方法,也就是消息发送方。

确定通信的消息内容

在AndroidStudio中,通信的消息内容以 .aidl 后缀格式的文件默认保存在开发目录 /src/main/aidl 下。在 .adil 格式的文件中,编码规则与Java语言一致,其中内容与Java语言中的接口定义类似。
其中支持通信的消息类型分为两类,一类是基本类型,使用基本类型时不需要在文件开头增加导入包名的声明,这些基本类型包括Java语言中的八种基本数据类型, String, CharSequence, List, Map;还有一类是附加类型,除了第一类基本类型,其他在 .java 格式的文件中定义的任何实现android.os.Parcelable接口的类都可以作为附加类型消息传递。附加类型在使用时需要在 .aidl 文件开头增加导入该类型所在包名的声明。

关于附加类型的定义,以com.java.process.myinterface.Person类型为例。
不仅在 /src/main/java/com/java/process/myinterface 目录下创建 Person.java ,在其中定义Person类并实现Parcelable接口的相关方法。
还要在 /src/main/aidl/com/java/process/myinterface 目录下创建对应的 Person.aidl 文件,在其中除了指定包名外,还要声明该类为parcelable接口,示例代码如下

package com.java.process.myinterface;
parcelable Person;

.aidl.java 格式的文件中定义的接口相比有两处不同,其一是无需访问修饰符,其二是对基本数据类型增加数据流向标识符(包括数据流入接收方的in,数据在接收方修改后可流出的out, 数据可流向接收方并流出的inout )。

关于通信消息的定义,以com.java.process.myinterface.DataInterface类型为例。
只需要在 /src/main/aidl/com/java/process/myinterface 目录下创建 DataInterface.aidl 文件,在其中定义相关接口即可。示例代码如下

package com.java.process.myinterface;
import com.java.process.myinterface.Person;
interface DataInterface{
    void setVersion(int version);
    int getVersion();
    void updatePerson(in Person person);
    Person getMainPerson();
}

在通信消息定义之后,可以使用AndroidStudio的编译指令 Build - Make Project 编译当前项目,以使得AndroidStudio自动生成消息定义的 .java 文件。上文 DataInterface.aidl 文件在项目编译之后,会在 /build/generated/aidl_source_output_dir/debug/out/com/java/process/myinterface 目录下生成不可编辑的 DataInterface.java 文件,之后即可在项目中通过导包import com.java.process.myinterface.DataInterface;的形式正常使用该接口类了。

通信接收方的服务接收处理

通信接收方就是常说的服务端,通常是自定义的服务Service类,主要负责实现上述通信消息内容的接口,以此接收消息内容并处理。
针对自定义的服务Service类,这里就用到在加载服务文章中提到的绑定服务的生命周期了。重写onBind(Intent intent)方法,在该方法中返回android.os.IBinder类型的对象实例。

而这个IBinder对象是怎么创建的呢?在上文通信消息定义之后自动生成的接口类中,也自动生成了其Stub内部类,在创建该内部类的无参构造方法时,即可自动实现其相关接口方法,继续使用上文示例,这里在服务Service中的接口实现代码如下

package com.java.process.myinterface;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import com.java.process.myinterface.DataInterface;

public class ProcessService extends Service {

    private String versionName;
    private Person person;
    private final DataInterface.Stub binder=new DataInterface.Stub() {

        @Override
        public String getVersionName() throws RemoteException {
            //这里可以返回消息versionName给客户端
            return versionName;
        }

        @Override
        public void setVersionName(String versionName) throws RemoteException {
            //这里接收处理来自客户端的消息versionName
            ProcessService.this.versionName=versionName;
        }

        @Override
        public void updatePerson(Person person) throws RemoteException {
            //这里接收处理来自客户端的消息person
            ProcessService.this.person.setName(person.getName());
            ProcessService.this.person.setAge(person.getAge());
        }

        @Override
        public Person getMainPerson() throws RemoteException {
            //这里可以返回消息person给客户端
            return ProcessService.this.person;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        versionName="defaultName";
        person=new Person();
        person.setName("initName");
        person.setAge(10);
    }

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

通信发送方的服务绑定

通信发送方就是所谓的客户端了,发送方是与上文中的接收方不在同一个进程的,所以发送方通常需要先绑定接收方的自定义服务Service,也就是在发送方通过上下文环境Context对象调用bindService(Intent service, ServiceConnection conn, int flags)方法,这里的参数在加载服务中已有详细说明。
conn 参数的onServiceConnected(ComponentName name, IBinder service)方法中通过调用静态方法(通信消息定义接口的内部类Stub.asInterface(IBinder service)方法)得到通信消息的定义接口的实例化对象。在当前客户端绑定自定义服务Service成功之后,回调该方法,即可将通信消息的定义接口的实例化对象赋值给全局变量使用。
conn 参数的onServiceDisconnected(ComponentName name)方法中,要记得将全局通信消息定义接口变量的实例化对象置为空,否则在当前客户端与自定义服务Service断开连接后,还调用全局变量的通信消息定义接口的实例化对象的相关方法,可能会出现OOM内存泄露的问题。

最后在需要发送消息的位置,只需要调用全局通信消息定义接口变量的相关方法即可。其中用到的示例代码如下

    private DataInterface dataInterface;
    private ServiceConnection conn=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            dataInterface = DataInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            dataInterface = null;
        }

    //此处省略对文本编辑控件editText的定义
    //该方法在点击editText控件时回调
    public void clickName(View view) {
        try {
            String setName = editText.getText().toString();
            dataInterface.setVersionName(setName);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    };

有关Android系统编程入门系列之服务Service中的进程间通信的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  5. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  7. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  8. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  9. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  10. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

随机推荐