import tkinter as tk
from tkinter import scrolledtext, ttk, filedialog, messagebox
import subprocess
import threading
import os
import datetime
class ADBBackupTool:
def __init__(self, root):
self.root = root
root.title("晶晨/海思ADB备份工具")
def center_window(width, height, y_offset=0):
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2 + y_offset
return f"{width}x{height}+{x}+{y}"
root.geometry(center_window(1200, 700, -50))
self.context_menu = tk.Menu(root, tearoff=0)
self.context_menu.add_command(label="复制", command=lambda: self.copy_text())
self.context_menu.add_command(label="粘贴", command=lambda: self.paste_text())
self.context_menu.add_separator()
self.context_menu.add_command(label="全选", command=lambda: self.select_all())
self.main_frame = tk.Frame(root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.main_frame.columnconfigure(0, weight=1)
self.main_frame.rowconfigure(0, weight=1)
RIGHT_PANE_WIDTH = 300
self.cmd_frame = tk.Frame(self.main_frame)
self.cmd_frame.grid(row=0, column=0, sticky="nsew")
self.cmd_label = tk.Label(self.cmd_frame, text="命令窗口", font=('Arial', 10, 'bold'))
self.cmd_label.pack(anchor='w')
self.cmd_text = scrolledtext.ScrolledText(
self.cmd_frame,
wrap=tk.WORD,
font=('Consolas', 10),
bg='black',
fg='white',
insertbackground='white'
)
self.cmd_text.pack(fill=tk.BOTH, expand=True)
self.cmd_text.bind("<Button-3>", self.show_context_menu)
self.function_frame = tk.Frame(self.main_frame, width=RIGHT_PANE_WIDTH)
self.function_frame.grid(row=0, column=1, sticky="nsew", padx=(5, 0))
self.function_frame.pack_propagate(False)
self.conn_frame = tk.LabelFrame(self.function_frame, text="连接设置", padx=5, pady=5)
self.conn_frame.pack(fill=tk.X, pady=(0, 5))
ip_port_frame = tk.Frame(self.conn_frame)
ip_port_frame.pack(fill=tk.X, pady=2)
tk.Label(ip_port_frame, text="IP地址:").pack(side=tk.LEFT, padx=(0, 5))
self.ip_entry = tk.Entry(ip_port_frame, width=15)
self.ip_entry.pack(side=tk.LEFT, padx=(0, 10))
self.ip_entry.insert(0, "192.168.31.200")
self.ip_entry.bind("<Button-3>", self.show_context_menu)
tk.Label(ip_port_frame, text="端口号:").pack(side=tk.LEFT, padx=(0, 5))
self.port_entry = tk.Entry(ip_port_frame, width=9)
self.port_entry.pack(side=tk.LEFT)
self.port_entry.insert(0, "5555")
self.port_entry.bind("<Button-3>", self.show_context_menu)
self.connect_btn = tk.Button(
self.conn_frame,
text="连接设备",
command=self.connect_device,
bg="#4CAF50",
fg="white"
)
self.connect_btn.pack(fill=tk.X, pady=(5, 0))
self.query_frame = tk.LabelFrame(self.function_frame, text="快速查询", padx=5, pady=5)
self.query_frame.pack(fill=tk.X, pady=(0, 5))
self.cmd_var = tk.StringVar()
self.cmd_combobox = ttk.Combobox(
self.query_frame,
textvariable=self.cmd_var,
values=["请选择命令", "晶晨分区获取", "晶晨dtb备份到桌面", "海思分区获取","海思分区获取2", "保存海思分区表", "U盘路径查询"],
state="readonly",
height=5,
width=25
)
self.cmd_combobox.pack(fill=tk.X, pady=2)
self.cmd_combobox.current(0) # 默认选中"请选择命令"
self.cmd_combobox.bind("<<ComboboxSelected>>", self.update_cmd_entry)
self.cmd_mapping = {
"晶晨分区获取": "adb shell ls /dev/block",
"晶晨dtb备份到桌面": 'adb pull "/dev/dtb" "C:\\Users\\Administrator\\Desktop\\dtb"',
"海思分区获取": 'adb shell "cd /dev/block/platform/soc/by-name && ls -l"',
"海思分区获取2": "adb shell cat /proc/partitions ",
"保存海思分区表": "save_hisilicon_partitions",
"U盘路径查询": "adb shell df"
}
self.cmd_entry = tk.Entry(self.query_frame)
self.cmd_entry.pack(fill=tk.X, pady=2)
self.cmd_entry.insert(0, "可手动输入")
self.cmd_entry.bind("<Button-3>", self.show_context_menu)
self.cmd_entry.bind("<FocusIn>", self.on_entry_focus_in)
self.cmd_entry.bind("<Key>", self.on_entry_key_press)
self.run_cmd_btn = tk.Button(
self.query_frame,
text="执行命令",
command=self.execute_custom_cmd,
bg="#2196F3",
fg="white"
)
self.run_cmd_btn.pack(fill=tk.X, pady=(0, 5))
self.partition_select_frame = tk.LabelFrame(self.function_frame, text="选择要备份的分区", padx=5, pady=5)
self.partition_select_frame.pack(fill=tk.BOTH, expand=True)
self.partition_container = tk.Frame(self.partition_select_frame)
self.partition_container.pack(fill=tk.BOTH, expand=True)
self.partition_scrollbar = ttk.Scrollbar(self.partition_container)
self.partition_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.partition_canvas = tk.Canvas(
self.partition_container,
yscrollcommand=self.partition_scrollbar.set,
bg='#f0f0f0',
highlightthickness=0
)
self.partition_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.partition_scrollbar.config(command=self.partition_canvas.yview)
self.partition_list_frame = tk.Frame(self.partition_canvas, bg='#f0f0f0')
self.partition_canvas.create_window((0, 0), window=self.partition_list_frame, anchor="nw")
self.partition_list_frame.bind(
"<Configure>",
lambda e: self.partition_canvas.configure(
scrollregion=self.partition_canvas.bbox("all")
)
)
self.partition_vars = {}
self.chip_type = None
self.select_all_var = tk.BooleanVar()
self.select_all_check = tk.Checkbutton(
self.partition_list_frame,
text="全选",
variable=self.select_all_var,
command=self.toggle_select_all,
bg='#f0f0f0',
anchor='w',
font=('Arial', 10)
)
self.select_all_check.pack(fill=tk.X, pady=2, padx=5)
self.backup_btn = tk.Button(
self.function_frame,
text="备份选中分区",
command=self.start_backup,
bg="#9C27B0",
fg="white"
)
self.backup_btn.pack(fill=tk.X, pady=(5, 0))
self.status_bar = tk.Label(root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
self.check_adb()
def save_hisilicon_partitions(self):
"""保存海思分区表到桌面"""
if not hasattr(self, 'chip_type') or self.chip_type != "海思":
messagebox.showerror("错误", "请先获取海思分区表")
return
desktop_path = "C:\\Users\\Administrator\\Desktop"
save_path = os.path.join(desktop_path, "海思分区表.txt")
try:
content = self.cmd_text.get("1.0", tk.END)
lines = content.split('\n')
partition_lines = [line for line in lines if "->" in line]
if not partition_lines:
messagebox.showerror("错误", "未找到分区信息")
return
with open(save_path, 'w', encoding='utf-8') as f:
f.write("海思设备分区表\n")
f.write(f"生成时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("="*50 + "\n")
for line in partition_lines:
f.write(line + "\n")
self.append_cmd_output(f"分区表已保存到桌面: 海思分区表.txt")
messagebox.showinfo("成功", "分区表已保存到桌面:\n海思分区表.txt")
except Exception as e:
self.append_cmd_output(f"保存分区表出错: {str(e)}")
messagebox.showerror("错误", f"保存分区表出错:\n{str(e)}")
def on_entry_focus_in(self, event):
if self.cmd_entry.get() == "请选择命令或手动输入":
self.cmd_entry.delete(0, tk.END)
def on_entry_key_press(self, event):
if self.cmd_entry.get() == "请选择命令或手动输入":
self.cmd_entry.delete(0, tk.END)
def update_cmd_entry(self, event):
selected = self.cmd_combobox.get()
self.cmd_entry.delete(0, tk.END)
self.cmd_entry.insert(0, self.cmd_mapping.get(selected, ""))
def show_context_menu(self, event):
self.current_widget = event.widget
try:
self.current_widget.focus_set()
self.context_menu.post(event.x_root, event.y_root)
except Exception as e:
print(f"显示右键菜单出错: {e}")
return "break"
def copy_text(self):
try:
if isinstance(self.current_widget, (tk.Entry, scrolledtext.ScrolledText)):
self.current_widget.event_generate("<<Copy>>")
except Exception as e:
print(f"复制出错: {e}")
def paste_text(self):
try:
if isinstance(self.current_widget, (tk.Entry, scrolledtext.ScrolledText)):
self.current_widget.event_generate("<<Paste>>")
except Exception as e:
print(f"粘贴出错: {e}")
def select_all(self):
try:
if isinstance(self.current_widget, tk.Entry):
self.current_widget.select_range(0, tk.END)
self.current_widget.icursor(tk.END)
elif isinstance(self.current_widget, scrolledtext.ScrolledText):
self.current_widget.tag_add(tk.SEL, "1.0", tk.END)
self.current_widget.mark_set(tk.INSERT, "1.0")
self.current_widget.see(tk.INSERT)
except Exception as e:
print(f"全选出错: {e}")
def check_adb(self):
try:
result = subprocess.run("adb version", shell=True, capture_output=True, text=True)
if "Android Debug Bridge" in result.stdout:
self.append_cmd_output("ADB已就绪\n" + result.stdout.split('\n')[0])
else:
self.append_cmd_output("错误: ADB未正确安装")
messagebox.showerror("错误", "ADB未正确安装,请先安装ADB工具")
except Exception as e:
self.append_cmd_output(f"检查ADB出错: {str(e)}")
messagebox.showerror("错误", f"检查ADB出错: {str(e)}")
def execute_custom_cmd(self):
cmd = self.cmd_entry.get()
if not cmd:
self.append_cmd_output("错误: 请输入要执行的命令")
return
if cmd == "save_hisilicon_partitions":
self.save_hisilicon_partitions()
return
self.append_cmd_output(f"执行命令: {cmd}")
threading.Thread(target=self._execute_cmd, args=(cmd,), daemon=True).start()
def _execute_cmd(self, cmd):
try:
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.root.after(0, self.append_cmd_output, output.strip())
stderr = process.stderr.read()
if stderr:
self.root.after(0, self.append_cmd_output, f"错误:\n{stderr.strip()}")
self.root.after(0, self.append_cmd_output, f"命令执行完成,返回值: {process.returncode}")
if "ls /dev/block" in cmd or "by-name" in cmd:
self._parse_partitions(cmd)
except Exception as e:
self.root.after(0, self.append_cmd_output, f"执行命令出错: {str(e)}")
def _parse_partitions(self, cmd):
for widget in self.partition_list_frame.winfo_children()[1:]:
widget.destroy()
self.partition_vars.clear()
partitions = []
if "by-name" in cmd:
self.chip_type = "海思"
self.append_cmd_output("检测到海思设备分区表")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
for line in result.stdout.split('\n'):
if "->" in line:
part_name = line.split("->")[-1].split("/")[-1].strip()
if part_name:
partitions.append(part_name)
else:
self.chip_type = "晶晨"
self.append_cmd_output("检测到晶晨设备分区表")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
for line in result.stdout.split('\n'):
if line and not line.startswith("total") and not line.startswith("ls:"):
part_name = line.split()[-1]
if part_name and part_name not in ["", "by-name", "platform"]:
partitions.append(part_name)
if partitions:
self._display_partitions(partitions)
self.append_cmd_output("分区列表已更新,请在右侧选择要备份的分区")
else:
self.append_cmd_output("未找到分区信息")
def connect_device(self):
ip = self.ip_entry.get()
port = self.port_entry.get()
if not ip or not port:
self.append_cmd_output("错误: 请输入IP地址和端口号")
return
self.append_cmd_output(f"尝试连接设备: {ip}:{port}")
self.connect_btn.config(state=tk.DISABLED, text="连接中...")
threading.Thread(target=self._adb_connect, args=(ip, port), daemon=True).start()
def _adb_connect(self, ip, port):
try:
cmd = f"adb connect {ip}:{port}"
self.append_cmd_output(f"执行: {cmd}")
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.root.after(0, self.append_cmd_output, output.strip())
stderr = process.stderr.read()
if stderr:
self.root.after(0, self.append_cmd_output, f"错误:\n{stderr.strip()}")
if process.returncode == 0:
self.root.after(0, self.status_bar.config, {"text": f"已连接: {ip}:{port}"})
else:
self.root.after(0, self.append_cmd_output, "连接失败!")
except Exception as e:
self.root.after(0, self.append_cmd_output, f"连接出错: {str(e)}")
finally:
self.root.after(0, self.connect_btn.config, {"state": tk.NORMAL, "text": "连接设备"})
def _display_partitions(self, partitions):
for part in partitions:
self.partition_vars[part] = tk.BooleanVar()
cb = tk.Checkbutton(
self.partition_list_frame,
text=part,
variable=self.partition_vars[part],
anchor='w',
bg='#f0f0f0',
font=('Arial', 10)
)
cb.pack(fill=tk.X, pady=2, padx=5)
self.partition_list_frame.update_idletasks()
self.partition_canvas.config(scrollregion=self.partition_canvas.bbox("all"))
self.partition_canvas.bind_all("<MouseWheel>",
lambda event: self.partition_canvas.yview_scroll(int(-1 * (event.delta / 120)),
"units"))
def toggle_select_all(self):
select_all = self.select_all_var.get()
for var in self.partition_vars.values():
var.set(select_all)
def start_backup(self):
selected_partitions = [part for part, var in self.partition_vars.items() if var.get()]
if not selected_partitions:
messagebox.showwarning("警告", "请至少选择一个分区进行备份")
return
backup_dir = filedialog.askdirectory(title="选择备份保存目录")
if not backup_dir:
self.append_cmd_output("备份已取消")
return
self.append_cmd_output(f"将备份保存到: {backup_dir}")
self.backup_btn.config(state=tk.DISABLED, text="备份中,请稍等...")
threading.Thread(
target=self._perform_backup,
args=(selected_partitions, backup_dir),
daemon=True
).start()
def _perform_backup(self, partitions, backup_dir):
try:
self.append_cmd_output("准备备份环境...")
self._execute_adb_command("adb shell \"rm -rf /sdcard/backup\"")
self._execute_adb_command("adb shell \"mkdir -p /sdcard/backup\"")
for i, partition in enumerate(partitions):
progress = (i + 1) / len(partitions) * 100
self.append_cmd_output(f"正在备份 {partition} ({i + 1}/{len(partitions)}) - {progress:.1f}%")
backup_cmd = f"adb shell \"dd if=/dev/block/{partition} | gzip -c > /sdcard/backup/{partition}.img.gz\""
self._execute_adb_command(backup_cmd)
self.append_cmd_output("正在从设备下载备份文件...")
pull_cmd = f"adb pull /sdcard/backup \"{backup_dir}\""
self._execute_adb_command(pull_cmd)
self._execute_adb_command("adb shell \"rm -rf /sdcard/backup\"")
self.append_cmd_output(f"备份完成! 文件已保存到: {backup_dir}")
messagebox.showinfo("完成", f"备份已完成,文件保存在:\n{backup_dir}")
except Exception as e:
self.append_cmd_output(f"备份出错: {str(e)}")
messagebox.showerror("错误", f"备份过程中出错:\n{str(e)}")
finally:
self.root.after(0, self.backup_btn.config, {"state": tk.NORMAL, "text": "备份选中分区"})
def _execute_adb_command(self, cmd):
self.append_cmd_output(f"执行: {cmd}")
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.root.after(0, self.append_cmd_output, output.strip())
stderr = process.stderr.read()
if stderr:
self.root.after(0, self.append_cmd_output, f"错误:\n{stderr.strip()}")
if process.returncode != 0:
raise Exception(f"命令执行失败: {cmd}")
def append_cmd_output(self, text):
self.cmd_text.insert(tk.END, text + "\n")
self.cmd_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = ADBBackupTool(root)
root.mainloop() 在这个基础上曾加http下载 sparsege格式 这个先取消