最近搞了一个擎云585的终端, 是个Kirin 990的机器, 真的是非常的垃圾啊. 寻思着既然是个ARM机器, 那用安卓应该会很棒吧? 发现在应用商店里有一个"移动运行环境(KMRE)". 遂安装试试. 然而安装不能用啊
背景
为什么我不能直接安装呢? 是因为我使用了docker-ce
. 系统自带的docker.io
版本比较老旧, 不能满足我的需求, 所以我安装了docker-ce
. 但是现在kmre
软件包就是依赖docker.io
, 那该咋办呢?
(BTW, 我搜了一下Kylin OS V10SP1是基于Ubuntu bionic的, 所以我使用了docker官方的Ubuntu bionic源就可以了:
deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu bionic stable
改造kmre
首先想到的是解包原版的kmre
并修改它的依赖.
# 注意这里是apt-get而不是apt, apt不带download命令
# 下载kmre软件包
apt-get download kmre
#解包
dpkg-deb -R kmre_2.3.19.0hw_arm64.deb kmre-mod
然后修改文件kmre-mod/DEBIAN/control文件, 把其中Depends
的docker.io给修改为docker-ce. 这里我除了修改依赖关系外, 我还修改了软件包的版本号: 从2.3.19.0hw修改为了2.3.19.1hw. 因为我发现如果不增加一个版本号的话, 自带的应用商店会一直提醒"更新kmre", 这样就不能在应用商店中安装Android应用了.
之后使用dpkg
命令重新打包即可.
# 重新打包
dpkg-deb --build kmre-mod kmre-fixed.deb
# 安装
sudo apt install ./kmre-fixed.deb
改造libkmre
按照上述方法安装了kmre
之后, 可以使用命令startapp com.android.settings
启动环境试试. 正常来说这里应该会启动一个安卓系统的"设置"程序.
但是到这一步还是会遇到一个问题, 就是当运行KMRE APK 安装器时会遇到报错: "麒麟移动运行环境未安装!请到软件商店安装移动环境"
查看项目的源代码, 在翻译文件开始定位报错的根源. 最终可以定位到是kylin-kmre-apk-installer调用了/usr/lib/libkmre.so中的is_android_env_installed
方法检测kmre.
继续查看libkmre.so的源代码, 原来在is_android_env_installed
里也检查了是否安装了docker.io
. 将该仓库克隆到本地, 之后将其中的is_deb_package_installed("docker.io")
全部给替换成(is_deb_package_installed("docker.io") || is_deb_package_installed("docker-ce"))
, 然后重新构建该项目.
# 构建前需要安装protobuf依赖, 包括protoc和libprotobuf-dev.
# 如果遇到依赖问题的话, 可以试试先把冲突的包给remove掉, 再逐步安装
make -j8
sudo make uninstall
# 这里可以修改Makefile, 把install指令相关的行给接触注释, 之后直接
#sudo make install
# 或者直接手工复制
sudo cp ./libkmre.so /usr/lib/libkmre.so && chmod +x /usr/lib/libkmre.so
之后再启动KMRE APK 安装器的时候报错就变成了"麒麟移动运行环境未启动,请到软件商店启动移动环境". 这时候直接启动一下"设置"就可以了
startapp com.android.settings
安装adb
本来安装adb
只需要一条命令sudo apt install adb
就可以了. 然而我这边安装的时候却遇到了依赖问题.
adb
依赖android-libadb
, 而android-libadb
依赖android-libbase=1:8.1.0+r23-5kylin2
. 但是不知道为什么, apt
说我的系统里已经安装了android-libbase=1:8.1.0+r23-5kylin2k0.2
.
我解决这个依赖问题就是先卸载再重新安装:
# 先卸载依赖, 每次卸载都看看它的影响有多大.
sudo apt remove kmre-apk-installer
sudo apt remove android-libadb
sudo apt remove android-libbase
sudo apt remove android-liblog
# 安装adb
sudo apt install adb
sudo apt install kmre-apk-installer
之后就可以正常使用了.
管理应用
本以为使用adb
就可以管理应用了, 然而执行adb devices
后发现竟然发现不到设备. emmmmmm. 查看了一下libkmre.so
的源代码, 发现可以用这个库来管理设备. 用ChatGPT帮忙生成了一份代码如下:
import ctypes
import json
import subprocess
import sys
import tkinter as tk
from tkinter import ttk, messagebox
LIB_PATH = "/usr/lib/libkmre.so"
class AppViewer(tk.Tk):
def __init__(self):
super().__init__()
self.title("App 列表查看器")
self.geometry("600x400")
self.lib = self.load_library()
self.app_list = self.get_app_list()
self.tooltip = None
self.last_iid = None
self.create_table()
def load_library(self):
try:
lib = ctypes.CDLL(LIB_PATH)
lib.get_installed_applist.restype = ctypes.c_char_p
lib.uninstall_app.argtypes = [ctypes.c_char_p]
lib.uninstall_app.restype = ctypes.c_int
return lib
except OSError:
messagebox.showerror("错误", "加载动态链接库 libkmre.so 失败")
self.destroy()
sys.exit(1)
def get_app_list(self):
try:
result = self.lib.get_installed_applist()
json_str = result.decode("utf-8")
return json.loads(json_str)
except Exception as e:
messagebox.showerror("错误", f"获取应用列表失败: {str(e)}")
self.destroy()
sys.exit(1)
def create_table(self):
columns = ("app_name", "version_name", "start", "uninstall")
self.tree = ttk.Treeview(self, columns=columns, show="headings")
self.tree.heading("app_name", text="应用名称")
self.tree.heading("version_name", text="版本号")
self.tree.heading("start", text="启动")
self.tree.heading("uninstall", text="卸载")
self.tree.column("app_name", width=250, anchor="center")
self.tree.column("version_name", width=100, anchor="center")
self.tree.column("start", width=60, anchor="center")
self.tree.column("uninstall", width=60, anchor="center")
self.tree.pack(fill=tk.BOTH, expand=True)
for i, app in enumerate(self.app_list):
self.tree.insert("", "end", iid=i, values=(app["app_name"], app["version_name"], "[▶ 启动]", "[🗑️ 卸载]"))
self.tree.bind("<Motion>", self.on_mouse_motion)
self.tree.bind("<Leave>", lambda e: self.hide_tooltip())
self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
def on_mouse_motion(self, event):
region = self.tree.identify("region", event.x, event.y)
if region != "cell":
self.hide_tooltip()
return
row_id = self.tree.identify_row(event.y)
if not row_id or row_id == self.last_iid:
return
self.last_iid = row_id
pkg = self.app_list[int(row_id)]["package_name"]
self.show_tooltip(pkg)
def on_tree_click(self, event):
row_id = self.tree.identify_row(event.y)
col = self.tree.identify_column(event.x)
if row_id:
if col == "#3": # 点击启动按钮
app = self.app_list[int(row_id)]
self.start_app(app["package_name"])
elif col == "#4": # 点击卸载按钮
app = self.app_list[int(row_id)]
self.confirm_uninstall(app["package_name"])
def show_tooltip(self, text):
self.hide_tooltip()
x = self.winfo_pointerx()
y = self.winfo_pointery()
self.tooltip = tk.Toplevel(self)
self.tooltip.wm_overrideredirect(True)
self.tooltip.geometry(f"+{x + 10}+{y + 10}")
label = tk.Label(self.tooltip, text=text, background="yellow", relief="solid", borderwidth=1)
label.pack()
def hide_tooltip(self):
if self.tooltip:
self.tooltip.destroy()
self.tooltip = None
self.last_iid = None
def start_app(self, package_name):
try:
subprocess.Popen(["startapp", package_name])
except Exception as e:
messagebox.showwarning("启动失败", f"无法启动 {package_name}:{str(e)}")
def confirm_uninstall(self, package_name):
# 弹出确认框
response = messagebox.askyesno("确认卸载", f"确定要卸载 {package_name} 吗?")
if response:
self.uninstall_app(package_name)
def uninstall_app(self, package_name):
try:
result = self.lib.uninstall_app(package_name.encode("utf-8"))
if result == 1:
messagebox.showinfo("成功", f"{package_name} 卸载成功")
elif result == -1:
messagebox.showerror("错误", f"{package_name} 卸载失败:未指明的原因")
elif result == -2:
messagebox.showerror("错误", f"{package_name} 卸载失败:设备管理器问题")
elif result == -3:
messagebox.showerror("错误", f"{package_name} 卸载失败:用户受限")
elif result == -4:
messagebox.showerror("错误", f"{package_name} 卸载失败:设备所有者已阻止")
elif result == -5:
messagebox.showerror("错误", f"{package_name} 卸载失败:操作中止")
elif result == -6:
messagebox.showerror("错误", f"{package_name} 卸载失败:共享库依赖")
elif result == -7:
messagebox.showerror("错误", f"{package_name} 卸载失败:其他原因")
else:
messagebox.showerror("错误", f"{package_name} 卸载失败:未知错误")
except Exception as e:
messagebox.showerror("错误", f"卸载过程中发生错误:{str(e)}")
if __name__ == "__main__":
app = AppViewer()
app.mainloop()