Jimmy Chen

A Programmer

Native(C、C++)跨层访问Java注册的service

  上一篇我们介绍了在Java层如何跨层访问在Native(C、C++)层注册的Binder service。这一篇我们打算反过来,在Native(C、C++)层跨层访问Java层注册的Binder service。如果对上一篇有初步了解的话,对这一篇的实现应该也不会有太多的问题,毕竟都是通过Binder调用。老样子,本篇的示例代码也是在没有添加selinux权限下进行的

Java层实现service

  Java层在AIDL的帮助下,实现service会比较简单,所以接下来service的实现我们就不用AIDL的方式了,直接是自己写代码进行实现(嗯,比较作,偏不走简单的路,真是作死)

ISayHelloServiceInterface.java

package com.jimmy.sayhelloservice;

import android.os.IInterface;

public interface ISayHelloServiceInterface extends IInterface {
    void sayHello(String str) throws android.os.RemoteException;
    void displayDialog(String str) throws android.os.RemoteException;
}

和C、C++实现service有点类似哈,这里也是先继承IInterface,作为service服务端和客户端都需要实现的基类,下面看服务端的代码实现

SayHelloService.java

package com.jimmy.sayhelloservice;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Binder;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import android.view.WindowManager;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Locale;
import java.util.concurrent.TimeUnit;

public class SayHelloService extends Binder implements ISayHelloServiceInterface {
    private static final String DESCRIPTOR = "com.jimmy.sayhelloservice.SayHelloService";
    private Context mContext = null;

    private static final int DISPLAY_DIALOG = 0;

    public SayHelloService(Context mContext) {
        this.mContext = mContext;
        attachInterface(this, DESCRIPTOR);
    }

    @Override
    public void sayHello(String str) throws RemoteException {
        Log.e("JIMMY", "111sayHello str = " + str);
    }

    // 实现两个基类的方法
    @Override
    public void displayDialog(String str) throws RemoteException {
        Message message = handler.obtainMessage();
        message.what = DISPLAY_DIALOG;
        message.obj = str;
        handler.sendMessage(message);
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    // asInterface的作用是根据调用是否属于同进程而返回不同的实例对象
    public static ISayHelloServiceInterface asInterface(android.os.IBinder obj)
    {
        if ((obj==null)) {
            return null;
        }
        android.os.IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
        if (((iInterface != null)&&(iInterface instanceof SayHelloService))) {
            return ((SayHelloService)iInterface);
        }
        return new SayHelloServiceManager(obj);
    }

    // 这里做一个弹框功能,弹框需要运行在Hanlder中
    Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case DISPLAY_DIALOG: {
                    String str = (String)msg.obj;
                    AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
                    builder.setTitle("SayHelloService");
                    builder.setMessage("Message : " + str);
                    builder.setIcon(R.mipmap.ic_launcher_round);
                    builder.setCancelable(false);
                    builder.setPositiveButton("YES", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Log.e("JIMMY", "SayHelloService displaydialog click yes!");
                        }
                    });
                    builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Log.e("JIMMY", "SayHelloServide displaydialog click no!");
                        }
                    });
                    AlertDialog dialog = builder.create();
                    dialog.setOnShowListener(new DialogInterface.OnShowListener() {
                        private static final int AUTO_DISMISS_MILIS = 15 * 1000;
                        @Override
                        public void onShow(final DialogInterface dialog) {
                            Log.e("JIMMY", "SayHelloService dialog has been showed!");
                            final Button positiveButton = ((AlertDialog)dialog).getButton(AlertDialog.BUTTON_NEGATIVE);
                            final CharSequence positiveButtonText = positiveButton.getText();
                            new CountDownTimer(AUTO_DISMISS_MILIS, 1000) {

                                @Override
                                public void onTick(long millisUntilFinished) {
                                    positiveButton.setText(String.format(Locale.getDefault(),
                                            "%s (%d)",
                                            positiveButtonText,
                                            TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) + 1));
                                }

                                @Override
                                public void onFinish() {
                                    if (((AlertDialog)dialog).isShowing()) {
                                        //dialog.dismiss();
                                    }
                                }
                            }.start();
                        }
                    });
                    dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                        @Override
                        public void onDismiss(DialogInterface dialog) {
                            Log.e("JIMMY", "SayHelloService dialog has dismissed!");
                        }
                    });
                    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
                    Log.e("JIMMY", "111OnTransact dialog begin to show!");
                    dialog.show();
                };
                break;

                default:
                    break;
            }
        }
    };

    // onTransact方法,其实sayHello和displayDialog的具体代码也可以在这里实现,然后讲sayHello和displayDialog留空
    @Override
    protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case TRANSACTION_sayhelloservice : {
                Log.e("JIMMY", "111onTransact TRANSACTION_sayhelloservice code!");
                data.enforceInterface(DESCRIPTOR);
                String str = data.readString();
                sayHello(str);
            }
            break;

            case TRANSACTION_displaydialog: {
                Log.e("JIMMY", "111onTransact TRANSACTION_displaydialog code!");
                data.enforceInterface(DESCRIPTOR);
                String str = data.readString();
                displayDialog(str);
            }
            break;

            default:
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

    // 定义两个Transact的定义值
    static final int TRANSACTION_sayhelloservice = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_displaydialog = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

从这里看,其实自己使用Java手动实现一个binder service也不是特别复杂。接下来我们看客户端的代码。

SayHelloServiceManager.java

package com.jimmy.sayhelloservice;

import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;

public class SayHelloServiceManager implements ISayHelloServiceInterface {
    private IBinder mRemote;
    private static final String DESCRIPTOR = "com.jimmy.sayhelloservice.SayHelloService";

    SayHelloServiceManager(IBinder remote) {
        this.mRemote = remote;
    }

    @Override
    public void sayHello(String str) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            Log.e("JIMMY", "SayHelloServiceManager str = " + str);
            data.writeString(str);
            mRemote.transact(SayHelloService.TRANSACTION_sayhelloservice, data, reply, 0);
            reply.readException();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    @Override
    public void displayDialog(String str) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            data.writeString(str);
            mRemote.transact(SayHelloService.TRANSACTION_displaydialog, data, reply, 0);
            reply.readException();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    @Override
    public IBinder asBinder() {
        return mRemote;
    }
}

没什么特别的,需要注意的是要先将Token写到Parcel中,然后再讲需要发送的数据写到Parcel。否则可能会出现Service处理失败的情况。当然,如果只是想通过Java注册service的话,客户端的代码都可以不用实现的,不过博主是打算在APK里面进行一些简单的测试,所以客户端的代码也就一起实现了。最后是注册service和测试的代码了,博主统一写到MainActivity中了

MainActivity.java

package com.jimmy.sayhelloservice;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;

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

public class MainActivity extends AppCompatActivity {
    private Button btnSayHello;
    private Button btnShowDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnSayHello = (Button) findViewById(R.id.sayhello);
        btnShowDialog = (Button) findViewById(R.id.dialog);
        Class<?> serviceManager = null;
        Method addServiceMethod = null;
        Method getServiceMethod = null;
        try {
            serviceManager = Class.forName("android.os.ServiceManager");
            addServiceMethod = serviceManager.getDeclaredMethod("addService", String.class, IBinder.class);
            addServiceMethod.invoke(null, "sayHelloService", new SayHelloService(getApplicationContext()));
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            getServiceMethod = serviceManager.getDeclaredMethod("getService", String.class);
            IBinder proxy = (IBinder) getServiceMethod.invoke(null, "sayHelloService");
            final SayHelloServiceManager manager = new SayHelloServiceManager(proxy);

            btnSayHello.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (manager != null) {
                        try {
                            manager.sayHello("hello, call manager.sayHello method");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

            btnShowDialog.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (manager != null) {
                        try {
                            manager.displayDialog("show dialog");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

布局有两个button,分别用来测试sayHello和displayDialog binder调用的。好的Java层的service 实现就到这里了,接下来就到了在native层怎么通过binder调用到这个Java service了。

Native(C、C++)调用service

ISayHelloService.h

//
// Created by jimmy on 2/29/20.
//

#ifndef ANDROID_ISAYHELLOSERVICE_H
#define ANDROID_ISAYHELLOSERVICE_H

#include <utils/Errors.h>  // for status_t
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>

namespace android {
    class ISayHelloService : public IInterface
    {
        public:
            // 重要的宏定义,提供Service的asInterface方法和descriptor成员
            DECLARE_META_INTERFACE(SayHelloService);

            // 实际工作的成员函数
            virtual void sayHello(std::string /* str */) {};
            virtual void displayDialog(std::string /* str */) {};
    };
}

#endif //ANDROID_ISAYHELLOSERVICE_H

emmm,C、C++也是老样子的,定义一个公共头文件,继承IInterface,作为binder服务端和客户端都需要实现的公共类。因为我们是希望在Native中通过binder调用Java层的service,所以后面我们只需要实现客户端的代码即可

ISayHelloService.cpp

//
// Created by jimmy on 2/29/20.
//

#define LOG_TAG "JIMMY"
#include "include/ISayHelloService.h"

#include <utils/Errors.h>
#include <utils/String8.h>

//
namespace android{

    enum {
        SAY_HELLO = IBinder::FIRST_CALL_TRANSACTION,
        DISPLAY_DIALOG
    };


    class BpSayHelloService : public BpInterface<ISayHelloService>
    {
    public:
        BpSayHelloService(const sp<IBinder>& impl)
                : BpInterface<ISayHelloService>(impl)
        {

        }

        virtual void sayHello(std::string str) {
            ALOGD("sayHello() getInterfaceDescriptor = %s", String8(ISayHelloService::getInterfaceDescriptor()).string());
            Parcel data, reply;
            data.writeInterfaceToken(String16(ISayHelloService::getInterfaceDescriptor()));
            data.writeString16(String16(str.c_str()));
            remote()->transact(SAY_HELLO, data, &reply);
            int code = reply.readExceptionCode();
            ALOGD("sayHello() readExceptionCode = %d", code);
            return;
        }

        virtual void displayDialog(std::string str) {
            ALOGD("displayDialog() getInterfaceDescriptor = %s", String8(ISayHelloService::getInterfaceDescriptor()).string());
            Parcel data, reply;
            data.writeInterfaceToken(String16(ISayHelloService::getInterfaceDescriptor()));
            data.writeString16(String16(str.c_str()));
            remote()->transact(DISPLAY_DIALOG, data, &reply);
            int code = reply.readExceptionCode();
            ALOGD("displayDialog() readExceptionCode = %d", code);
            return;
        }
    };

    // 关键的宏,完成DECLARE_META_INTERFACE宏中定义的方法
    IMPLEMENT_META_INTERFACE(SayHelloService, "com.jimmy.sayhelloservice.SayHelloService");
}

这个就是客户端的代码,文件名起得不好,记住这个是客户端的代码就行了。另外就是两个定义宏了DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏了,接下来我们看看客户端的实现代码。

TestClient.cpp

#define LOG_TAG "JIMMY"

#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <utils/String16.h>

#include "include/ISayHelloService.h"

int main(void)
{
    /* get service */
    android::sp<android::IServiceManager> sm = android::defaultServiceManager();
    android::sp<android::IBinder> binder = sm->getService(android::String16("sayHelloService"));
    if (binder == NULL) {
        ALOGD("client get service return NULL");
        return -1;
    }

    android::sp<android::ISayHelloService> service = android::interface_cast<android::ISayHelloService>(binder);
    service->sayHello("Say hello from native!");

    service->displayDialog("Display dialog from native!");

    return 0;
}

常规流程,获取service,然后调用就对了。最后就是Android.mk了

Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
        ISayHelloService.cpp

LOCAL_SHARED_LIBRARIES := \
        libbinder           \
        libcutils           \
        liblog          \
        libutils            \

LOCAL_C_INCLUDES :=             \
        $(TOP)/frameworks/arithmetic/include    \
        $(TOP)/frameworks/native/include

LOCAL_CLANG := true

LOCAL_MODULE := libsayhelloservice

include $(BUILD_SHARED_LIBRARY)



include $(CLEAR_VARS)

LOCAL_SRC_FILES := \
        TestClient.cpp


LOCAL_SHARED_LIBRARIES := \
        libbinder           \
        libcutils           \
        liblog          \
        libutils            \
        libsayhelloservice \

LOCAL_C_INCLUDES :=             \
        $(TOP)/frameworks/arithmetic/include    \
        $(TOP)/frameworks/native/include

LOCAL_CLANG := true

LOCAL_MODULE := test_sayhello_client

include $(BUILD_EXECUTABLE)


include $(call all-makefiles-under,$(LOCAL_PATH))

好了,最后push这个test_sayhello_client可执行程序push到system/bin 然后先运行servcie所在的apk,apk会先往system_server注册service,然后执行test_sayhello_client就可以实现native(C、C++)跨层访问Java注册的service了。OK这篇到这里,各位看官有什么问题可以留言一起交流交流。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

%d 博主赞过: