第二章:Android串口权限配置

好,咱们接着聊。上一章我们把串口硬件和驱动层面的事情理清了,这一章要解决一个很现实的问题——你的App凭什么能访问串口?

说白了,Android系统对硬件访问管得很严。你写个App想直接打开/dev/ttyS0?没门。系统会告诉你“Permission denied”。我刚开始做串口开发时,就被这个权限问题卡了整整两天。

2.1 root权限获取

先说说最直接的办法——root。拿到root权限,就等于拿到了系统的最高控制权。你的App可以想干嘛就干嘛,串口随便开。

⚠️ 注意:生产环境不建议root。root会破坏系统安全机制,Google Play上架的应用也不允许请求root权限。我一般只在开发调试阶段用。

2.1.1 判断是否已root

怎么知道设备有没有root?写个简单的方法试试:

public static boolean isDeviceRooted() {
    String[] paths = {
        "/system/bin/su",
        "/system/xbin/su",
        "/sbin/su",
        "/system/sd/xbin/su"
    };
    for (String path : paths) {
        if (new File(path).exists()) {
            return true;
        }
    }
    return false;
}

嗯,这个方法不完美,但够用。有些设备会伪装su文件,不过咱们做嵌入式开发,一般不会遇到那种情况。

2.1.2 动态申请root权限

如果你的App需要临时提权,可以用Runtime执行su命令:

public static boolean grantRootPermission() {
    try {
        Process process = Runtime.getRuntime().exec("su");
        DataOutputStream os = new DataOutputStream(process.getOutputStream());
        os.writeBytes("chmod 666 /dev/ttyS0\n");
        os.writeBytes("exit\n");
        os.flush();
        process.waitFor();
        return process.exitValue() == 0;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

这里有个坑——用户设备上会弹出root授权弹窗。如果用户点了“拒绝”,那后面所有操作都白搭。我曾经在一个项目里没处理这个情况,结果现场演示时翻车了……

2.2 SELinux策略修改

root搞定了,但有时候还是打不开串口。为什么?因为SELinux在背后拦着。

SELinux是Linux内核的安全模块,Android从4.3开始强制启用。它比传统的Linux权限模型更严格——就算你是root,SELinux说不让你访问,你就访问不了。

💡 核心概念:SELinux有三种模式——Enforcing(强制)、Permissive(宽容)、Disabled(禁用)。调试阶段可以临时设为Permissive,但正式产品必须用Enforcing。

2.2.1 临时关闭SELinux

调试时最省事的办法:

adb shell setenforce 0

这条命令把SELinux设为Permissive模式。它只记录违规行为,不阻止操作。我习惯在开发板上先跑这条命令,确认串口能正常工作,再去细化策略。

⚠️ 注意:setenforce 0只对当前启动有效。重启后恢复为Enforcing。别问我怎么知道的——我有一块板子,每次重启都要重新设一遍,后来干脆写了个init脚本。

2.2.2 永久修改SELinux策略

要永久生效,得改SELinux策略文件。以串口设备/dev/ttyS0为例:

# 创建策略文件 uart.te
type uart_device_t;
dev_type(uart_device_t)

# 允许init进程访问
allow init uart_device_t:chr_file rw_file_perms;

# 允许system_app访问
allow system_app uart_device_t:chr_file rw_file_perms;

然后编译并加载:

make -j8
adb push out/target/product/xxx/ramdisk.img /tmp/
adb shell reboot

嗯,编译SELinux策略是个技术活。我建议你第一次做的时候,先找个现成的平台参考一下。我自己就吃过亏——写错一个allow规则,系统直接起不来。

2.2.3 查看SELinux日志

如果串口还是打不开,先查查SELinux有没有拦截:

adb shell dmesg | grep avc
adb logcat -b events | grep avc

看到类似这样的日志,就是被拦了:

avc: denied { read write } for pid=1234 comm="app_process" 
     name="ttyS0" dev="tmpfs" ino=5678 
     scontext=u:r:untrusted_app:s0 
     tcontext=u:object_r:device:s0 tclass=chr_file

这条日志告诉你:哪个进程(pid=1234)、想干什么(read write)、被谁拦了(untrusted_app)。拿着这些信息去写allow规则就行。

2.3 串口节点读写权限设置

root和SELinux都搞定了,最后一步——确保串口节点本身有正确的读写权限。

2.3.1 查看当前权限

adb shell ls -l /dev/ttyS0

正常输出应该是:

crw-rw---- 1 root dialout 4, 64 2024-01-01 10:00 /dev/ttyS0

看到没?权限是660,属主是root,属组是dialout。如果你的App不在dialout组里,就打不开。

2.3.2 修改权限的几种方式

方式一:chmod(临时)

adb shell chmod 666 /dev/ttyS0

简单粗暴,但重启后失效。我一般只在调试时用。

方式二:修改udev规则(推荐)

在/etc/udev/rules.d/下创建规则文件:

# /etc/udev/rules.d/99-serial.rules
KERNEL=="ttyS0", MODE="0666", GROUP="dialout"

这样每次设备插入或系统启动时,udev会自动设置权限。我在好几个项目里都用这个方案,稳定得很。

方式三:init.rc中设置

如果你的设备用init.rc管理服务,可以加一条:

on boot
    chmod 0666 /dev/ttyS0
    chown system system /dev/ttyS0

这种方式适合定制ROM的场景。我帮客户做平板项目时,就是这么干的。

2.3.3 权限检查清单

检查项 命令 期望结果
设备节点存在 ls -l /dev/ttyS0 crw-rw---- 或 crw-rw-rw-
SELinux状态 getenforce Permissive 或 Enforcing(有策略)
App所属组 id 包含dialout或root
root权限 su -c "id" uid=0(root)
💡 我的经验:遇到串口打不开,按这个顺序排查——先看设备节点有没有,再看权限对不对,然后查SELinux日志,最后确认App的uid/gid。90%的问题都能在这几步里解决。

2.4 避坑指南

最后分享几个我踩过的坑:

  • 别用777权限——虽然能解决问题,但安全风险太大。我见过有人把整个/dev目录都设成777,结果系统被入侵了。
  • 注意SELinux策略的优先级——就算你chmod 666了,SELinux照样能拦你。这两个是独立的安全机制。
  • 多串口设备要逐个配置——别以为配了一个ttyS0,ttyS1就自动好了。我有个项目用了4个串口,每个都得单独配。
  • 重启后权限丢失是常态——所以一定要用udev规则或init.rc来持久化配置。

嗯,权限配置这块就这些。下一章我们聊聊怎么在Android应用层打开串口、读写数据。到时候你会看到,前面这些准备工作到底有多重要。