草庐IT

Android的进阶学习(四)--AIDL的使用与理解

MathiasLuo 2023-09-17 原文

我最初接触aidl的时候,就感觉这个好难,一下一大堆的代码就出来了。总觉得这种东西会用就行,懂它什么的因该是大神们的事,但是知其然,不知其所以然,用起来总是觉得怪怪的,所以就决定慢慢理一下它。
其实一步一步看懂,也很简单的。


aidl的使用

最常见的aidl的使用就是Service的跨进程通信了,那么我们就写一个ActivityService的跨进程通信吧。
首先,我们就在AS里面新建一个aidl文件(ps:现在AS建aidl不要求和java包名相同了):

package aidl;
interface IMyInterface {
    String getInfor(String s);
}

可以看到,在这里面我们就一个方法getInfor(String s),接受一个字符串参数,然后返回一个字符串,恩,相当的简单。

接着你sync project一下就可以在app/generated/source/aidl/debug/aidl里面发现由aidl文件生成的java文件了。点进去一看,可能你也被这个貌似'庞大'的类给吓住了,那现在我们就先不管它了。

然后就看看Service:

public class MyService extends Service { 
   
public final static String TAG = "MyService";

private IBinder binder = new IMyInterface.Stub() {
        @Override       
        public String getInfor(String s) throws RemoteException { 
            Log.i(TAG, s); 
            return "我是 Service 返回的字符串"; 
       }
    };
@Overrid
public void onCreate() {
    super.onCreate(); 
    Log.i(TAG, "onCreat");    
}       
@Override    
public IBinder onBind(Intent intent) { 
    return binder;  
    }
}

这里我们写了一个Service,看一下也比较简单。先new了一IMyInterface.Stub()并把它向上转型成了IBinder,最后在onBind方法中返回回去。可能你注意到了,在IMyInterface.Stub()的内部我们重写getInfor(String s) 方法,没错这就是我们 aidl文件中定义的接口。
对了,因为我们希望看到的是跨进程通信,所以我们把MyService定义成新启动一个进程:

<service
    android:name=".server.MyService"
    android:process="com.mathiasluo.remote" />

定义为启动在新进程中,只需要在AndroidMainfest.xml中声明是加上一个process属性即可,不过这里有两个地方值得注意:1.组件默认的进程名就是包名;2.定义新的进程名的时候需要以包的形式(eg: com.luo.aidl)。

接着,我们继续向下看:


public class MainActivity extends AppCompatActivity {
    public final static String TAG = "MainActivity";
    private IMyInterface myInterface;
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myInterface = IMyInterface.Stub.asInterface(service);
            Log.i(TAG, "连接Service 成功");
            try {
                String s = myInterface.getInfor("我是Activity传来的字符串");
                Log.i(TAG, "从Service得到的字符串:" + s);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "连接Service失败");
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startAndBindService();
    }
    private void startAndBindService() {
        Intent service = new Intent(MainActivity.this, MyService.class);
        //startService(service);
        bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
    }
}

由于主要是ServiceActivity间的通信,所以为了让代码整洁就没有写UI
了。
onCreate(Bundle savedInstanceState)中,我们调用了自己定义的一个方法startAndBindService(),这个方法里面我们生成了一个Intent,然后 bindService了这个Intent传入了三个参数分别是IntentServiceConnectionFlag

Intent我们就不用说了,我们看看后面两个参数:
Activity中,我们new了一个ServiceConnection并实现了他的两个方法onServiceConnectedonServiceDisconnected。在onServiceConnected中我们通过IMyInterface.Stub.asInterface(service)把传来的IBinder转换成了我们定义的IMyInterface。然后我们调用了getInfor方法,传递了个字符串和获取从MyService传来的字符串,并且打印了Log
对于我们传的Context.BIND_AUTO_CREATE的意思就是说:如果绑定Service的时候,Service还没有被创建,就创建它。当然,这里还有很多Flag,我也不一一说了。

然后,我们的编码就完成了,开始运行测试一下把!

Paste_Image.png
Paste_Image.png

看一下运行结果,在这两个不同的进程中都得到了我们想要的结果,所以,一个用aidl实现的跨进程通信就这样完成了。当然我们的demo(这次不能拼错了)中,getInfor没有任何逻辑,你也可以加一些逻辑去执行一些复杂的操作。


aidl的理解

现在我们会使用aidl了,那我们还不去掀开它神秘的面纱。

Service中的IBinder

还记得我们在MyService中利用new IMyInterface.Stub()向上转型成了IBinder然后在onBind方法中返回的。那我们就看看IMyInterface.Stub吧:

public static abstract class Stub extends android.os.Binder implements aidl.IMyInterface {
..........
}

可以看到,StubIMyInterface中的一个静态抽象类,继承了Binder,并且实现了IMyInterface接口。这也就解释了我们定义IMyInterface.Stub的时候为什么需要实现IMyInterface中的方法了,也说明了为什么我们可以把IMyInterface.Stub向上转型成IBinder了。

Activity中的IMyInterface

Activity中,通过ServiceConnection连接MyService并成功回调onServiceConnected中我们把传回来的IBinder通过IMyInterface.Stub.asInterface(service)转换成为IMyInterface,那就来看看这里是如何转换的吧:

public static abstract class Stub extends android.os.Binder implements aidl.IMyInterface {

..........

public static aidl.IMyInterface asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    //检查Binder是不是在当前进程
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof aidl.IMyInterface))) {
        return ((aidl.IMyInterface) iin);
    }
    return new aidl.IMyInterface.Stub.Proxy(obj);
}
}

首先,我们因该明白的是,传回来的IBinder就是我们在ServiceonBind( )方法所returnIBinder,然后我们调用Stub中的静态方法asInterface并把返回来的IBinder当参数传进去。
asInterface方法中,首先判断了传进来的IBinder是不是null,如果为null就返回一个null;接着就判断传进来的IBinder是不是就在当前进程里面,如果是的话就直接返回IMyInterface,不是的话就返回IMyInterface.Stub.Proxy(obj)。这里我觉得需要明白的是:直接返回的IMyInterface是实现了定义的接口方法getInfor的。因为在IMyInterface.Stub中所实现的。当然如果是在同一进程中,那么我们调用IMyInterface的方法时就是在本地调用方法,直接调用就可以了。

如果没在同一进程,就会返回IMyInterface.Stub.Proxy(obj):

  private static class Proxy implements aidl.IMyInterface {
        private android.os.IBinder mRemote;
        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }
        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }
        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }
        @Override
        public java.lang.String getInfor(java.lang.String s) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.lang.String _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeString(s);
               //传送数据到远程的
                mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
                _reply.readException();
              //接受从远端传回的数据
                _result = _reply.readString();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        } 
   }
    static final int TRANSACTION_getInfor = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

Proxy中,我们首先把Service连接成功返回的IBinder它的内部变量mRemote,这里在提一下,这里得IBinder还是是MyServiceonBind所返回的。然后,当我们调用IMyInterface的方法的时候,其实就是调用的Proxy的方法了,这也是为什么这个类叫做Porxy的原因了。

当调用IMyInterface.getInfor(String s) ,我们就看Proxy中的getInfor,先获取了两个Parcel对象 _data_data,从变量名就可以看出,一个是传送数据的,另一个则是接受返回数据的。接着,向_data中写入了DESCRIPTOR(也就是这个类的全名),再写入了方法参数。然后就到了最重要的一步了,

mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);

这里我们调用了IBindertransact方法,来把数据传给远端的服务器。然后在我们远程的MyService中,里面的Stub中就会回调onTransact()(因为你把数据传个远程的服务,远端的服务收到数据也就回调了)

注意:这里是在远程的服务里调用的。

@Overridepublic boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    switch (code) {
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_getInfor: {
            data.enforceInterface(DESCRIPTOR);
            java.lang.String _arg0;
           //取出参数
            _arg0 = data.readString();
           // 远程服务调用自己本地实现的方法获取返回值
            java.lang.String _result = this.getInfor(_arg0);
            reply.writeNoException();
           //写入返回值
            reply.writeString(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

onTransact方法是在Stub的内部实现的。
先看一下它的四个参数:
code:每个方法都有一个int类型的数字用来区分(后面中的swicth),在我们例子中也就是我们Proxy中的Stub.TRANSACTION_getInfor
data:传过来的数据,其中包含我们的参数,以及类的描述。
reply:传回的数据,我们要写入是否发生了Exception,以及返回值
flags:该方法是否有返回值 ,0表示有返回值。

调用onTransact就表示有数据传来,首先就会通过swicth判断是哪个方法,然后取出方法参数,调用本地实现的方法获取返回值,写入返回值到reply。最后,返回true,才会把数据发送出去,发挥false就不会把结果返回给Activity了。这里也就是说,只有返回true,我们Proxy中才能接受从远端传回的数据

               //传送数据到远程的
                mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
                _reply.readException();
               //接受从远端传回的数据
                _result = _reply.readString();

注意:Service也是把数据发送出来,让客户端接受的。

Service发出了数据,客户端接收到了,就会一层一层返回去。所以,当我们简单的调用IMyInterfacegetInfor时候,先是Proxytransact发送出数据,然后服务端的onTransact接受并处理传来的数据,再把处理得到的数据写入返回值并发送给客户端,客户端读取值后就成为调用方法的返回值返回了。

然后aidl的基本原理就是这样了,看明白了aidl,才发现原来aidl不过就是帮我们生成了那些数据写入,传送,读取的方法而已。所以,我们自己写一个不要aidl的跨进程通信也是很容易的。


最后

有关Android的进阶学习(四)--AIDL的使用与理解的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. 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请求没有正确的命名空间。任何人都可以建议我

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

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

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐