aidl 实现native service和App通信

之前一直写的是android应用之间的aidl开发,最近学习的是native service和App之间通过aidl进行通信,这里记录一下。主要介绍的是native service和App端aidl的实现,至于service的编译过程我这里没有详细的记录。我所用的android版本是11.

1、定义aidl文件

aidl文件中定义了我们想要实现的功能,是后续一切的开始,为了测试回调功能,我这里定义了一个回调接口和一个自定义数据类型FileInfo, 用来跨进程传递信息的自定义数据结构。

FileInfo.aidl

package com.hht;

/**@hide*/
parcelable FileInfo cpp_header "FileInfo.h";

IFileInfoCallback.aidl

package com.hht;
import com.hht.FileInfo;

interface IFileInfoCallback{
    void backFileInfo(in FileInfo fileInfo);
}

IGetFileInfo.aidl

package com.hht;
import com.hht.IFileInfoCallback;

interface IGetFileInfo{
    // @utf8InCpp注解可以使生成的头文件中String与std::string对应起来
    void getFileInfo(@utf8InCpp String name);
    // register callback
    void registerCallback(IFileInfoCallback callback);

}

上面我们定义了三个aidl文件,不过自定义类型FileInfo的定义并不在这里,而是在外面,它继承Parceable, 并实现writeToParcel和readFromParcel两个函数。

FileInfo.h

#ifndef FILE_INFO_H
#define FILE_INFO_H

#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <utils/String16.h>

namespace com{
    namespace hht{
        struct FileInfo:public android::Parcelable{
            public:
                android::String16
                    name,  // file name
                    ctime; // create time
                int64_t fileSize;

            virtual android::status_t writeToParcel(android::Parcel *parcel) const override;
            virtual android::status_t readFromParcel(const android::Parcel* parcel) override;

        };
    }
}


FileInfo.cpp

#include "FileInfo.h"

namespace com{
    namespace hht
    {
        android::status_t FileInfo::writeToParcel(android::Parcel *parcel) const{
            android::status_t res;

            if (parcel == nullptr)
            {
                return android::BAD_VALUE;
            }

            res = parcel->writeString16(name);
            if (res != android::OK) return res;
            res = parcel->writeString16(ctime);
            if (res != android::OK) return res;
            res = parcel->writeInt64(fileSize);
            if (res != android::OK) return res;
            return res;
            
        }

        android::status_t FileInfo::readFromParcel(const android::Parcel *parcel){

            if (parcel == nullptr)
            {
                return android::BAD_VALUE;
            }

            parcel->readString16();
            parcel->readString16();
            parcel->readInt64(&fileSize);

            return android::OK;
        }
        
    } // namespace hht
    
}

看一下整个工程的结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WIkY8ZuC-1642559685168)(screenshot-20220118-134720.png)]

Android.bp文件是我写来编译整个工程的,需要借助android源码才能编译

cc_library_static{
    name:"libaidltest",
    local_include_dirs:[
        "include"
    ],
    aidl:{
        local_include_dirs:["aidl"],
        include_dirs:[
            "frameworks/native/aidl/binder"
        ],
        export_aidl_headers:true,
    },

    srcs:[
        "FileInfo.cpp",
        ":libfile_aidl"
    ],

    shared_libs:[
        "libbinder",
        "libutils",
    ],
}

filegroup{
    name:"libfile_aidl",
    srcs:[
        "aidl/com/hht/FileInfo.aidl",
        "aidl/com/hht/IFileInfoCallback.aidl",
        "aidl/com/hht/IGetFileInfo.aidl"
    ],
    path:"aidl",
}

在该工程路径下终端输入mm命令开始编译,编译成功的话会在out目录下找到对应的头文件,具体的目录看编译完成时的提示。

2、实现aidl中定义的函数的功能

在out 目录下我们可以看到生成的Bn和Bp头文件,可以不用将其复制过来,直接引用即可,我们定义一个类来继承BnGetFileInfo,然后将生成的函数定义复制过来,文件内容如下

GetFileInfo.h

#ifndef GET_FILE_INFO_H
#define GET_FILE_INFO_H

#include <com/hht/BnGetFileInfo.h>
#include <com/hht/BnFileInfoCallback.h>
#include "FileInfo.h"

class MyFileService:public com::hht::BnGetFileInfo
{
    public:
        MyFileService();
        ~MyFileService();
        ::android::sp<::com::hht::IFileInfoCallback> mycallback;
        virtual::android::binder::Status getFileInfo(const ::std::string& name);
        virtual::android::binder::Status registerCallback(const ::android::sp<::com::hht::IFileInfoCallback>& callback);
};
#endif

在类MyFileService中,我们定义了一个IFileInfoCallback类型的成员, 它用来保存回调的对象。

GetFileInfo.cpp

#include "GetFileInfo.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <iostream>
#include <string>
#include <time.h>

MyFileService::MyFileService(){

}

MyFileService::~MyFileService(){

}

::android::binder::Status MyFileService::getFileInfo(const ::std::string& name){
    struct stat st;
    // 文件不存在或者是不能读取
    if (stat(name.c_str(),&st) < 0)
    {
        return ::android::binder::Status::ok();
    }
    com::hht::FileInfo fileInfo;
    fileInfo.fileSize = st.st_size;
    fileInfo.name = android::String16(name.c_str());
    fileInfo.ctime = android::String16(asctime(localtime(&(st.st_mtime))));
    mycallback->backFileInfo(fileInfo);
    return ::android::binder::Status::ok();
    
}

::android::binder::Status MyFileService::registerCallback(const ::android::sp<::com::hht::IFileInfoCallback>& callback){
    mycallback = callback;
    return ::android::binder::Status::ok();
}

上面的函数也没什么好解释的,就是利用stat读取文件的基本信息,回调注册时将回调的对象保存起来。其实看逻辑的话我们完全没有必要注册一个回调,直接返回就可以了,我这里只是为了测试回调这个功能才这样写的。最后,我们还要将服务注册到serviceManager中,注册的函数调用如下

main.cpp

#include <sys/types.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/log.h>
#include <GetFileInfo.h>

 
using namespace android;
 
int main(int argc, char** argv) {

    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    ALOGI("ServiceManager:%p",sm.get());
    sm->addService(String16("MyFileService"),new MyFileService());
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

    return 0;
}

修改Android.bp文件,生成可执行文件,我这里为了方便测试,还添加了rc文件和配置了te规则实现这个服务的开机启动,如果是为了简单的测试可以不必这样,将生成的so文件push到/system/lib和/system/lib64目录,将可执行文件myfileservice放到/system/bin目录,重启手机,再手动的执行可执行文件。push之前记得先root和remount, 否则会提示没有权限。如果你也想实现开机启动,请看我的另一篇文章。

https://editor.csdn.net/md/?articleId=122440555

打开手机shell, 通过ps查看我们的服务是否已经在执行了。

3、应用连接服务

这里我写一个简单的App测试我们的服务,首先将我们定义的aidl文件复制到工程的aidl文件夹中,需要对FileInfo.aidl做一点改变。

package com.hht;

/**@hide*/
parcelable FileInfo;

我们在com.hht这个包下新建一个FileInfo类,同样需要继承Parceable接口。

package com.hht;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

public class FileInfo implements Parcelable {

    public String name;
    public String ctime;
    public int fileSize;

    protected FileInfo(Parcel in) {
        name = in.readString();
        ctime = in.readString();
        fileSize = in.readInt();
    }

    public static final Creator<FileInfo> CREATOR = new Creator<FileInfo>() {
        @Override
        public FileInfo createFromParcel(Parcel in) {
            return new FileInfo(in);
        }

        @Override
        public FileInfo[] newArray(int size) {
            return new FileInfo[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCtime() {
        return ctime;
    }

    public void setCtime(String ctime) {
        this.ctime = ctime;
    }

    public int getFileSize() {
        return fileSize;
    }

    public void setFileSize(int fileSize) {
        this.fileSize = fileSize;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(ctime);
        dest.writeInt(fileSize);
    }

    @NonNull
    @Override
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("name:"+name+"\n");
        buffer.append("size:"+fileSize+"\n");
        buffer.append("ctime:"+ctime+"\n");
        return buffer.toString();
    }
}

接下来我在MainActivity中简单的连接上服务,注册回调,然后测试一下回调功能是否实现,代码很简单。

package com.hht.example1;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.hht.FileInfo;
import com.hht.IFileInfoCallback;
import com.hht.IGetFileInfo;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MainActivity extends Activity {
    private static final String TAG = "MainExample";

    private Button button;
    // 这里必须是实现IFileInfoCallback.Stub(),而不是IFileInfoCallback这个接口
    private IFileInfoCallback mycallback = new IFileInfoCallback.Stub() {

        @Override
        public void backFileInfo(FileInfo fileInfo) throws RemoteException {
            Log.d(TAG, fileInfo.toString());
        }
    };

    private IGetFileInfo getFileInfoService;

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

        button = findViewById(R.id.button);
        connect_service();

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    getFileInfoService.getFileInfo("/data/local/tmp/test.txt");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    private void connect_service()  {
        try {
            Class<?> clazz = Class.forName("android.os.ServiceManager");

            Method method = clazz.getMethod("getService",String.class);

            IBinder binder = (IBinder) method.invoke(null,"MyFileService");

            if (binder != null){
                getFileInfoService = IGetFileInfo.Stub.asInterface(binder);
                // 千万不要忘记注册
                getFileInfoService.registerCallback(mycallback);
                Log.d(TAG, "连接成功");
            }

        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }
}

连接成功后就可以进行测试了,经验证可以实现回调,总结一下需要注意的是:

1、自定义数据结构FileInfo在两端都需要实现,都实现了Parceable接口。

2、bp文件中不需要将FileInfo.aidl加入编译。

死亡回调

有时候服务端可能因为某种原因死亡了,但是客户端不知道,这是可以在客户端写一个监听,服务死亡时就会收到Binder的通知。

try {
    operation.asBinder().linkToDeath(new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d("binder", "服务端死亡啦~!");
        }
    }, 0);
} catch (RemoteException e) {
    e.printStackTrace();
}

4、aidl的一些其他知识点

oneway关键字

它用于修改远程调用的行为,对于本地调用加不加都没有区别。首先,什么是远程调用呢?它就是异步调用,如果我们调用了一个远程方法,这个方法可能耗时很长,如果我们不想等待,就可以加上这个关键字,那调用后就会立即返回不会被阻塞在这里。

interface IMedia {
    oneway void start();//异步,假设执行2秒
    oneway void stop();//异步,假设执行2秒
    int getVolume();// 同步
}

数据走向标记

aidl中支持java中所有的原语类型(即8中基本类型),此外还支持String, CharSequence, List, Map, (事实上接受的是ArrayList和HashMap), 对于非原语类型,需要加上数据走向的标记,in, out, inout, 原语类型默认是in,不能是其他的方向。

定义接口注意事项

  • 自动生成的IBinder接口中包含了我们之间加的所有注释。
  • 可以在aidl接口中定义String常量和int类型的常量。
  • 方法调用是由transact()方法分派的,该代码通常基于接口中的方法索引,由于这会增加版本控制的难度,因此我们可以手动配置,如 void method() = 10;
  • 使用@nullable注释可以空参数或者返回类型
  • 对于native service端定义的aidl接口,对于String类型的参数可以加上@utf8InCpp注解,生成的头文件中String类型会被自动转换成std::string

接口实现

如果要实现aidl中定义的接口,那么需要实现是它的子类Stub, 它是父类接口的抽象实现,并且会实现接口中的所有方法。Stub中还有个非常重要的方法,其中最值得注意的是asInterface()方法, 该方法会接收IBinder( 通常是传递给客户端onServiceConnected()回调的方法)方法回调的参数, 并返回Stub接口的实例。

带Bundle参数(包含Parcelable类型)的方法

如果您的 AIDL 接口包含接收Bundle作为参数(预计包含 Parcelable 类型)的方法,则在尝试从Bundle读取之前,请务必通过调用 Bundle.setClassLoader(ClassLoader) 设置Bundle的类加载器。否则,即使您在应用中正确定义 Parcelable 类型,也会遇到 ClassNotFoundException。例如,

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect" */
    void saveRect(in Bundle bundle);
}

如下方实现所示,在读取 Rect 之前,ClassLoader 已在 Bundle 中完成显式设置

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值