1. 背景
由于公司的办公电脑也用于深度学习的模型训练,该电脑就不能同时大型软件(连Chrome也不能多开标签),避免影响训练。于是就想把新手机“红米Note 12 Turbo”利用起来,处理一些“轻办公”的工作。
找到方案是:
- 方案1:Android系统利用
chroot
安装完整Linux系统,运行基于X11的图形化软件(例如Chrome),PC端运行X server
。 - 方案2:手机运行Android桌面模式,并投屏到PC。
由于方案1运行不流畅、不能方便利用GPU加速、软件兼容不佳等,只能放弃。方案2整理了一下,能达到比较高的实用性。
2. 原理
据说从Android 10开始,Android系统内置了桌面模式。该模式下,App可以自由拖动显示位置,并调整窗口大小(跟PC操作系统一样)。
有趣的是,“开发者选项”里开启了“模拟辅助显示设备”,就可以启用桌面模式,而且基本不影响手机正常显示模式。可以认为同一个Android手机上同时运行“手机模式”和“桌面模式”,App能在这两个模式之间显示。
最后,利用scrcpy
把“模拟辅助显示设备”投屏到PC端,就可以使用大屏幕显示“桌面模式”,并且利用鼠标键盘进行输入。
据说手机直连显示器(需要手机直接输出HDMI功能),或者无线连接Miracast,都可以显示“桌面模式”。但是手上没有相关设备,不能验证。
3. 配置
3.1. PC端软件
3.2. Android必要配置
3.3. 启动命令
在PC端的命令窗口执行以下命令即可启动。Linux Shell脚本参考如下:
#!/bin/bash
CMD_ADB=/opt/android-sdk/platform-tools/adb
CMD_SCRCPY=/opt/scrcpy/scrcpy
PHONE_NAME=MyPhone
# Turn on Simulate secondary displays of android phone
"${CMD_ADB}" shell settings put global overlay_display_devices 1920x1080/180
# Get display-id of Simulate secondary displays
DISPLAY_ID=$("${CMD_SCRCPY}" --list-displays | "${CMD_ADB}" shell "grep -o 'display-id=[1-9][0-9]*' | sed 's/display-id=\([1-9][0-9]*\)/\1/'")
echo Display ID: ${DISPLAY_ID}
# Cast screen
"${CMD_SCRCPY}" --display-id=${DISPLAY_ID} --keyboard=uhid --mouse=sdk --no-audio --power-off-on-close --shortcut-mod="lctrl,rctrl" --stay-awake --turn-screen-off --window-title="${PHONE_NAME} - Android Desktop Mode" --window-x=0 --window-y=25
# Turn off Simulate secondary displays of android phone
${CMD_ADB} shell settings put global overlay_display_devices null
说明:
Windows的CMD批处理脚本,参考如下:
@echo off
SETLOCAL ENABLEEXTENSIONS
rem Params
rem %1: SERIAL|HOST[:PORT]
set P_SERIAL=%~1
set PHONE_NAME=MyPhone
set CMD_ADB="D:\Program Files\android-sdk\platform-tools\adb.exe"
set ADB_SERIAL=
set CMD_SCRCPY="D:\Program Files\scrcpy\scrcpy.exe"
set TEMP_FILE="C:\Users\%username%\AppData\Local\Temp\scrcpy_display_id_%RANDOM%.txt"
call :func_main
goto end
rem Log message. If error, do exit.
rem Params
rem %1: log level
rem %2: message
:func_log
set lv_index=%~1
if "%lv_index%" == "i" (
set lv=INFO
) else if "%lv_index%" == "w" (
set lv=WARN
) else if "%lv_index%" == "e" (
set lv=ERR
) else (
set lv=NONE
)
echo [%lv%] %~2
if "%lv_index%" == "e" (
goto end
)
goto :eof
:func_init
if "%P_SERIAL%" == "" goto :eof
rem Do connect
call :func_connect is_connected
if "%is_connected%" == "y" (
goto :eof
)
call :func_log w,"Connect failed. SERIAL or HOST is invalid, or need pairing"
rem Do pair
set /p need_pair=Need to pair device? Enter "y" for yes, otherwise exit:
if "%need_pair%" neq "y" (
echo Exit
goto end
)
call :func_pair is_paired
if "%is_paired%" neq "y" (
call :func_log e,"Pair failed, exit"
) else (
call :func_log i,"Pair succeed"
)
call :func_connect is_connected
if "%is_connected%" neq "y" (
call :func_log e,"Connect failed, exit"
) else (
call :func_log i,"Connect succeed"
)
goto :eof
:func_check_serialno
set result=n
for /f "delims=" %%i in ('%CMD_ADB% -s "%P_SERIAL%" get-serialno') do set dev_serialno=%%i
if "%P_SERIAL%" == "%dev_serialno%" (
set result=y
)
set %~1=%result%
goto :eof
:func_connect
set result=n
for /f "delims=" %%i in ('%CMD_ADB% connect "%P_SERIAL%"') do set dev_connect=%%i
if "%dev_connect:~0,9%" == "connected" (
set result=y
) else if "%dev_connect:~0,17%" == "already connected" (
set result=y
)
set ADB_SERIAL=-s %P_SERIAL%
set %~1=%result%
goto :eof
:func_pair
set result=n
echo Enter "HOST[:PORT]" of the paired device: & set /p pair_host=
echo Enter "PAIRING CODE": & set /p pair_code=
for /f "delims=" %%i in ('%CMD_ADB% pair "%pair_host%" %pair_code%') do set dev_pair=%%i
echo dev_pair: %dev_pair%
if "%dev_pair:~0,19%" == "Successfully paired" (
set result=y
)
set %~1=%result%
goto :eof
rem main function
:func_main
call :func_init
rem Turn on auxiliary display device
%CMD_ADB% %ADB_SERIAL% shell settings put global overlay_display_devices 1920x1080/180
rem Get display id of auxiliary display device
%CMD_SCRCPY% %ADB_SERIAL% --list-displays | %CMD_ADB% %ADB_SERIAL% shell "grep -o 'display-id=[1-9][0-9]*' | sed 's/display-id=\([1-9][0-9]*\)/\1/'" > %TEMP_FILE%
set /p DISPLAY_ID=<%TEMP_FILE%
del %TEMP_FILE%
call :func_log i,"Display id: %DISPLAY_ID%"
rem adb shell am start-activity --display %DISPLAY_ID% cu.axel.smartdock/cu.axel.smartdock.activities.MainActivity
rem adb shell am stack list | adb shell "grep 'cu.axel.smartdock/cu.axel.smartdock.activities.MainActivity' | sed 's/^[[:space:]]*taskId=\([1-9][0-9]*\).*/\1/i'"
rem Run scrcpy
%CMD_SCRCPY% %ADB_SERIAL% --display-id=%DISPLAY_ID% --keyboard=uhid --mouse=sdk --no-audio --power-off-on-close --shortcut-mod="lctrl,rctrl" --stay-awake --turn-screen-off --window-title="%PHONE_NAME% - Android Desktop Mode" --window-x=0 --window-y=25
rem Turn off auxiliary display device
%CMD_ADB% %ADB_SERIAL% shell settings put global overlay_display_devices null
goto :eof
:end