之前用python做了个命令行的爬虫,在安卓上运行时还得打开termux,用上虚拟键盘,很不方便,于是一直想改为Android版本的。近期刚发现Kivy/KivyMD可以基于Python进行跨平台开发,国庆期间进行了尝试。在此记录一些踩坑经历,供有相同需求的同学参考。
一、解决中文乱码问题
Kivy和KivyMD原生不支持中文,采用的默认字体是英文字体,直接运行起来就会显示方框。
Kivy中可以通过设置LabelBase修改字体,但是每一个控件都要在代码中增加font_style的定义,非常不方便。而KivyMD的控件就没有提供定义LabelBase改字体的功能。
而且,即使在本机开发环境中修改了字体设置,在打包生成apk文件安装后,在手机上运行时仍然是乱码。
下面是使用buildozer打包时,彻底解决中文乱码问题的方法。
第一步,修改kivy字体设置
buildozer打包过程中,会首先下载kivy,并保存在.buildozer/android/platform/build-armeabi-v7a/packages/kivy/2.0.0.zip
解压该文件,修改config.py:
elif version == 16:
Config.setdefault('kivy', 'default_font', [
'Roboto',
'data/fonts/msyh.ttc'])
这里我用的是msyh.ttc(微软雅黑)。Roboto是kivy内部定义的字体类型名称,不用修改,这样的话其它文件引用Roboto时,相当于引用了中文字体,这样改动的地方最少。
然后要将新字体的文件复制到kivy/data/fonts/目录下。重新打包生成2.0.0.zip文件,替换原文件。
第二步,修改kivyMD的字体设置
运行一次buildozer android debug后,会将kivyMD以及其它需要的包下载到.buildozer/android/platform/build-armeabi-v7a/build/python-installs/yourapp/这个目录下。
(刚才的 kivy的2.0.0.zip压缩包也会被解压到这个目录下,如果第一步没改,也可以在这里修改)
首先将新增的msyh.ttc字体文件复制到kivymd/fonts/目录下,然后修改font_definitions.py.
fonts = [
{
"name": "Roboto",
"fn_regular": fonts_path + "msyh.ttc",
"fn_bold": fonts_path + "msyh.ttc",
"fn_italic": fonts_path + "msyh.ttc",
"fn_bolditalic": fonts_path + "msyh.ttc",
},
{
"name": "RobotoThin",
"fn_regular": fonts_path + "msyh.ttc",
"fn_italic": fonts_path + "msyh.ttc",
},
{
"name": "RobotoLight",
"fn_regular": fonts_path + "msyh.ttc",
"fn_italic": fonts_path + "msyh.ttc",
},
{
"name": "RobotoMedium",
"fn_regular": fonts_path + "msyh.ttc",
"fn_italic": fonts_path + "msyh.ttc",
},
{
"name": "RobotoBlack",
"fn_regular": fonts_path + "msyh.ttc",
"fn_italic": fonts_path + "msyh.ttc",
},
{
"name": "Icons",
"fn_regular": fonts_path + "materialdesignicons-webfont.ttf",
},
]
这里,同样不修改Boboto的字体类型名称,这样不用修改其它诸多py文件。我是都改为了msyh.ttc,你也可以按需增加light、Black等字体。
第三步,重新编译打包
删除所有的pyc文件,删除/home/kali/Documents/kivy_exp/.buildozer/android/platform/build-armeabi-v7a/dists/yourapp__armeabi-v7a下所有上一次打包时生成的文件,重新用buildozer打包。
中文乱码问题即可完美解决。
二、安卓调试问题
直接运行adb logcat
,会显示大量老旧和无关调试信息,造成定位我们程序Bug的难度增加。
这里,可以先清空log文件。
adb logcat -c
然后通过搜索‘Python for android ended’这句话,快速找到出错点。
比如下面的log文件就显示出,程序崩溃是因为没有加载docutils模块。
10-06 23:20:08.444 4181 6396 I python : File "/home/xxx/Documents/kivy_exp/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/yourapp/kivy/lang/builder.py", line 663, in _apply_rule
10-06 23:20:08.444 4181 6396 I python : File "/home/xxx/Documents/kivy_exp/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/yourapp/kivy/lang/builder.py", line 621, in _apply_rule
10-06 23:20:08.444 4181 6396 I python : File "/home/xxx/Documents/kivy_exp/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/yourapp/kivy/factory.py", line 154, in __getattr__
10-06 23:20:08.445 4181 6396 I python : File "/home/xxx/Documents/kivy_exp/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/yourapp/kivy/uix/rst.py", line 81, in <module>
10-06 23:20:08.445 4181 6396 I python : ModuleNotFoundError: No module named 'docutils'
10-06 23:20:08.445 4181 6396 I python : Python for android ended.
三、buildozer.spec的设置问题
buildozer.spec的设置很重要,往往决定了打包apk过程能否顺利,以及apk安装到手机后能否正常运行。以下几个设置需要关注。
一是依赖模块要全
比如我的程序的依赖模块如下
requirements = python3,kivy,https://github.com/kivymd/KivyMD/archive/master.zip,pygments,sdl2_ttf,pillow,requests,urllib3,datetime,beautifulsoup4,baidu-aip,chardet,idna,docutils,android,pyjnius
KivyMD默认版本过低,必须用github上的。
pygments,sdl2_ttf,pillow,chardet,idna,docutils是编译kivy必须的。
android是请求安卓权限必须的(特别是在安卓10和11上)。
二是安卓权限要定义,并在程序运行中动态请求
安卓10以后,对权限的管理大为改变,我初期调试时多次因为权限设置问题通不过。
buildozer.spec中要进行定义:
android.permissions = WRITE_EXTERNAL_STORAGE,READ_EXTERNAL_STORAGE,INTERNET,WAKE_LOCK
android.wakelock = True
然后代码中要进行相应申请:
from kivy.utils import platform
if platform == "android":
from android.permissions import request_permissions, Permission
request_permissions([Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE, Permission.INTERNET])
from android.storage import primary_external_storage_path
appwd=primary_external_storage_path()+'/Download/yourapp'
四、用pyinstaller打包成单个EXE的问题
pyinstaller打包成单个exe时,主要要解决子目录资源的包含路径问题,否则经常容易出错。
在我的项目中,包含两个子目录:kv目录下有多个kv文件,assets目录下是图标和图片。
一是最终的spec文件如下:
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.building.build_main import *
import sys
import os
from kivy_deps import sdl2, glew
from kivymd import hooks_path as kivymd_hooks_path
a = Analysis(['main.py'],
pathex=['os.getcwd()'],
binaries=[],
datas=[("assets\\*","assets"),
("kv\\*","kv"),
],
hiddenimports=[],
hookspath=[kivymd_hooks_path],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=None)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
debug=False,
strip=False,
upx=True,
name="gjcinfo",
console=False,
icon='assets\\logo.ico',
)
这时如果直接打包成exe,运行时仍然会显示找不到kv文件的错误。这是因为exe运行解压时,会生成临时目录_MEIPASS,打包进去的kv文件都被展开到临时目录下。这就要在main.py中进行修改:
from kivy.resources import resource_add_path
if __name__ == "__main__":
if hasattr(sys, '_MEIPASS'):
resource_add_path(os.path.join(sys._MEIPASS))
MainApp().run()
修改完后运行pyinstaller.exe xxxx.spec
,成功。
五、在安卓上使用webview的问题
网上关于此问题最好的示代代码如下:
https://insideconspiracy.blogspot.com/2020/03/inline-webview-within-your-kivy-app.html
六、安装buildozer时显示找不到cython
sudo ln -s /usr/bin/cython3 /usr/local/bin/cython