kivy打包虚拟机,手机软件运行日志等经验分享,一个电话号码查询小应用实例

时间:2024-03-11 16:05:31

我是python小白一枚,对kivy开发手机app产生了兴趣,并没感觉到kivy写代码有多难,折腾打包成手机apk倒是花了好长时间,走过了大大小小的坑,这里把经验记录下来,供大家参考。

kivy打包有几种方法,可以自己配置环境,通过python for android(p4a),或者buildozer打包,也可以使用别人配置好环境的虚拟机打包。配置环境坑实在太多了,建议直接研究使用虚拟机打包,省时省力,把更多精力用在代码上吧。

我打包用的虚拟机是某大佬做的,网址是:https://github.com/nkiiiiid/kivy-apk

虚拟机解压安装后占将近50GB空间,如果电脑硬盘不够用,可以下载到移动硬盘上,安装在移动硬盘上。

具体的使用方法,上面的网页有详细说明。

手机软件运行日志查看方法,大佬们用的都是mumu模拟器,操作太复杂了,小白的我实在不爱花那么多时间研究了,就用了另一个简单点的笨方法,在ubuntu系统上安装好adb环境后,按以下步骤操作就能查看日志了:

1. 手机打开开发者模式
2. 开发者模式里面打开USB调试
3. 电脑下载adb工具
4. 手机连接电脑
5. 电脑adb devices看到有设备
6. 电脑adb shell logcat -s python
7. 手机打开APP

再说说打包的配置文件修改吧,buildozer.spec是打包失败的祸根,很多手机上失退,打包失败都可能是配置不对造成的。遇到失败或闪退,首先查看手机运行日志,其次认真在buildozer.spec上找原因。下面附上一个我打包用过的buildozer.spec供参考,不同的应用程序在打包时都要相应地修改配置文件上的参数

[app]

# (str) Title of your application
title = JEA

# (str) Package name
package.name = JEA

# (str) Package domain (needed for android/ios packaging)
package.domain = org.kivydev

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas,ttf

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png

# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec

# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin

# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg

# (str) Application versioning (method 1)
version = 1.8
# (str) Application versioning (method 2)
# version.regex = __version__ = [\'"](.*)[\'"]
# version.filename = %(source.dir)s/main.py

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy,requests,beautifulsoup4

# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy

# (list) Garden requirements
#garden_requirements =

# (str) Presplash of the application
presplash.filename = %(source.dir)s/data/presplash.png

# (str) Icon of the application
icon.filename = %(source.dir)s/data/icon.png

# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = all

# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY

#
# OSX Specific
#

#
author = 漏 Copyright Guoming Liu

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0

# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF

# (list) Permissions
#android.permissions = INTERNET
android.permissions = INTERNET,ACCESS_WIFI_STATE,ACCESS_NETWORK_STATE,CHANGE_NETWORK_STATE,CHANGE_WIFI_STATE,WRITE_EXTERNAL_STORAGE,BIND_INPUT_METHOD

# (int) Target Android API, should be as high as possible.
android.api = 27

# (int) Minimum API your APK will support.
android.minapi = 21

# (int) Android SDK version to use
#android.sdk = 27

# (str) Android NDK version to use
android.ndk = 19c

# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
android.ndk_api = 21

# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True

# (str) Android NDK directory (if empty, it will be automatically downloaded.)
android.ndk_path = /home/kivydev/andr/android-ndk-r19c

# (str) Android SDK directory (if empty, it will be automatically downloaded.)
android.sdk_path = /home/kivydev/andr/android-sdk-linux

# (str) ANT directory (if empty, it will be automatically downloaded.)
android.ant_path = /home/kivydev/andr/apache-ant-1.9.4

# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False

# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False

# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity

# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"

# (list) Pattern to whitelist for the whole project
#android.whitelist =

# (str) Path to a custom whitelist file
#android.whitelist_src =

# (str) Path to a custom blacklist file
#android.blacklist_src =

# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don\'t add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar

# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =

# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =

# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =

# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the \'android.gradle_dependencies\' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"

# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url \'https://kotlin.bintray.com/ktor\' }"
#android.add_gradle_repositories =

# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude \'META-INF/common.kotlin_module\'", "exclude \'META-INF/*.kotlin_module\'"
#android.add_gradle_repositories =

# (list) Java classes to add as activities to the manifest.
#android.add_activites = com.example.ExampleActivity

# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME

# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png

# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard

# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so

# (bool) Indicate whether the screen should stay on
# Don\'t forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False

# (list) Android application meta-data to set (key=value format)
#android.meta_data =

# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =

# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =

# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D

# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1

# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a

#
# Python for android (p4a) specific
#

# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy

# (str) python-for-android branch to use, defaults to master
#p4a.branch = master

# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =

# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =

# (str) Filename to the hook for p4a
#p4a.hook =

# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2

# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =


#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0

# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"

# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s


[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2

# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1

# (str) Path to build artifact storage, absolute or relative to spec file
build_dir = /home/kivydev/test/.buildozer

# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin

# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let\'s take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#


# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

特别要强调一下,requirements这个配置参数中一定要把你打包要含的文件扩展名全写进去,否则会出错。permissions参数非常重要,如果使用网络,使用存储空间或者用到其它权限,一定要在这里声明,否则肯定闪退,关于权限,网上有其它很详细的中文贴子,大家可以参考。

我在应用程序中使用了bs4库,但打包时在requirements中写上bs4闪退了,后来听大佬建议改成全称beautifulsoup4,就成功了。反正是,各种坑,大家过了入门这个阶段,就一片光明了。

下面我把我调试成功,能在手机上正确运行的一个应用发在这里,供大家参考,程序中涉及到ScrollView,中文字体使用,ScreenManager等多个技术点,虽然是小应用,但可以参考。

# main.py
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.core.text import LabelBase from kivy.uix.label import Label from kivy.lang import Builder from kivy.uix.screenmanager import ScreenManager, Screen from kivy.uix.scrollview import ScrollView from kivy.uix.button import Button import re import requests from bs4 import BeautifulSoup LabelBase.register(name=\'droid\',fn_regular=\'droid.ttf\') class SecondWindow(Screen): def ShowEarthquake(self): eq = self.ids.eq_list url = \'http://news.ceic.ac.cn/index.html\' #headers={"Connection":"close",\'User-Agent\': "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"} resp = requests.get(url) resp.encoding = resp.apparent_encoding content = resp.text soup = BeautifulSoup(content, \'html.parser\') rows = soup.find_all(\'tr\') catalog = [] for row in rows: cell = [i.text for i in row.find_all(\'td\')] if len(cell) != 0: mag = cell[0] eq_time = cell[1] latitude = cell[2] longitude = cell[3] depth = cell[4] location = cell[5] string = f"{eq_time},{location}(纬度:{latitude},经度:{longitude})发生{mag}级地震,震源深度{depth}公里" catalog.append(string) temp = \'据中国地震台网测定:\' for line in catalog: temp = temp + \'\n\' + line eq.text = temp class WindowManager(ScreenManager): pass # We must inherited from BoxLayout,Screen class in MainWindow,otherwise the screen in app will go wrong class MainWindow(BoxLayout, Screen): def validate_user(self): dic = {\'张三\': [\'12345678900\', \'000001\'], \'李四\': [\'18888888888\', \'12345678\']} user = self.ids.name_field # get the name from id in kv fil small = self.ids.small big = self.ids.big if user.text in dic: short_number = dic[user.text][0] long_number = dic[user.text][1] small.text = f"[color=#0000FF]大号:{short_number} [/color]" big.text = f"[color=#0000FF]小号:{long_number} [/color]" else: small.text = \'\' big.text = \'\' small.text = "[color=#FF0000]对不起,查无此人 [/color]" class PhoneApp(App): def build(self): return kv kv = Builder.load_file("phone.kv") if __name__ == "__main__": sa = PhoneApp() sa.run()

#phone.kv

WindowManager: MainWindow: SecondWindow: #<FlatButton@ButtonBehavior+Label> # font_size: 30 <MainWindow>: id: main_win name: "First" orientation: "vertical" space_x: self.size[0]/6 #space_x defines the width of space to 1/3 full screen_width canvas.before: Color: rgba: (0,0,0,1) #white Color Rectangle: size: self.size pos: self.pos BoxLayout: size_hint_y: 0.1 canvas.before: Color: rgba: (.06,.45,.45,1) #white Color Rectangle: size: self.size pos: self.pos Label: font_name: \'droid\' text: "吉林省地震局电话查询" font_size: self.height / 3 size_hint_x: 1 BoxLayout: size_hint_y: 0.8 orientation: "vertical" BoxLayout: size_hint_y: 0.5 orientation: "vertical" padding: main_win.space_x, 5 spacing: 10 canvas.before: Color: rgba: (1, 222/255, 173/255, 1) #white Color Rectangle: size: self.size pos: self.pos BoxLayout: size_hint_y: 0.3 TextInput: id: name_field size_hint_x: 0.7 font_size: self.height * 3 / 5 padding_y: [self.height / 2.0 - (self.line_height / 2.0) * len(self._lines), 0] font_name: \'droid\' hint_text: "姓名" multiline: False focus: True on_text_validate: root.validate_user() Button: size_hint_x: 0.3 font_size: self.height * 2 /5 background_color: (32/255,178/255,170/255,1) font_name: \'droid\' text: \'查询\' on_release: root.validate_user() Label: size_hint_y: 0.3 id: small font_name: \'droid\' font_size: self.height / 2 pos: 10, 10 text: \'\' markup: True Label: size_hint_y: 0.3 id: big font_name: \'droid\' font_size: self.height / 2 pos: 10, 10 text: \'\' markup: True BoxLayout: size_hint_y: 0.5 canvas.before: Color: rgba: (1, 222/255, 173/255, 1) #white Color Rectangle: size: self.size pos: self.pos BoxLayout: Label: size_hint_x: 0.2 text: \'\' Button: size_hint_x: 0.6 size_hint_y: 0.4 pos_hint: {\'center_x\': .5, \'center_y\': .5} background_color: (32/255,178/255,170/255,1) font_size: self.height * 2 / 5 font_name: \'droid\' text: \'最新地震\' on_release: app.root.current = "Second" Label: size_hint_x: 0.2 text: \'\' BoxLayout: size_hint_y: 0.1 canvas.before: Color: rgba: (.06,.45,.45,1) #white Color Rectangle: size: self.size pos: self.pos Label: font_name: \'droid\' text: "软件研发:长白山火山监测站 研发日期:2020-07-03" font_size: self.height / 4 bold: True size_hint_x: 1 <SecondWindow>: name: "Second" BoxLayout: orientation: "vertical" Button: size_hint_y: 0.1 font_name: \'droid\' font_size: self.height * 3 / 5 text: "震情信息查询" on_release: root.ShowEarthquake() ScrollView: size_hint_y: 0.8 id: scrlv #size_hint: (1, 0.5) do_scroll_x: False do_scroll_y: True TextInput: id: eq_list font_name: \'droid\' size_hint_x: 1.0 font_size: 70 size_hint: 1, None #text_size: self.width,None height: max( (len(self._lines)+1) * self.line_height, scrlv.height) focus: True markup: True Button: size_hint_y: 0.1 font_name: \'droid\' font_size: self.height * 3 /5 text: "返回首页" on_release: app.root.current = "First"

  最后还要分享一个经验,上面的手机应用程序打包安装到手机上后,可能会遇到无法在人名输入框中使用输入法的问题,可以这样子解决:

      如果是华为手机:设置--系统--语言和输入法--安全输入,把这个安全输入关掉,就行了 
      根本的解决方式应该是:
Kivy 除了设置中文字体,真正支持中文IME输入还需要替换SDL2.dll,修改SDL_windowskeyboard.c代码,定位到IME_Init 函数 videodata->ime_uiless = UILess_SetupSinks(videodata); 语句,注释掉,重新编译生成dll并替换。
上两行是我转贴过来的,我小白的水平解决不了,就用手机设置的方式暂时对付,哪位大佬能修改SDL2.dll成功,分享经验给我啊
上面代码在手机上的运行效果如下: