在自己的安卓App中使用Termux的终端
Termux 的核心终端显示和模拟功能被封装在 terminal-view 和 terminal-emulator 这两个模块里,并且,它们享有基于 Apache 2.0 协议的例外条款,可以在闭源项目中使用。
首先,我们通过git clone克隆Termux项目
git clone https://github.com/termux/termux-app.git
然后,把里面的terminal-view和terminal-emulator模块复制到我们的app模块同级目录下

修改settings.gradle添加:
include ':terminal-emulator' include ':terminal-view'
在项目gradle.properties中添加(从Termux仓库复制的,根据需要修改)
minSdkVersion=21 targetSdkVersion=28 ndkVersion=29.0.14206865 compileSdkVersion=36
之后就能看到这两个模块出现了

在app/build.gradle中添加
dependencies { implementation project(':terminal-view') implementation project(':terminal-emulator') }
在layout中使用:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.termux.view.TerminalView android:id="@+id/terminal_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
编写代码创建Session绑定到TerminalView (需要自己实现Client的相关方法),这里是我的例子
package top.wsdx233.randroid
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.inputmethod.InputMethodManager
import androidx.activity.ComponentActivity
import com.termux.terminal.TerminalSession
import com.termux.terminal.TerminalSessionClient
import com.termux.view.TerminalView
import com.termux.view.TerminalViewClient
import java.io.File
class TerminalActivity : ComponentActivity() {
private var terminalSession: TerminalSession? = null
private lateinit var terminalView: TerminalView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.terminal_activity_layout)
terminalView = findViewById(R.id.terminal_view)
// 设置背景为黑色,Termux 默认字体是浅色的
terminalView.setBackgroundColor(Color.BLACK)
terminalView.setTextSize(40)
// 保持屏幕常亮
terminalView.keepScreenOn = true
terminalView.setTerminalViewClient(object : TerminalViewClient {
// 缩放比例
override fun onScale(scale: Float): Float = 1.0f
override fun onSingleTapUp(e: MotionEvent?) {
// 获取焦点
terminalView.requestFocus()
// 调用系统输入法管理器显示软键盘
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(terminalView, InputMethodManager.SHOW_IMPLICIT)
}
override fun shouldBackButtonBeMappedToEscape(): Boolean = false
override fun shouldUseCtrlSpaceWorkaround(): Boolean = false
override fun isTerminalViewSelected(): Boolean = true
override fun copyModeChanged(copyMode: Boolean) {}
override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean {
// 如果是回车键,且 Session 已经结束,则移除 Session 并关闭 Activity
if (keyCode == KeyEvent.KEYCODE_ENTER && session != null && !session.isRunning) {
finish()
return true
}
// 返回 false 交给 TerminalView 内部逻辑处理(发送给终端)
return false
}
override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean {
// 同上,不拦截抬起事件
return false
}
override fun onLongPress(event: MotionEvent?): Boolean = false
// 下面这些键位读取用于处理 Ctrl/Alt 组合键
// 可用于虚拟按键栏,在这里返回虚拟按键的状态(参考termux官方app)
override fun readControlKey(): Boolean = false
override fun readAltKey(): Boolean = false
override fun readShiftKey(): Boolean = false
override fun readFnKey(): Boolean = false
override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean {
// 返回 false 让 TerminalView 处理字符输入
return false
}
override fun shouldEnforceCharBasedInput(): Boolean = false
override fun onEmulatorSet() {}
override fun logError(tag: String?, message: String?) {}
override fun logWarn(tag: String?, message: String?) {}
override fun logInfo(tag: String?, message: String?) {}
override fun logDebug(tag: String?, message: String?) {}
override fun logVerbose(tag: String?, message: String?) {}
override fun logStackTraceWithMessage(tag: String?, message: String?, e: Exception?) {}
override fun logStackTrace(tag: String?, e: Exception?) {}
})
val workDir = File(filesDir, "radare2/bin").absolutePath
File(workDir).mkdirs()
val envs = mutableListOf<String>()
envs.add("LD_LIBRARY_PATH=${File(filesDir,"radare2/lib")}")
val session = TerminalSession(
"/system/bin/sh",
workDir,
null,
envs.toTypedArray(),
2000,
object : TerminalSessionClient {
override fun onTextChanged(changedSession: TerminalSession) {
// 通知 View 刷新内容
terminalView.onScreenUpdated()
}
override fun onTitleChanged(changedSession: TerminalSession) {}
override fun onSessionFinished(finishedSession: TerminalSession) {
if (finishedSession.exitStatus != 0) {
// 这里处理非正常退出
}
finish()
}
override fun onCopyTextToClipboard(session: TerminalSession, text: String?) {
// 实现复制
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("Terminal Output", text)
clipboard.setPrimaryClip(clip)
}
override fun onPasteTextFromClipboard(session: TerminalSession?) {
// 实现粘贴
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = clipboard.primaryClip
if (clip != null && clip.itemCount > 0) {
val pasteText = clip.getItemAt(0).coerceToText(this@TerminalActivity).toString()
session?.write(pasteText)
}
}
override fun onBell(session: TerminalSession) {}
override fun onColorsChanged(session: TerminalSession) {}
override fun onTerminalCursorStateChange(state: Boolean) {}
override fun setTerminalShellPid(session: TerminalSession, pid: Int) {}
override fun getTerminalCursorStyle(): Int = 0
override fun logError(tag: String?, message: String?) {}
override fun logWarn(tag: String?, message: String?) {}
override fun logInfo(tag: String?, message: String?) {}
override fun logDebug(tag: String?, message: String?) {}
override fun logVerbose(tag: String?, message: String?) {}
override fun logStackTraceWithMessage(tag: String?, message: String?, e: Exception?) {}
override fun logStackTrace(tag: String?, e: Exception?) {}
}
)
this.terminalSession = session
terminalView.attachSession(session)
// 启动时尝试获取焦点,方便物理键盘直接输入
terminalView.requestFocus()
}
}
效果


浙公网安备 33010602011771号