ARTICLE · 992054
利用Python开发App实战
按钮能点,列表能刷,电脑上跑得也挺顺。结果打成 APK 装到手机上,接口直接请求失败。
这种问题我第一次用 Python 做 App 时就碰到过。后来折腾完 Kivy、接口请求、本地存储和 Android 打包,我对 Python 开发 App 的看法变了:能不能做不是问题,问题是别拿写脚本的思路去写 App。
这次直接做个实际点的东西:服务器巡检 App。
平时有几台测试服务,偶尔需要看一下接口是不是活着。总不能每次掏电脑 curl,所以我想做个很轻的小工具,手机点一下,直接显示服务状态和响应耗时。
界面不复杂:
服务器巡检
用户中心 正常 82ms
订单服务 正常 117ms
文件服务 异常 timeout
[ 立即巡检 ]
Python 这边我用 Kivy。原因也简单,它不是拿网页套个壳,Python 代码本身就能驱动界面,而且后面能继续打 Android 包。
先把页面架起来。
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
classInspectPage(BoxLayout):
def__init__(self, **kwargs):
super().__init__(orientation="vertical", spacing=12, padding=18, **kwargs)
self.title = Label(
text="服务器巡检",
size_hint_y=None,
height=70,
font_size=26
)
self.result = Label(
text="还没巡检",
halign="left",
valign="top"
)
self.check_btn = Button(
text="立即巡检",
size_hint_y=None,
height=56
)
self.check_btn.bind(on_press=self.start_check)
self.add_widget(self.title)
self.add_widget(self.result)
self.add_widget(self.check_btn)
defstart_check(self, _):
self.result.text = "正在检查..."
classOpsApp(App):
defbuild(self):
return InspectPage()
OpsApp().run()
这段东西跑起来,一个最基本的 App 就有了。
但这里只是“能显示”。
真正开始写巡检逻辑时,有个地方我第一眼就不太想这么干:
defstart_check(self, _):
requests.get(url)
把 HTTP 请求直接塞进按钮事件里,接口稍微慢一点,整个界面就像死了一样。
桌面脚本这么写可能没感觉,手机 App 不行。
UI 线程就是 UI 线程,网络请求老老实实扔出去。
我后来改成线程执行巡检,完成后再回到主线程更新界面:
import time
import requests
from threading import Thread
from kivy.clock import Clock
SERVICES = {
"用户中心": "https://api.example.com/user/health",
"订单服务": "https://api.example.com/order/health",
"文件服务": "https://api.example.com/file/health",
}
defprobe(name, url):
started = time.perf_counter()
try:
resp = requests.get(url, timeout=3)
cost = int((time.perf_counter() - started) * 1000)
if200 <= resp.status_code < 300:
return name, "正常", cost
return name, f"HTTP {resp.status_code}", cost
except requests.RequestException as exc:
return name, exc.__class__.__name__, None
按钮事件也改掉:
defstart_check(self, _):
self.check_btn.disabled = True
self.result.text = "正在检查..."
Thread(target=self.run_checks, daemon=True).start()
defrun_checks(self):
lines = []
for name, url in SERVICES.items():
service, state, cost = probe(name, url)
if cost isNone:
lines.append(f"{service}{state}")
else:
lines.append(f"{service}{state}{cost}ms")
text = "\n".join(lines)
Clock.schedule_once(
lambda _: self.show_result(text),
0
)
defshow_result(self, text):
self.result.text = text
self.check_btn.disabled = False
到这里才比较像一个真能用的小工具。
这里还有个细节我一般不会省:timeout 必须写。
移动网络比办公室环境复杂得多。Wi-Fi 切 5G、信号抖一下、DNS 卡住,这些都可能把请求拖住。没有超时控制,一个服务没响应,巡检按钮能在那里转半天。
接下来是保存配置。
写死三个地址只是演示,实际用的时候,我肯定不想每改一次 IP 都重新发 APK。
这种小数据没必要先上 SQLite,一个 JSON 文件够用了。
import json
from pathlib import Path
classServerConfig:
def__init__(self, root):
self.file = Path(root) / "servers.json"
defload(self):
ifnot self.file.exists():
return []
try:
return json.loads(
self.file.read_text(encoding="utf-8")
)
except (OSError, json.JSONDecodeError):
return []
defsave(self, servers):
payload = json.dumps(
servers,
ensure_ascii=False,
indent=2
)
self.file.write_text(payload, encoding="utf-8")
这里我故意没把所有东西都数据库化。
一个巡检工具,可能就存十来条地址。这个时候为了“架构完整”塞一层 ORM,我反而嫌麻烦。什么时候需要查询、排序、历史记录越来越多了,再换 SQLite。
Python 做这种内部 App,我觉得最大的优势就在这:代码可以收得很短。
最后就是最容易把人折腾烦的打包。
桌面执行:
python main.py
跟 Android 上跑 APK 完全不是一回事。
通常会通过 Buildozer 管理 Android 打包,在配置里至少要把 Python 依赖声明进去,例如:
requirements = python3,kivy,requests
android.permissions = INTERNET
我第一次打包时就漏过网络权限。
电脑上接口测得好好的,APK 一装,请求全部失败。代码翻了半天没发现问题,最后才发现压根不是 Python 请求逻辑的问题。
这种坑挺典型:开发 App 之后,排查范围已经不能只盯着 Python 代码了。
权限、Android 生命周期、文件目录、网络策略、线程,这些都会冒出来。
如果只是做公司内部巡检、仓库扫描、设备管理、数据录入、简单看板这一类工具,我现在反而挺愿意用 Python。
后台接口本来就是 Python,再配一个 Kivy 客户端,一套语言把数据处理、网络请求和业务逻辑都接住,开发速度确实快。
但如果要做重动画、复杂交互、深度调用手机硬件的商业 App,我一般不会硬上 Python。
工具就是这么回事。
能拿二十行代码解决的东西,别为了“技术栈高级”写两百行;但平台已经明显不合适了,也别因为自己熟 Python 就硬扛。
这个巡检 App 后面其实还能继续加:失败接口标红、保存历史耗时、连续异常触发通知、扫描二维码录入服务器地址。
不过先把“点一下,知道哪台服务挂了”做稳,比先堆十个功能靠谱得多。