守护进程通信之Socket

前置文章

创建Android守护进程(底层服务)

前言

在文章 《创建Android守护进程(底层服务) 》 中,学习了如何创建一个 Android 守护进程,但是这个进程还没有做任何有价值的事情。因此,在此篇文章中,来学习如何利用 Android 守护进程做一些事情。

在本文中,将讲述一个上层的 Android APP 如何和 Android 守护进程建立通信,传输数据,完成某项功能。本文将以 APP 读取 Android 设备 CPU 频率为例,APP 与守护进程通过建立 socket 通信,守护进程通过 socket 通道,把 CPU 频率上报到 APP 显示。

注:本文承接 《创建Android守护进程(底层服务) 》 一文中的内容。采用 MTK Android 8.0 的基带平台代码

本文涉及的主要代码,已经上传到 Github:

  1. nativeservice - 守护进程
  2. SocketConnectNative - APP

添加Socket配置

在开机启动配置文件中添加创建 socket,如下代码的 socket…部分,其它代码和文章《创建Android守护进程(底层服务) 》是相同的。

service nativeservice /system/bin/nativeservice
    class main #main类,属于main的服务会开机被运行,且死掉会重启
    group system #属于 system 组
    #user system #以system用户启动,不设置以root用户启动
    seclabel u:r:nativeservice:s0 #SeAndroid SContext,domain是nativeservice
    restorecon nativeservice
    socket nativeservice stream 0666 root root #创建nativeservice socket,用户为 root
    write /proc/bootprof "start nativeservice"

代码路径:system/core/rootdir/init.rc

添加 SeAndroid 权限

承接文章《创建Android守护进程(底层服务) 》中的内容,修改或者添加部分内容。

声明 socket 通道文件的角色

type nativeservice_socket, file_type;

在文件 device/mediatek/sepolicy/basic/non_plat/file.te 中添加。

声明 socket 通道文件的安全上下文

/dev/socket/nativeservice         u:object_r:nativeservice_socket:s0

在文件 device/mediatek/sepolicy/basic/non_plat/file_contexts 中添加。

因为用的 socket 命名空间是 RESERVED,因此,socket 通道会在 /dev/socket/ 地下,详情可以查阅文件frameworks/base/core/java/android/net/LocalSocketAddress.java。

配置使用者权限

以上两个声明文件的角色和安全上下文,后面就是给某个进程赋予该角色被操作的权限。本文中,采用 System app 来和守护进程建立 socket 连接,因此,添加如下代码

allow system_app nativeservice_socket:sock_file { write append };
allow system_app nativeservice:unix_stream_socket { connectto };

在文件 device/mediatek/sepolicy/basic/non_plat/system_app.te 中添加。

proc 文件权限

守护进程需要打开和读取 /proc/ 底下的 CPU 频率文件,因此给守护进程 nativeservice 授予如下权限

allow nativeservice proc:file { open read };

在文件 device/mediatek/sepolicy/basic/non_plat/nativeservice.te 中添加。

编写守护进程代码

先看一下现在守护进程代码的目录架构,比文章 《创建Android守护进程(底层服务) 》 中多了 NativeServiceListener.cpp 和 NativeServiceListener.h 两个文件,修改了 native_main.cpp 文件。

这里写图片描述

native_main.cpp 代码

//
// Created familyyuan.
//

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <cutils/sockets.h>
#include <cutils/log.h>
#include "NativeServiceListener.h"

#include <fcntl.h>
#include <android-base/logging.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <cutils/uevent.h>

#include <sys/ioctl.h>

using namespace android;

#define MAX_EPOLL_EVENTS 40

#define BUFFER_SIZE PIPE_BUF

int main(int argc, char *argv[]) {
    SLOGD("native_service start");
    //
    fcntl(android_get_control_socket("nativeservice"), F_SETFD, FD_CLOEXEC);
    //核心代码,就这几行,实例化 socket 监听器,监听 socket 连接
    NativeServiceListener *cl;
    cl = new NativeServiceListener("nativeservice", true);
    if (cl->startListener()) {
        SLOGE("native_service Unable to start NativeServiceListener (%s)", strerror(errno));
        exit(1);
    }

    while(1){
        sleep(1000);
    }

    SLOGD("native_service die");
    return 0;
}

代码文件 system/core/nativeservice/native_main.cpp

NativeServiceListener.h 文件

声明继承 SocketListener.h 的头文件,当 socket 建立连接收到数据,会回调 onDataAvailable() 函数。

/* Copyright (C) 2016 Tcl Corporation Limited */
#ifndef _NATIVESERVICESOCKETLISTENER_H
#define _NATIVESERVICESOCKETLISTENER_H

#include "SocketListener.h"

class SocketClient;

class NativeServiceListener : public SocketListener {
public:
    static const int CMD_ARGS_MAX = 26;
    static const int CMD_BUF_SIZE = 1024;

    /* 1 out of errorRate will be dropped */
    int errorRate;
protected:
    bool onDataAvailable(SocketClient *c);
public:
    NativeServiceListener(const char *socketName);
    NativeServiceListener(const char *socketName, bool withSeq);
    NativeServiceListener(int sock);
    virtual ~NativeServiceListener() {}

private:
    void init(const char *socketName, bool withSeq);
};
#endif

代码文件 system/core/nativeservice/NativeServiceListener.h。

NativeServiceListener.cpp 文件

这个文件就是 SocketListener 的实现类,在 onDataAvailable() 函数中做事情。

/* Copyright (C) 2016 Tcl Corporation Limited */
#define LOG_TAG "NativeServiceListener"

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/sysinfo.h>

#include <cutils/log.h>
#include <sysutils/SocketClient.h>
#include <cutils/properties.h>

#include "NativeServiceListener.h"

static int file_fd;

#define DUMP_ARGS 1

#define UNUSED __attribute__((unused))

NativeServiceListener::NativeServiceListener(const char *socketName, bool withSeq) :
                            SocketListener(socketName, true, withSeq) {
    init(socketName, withSeq);
}

NativeServiceListener::NativeServiceListener(const char *socketName) :
                            SocketListener(socketName, true, false) {
    init(socketName, false);
}

NativeServiceListener::NativeServiceListener(int sock) :
                            SocketListener(sock, true) {
    init(NULL, false);
}

void NativeServiceListener::init(const char *socketName UNUSED, bool withSeq) {
}

bool NativeServiceListener::onDataAvailable(SocketClient *c) {
    char buffer[CMD_BUF_SIZE] = {0};
    int len;
    //读取 socket 客户端传来的数据
    len = TEMP_FAILURE_RETRY(read(c->getSocket(), buffer, sizeof(buffer)));
    if (len < 0) {
        SLOGE("native_service read() failed (%s)", strerror(errno));
        return false;
    } else if (!len) {
        SLOGD("native_service socket data %s", buffer);
        return false;
    }
    SLOGD("native_service runnig");

    char buffer_data[20];
    int res = -1;
    //读取 cpu 当前频率,基于 MTK Android 8.0 基带,笔者设备是 8 核 MTK 芯片,
    //L 和 LL 核,本文读取 LL 核的频率
    file_fd = TEMP_FAILURE_RETRY(open("/proc/cpufreq/MT_CPU_DVFS_LL/cpufreq_freq",O_RDONLY));
    if (file_fd < 0) {
        SLOGD("native_service open failed");
    } else {
        SLOGD("native_service open success");
    }
    if (file_fd != -1){
        res = read(file_fd, buffer_data, sizeof(buffer_data));
        SLOGD("native_service result=%s", buffer_data);
    } else {
        SLOGD("native_service open failed");
    }

    if(res > 0){
        char *buf;
        int ret = 0;
        strtok(buffer_data, "\n");
        //把返回给客户端的数据组装成 json 格式
        ret = asprintf(&buf, "%s%s%s%s", "{\"code\":200,", "\"cpu\":\"", buffer_data, "\"}");
        SLOGD("native_service for java result=%s", buf);
        //往 socket 客户端写返回数据
        if (ret != -1) {
            c->sendMsg(buf);
        } else {
            c->sendMsg(buffer_data);
        }
        free(buf);
    } else {
        SLOGD("native_service open failed %d", 500);
        c->sendCode(500);
    }
    close(file_fd);
    free(buffer);
    free(buffer_data);
    return true;
}

代码文件 system/core/nativeservice/NativeServiceListener.cpp。

小结

至此,底层的代码就编写完毕了,以上代码编译开机,在 /dev/socket/ 下创建了 nativeservice socket 文件,如下图

这里写图片描述

守护进程接收到 socket 连接后打印的 log

这里写图片描述

编写 APP 代码

APP 实现的功能是与 Nativeservice 建立 socket 连接,读取 socket 返回的 cpu 频率数据,显示在屏幕上。如下图所示,有一个 Activity 界面,启动一个 Service,Service 和 nativeservcie 建立 socket 连接,每隔 1 秒拿一次 cpu 频率数据,显示在一个浮动 view 上。
这里写图片描述

如下是 Service 的代码,更多代码请看 Github - SocketConnectNative

package yuan.family.com.socketconnectnative;
public class SocketConnNativeService extends Service {
    final String TAG = SocketConnNativeService.class.getSimpleName();
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams wmParams;
    private LocalSocket mSocket;
    private InputStream mIn;
    private OutputStream mOut;
    private Handler mHandler;
    private Handler mThreadHandler;
    private HandlerThread mHandlerThread;
    private final int CONN_SOCKET = 101;
    private final int SOCKET_RESULT = 101;

    private TextView mCpuFreqTV;

    public SocketConnNativeService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
    /**
    * 浮动窗口的 window 和 view 配置
    */
    private void initWindowParams() {
        mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);
        wmParams = new WindowManager.LayoutParams();
        wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        wmParams.format = PixelFormat.TRANSLUCENT;
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;
        wmParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    }
    /**
    * 引入 view,实例化组件,把 view 显示出来
    */
    private void initView() {
        initWindowParams();
        View dialogView = LayoutInflater.from(this).inflate(R.layout.cpu_freq_view, null);
        mCpuFreqTV = dialogView.findViewById(R.id.cpu_freq);
        dialogView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mWindowManager.removeViewImmediate(v);
                stopSelf();
            }
        });
        mWindowManager.addView(dialogView, wmParams);
    }
    /**
    * 由于使用的是 Android 8.0 需要把 service 推到前台,不然被系统杀掉
    */
    private Notification getNotification(){
        NotificationManager mNotiManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel mChannel = new NotificationChannel("socket_native_conn", "socket_native_conn", NotificationManager.IMPORTANCE_DEFAULT);
        mNotiManager.createNotificationChannel(mChannel);
        Notification.Builder mBuilder = new Notification.Builder(this);
        mBuilder.setShowWhen(false);
        mBuilder.setAutoCancel(false);
        mBuilder.setSmallIcon(R.mipmap.ic_launcher);
        mBuilder.setContentText("SocketConnNative keep");
        mBuilder.setContentTitle("SocketConnNative keep");
        mBuilder.setChannelId("socket_native_conn");

        return mBuilder.build();
    }
    /**
    * 与 socket 建立连接,获取输入输出流
    */
    private boolean connect() {
        if (mSocket != null && mSocket.isConnected()) {
            return true;
        }
        try {
            // a non-server socket
            mSocket = new LocalSocket();
            // LocalSocketAddress.Namespace.RESERVED keep namespace
            LocalSocketAddress address = new LocalSocketAddress("nativeservice", LocalSocketAddress.Namespace.RESERVED);
            mSocket.connect(address);
            mIn = mSocket.getInputStream();
            mOut = mSocket.getOutputStream();
        } catch (Exception ex) {
            ex.printStackTrace();
            //disconnect();
            return false;
        }
        return true;
    }

    public void disconnect() {
        try {
            if (mSocket != null) {
                mSocket.shutdownInput();
                mSocket.shutdownOutput();
                mSocket.close();
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            if (mIn != null) {
                mIn.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        try {
            if (mOut != null) {
                mOut.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        mSocket = null;
        mIn = null;
        mOut = null;
    }

    protected boolean sendCommand(byte[] cmd) {
        try {
            String prefixCmd = "0 traceability ";
            byte fullCmd[] = new byte[prefixCmd.length() + cmd.length];
            System.arraycopy(prefixCmd.getBytes(), 0, fullCmd, 0, prefixCmd.length());
            System.arraycopy(cmd, 0, fullCmd, prefixCmd.length(), cmd.length);
            if (mOut != null) {
                mOut.write(fullCmd, 0, fullCmd.length);
            }
        } catch (Exception ex) {
            Log.e(TAG, "write error");
            return false;
        }
        return true;
    }

    public String connSocketNative(byte[] cmd) {
        byte[] result = new byte[128];
        StringBuilder stringBuilder = new StringBuilder();
        if (!connect()) {
            Log.d(TAG, "Connecting nativeservice proxy fail!");
            mThreadHandler.sendEmptyMessage(CONN_SOCKET);
        } else if (!sendCommand(cmd)) {
            Log.d(TAG, "Send command to nativeservice proxy fail!");
            mThreadHandler.sendEmptyMessage(CONN_SOCKET);
        } else {
            BufferedReader br = null;
            try {
                //读取 nativeservice 返回的 cpu 频率数据
                br = new BufferedReader(new InputStreamReader(mIn, "UTF-8"));
                String resultStr = br.readLine();
                while (!TextUtils.isEmpty(resultStr)) {
                    stringBuilder.append(resultStr);
                    resultStr = br.readLine();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        disconnect();
        return stringBuilder.toString().replace("[?]", "").trim();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1, getNotification());

        mHandler = new MainHandler(Looper.getMainLooper());
        mHandlerThread = new HandlerThread("thread", Thread.MAX_PRIORITY);
        mHandlerThread.start();
        mThreadHandler = new ThreadHandler(mHandlerThread.getLooper());
        initView();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mThreadHandler.sendEmptyMessage(CONN_SOCKET);
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }
    /**
    * socket 连接是耗时操作,需要在子线程完成
    */
    private class ThreadHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CONN_SOCKET:
                    String result = connSocketNative(new byte[]{'A'});
                    Gson gson = new Gson();
                    CpuInfo cpuInfo = gson.fromJson(result, CpuInfo.class);
                    //获取成功,将 cpu 频率数据发给 UI 线程
                    if (cpuInfo != null && cpuInfo.getCode() == 200) {
                        mHandler.sendMessage(Message.obtain(mHandler, SOCKET_RESULT, cpuInfo.getCpu()));
                    }
                    break;
                default:
                    break;
            }
        }

        public ThreadHandler(Looper looper) {
            super(looper);
        }
    }
    /**
    * UI 线程获取到数据后,显示在 TextView 上
    * 每隔 1 秒钟获取一次
    */
    private class MainHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SOCKET_RESULT:
                    mCpuFreqTV.setText(msg.obj.toString());
                    mThreadHandler.sendEmptyMessageDelayed(CONN_SOCKET, 1000);
                    break;
                default:
                    break;
            }
        }

        public MainHandler(Looper looper) {
            super(looper);
        }
    }
}

总结

守护进程在文章 《创建Android守护进程(底层服务) 》 的基础上,没有太多的修改和添加,主要三点,一是配置 SeAndroid 权限,二是增加加载 CPU 频率的代码,三是添加 Socket 监听器。上层 APP 是纯新的代码,但是代码都比较简单,核心代码就在 SocketConnNativeService.java 文件中。守护进程通过 socket 上报给 APP 的数据基于 Json 数据格式,便于 APP 解析,本文中使用 Google 的 Json 数据处理框架 Gson。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值