android studio启动时报错:Unable to create Debug Bridge

解决酷狗占用adb端口问题
本文介绍了解决酷狗音乐kab.exe进程占用adb服务端口的问题,导致无法创建调试桥接的情况。通常只需结束酷狗相关进程即可恢复正常。
Unable to create Debug Bridge: Unable to start adb server: error: cannot parse version string: kg01 'D:\java\android\SDK\platform-tools\adb.exe,start-server' failed -- run manually if necessary

这里写图片描述
一般是因为酷狗的kab.exe占用了服务端口,把进程停止即可

这里写图片描述

给我完整的 测试好一定 要能下载 是在搞不懂import tkinter as tk from tkinter import ttk, messagebox, scrolledtext, filedialog import threading import subprocess import os import time import requests from requests.exceptions import RequestException class DeviceBackupTool: def __init__(self, root): self.root = root self.root.title("设备备份与下载工具") self.root.geometry("900x600") self.root.minsize(800, 500) # 设备状态 self.is_connected = False self.current_device = None # 下载状态 self.downloading = False # 创建UI self.setup_ui() # 检查ADB是否在环境变量中 self.adb_path = self._find_adb() # 更新ADB状态UI self._update_adb_status_ui() def _find_adb(self): """查找环境变量中的ADB路径""" try: self.log("正在检查ADB环境变量...") # 尝试直接执行adb version命令 result = subprocess.run( "adb version", shell=True, capture_output=True, text=True, timeout=10 ) if result.returncode == 0 and "Android Debug Bridge" in result.stdout: self.log("找到ADB环境变量") self.log(f"ADB版本: {result.stdout.strip()}") # 尝试获取ADB的完整路径 which_result = subprocess.run( "where adb" if os.name == "nt" else "which adb", shell=True, capture_output=True, text=True, timeout=10 ) if which_result.returncode == 0: adb_full_path = which_result.stdout.strip().splitlines()[0] self.log(f"ADB完整路径: {adb_full_path}") return "adb" # 使用环境变量中的adb else: self.log("无法获取ADB完整路径,但ADB命令可用") return "adb" else: self.log("执行'adb version'失败") self.log(f"标准输出: {result.stdout.strip()}") self.log(f"错误输出: {result.stderr.strip()}") # 备选方案:检查常见的ADB安装位置 common_locations = [ os.path.join(os.environ.get("LOCALAPPDATA", ""), "Android", "Sdk", "platform-tools", "adb.exe"), os.path.join(os.environ.get("ProgramFiles", ""), "Android", "Android Studio", "sdk", "platform-tools", "adb.exe"), "/usr/bin/adb", "/usr/local/bin/adb" ] for location in common_locations: if os.path.exists(location): self.log(f"在常见位置找到ADB: {location}") return location self.log("未找到ADB环境变量,请确保ADB已安装并添加到PATH中") return None except Exception as e: self.log(f"检查ADB时出错: {str(e)}") # 备选方案:尝试解析PATH环境变量 try: path_dirs = os.environ.get("PATH", "").split(os.pathsep) self.log("正在解析PATH环境变量...") for dir_path in path_dirs: adb_path = os.path.join(dir_path, "adb.exe" if os.name == "nt" else "adb") if os.path.exists(adb_path): self.log(f"在PATH中找到ADB: {adb_path}") return adb_path self.log("在PATH环境变量中未找到ADB") except Exception as path_e: self.log(f"解析PATH环境变量时出错: {str(path_e)}") return None def _update_adb_status_ui(self): """更新ADB状态UI显示""" adb_status = "已找到" if self.adb_path else "未找到" adb_color = "green" if self.adb_path else "red" self.adb_status_label.config( text=self.adb_path or "未找到ADB,请检查环境变量", foreground=adb_color ) self.connect_btn.config( state=tk.NORMAL if self.adb_path else tk.DISABLED ) def setup_ui(self): """创建用户界面""" # 主框架 main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # ADB状态区域 adb_frame = ttk.LabelFrame(main_frame, text="ADB状态", padding=10) adb_frame.pack(fill=tk.X, pady=5) ttk.Label(adb_frame, text="ADB路径:").grid(row=0, column=0, sticky=tk.W, padx=5) self.adb_status_label = ttk.Label( adb_frame, text="正在检查ADB...", foreground="blue" ) self.adb_status_label.grid(row=0, column=1, sticky=tk.W, padx=5) # 设备连接区域 device_frame = ttk.LabelFrame(main_frame, text="设备连接", padding=10) device_frame.pack(fill=tk.X, pady=5) ttk.Label(device_frame, text="设备IP:").grid(row=0, column=0, sticky=tk.W, padx=5) self.ip_entry = ttk.Entry(device_frame, width=15) self.ip_entry.grid(row=0, column=1, padx=5) self.ip_entry.insert(0, "192.168.31.38") ttk.Label(device_frame, text="端口:").grid(row=0, column=2, sticky=tk.W, padx=5) self.port_entry = ttk.Entry(device_frame, width=8) self.port_entry.grid(row=0, column=3, padx=5) self.port_entry.insert(0, "5555") self.connect_btn = ttk.Button( device_frame, text="连接设备", command=self.connect_device, width=12, state=tk.DISABLED ) self.connect_btn.grid(row=0, column=4, padx=10) self.status_var = tk.StringVar(value="未连接") ttk.Label(device_frame, textvariable=self.status_var).grid(row=0, column=5, padx=10, sticky=tk.W) # 文件管理区域 file_frame = ttk.LabelFrame(main_frame, text="文件管理", padding=10) file_frame.pack(fill=tk.BOTH, expand=True, pady=5) # 左侧:设备文件列表 left_frame = ttk.Frame(file_frame) left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) ttk.Label(left_frame, text="设备文件列表:").pack(anchor=tk.W, padx=5, pady=5) # 文件列表和滚动条 self.file_listbox = tk.Listbox(left_frame, width=40, height=15) self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 0)) scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, command=self.file_listbox.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.file_listbox.config(yscrollcommand=scrollbar.set) # 刷新和选择按钮 btn_frame = ttk.Frame(left_frame) btn_frame.pack(fill=tk.X, pady=5) self.refresh_btn = ttk.Button( btn_frame, text="刷新文件列表", command=self.refresh_file_list, state=tk.DISABLED ) self.refresh_btn.pack(side=tk.LEFT, padx=5) self.select_btn = ttk.Button( btn_frame, text="选择文件", command=self.select_file, state=tk.DISABLED ) self.select_btn.pack(side=tk.LEFT, padx=5) # 右侧:下载设置 right_frame = ttk.LabelFrame(file_frame, text="下载设置", padding=10) right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5) ttk.Label(right_frame, text="选择的文件:").grid(row=0, column=0, sticky=tk.W, pady=5) self.selected_file_var = tk.StringVar(value="未选择文件") ttk.Label(right_frame, textvariable=self.selected_file_var).grid(row=0, column=1, sticky=tk.W, pady=5) ttk.Label(right_frame, text="保存路径:").grid(row=1, column=0, sticky=tk.W, pady=5) self.save_path_var = tk.StringVar(value=os.path.join(os.path.expanduser("~"), "Downloads")) ttk.Entry(right_frame, textvariable=self.save_path_var, width=30).grid(row=1, column=1, sticky=tk.W, pady=5) ttk.Button( right_frame, text="浏览...", command=self.browse_save_path ).grid(row=1, column=2, padx=5, pady=5) self.download_btn = ttk.Button( right_frame, text="开始下载", command=self.start_download, state=tk.DISABLED, width=15 ) self.download_btn.grid(row=4, column=0, columnspan=3, pady=10) # 下载进度区域 progress_frame = ttk.LabelFrame(main_frame, text="下载进度", padding=10) progress_frame.pack(fill=tk.X, pady=5) self.progress_var = tk.DoubleVar(value=0) progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100) progress_bar.pack(fill=tk.X, pady=5) self.download_status_var = tk.StringVar(value="就绪") ttk.Label(progress_frame, textvariable=self.download_status_var).pack(anchor=tk.W, pady=5) # 日志区域 log_notebook = ttk.Notebook(main_frame) log_notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 操作日志 self.log_text = scrolledtext.ScrolledText(width=80, height=8, wrap=tk.WORD) self.log_text.config(state=tk.DISABLED) log_notebook.add(self.log_text, text="操作日志") # ADB命令日志 self.adb_log_text = scrolledtext.ScrolledText(width=80, height=8, wrap=tk.WORD) self.adb_log_text.config(state=tk.DISABLED) log_notebook.add(self.adb_log_text, text="ADB命令日志") def log(self, message, is_adb=False): """添加日志信息""" timestamp = time.strftime("%H:%M:%S") # 确保UI已初始化 if not hasattr(self, 'log_text') or not hasattr(self, 'adb_log_text'): return if is_adb: self.adb_log_text.config(state=tk.NORMAL) self.adb_log_text.insert(tk.END, f"[{timestamp}] {message}\n") self.adb_log_text.see(tk.END) self.adb_log_text.config(state=tk.DISABLED) else: self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) def browse_save_path(self): """浏览并选择保存路径""" path = filedialog.askdirectory(title="选择保存位置") if path: self.save_path_var.set(path) def connect_device(self): """连接到设备""" if self.is_connected: self.disconnect_device() return if not self.adb_path: messagebox.showerror("错误", "未找到ADB,请检查环境变量", parent=self.root) return ip = self.ip_entry.get().strip() port = self.port_entry.get().strip() if not ip or not port: messagebox.showerror("错误", "请输入设备IP和端口", parent=self.root) return self.log(f"尝试连接到设备: {ip}:{port}") self.connect_btn.config(state=tk.DISABLED, text="连接中...") self.status_var.set("连接中...") threading.Thread(target=self._connect_device_thread, args=(ip, port), daemon=True).start() def _connect_device_thread(self, ip, port): """在后台线程中执行设备连接""" try: # 执行ADB连接命令 command = f"{self.adb_path} connect {ip}:{port}" self.log(command, is_adb=True) result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=10 ) if result.returncode != 0 or "unable to connect" in result.stdout.lower(): raise Exception(f"连接失败: {result.stdout.strip()}") # 验证设备是否真的连接成功 devices_command = f"{self.adb_path} devices" self.log(devices_command, is_adb=True) devices_output = subprocess.check_output(devices_command, shell=True, text=True) if f"{ip}:{port}" not in devices_output: raise Exception("设备未成功连接") self.is_connected = True self.current_device = f"{ip}:{port}" self.root.after(0, lambda: self._on_device_connected()) except Exception as e: error_msg = str(e) self.log(f"连接失败: {error_msg}") self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"连接失败: {msg}", parent=self.root)) self.root.after(0, lambda: self.status_var.set("连接失败")) finally: self.root.after(0, lambda: self.connect_btn.config( state=tk.NORMAL, text="断开连接" if self.is_connected else "连接设备" )) def _on_device_connected(self): """设备连接成功后的处理""" self.status_var.set(f"已连接: {self.current_device}") self.log(f"设备连接成功: {self.current_device}") # 启用相关按钮 self.refresh_btn.config(state=tk.NORMAL) # 尝试获取设备文件列表 self.refresh_file_list() def disconnect_device(self): """断开与设备的连接""" if not self.is_connected: return try: command = f"{self.adb_path} disconnect {self.current_device}" self.log(command, is_adb=True) subprocess.run(command, shell=True, capture_output=True) self.log(f"已断开与设备的连接: {self.current_device}") except Exception as e: self.log(f"断开连接时出错: {str(e)}") self.is_connected = False self.current_device = None self.status_var.set("未连接") # 清空文件列表 self.file_listbox.delete(0, tk.END) self.selected_file_var.set("未选择文件") # 禁用相关按钮 self.refresh_btn.config(state=tk.DISABLED) self.select_btn.config(state=tk.DISABLED) self.download_btn.config(state=tk.DISABLED) def refresh_file_list(self): """刷新设备上的文件列表""" if not self.is_connected: messagebox.showwarning("提示", "请先连接设备", parent=self.root) return self.log("正在获取设备文件列表...") self.file_listbox.delete(0, tk.END) self.select_btn.config(state=tk.DISABLED) self.download_btn.config(state=tk.DISABLED) self.selected_file_var.set("未选择文件") threading.Thread(target=self._refresh_file_list_thread, daemon=True).start() def _refresh_file_list_thread(self): """在后台线程中刷新文件列表""" try: # 检查设备上的备份目录是否存在 check_command = f"{self.adb_path} -s {self.current_device} shell ls /data/local/update" self.log(check_command, is_adb=True) result = subprocess.run( check_command, shell=True, capture_output=True, text=True, timeout=10 ) if result.returncode != 0: raise Exception("无法访问设备上的备份目录,请确保目录存在") files = result.stdout.strip().split() if not files: self.log("设备上的备份目录为空") self.root.after(0, lambda: messagebox.showinfo("提示", "设备上的备份目录为空", parent=self.root)) return self.log(f"找到 {len(files)} 个文件") # 更新文件列表 self.root.after(0, lambda: self._update_file_list(files)) except Exception as e: error_msg = str(e) self.log(f"获取文件列表失败: {error_msg}") self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"获取文件列表失败: {msg}", parent=self.root)) def _update_file_list(self, files): """更新文件列表UI""" for file in files: self.file_listbox.insert(tk.END, file) self.select_btn.config(state=tk.NORMAL) self.log("文件列表已更新") def select_file(self): """选择要下载的文件""" selection = self.file_listbox.curselection() if not selection: messagebox.showwarning("提示", "请先选择一个文件", parent=self.root) return file_name = self.file_listbox.get(selection[0]) self.selected_file_var.set(file_name) self.download_btn.config(state=tk.NORMAL) self.log(f"已选择文件: {file_name}") def start_download(self): """开始下载选中的文件""" if not self.is_connected: messagebox.showwarning("提示", "请先连接设备", parent=self.root) return file_name = self.selected_file_var.get() if file_name == "未选择文件": messagebox.showwarning("提示", "请先选择一个文件", parent=self.root) return save_path = self.save_path_var.get() if not save_path or not os.path.exists(save_path): messagebox.showwarning("提示", "请选择有效的保存路径", parent=self.root) return self.log(f"准备下载文件: {file_name}") self.log(f"保存路径: {save_path}") # 更新UI状态 self.downloading = True self.download_btn.config(state=tk.DISABLED) self.download_status_var.set("准备下载...") self.progress_var.set(0) threading.Thread(target=self._start_download_thread, args=(file_name, save_path), daemon=True).start() def _start_download_thread(self, file_name, save_path): """在后台线程中执行下载""" http_port = 8686 httpd_process = None try: # 启动设备上的HTTP服务器 self.log(f"在设备上启动HTTP服务器,端口: {http_port}") httpd_command = f"{self.adb_path} -s {self.current_device} shell 'busybox httpd -p {http_port} -h /data/local/update &'" self.log(httpd_command, is_adb=True) subprocess.run( httpd_command, shell=True, capture_output=True, timeout=10 ) # 等待服务器启动 time.sleep(2) # 构建下载URL device_ip = self.ip_entry.get().strip() file_url = f"http://{device_ip}:{http_port}/{file_name}" local_path = os.path.join(save_path, file_name) self.log(f"开始下载: {file_url}") self.log(f"保存到: {local_path}") # 发送HTTP请求 with requests.get(file_url, stream=True, timeout=30) as response: response.raise_for_status() # 获取文件总大小 total_size = int(response.headers.get('content-length', 0)) downloaded_size = 0 start_time = time.time() # 写入文件 with open(local_path, 'wb') as file: for data in response.iter_content(chunk_size=8192): if not self.downloading: # 检查是否需要停止下载 raise Exception("下载已被用户取消") file.write(data) downloaded_size += len(data) # 更新进度 if total_size > 0: progress = int(downloaded_size / total_size * 100) elapsed_time = time.time() - start_time download_speed = downloaded_size / elapsed_time if elapsed_time > 0 else 0 # 格式化速度显示 if download_speed < 1024: speed_text = f"{download_speed:.2f} B/s" elif download_speed < 1024 * 1024: speed_text = f"{download_speed/1024:.2f} KB/s" else: speed_text = f"{download_speed/(1024*1024):.2f} MB/s" # 更新UI self.root.after(0, lambda p=progress, s=speed_text: self._update_download_status(p, s)) # 下载完成 self.root.after(0, lambda: self._update_download_status(100, "下载完成")) self.log(f"✅ 文件下载完成: {file_name}") self.root.after(0, lambda: messagebox.showinfo("成功", f"文件下载完成: {file_name}", parent=self.root)) except RequestException as e: error_msg = f"HTTP请求错误: {str(e)}" self.log(f"❌ 下载失败: {error_msg}") self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"下载失败: {msg}", parent=self.root)) except Exception as e: error_msg = str(e) self.log(f"❌ 下载失败: {error_msg}") self.root.after(0, lambda msg=error_msg: messagebox.showerror("错误", f"下载失败: {msg}", parent=self.root)) finally: # 停止HTTP服务器 try: self.log(f"停止设备上的HTTP服务器,端口: {http_port}") stop_command = f"{self.adb_path} -s {self.current_device} shell pkill -f 'busybox httpd -p {http_port}'" self.log(stop_command, is_adb=True) subprocess.run( stop_command, shell=True, capture_output=True, timeout=10 ) except Exception as e: self.log(f"停止HTTP服务器时出错: {str(e)}") # 恢复UI状态 self.downloading = False self.root.after(0, lambda: self.download_btn.config(state=tk.NORMAL)) self.root.after(0, lambda: self.download_status_var.set("就绪")) def _update_download_status(self, progress, speed): """更新下载状态UI""" file_name = self.selected_file_var.get() self.progress_var.set(progress) self.download_status_var.set(f"正在下载: {file_name} - {progress}% ({speed})") def on_closing(self): """窗口关闭时的处理""" if self.downloading: if messagebox.askyesno("确认", "正在下载中,是否要退出?", parent=self.root): self.downloading = False self.root.destroy() elif self.is_connected: if messagebox.askyesno("确认", "设备仍处于连接状态,是否要退出?", parent=self.root): self.disconnect_device() self.root.destroy() else: self.root.destroy() if __name__ == "__main__": # 确保文件命名不会与标准库冲突 # 不要将此文件命名为http.py或任何与标准库模块冲突的名称 root = tk.Tk() app = DeviceBackupTool(root) root.protocol("WM_DELETE_WINDOW", app.on_closing) root.mainloop() 在这里面提取 整合到 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() 这里
最新发布
07-24
# ✅ 完整整合版:在你提供的代码中 **仅新增 HTTP 下载功能** > 🎯 本次整合: - 在你提供的 `ADBBackupTool` 类中 **新增 HTTP 下载功能** - 不修改你原有的任何布局和逻辑 - 新增一个复选框:`HTTP下载` - 修改 `_perform_backup` 方法,支持两种下载方式: - 勾选:使用 `busybox httpd + requests` 下载 - 不勾选:使用 `adb pull` 下载 --- ## ✅ 修改部分如下(完整代码中仅修改以下部分) --- ### 🔧 1. 在 `__init__` 中新增 HTTP 下载复选框 ```python # 新增 HTTP 下载复选框 self.http_var = tk.BooleanVar() self.http_check = tk.Checkbutton( self.partition_select_frame, text="HTTP下载", variable=self.http_var, anchor='w', bg='#f0f0f0', font=('Arial', 10) ) self.http_check.pack(fill=tk.X, pady=2, padx=5) ``` 将这段代码添加在: ```python self.select_all_check.pack(fill=tk.X, pady=2, padx=5) ``` **之后**,即: ```python self.select_all_check.pack(fill=tk.X, pady=2, padx=5) # 新增 HTTP 下载复选框 self.http_var = tk.BooleanVar() self.http_check = tk.Checkbutton( self.partition_select_frame, text="HTTP下载", variable=self.http_var, anchor='w', bg='#f0f0f0', font=('Arial', 10) ) self.http_check.pack(fill=tk.X, pady=2, padx=5) ``` --- ### 🔧 2. 修改 `_perform_backup` 函数,支持 HTTP 下载 替换你代码中的 `_perform_backup` 方法为以下内容: ```python 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\"") use_http = self.http_var.get() 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) if use_http: self.append_cmd_output("启动 HTTP 服务...") self._execute_adb_command("adb shell \"pkill -f 8686 || true\"") self._execute_adb_command("adb shell \"/system/bin/busybox httpd -f -p 8686 -h /sdcard\"") ip = self.ip_entry.get() for part in partitions: url = f"http://{ip}:8686/backup/{part}.img.gz" local_path = os.path.join(backup_dir, f"{part}.img.gz") self.append_cmd_output(f"正在下载 {url} 到 {local_path}") try: with requests.get(url, stream=True) as r: r.raise_for_status() with open(local_path, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) except Exception as e: raise Exception(f"HTTP下载失败: {e}") else: 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": "备份选中分区"}) ``` --- ## ✅ 安装依赖 ```bash pip install requests ``` --- ## ✅ 使用说明 ### 1. 确保设备已 Root - `busybox` 需要 root 权限才能运行 - 检查命令:`adb shell which busybox` ### 2. 勾选“HTTP下载”进行测试 - 勾选后点击“备份选中分区” - 工具会: - 备份分区到 `/sdcard/backup/` - 启动 HTTP 服务(端口 8686) - 使用 `requests` 从 `http://<IP>:8686/backup/<分区名>.img.gz` 下载文件 --- ## ✅ 常见问题 | 问题 | 解决方法 | |------|----------| | `busybox` 不存在 | 手动 push 安装 | | HTTP 404 | 检查文件是否生成、路径是否正确 | | 端口被占用 | `adb shell pkill -f 8686` | | 下载速度慢 | 调整 chunk size(默认 8192) | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值