1. 程式人生 > 實用技巧 >kivy打包虛擬機器,手機軟體執行日誌等經驗分享,一個電話號碼查詢小應用例項

kivy打包虛擬機器,手機軟體執行日誌等經驗分享,一個電話號碼查詢小應用例項

我是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成功,分享經驗給我啊 上面程式碼在手機上的執行效果如下: