在进行安卓手机APP测试时,我们经常需要将APP的安装包通过adb装进手机中,这个过程有些繁琐和枯燥。为了提高工作效率,我想是否可以开发一个桌面小工具,让我们能够在界面上通过拖拽安装的方式实现这个过程?答案当然是可以的,本文我将带你使用Python + PyQt + ADB手把手的开发一个安卓APK安卓小工具。
一、需求分析
- 创建一个GUI应用程序来安装APK文件。
- 显示设备列表,允许用户选择要安装APK文件的设备。
- 允许用户通过拖放APK文件或通过选择文件对话框来选择要安装的APK文件。
- 在后台线程中执行APK安装操作,并在安装完成时显示提示对话框。
- 显示安装状态和错误信息。
二、方案设计
基于以上需求,可以设计以下方案来实现这个APK安装器应用程序:
- 使用PyQt5模块创建一个GUI应用程序窗口,并设置窗口的标题、大小和样式。
- 在窗口中创建一个QListWidget部件来显示设备列表,并使用QVBoxLayout布局将其添加到窗口中。
- 创建QLabel部件来显示相关文本,如"Device List:"和"Drag and drop APK here:"。
- 创建一个QPushButton部件作为安装APK的按钮,并将其连接到相应的槽函数。
- 创建一个QLabel部件用于显示安装状态信息。
- 创建一个自定义的WorkerThread类,继承自QThread,用于在后台线程中执行APK安装操作。该类应包含一个安装完成的信号。
- 在WorkerThread类中,实现run函数来执行APK安装操作,并在安装完成时发射安装完成的信号。
- 创建一个MainWindow类,继承自QMainWindow,作为主窗口类。在构造函数中,创建各个部件,并设置其样式和布局。
- 在MainWindow类中,实现相应的槽函数,如check_adb函数用于检查ADB是否已安装和设置了环境变量,populate_device_list函数用于获取设备列表,install_apk函数用于安装APK文件,以及其他辅助函数。
- 将相应的信号与槽函数连接起来,例如将安装按钮的clicked信号连接到install_apk_clicked槽函数。
- 重写dragEnterEvent和dropEvent函数,以支持拖放APK文件的操作。
- 实现show_info_dialog和show_error_dialog函数,用于显示安装完成和错误的提示对话框。
- 在应用程序的主程序中,创建一个QApplication实例,创建MainWindow实例并显示窗口,最后启动应用程序事件循环。
三、完整代码示例
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QListWidget, QLabel, QVBoxLayout, QWidget, QPushButton, \
QFileDialog, QMessageBox
from PyQt5.QtCore import pyqtSlot, QThread, pyqtSignal
import subprocess
class WorkerThread(QThread):
finished_signal = pyqtSignal()
def __init__(self, device=None, apk_path=None):
super().__init__()
self.device = device
self.apk_path = apk_path
def run(self):
try:
subprocess.check_output(['adb', '-s', self.device, 'install', self.apk_path])
self.finished_signal.emit()
except subprocess.CalledProcessError as e:
print(e.output.decode())
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("APK Installer")
self.setGeometry(100, 100, 400, 300)
self.setStyleSheet("""
QMainWindow {
background-color: #222; /* 主窗口背景色为深灰色 */
}
QLabel {
color: #fff; /* 白色文字 */
font-size: 18px; /* 较大的字体 */
}
QPushButton {
background-color: #007BFF; /* 主色调蓝色 */
border: none;
color: #fff; /* 白色文字 */
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px; /* 圆角按钮 */
}
QPushButton:hover {
background-color: #0056b3; /* 深蓝色悬停效果 */
}
QPushButton:pressed {
background-color: #004080; /* 按下按钮效果 */
}
/* 为 QMessageBox 设置样式 */
QMessageBox {
background-color: #fff; /* 设置背景颜色为白色 */
}
QMessageBox QLabel {
color: #000; /* 设置文本颜色为黑色 */
}
""")
self.device_list = QListWidget()
self.device_label = QLabel("Device List:")
self.apk_label = QLabel("Drag and drop APK here:")
self.install_button = QPushButton("Install APK")
self.status_label = QLabel("")
layout = QVBoxLayout()
layout.addWidget(self.device_label)
layout.addWidget(self.device_list)
layout.addWidget(self.apk_label)
layout.addWidget(self.install_button)
layout.addWidget(self.status_label)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.worker_thread = WorkerThread()
self.worker_thread.finished_signal.connect(self.show_info_dialog)
self.install_button.clicked.connect(self.install_apk_clicked)
self.setAcceptDrops(True)
self.populate_device_list()
# 检查adb是否已安装并设置了环境变量
def check_adb(self):
try:
subprocess.check_output(['adb', 'version'])
return True
except FileNotFoundError:
return False
def install_apk(self, device, apk_path):
self.status_label.setText("安装进行中...")
self.worker_thread.device = device
self.worker_thread.apk_path = apk_path
self.worker_thread.start()
def populate_device_list(self):
if not self.check_adb():
print("ADB not found!")
return
devices = subprocess.check_output(['adb', 'devices']).decode().split('\n')[1:]
for device in devices:
if device.strip():
self.device_list.addItem(device.split('\t')[0])
@pyqtSlot()
def install_apk_clicked(self):
if self.device_list.currentItem() is None:
print("No device selected!")
return
apk_path, _ = QFileDialog.getOpenFileName(self, "Select APK", "", "APK Files (*.apk)")
if apk_path:
device = self.device_list.currentItem().text()
self.install_apk(device, apk_path)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
for url in event.mimeData().urls():
file_path = str(url.toLocalFile())
if file_path.endswith('.apk'):
self.install_apk_from_path(file_path)
def install_apk_from_path(self, apk_path):
if self.device_list.currentItem() is None:
print("No device selected!")
return
device = self.device_list.currentItem().text()
self.install_apk(device, apk_path)
def show_info_dialog(self):
QMessageBox.information(self, '提示', '安装完成')
self.status_label.setText("安装完成")
def show_error_dialog(self):
QMessageBox.critical(self, "错误", "安装失败")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
四、效果展示
可以看到界面的设备列表列出了当前电脑已连接的设备号
选中设备号,将电脑上的APK拖拽到界面范围内
下方显示安装进行中
安装完成后会有弹窗提示
五、代码分析
这段代码是一个基于PyQt5的APK安装器。它使用了QtWidgets模块中的各种小部件来创建一个GUI应用程序。以下是对代码的分析:
导入必要的模块:
sys
:用于处理系统相关的操作PyQt5.QtWidgets
:包含了用于创建GUI应用程序的各种小部件类PyQt5.QtCore
:包含了用于处理核心功能的类和函数subprocess
:用于在代码中执行外部命令
创建一个名为
WorkerThread
的自定义类,继承自QThread
。该类用于在后台线程中执行安装APK的操作。它包含一个自定义的finished_signal
信号,用于在安装完成时发射信号。创建一个名为
MainWindow
的自定义类,继承自QMainWindow
。该类是应用程序的主窗口,用于显示设备列表、APK安装按钮和状态标签等部件。在
MainWindow
类的构造函数中,设置窗口的标题、大小和样式表。创建
QListWidget
、QLabel
、QPushButton
和QLabel
等部件,并使用QVBoxLayout
布局将它们添加到窗口中。设置部件的样式。
创建一个
WorkerThread
实例,并将其finished_signal
信号连接到槽函数show_info_dialog
。将
install_button
按钮的clicked
信号连接到槽函数install_apk_clicked
。启用拖放功能,并重写
dragEnterEvent
和dropEvent
函数来处理拖放APK文件的操作。实现一些辅助函数,例如
check_adb
用于检查ADB是否已安装和设置了环境变量,populate_device_list
用于获取设备列表,install_apk
用于安装APK文件。在
install_apk_clicked
槽函数中,获取选择的APK文件路径,并调用install_apk
函数来安装APK文件。实现
show_info_dialog
槽函数,用于显示安装完成的提示对话框。实现
show_error_dialog
槽函数,用于显示安装失败的错误对话框。在
__main__
块中,创建一个QApplication
实例,创建MainWindow
实例并显示窗口,最后启动应用程序事件循环。
六、样式分析
根据上文代码中的样式美化部分,我们展开进行分析
self.setStyleSheet("""
QMainWindow {
background-color: #222; /* 主窗口背景色为深灰色 */
}
QLabel {
color: #fff; /* 白色文字 */
font-size: 18px; /* 较大的字体 */
}
QPushButton {
background-color: #007BFF; /* 主色调蓝色 */
border: none;
color: #fff; /* 白色文字 */
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px; /* 圆角按钮 */
}
QPushButton:hover {
background-color: #0056b3; /* 深蓝色悬停效果 */
}
QPushButton:pressed {
background-color: #004080; /* 按下按钮效果 */
}
/* 为 QMessageBox 设置样式 */
QMessageBox {
background-color: #fff; /* 设置背景颜色为白色 */
}
QMessageBox QLabel {
color: #000; /* 设置文本颜色为黑色 */
}
""")
QMainWindow
:设置主窗口的样式,将背景色设置为深灰色(#222)。QLabel
:设置标签(Label)的样式,将文字颜色设置为白色(#fff),字体大小为18px。QPushButton
:设置按钮(PushButton)的样式,背景色为主色调蓝色(#007BFF),去除按钮边框(border: none),文字颜色为白色,设置内边距为10px上下,20px左右,居中显示文字,禁用文字下划线,设置为内联块级元素,字体大小为16px,设置按钮间的外边距为4px上下,2px左右,设置鼠标悬停时的背景色为深蓝色(#0056b3),设置按钮按下时的背景色为更深的蓝色(#004080),并且设置按钮的边角为5px,使其呈现圆角。QMessageBox
:为消息框设置样式,将背景颜色设置为白色(#fff)。QMessageBox QLabel
:为消息框中的标签设置样式,将文本颜色设置为黑色(#000)。
这些样式定义了主窗口、标签、按钮和消息框的外观,以提升用户界面的美观度和可用性。