守护进程通信之Socket
前置文章
《创建Android守护进程(底层服务) 》
前言
在文章 《创建Android守护进程(底层服务) 》 中,学习了如何创建一个 Android 守护进程,但是这个进程还没有做任何有价值的事情。因此,在此篇文章中,来学习如何利用 Android 守护进程做一些事情。
在本文中,将讲述一个上层的 Android APP 如何和 Android 守护进程建立通信,传输数据,完成某项功能。本文将以 APP 读取 Android 设备 CPU 频率为例,APP 与守护进程通过建立 socket 通信,守护进程通过 socket 通道,把 CPU 频率上报到 APP 显示。
注:本文承接 《创建Android守护进程(底层服务) 》 一文中的内容。采用 MTK Android 8.0 的基带平台代码
本文涉及的主要代码,已经上传到 Github:
- nativeservice - 守护进程
- 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 #include #include #include #include #include "NativeServiceListener.h"#include #include #include #include #include #include #include using namespace android;#define MAX_EPOLL_EVENTS 40#define BUFFER_SIZE PIPE_BUFint 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 #include #include #include #include #include #include #include #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。
更多相关文章
- Android有用代码片断(六)
- 更改Android(安卓)AVD模拟器创建路径位置的方法
- [置顶] 搬家、备份后启动Android(安卓)PANIC :Could not open D:
- 深入解读Linux与Android的相互关系
- Android中JNI的使用
- Android进程 Handler Message Looper
- 爱Android更要懂Android
- Android多进程之Binder的意外死亡及权限校验
- Linux与Android