mirror of
https://gitea.winboll.cc/Studio/OriginMaster.git
synced 2026-02-04 13:32:20 +08:00
Termux应用包命令调用成功。
This commit is contained in:
@@ -73,12 +73,13 @@ dependencies {
|
||||
implementation 'com.alibaba:fastjson:1.2.76'
|
||||
|
||||
// AndroidX 类库
|
||||
api 'androidx.appcompat:appcompat:1.1.0'
|
||||
/*api 'androidx.appcompat:appcompat:1.1.0'
|
||||
//api 'com.google.android.material:material:1.4.0'
|
||||
//api 'androidx.viewpager:viewpager:1.0.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'*/
|
||||
|
||||
|
||||
// 米盟
|
||||
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||
@@ -88,6 +89,23 @@ dependencies {
|
||||
api 'com.google.code.gson:gson:2.8.5'
|
||||
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core:1.6.0"
|
||||
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "com.google.guava:guava:24.1-jre"
|
||||
/*
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||
*/
|
||||
implementation 'com.termux:terminal-emulator:0.118.0'
|
||||
implementation 'com.termux:terminal-view:0.118.0'
|
||||
implementation 'com.termux:termux-shared:0.118.0'
|
||||
|
||||
// WinBoLL库 nexus.winboll.cc 地址
|
||||
api 'cc.winboll.studio:libaes:15.15.7'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Jan 19 03:57:58 GMT 2026
|
||||
#Mon Jan 19 06:42:29 GMT 2026
|
||||
stageCount=11
|
||||
libraryProject=
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.10
|
||||
buildCount=12
|
||||
buildCount=25
|
||||
baseBetaVersion=15.11.11
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.winboll"
|
||||
android:sharedUserId="com.termux">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:sharedUserId="com.termux">
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
@@ -12,7 +13,12 @@
|
||||
|
||||
<!-- 对正在运行的应用重新排序 -->
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||
|
||||
<!-- Android 11+ 查询已安装应用权限 -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<!-- 可选:兼容低版本系统 -->
|
||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package cc.winboll.studio.winboll.termux;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import cc.winboll.studio.libappbase.LogUtils; // 替换 Log 为 LogUtils(与 Activity 一致)
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.shell.command.ExecutionCommand.Runner;
|
||||
|
||||
/**
|
||||
* Termux 命令调用工具类(基于 RunCommandService 原型封装)
|
||||
* 用于向 Termux 发送命令执行请求,兼容 Termux RUN_COMMAND Intent 规范
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/19 16:30:00
|
||||
* @LastEditTime 2026/01/20 10:15:00
|
||||
*/
|
||||
public class TermuxCommandExecutor {
|
||||
private static final String TAG = "TermuxCommandExecutor";
|
||||
// 核心修复:Termux 官方包名(无 .app 后缀)
|
||||
private static final String TERMUX_PACKAGE_NAME = "com.termux";
|
||||
// Termux RunCommandService 完整类名(包名+类名)
|
||||
private static final String TERMUX_RUN_CMD_SERVICE_CLASS = "com.termux.app.RunCommandService";
|
||||
private static final String TERMUX_RUN_CMD_ACTION = TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.ACTION_RUN_COMMAND;
|
||||
|
||||
/**
|
||||
* 执行 Termux 命令(核心方法)
|
||||
* @param context 上下文(如 Activity、Service)
|
||||
* @param command 要执行的命令路径(如 "/bin/ls"、"/data/data/com.termux/files/usr/bin/bash")
|
||||
* @param args 命令参数(如 ["-l", "/data/data/com.termux/files/home"])
|
||||
* @param workDir 工作目录(可为 null,默认 Termux 主目录)
|
||||
* @param isBackground 是否后台执行(true=后台,false=终端会话执行)
|
||||
* @param resultDir 命令结果输出目录(可为 null,不输出到文件)
|
||||
* @return 是否成功发送命令请求
|
||||
*/
|
||||
public static boolean executeCommand(Context context, String command, String[] args, String workDir, boolean isBackground, String resultDir) {
|
||||
// 1. 校验上下文和命令合法性
|
||||
if (context == null || command == null || command.isEmpty()) {
|
||||
LogUtils.e(TAG, "执行命令失败:上下文或命令为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 校验 Termux 是否安装(新增:提前校验,避免白跑流程)
|
||||
if (!isTermuxInstalled(context)) {
|
||||
LogUtils.e(TAG, "执行命令失败:Termux 未安装");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 创建 Intent 并设置目标 Service
|
||||
Intent intent = new Intent(TERMUX_RUN_CMD_ACTION);
|
||||
intent.setClassName(TERMUX_PACKAGE_NAME, TERMUX_RUN_CMD_SERVICE_CLASS); // 用正确包名
|
||||
intent.setPackage(TERMUX_PACKAGE_NAME); // 明确包名,避免歧义
|
||||
|
||||
// 4. 设置核心命令参数(遵循 Termux RunCommandService 规范)
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_COMMAND_PATH, command);
|
||||
if (args != null && args.length > 0) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_ARGUMENTS, args);
|
||||
LogUtils.d(TAG, "命令参数:" + String.join(",", args));
|
||||
}
|
||||
if (workDir != null && !workDir.isEmpty()) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_WORKDIR, workDir);
|
||||
LogUtils.d(TAG, "工作目录:" + workDir);
|
||||
}
|
||||
|
||||
// 5. 设置执行模式(后台/终端会话)
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_BACKGROUND, isBackground);
|
||||
String runner = isBackground ? Runner.APP_SHELL.getName() : Runner.TERMINAL_SESSION.getName();
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RUNNER, runner);
|
||||
LogUtils.d(TAG, "执行模式:" + (isBackground ? "后台" : "终端会话") + ",Runner:" + runner);
|
||||
|
||||
// 6. 设置命令结果输出(可选,输出到文件)
|
||||
if (resultDir != null && !resultDir.isEmpty()) {
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_DIRECTORY, resultDir);
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_SINGLE_FILE, true);
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_RESULT_FILE_BASENAME, "authcenter_cmd_result");
|
||||
LogUtils.d(TAG, "结果输出目录:" + resultDir);
|
||||
}
|
||||
|
||||
// 7. 允许替换参数中的逗号替代字符
|
||||
intent.putExtra(TermuxConstants.TERMUX_APP.RUN_COMMAND_SERVICE.EXTRA_REPLACE_COMMA_ALTERNATIVE_CHARS_IN_ARGUMENTS, true);
|
||||
|
||||
// 8. 发送请求(区分 Android O 及以上的前台服务)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent);
|
||||
LogUtils.d(TAG, "Android O+ 启动前台服务发送命令");
|
||||
} else {
|
||||
context.startService(intent);
|
||||
LogUtils.d(TAG, "启动普通服务发送命令");
|
||||
}
|
||||
LogUtils.i(TAG, "命令发送成功:command=" + command);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "命令发送失败:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化方法:执行 Termux 终端命令(默认工作目录,终端会话执行)
|
||||
* @param context 上下文
|
||||
* @param command 命令(如 "ls -l /home"、"echo 'hello termux'")
|
||||
* @return 是否成功发送
|
||||
*/
|
||||
public static boolean executeTerminalCommand(Context context, String command) {
|
||||
LogUtils.d(TAG, "调用 executeTerminalCommand,命令:" + command);
|
||||
if (command == null || command.isEmpty()) {
|
||||
LogUtils.e(TAG, "命令为空,执行失败");
|
||||
return false;
|
||||
}
|
||||
// 通过 bash 执行任意终端命令
|
||||
String[] args = {"-c", command};
|
||||
return executeCommand(
|
||||
context,
|
||||
"/data/data/com.termux/files/usr/bin/bash", // Termux 默认 bash 路径(正确)
|
||||
args,
|
||||
"/data/data/com.termux/files/home", // 默认工作目录
|
||||
false, // 终端会话执行(可见)
|
||||
null // 不输出到文件
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化方法:后台执行 Termux 命令(无输出文件)
|
||||
* @param context 上下文
|
||||
* @param command 命令路径
|
||||
* @param args 命令参数
|
||||
* @return 是否成功发送
|
||||
*/
|
||||
public static boolean executeBackgroundCommand(Context context, String command, String[] args) {
|
||||
LogUtils.d(TAG, "调用 executeBackgroundCommand,command=" + command);
|
||||
return executeCommand(
|
||||
context,
|
||||
command,
|
||||
args,
|
||||
null,
|
||||
true, // 后台执行
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Termux 是否安装(修复核心错误)
|
||||
* @param context 上下文
|
||||
* @return Termux 是否已安装
|
||||
*/
|
||||
public static boolean isTermuxInstalled(Context context) {
|
||||
LogUtils.d(TAG, "校验 Termux 是否安装,包名:" + TERMUX_PACKAGE_NAME);
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "校验失败:上下文为空");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 用正确的 Termux 包名查询(com.termux)
|
||||
context.getPackageManager().getPackageInfo(TERMUX_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||
LogUtils.d(TAG, "Termux 已安装");
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.w(TAG, "Termux 未安装:" + e.getMessage());
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "校验 Termux 安装状态异常:" + e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Termux 是否允许外部应用调用
|
||||
* @return 校验提示信息
|
||||
*/
|
||||
public static String checkTermuxExternalAppPermission() {
|
||||
String tip = "请确保 Termux 已开启「允许外部应用调用」权限:\n1. 打开 Termux 输入:termux-setup-storage\n2. 编辑配置文件:echo \"allow-external-apps = true\" > ~/.termux/termux.properties\n3. 重启 Termux 生效";
|
||||
LogUtils.d(TAG, "外部应用调用权限提示:" + tip);
|
||||
return tip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
package cc.winboll.studio.winboll.unittest;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.winboll.MainActivity;
|
||||
import cc.winboll.studio.winboll.R;
|
||||
import cc.winboll.studio.winboll.activities.BaseWinBoLLActivity;
|
||||
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/19 11:11
|
||||
* @LastEditTime 2026/01/19 15:30
|
||||
* @Describe Termux 环境测试(新增:读取Termux主目录文件列表功能)
|
||||
* @Date 2026/01/19 11:11:00
|
||||
* @LastEditTime 2026/01/20 15:30:00
|
||||
* @Describe Termux环境测试工具(跨包+sharedUserId模式)
|
||||
* 适配不同应用包名(当前包:cc.winboll.studio.winboll.beta / Termux包:com.termux)
|
||||
* 支持Termux目录读取、命令执行及结果展示,基于sharedUserId实现跨包权限适配
|
||||
*/
|
||||
public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
|
||||
|
||||
// 常量属性(置顶排列)
|
||||
public static final String TAG = "TermuxEnvTestActivity";
|
||||
// Termux主目录固定路径
|
||||
private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home/WinBoLLStudio";
|
||||
private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/.authcenter_cmd_result.tmp";
|
||||
|
||||
// 成员属性(常量后排列)
|
||||
private Toolbar mToolbar;
|
||||
private TextView tvMessage;
|
||||
private Handler mainHandler;
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
@@ -37,111 +47,320 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LogUtils.d(TAG, "onCreate() 调用,初始化Activity");
|
||||
setContentView(R.layout.activity_termux_env_test);
|
||||
initView();
|
||||
initToolbar();
|
||||
initTermuxDirectory();
|
||||
LogUtils.d(TAG, "onCreate() 执行完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图组件
|
||||
*/
|
||||
private void initView() {
|
||||
LogUtils.d(TAG, "initView() 开始初始化视图");
|
||||
tvMessage = (TextView) findViewById(R.id.tv_message);
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// 初始化提示信息
|
||||
StringBuilder initMsg = new StringBuilder();
|
||||
initMsg.append("Termux 测试工具(跨包+sharedUserId模式)\n");
|
||||
initMsg.append("-------------------------\n");
|
||||
initMsg.append("当前应用包名:").append(getPackageName()).append("\n");
|
||||
initMsg.append("Termux应用包名:com.termux\n");
|
||||
initMsg.append("sharedUserId:com.termux(需一致)\n");
|
||||
initMsg.append("-------------------------\n");
|
||||
tvMessage.setText(initMsg.toString());
|
||||
|
||||
LogUtils.d(TAG, "initView() 初始化完成,tvMessage初始值:" + initMsg.toString().trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Toolbar组件
|
||||
*/
|
||||
private void initToolbar() {
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化");
|
||||
LogUtils.d(TAG, "initToolbar() 开始初始化Toolbar");
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
|
||||
if (mToolbar == null) {
|
||||
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
|
||||
LogUtils.e(TAG, "initToolbar() 错误:未找到Toolbar组件");
|
||||
return;
|
||||
}
|
||||
|
||||
setSupportActionBar(mToolbar);
|
||||
mToolbar.setSubtitle(getTag());
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.d(TAG, "导航栏 点击返回按钮");
|
||||
LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击");
|
||||
getActivity().finish();
|
||||
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getActivity(), MainActivity.class);
|
||||
}
|
||||
});
|
||||
LogUtils.d(TAG, "initToolbar() 配置完成");
|
||||
|
||||
LogUtils.d(TAG, "initToolbar() 初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心功能:读取Termux主目录文件列表,返回字符串格式
|
||||
* @return 目录路径+文件列表(换行分隔),异常时返回错误信息
|
||||
* 初始化Termux目标目录(跨包+sharedUserId模式)
|
||||
*/
|
||||
public String readTermuxHomeFileList() {
|
||||
LogUtils.d(TAG, "开始读取Termux目录:" + TERMUX_HOME_PATH);
|
||||
private void initTermuxDirectory() {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() 开始初始化Termux目录,路径:" + TERMUX_HOME_PATH);
|
||||
File termuxDir = new File(TERMUX_HOME_PATH);
|
||||
|
||||
if (termuxDir.exists()) {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() Termux目录已存在,无需创建");
|
||||
return;
|
||||
}
|
||||
|
||||
tvMessage.append("正在创建Termux目标目录...\n");
|
||||
boolean createSuccess = termuxDir.mkdirs();
|
||||
if (createSuccess) {
|
||||
LogUtils.d(TAG, "initTermuxDirectory() Termux目录创建成功:" + TERMUX_HOME_PATH);
|
||||
tvMessage.append("Termux目录创建成功:");
|
||||
tvMessage.append(TERMUX_HOME_PATH);
|
||||
tvMessage.append("\n");
|
||||
} else {
|
||||
LogUtils.e(TAG, "initTermuxDirectory() 错误:Termux目录创建失败");
|
||||
tvMessage.append("警告:Termux目录创建失败!\n请检查:\n1.AndroidManifest.xml中sharedUserId是否为com.termux\n2.设备是否已root(部分机型需root才能跨包写私有目录)\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试读取Termux目录(按钮点击事件)
|
||||
*/
|
||||
public void onTestTermuxEnv(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxEnv() 按钮点击,开始读取Termux目录");
|
||||
tvMessage.append("\n【测试:读取Termux目录】\n");
|
||||
|
||||
String fileListStr = readTermuxHomeFileList();
|
||||
tvMessage.append(fileListStr);
|
||||
tvMessage.append("\n-------------------------\n");
|
||||
|
||||
LogUtils.d(TAG, "onTestTermuxEnv() 执行完成,读取结果长度:" + fileListStr.length() + "字符");
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试执行Termux命令(按钮点击事件)
|
||||
*/
|
||||
public void onTestTermuxCMD(View view) {
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击,开始执行Termux命令");
|
||||
tvMessage.append("\n【测试:执行Termux命令】\n");
|
||||
|
||||
// 1. 校验Termux是否安装
|
||||
if (!TermuxCommandExecutor.isTermuxInstalled(this)) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:未安装Termux应用");
|
||||
tvMessage.append("错误:未安装Termux应用(包名:com.termux)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 校验目录是否存在
|
||||
File termuxDir = new File(TERMUX_HOME_PATH);
|
||||
if (!termuxDir.exists()) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:Termux目录不存在,路径:" + TERMUX_HOME_PATH);
|
||||
tvMessage.append("错误:Termux目录不存在,无法执行命令\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 构造并执行命令(修复:用bash -c包裹多命令,确保输出统一重定向)
|
||||
tvMessage.append("正在执行命令...\n");
|
||||
String targetCmd = "ls -al " + TERMUX_HOME_PATH + " && echo '\\n系统信息:' && uname -a && echo '\\n当前用户:' && whoami";
|
||||
// 关键修复:用bash -c包裹命令,确保所有输出被重定向
|
||||
String cmdWithRedirect = "bash -c \"" + targetCmd + "\" > " + CMD_RESULT_FILE + " 2>&1";
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 执行命令:" + cmdWithRedirect);
|
||||
|
||||
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, cmdWithRedirect);
|
||||
if (!cmdSuccess) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:命令发送失败");
|
||||
tvMessage.append("命令发送失败!\n可能原因:\n1.Termux未开启外部应用调用权限\n2.sharedUserId配置不一致\n3.TermuxCommandExecutor类路径错误\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 延迟读取结果(调整延迟为2.5秒,适配命令执行耗时)
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "onTestTermuxCMD() 结果读取线程启动,延迟2.5秒");
|
||||
try {
|
||||
Thread.sleep(2500);
|
||||
final String result = readCmdResultDirectly();
|
||||
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tvMessage.append("命令执行结果:\n");
|
||||
tvMessage.append(result);
|
||||
tvMessage.append("\n-------------------------\n");
|
||||
deleteCmdResultFile();
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.e(TAG, "onTestTermuxCMD() 错误:结果读取线程中断", e);
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tvMessage.append("错误:读取结果失败,线程被中断\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨包读取Termux命令结果文件
|
||||
* @return 命令执行结果字符串
|
||||
*/
|
||||
private String readCmdResultDirectly() {
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() 开始读取命令结果,文件路径:" + CMD_RESULT_FILE);
|
||||
File resultFile = new File(CMD_RESULT_FILE);
|
||||
|
||||
// 校验文件存在性
|
||||
if (!resultFile.exists()) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:结果文件不存在");
|
||||
return "错误:结果文件不存在\n可能原因:\n1.命令执行失败\n2.延迟时间不足\n3.跨包写权限被拒绝(需root)";
|
||||
}
|
||||
|
||||
// 校验文件可读性
|
||||
if (!resultFile.canRead()) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:无结果文件读取权限");
|
||||
return "错误:无结果文件读取权限(sharedUserId配置错误或未root)";
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
StringBuilder result = new StringBuilder();
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(new FileInputStream(resultFile), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
result.append(line).append("\n");
|
||||
}
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() 文件读取完成,结果长度:" + result.length() + "字符");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:文件读取异常", e);
|
||||
return "错误:读取文件异常 → " + e.getMessage();
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
LogUtils.d(TAG, "readCmdResultDirectly() BufferedReader资源已关闭");
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "readCmdResultDirectly() 错误:BufferedReader关闭异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String resultStr = result.toString().trim();
|
||||
return resultStr.isEmpty() ? "命令执行成功,但无输出" : resultStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除命令结果临时文件
|
||||
*/
|
||||
private void deleteCmdResultFile() {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 开始删除临时文件,路径:" + CMD_RESULT_FILE);
|
||||
File resultFile = new File(CMD_RESULT_FILE);
|
||||
|
||||
if (!resultFile.exists()) {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件不存在,无需删除");
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultFile.canWrite()) {
|
||||
boolean deleteSuccess = resultFile.delete();
|
||||
if (deleteSuccess) {
|
||||
LogUtils.d(TAG, "deleteCmdResultFile() 临时文件删除成功");
|
||||
} else {
|
||||
LogUtils.w(TAG, "deleteCmdResultFile() 警告:临时文件删除失败");
|
||||
tvMessage.append("警告:临时结果文件删除失败\n");
|
||||
}
|
||||
} else {
|
||||
LogUtils.w(TAG, "deleteCmdResultFile() 警告:无临时文件删除权限");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨包读取Termux目录文件列表
|
||||
* @return 目录文件列表字符串
|
||||
*/
|
||||
private String readTermuxHomeFileList() {
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 开始读取目录,路径:" + TERMUX_HOME_PATH);
|
||||
File termuxHomeDir = new File(TERMUX_HOME_PATH);
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
// 1. 检查目录是否存在
|
||||
// 基础校验
|
||||
if (!termuxHomeDir.exists()) {
|
||||
String errorMsg = "错误:Termux目录不存在 → " + TERMUX_HOME_PATH;
|
||||
LogUtils.e(TAG, errorMsg);
|
||||
return errorMsg;
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:目录不存在");
|
||||
return "错误:Termux目录不存在 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
|
||||
// 2. 检查是否为目录
|
||||
if (!termuxHomeDir.isDirectory()) {
|
||||
String errorMsg = "错误:指定路径不是目录 → " + TERMUX_HOME_PATH;
|
||||
LogUtils.e(TAG, errorMsg);
|
||||
return errorMsg;
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:指定路径不是目录");
|
||||
return "错误:指定路径不是目录 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
|
||||
// 3. 检查读写权限
|
||||
if (!termuxHomeDir.canRead()) {
|
||||
String errorMsg = "错误:无目录读取权限 → " + TERMUX_HOME_PATH;
|
||||
LogUtils.e(TAG, errorMsg);
|
||||
return errorMsg;
|
||||
LogUtils.e(TAG, "readTermuxHomeFileList() 错误:无目录读取权限");
|
||||
return "错误:无目录读取权限(需满足:1.sharedUserId=com.termux 2.设备root)";
|
||||
}
|
||||
|
||||
// 4. 获取目录下所有文件/子目录
|
||||
// 获取文件列表
|
||||
File[] files = termuxHomeDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
String emptyMsg = "Termux目录为空 → " + TERMUX_HOME_PATH;
|
||||
LogUtils.d(TAG, emptyMsg);
|
||||
return emptyMsg;
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 目录为空");
|
||||
return "Termux目录为空 → " + TERMUX_HOME_PATH;
|
||||
}
|
||||
|
||||
// 5. 拼接文件列表字符串(包含类型、名称、路径)
|
||||
// 拼接结果字符串
|
||||
result.append("Termux主目录:").append(TERMUX_HOME_PATH).append("\n");
|
||||
result.append("文件/子目录总数:").append(files.length).append("\n");
|
||||
result.append("目录权限:").append("读:").append(termuxHomeDir.canRead()).append(" | 写:").append(termuxHomeDir.canWrite()).append("\n");
|
||||
result.append("-------------------------\n");
|
||||
|
||||
for (File file : files) {
|
||||
String fileType = file.isDirectory() ? "[目录]" : "[文件]";
|
||||
String fileName = file.getName();
|
||||
String filePath = file.getAbsolutePath();
|
||||
result.append(fileType).append(" ").append(fileName).append(" → ").append(filePath).append("\n");
|
||||
String fileSize = file.isFile() ? " | 大小:" + formatFileSize(file.length()) : "";
|
||||
String filePerm = " | 权限:r:" + file.canRead() + "/w:" + file.canWrite();
|
||||
result.append(fileType).append(" ").append(fileName).append(fileSize).append(filePerm).append(" → ").append(filePath).append("\n");
|
||||
}
|
||||
|
||||
// 6. 去除末尾多余换行
|
||||
String fileListStr = result.toString().trim();
|
||||
LogUtils.d(TAG, "Termux目录读取完成,结果长度:" + fileListStr.length());
|
||||
return fileListStr;
|
||||
LogUtils.d(TAG, "readTermuxHomeFileList() 读取完成,结果长度:" + result.length() + "字符");
|
||||
return result.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小(B→KB→MB)
|
||||
* @param length 文件长度(字节)
|
||||
* @return 格式化后的文件大小字符串
|
||||
*/
|
||||
private String formatFileSize(long length) {
|
||||
LogUtils.d(TAG, "formatFileSize() 调用,文件长度:" + length + "B");
|
||||
String sizeStr;
|
||||
if (length < 1024) {
|
||||
sizeStr = length + "B";
|
||||
} else if (length < 1024 * 1024) {
|
||||
sizeStr = String.format("%.1fKB", length / 1024.0);
|
||||
} else {
|
||||
sizeStr = String.format("%.1fMB", length / (1024.0 * 1024));
|
||||
}
|
||||
LogUtils.d(TAG, "formatFileSize() 格式化结果:" + sizeStr);
|
||||
return sizeStr;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy() 调用,清理资源");
|
||||
deleteCmdResultFile();
|
||||
if (mainHandler != null) {
|
||||
mainHandler.removeCallbacksAndMessages(null);
|
||||
LogUtils.d(TAG, "onDestroy() Handler资源已释放");
|
||||
}
|
||||
LogUtils.d(TAG, "onDestroy() 执行完成");
|
||||
}
|
||||
|
||||
public void onTestTermuxEnv(View view) {
|
||||
TextView tvMessage = findViewById(R.id.tv_message);
|
||||
// 测试:读取Termux目录文件列表并打印日志
|
||||
String fileListStr = readTermuxHomeFileList();
|
||||
tvMessage.append(fileListStr);
|
||||
}
|
||||
|
||||
public void onTestTermuxCMD(View view) {
|
||||
// pkg update && pkg upgrade -y
|
||||
// pkg install termux-api -y
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setPackage("com.termux");
|
||||
intent.setAction("com.termux.RUN_COMMAND");
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/home");
|
||||
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"ls"});
|
||||
intent.putExtra("com.termux.RUN_COMMAND_WAIT_FOR_RESULT", false);
|
||||
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
|
||||
if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(this, "Termux或Termux:API未正确安装", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestTermuxCMD"
|
||||
android:onClick="onTestTermuxCMD"/>
|
||||
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -40,7 +40,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Text"
|
||||
android:id="@+id/tv_message"/>
|
||||
android:id="@+id/tv_message"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user