赞
踩
因为我们学校被用作高考考场了,所以终于有10天时间发篇博客了(笑)
咳咳,进入正题
众所周知,Python开发安卓好像就三个选择:Kivy,BeeWare,p4a(Python-for-android)。这三个,我体验了前两个,发现几个问题:
1.Kivy是采用自研的UI和设计,导致效率低不说,很多Python第三方库都不能用...
2.BeeWare也有这个问题,很多Python第三方库都不能用,并且虽然是原生UI,但是很多安卓中的特性都不支持
3.p4a的问题就更大了,前提就得是有台Linux机器,打包环境配置及其费劲,还极大概率会出错,捣鼓半天都不一定有效,这把我们宝贵的时间全都浪费了....
下面给大家稍稍看一下页面区别(部分图片来自网络)
1.Kivy与原生开发的页面效果对比
2.BeeWare(图片来自网络)
可以看到,Beeware页面确实好了不少,但是据我所知好像它是不支持安卓传感器的,也不适合开发大型项目,只适合开发小工具,效果也不佳
当然,也不能光说人家的缺点,它们对于跨平台都有着较好的支持,如果你需要一次编写,处处运行的效果,就可以试试这两个库
“Python开发安卓应用怎么这么费劲,我想写点东西方便移动使用都没办法....”
"网上也没有对应的支持,那怎么办....."
"要不,自己写一个库来实现Python跨平台-安卓开发?"
说干就干,还真让我找到了方法:chaquopy
这是一个开源的,免费的Android插件(虽然最近才刚开源),可以帮你在安卓中打包进Python环境,再利用JNI技术实现调用Python;理论上只要没有平台依赖的第三方库(比如pywin32肯定不行)都可以被调用,诸如requests,tensorflow,pytorch,numpy好像都可以!但是这毕竟只是插件,没办法完全使用Python开发,还要写Java代码,好麻烦的说
网上chaquopy资料很少,基本上都是复制粘贴官网的极个别样例,只有一份全英文的文档摆在那..
没办法,百度翻译+文档生啃了一星期终于学明白了,(吐槽:它的类继承写的真有够麻烦的,还有什么动态代理和静态代理,头大)
先看效果(原本速度不是这么慢的,因为转换为GIF导致速度变慢了。原本的速度和普通APP完全无区别)
此APP除去Virgo库的代码,页面的控制逻辑完全采用Python构建
"Virgo库是什么?"
"为什么我pip安装不了?"
额,这是我正在写的一个项目,还没有完全囊括安卓的方方面面,所以先不上传pip了
(这个名字翻译过来是处女座,我觉得挺好听的(笑))
所以这个是什么?为什么Python可以写出这么好的页面?
原因是:这些页面完全不是用Python构建的,而是用安卓本身提供的控件构建的!也就是说,你可以用这个库做到与其他大型软件完全没有区别的UI设计,只不过后台逻辑语言由Java换成了Python而已!所有的元素,诸如按钮,输入框,信息对话框等等全都是Android提供的!甚至,你可以自由使用网上其他人写的安卓页面拓展!
让我们一部分一部分地看代码
.0 前提条件,准备两个XML文件用于写页面,完全使用Android开发者的方法写页面(后期我打算用我自制的一门标记语言来简化这个过程,已经写好了),这里只放出一小部分,大家看看就好
第一个页面的XML
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingLeft="5dp"
- android:paddingRight="5dp"
- android:id="@+id/mylayout">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingTop="60dp"
- android:paddingBottom="40dp"
- android:text="@string/login"
- android:textColor="#E6941A"
- android:textSize="30sp" />
-
- <EditText
- android:id="@+id/usr_input"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:autofillHints="username"
- android:background="@drawable/et_style"
- android:drawableStart="@mipmap/usr"
- android:drawablePadding="10dp"
- android:hint="@string/usr_hint"
- android:minLines="1"
- android:paddingLeft="10dp"
- android:paddingTop="10dp"
- android:paddingRight="10dp"
- android:paddingBottom="10dp"
- android:textColor="#2BD5B3"
- tools:ignore="InvalidId" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <EditText
- android:id="@+id/pwd_input"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:autofillHints="username"
- android:background="@drawable/et_style"
- android:drawableStart="@mipmap/password"
- android:drawablePadding="10dp"
- android:hint="@string/pwd_hint"
- android:minLines="1"
- android:paddingLeft="10dp"
- android:paddingTop="10dp"
- android:paddingRight="10dp"
- android:paddingBottom="10dp"
- android:selectAllOnFocus="true"
- android:textColor="#2BD5B3" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
-
- <Button
- android:id="@+id/login_btn"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/btu"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:text="@string/login_btn"
- android:textColor="#FFFFFFFF" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <Button
- android:id="@+id/register_btn"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/register_btn"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:text="@string/register_btn"
- android:textColor="#FFFFFFFF" />
-
- </LinearLayout>
第二个页面的XML
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/mylayout2"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingLeft="5dp"
- android:paddingTop="20dp"
- android:paddingRight="5dp"
- android:paddingBottom="20dp">
-
- <TextView
- android:id="@+id/textView3"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="欢迎来到用于测试的第二页!" />
-
- <Button
- android:id="@+id/button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="切回到上一页" />
-
- <Switch
- android:id="@+id/switch1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="开启测试开关1"
- tools:ignore="UseSwitchCompatOrMaterialXml" />
-
- <Switch
- android:id="@+id/switch2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="开启测试开关2"
- tools:ignore="UseSwitchCompatOrMaterialXml" />
-
- <TextView
- android:id="@+id/textView4"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="感谢" />
-
- </LinearLayout>
大家看看就好,关键的是类似 android:id="@+id/.."的代码,里面的【..】对应ID名称我们后面在Python代码中要用到;是不是这种构造方法挺费劲的,还需要写一大堆没啥意义的属性,很麻烦,所以我正准备移植自制的标记语言
1. 导入必要的模块和类,我都已经写注释了,大家可以自己看看
- from Virgo.android.APP.app import AndroidAPP # 导入AndroidAPP类,用于调控和管理各个Activity和切换
- from Virgo.android.ui.Element.Toast import toast # 导入吐司消息条(额,就是那个底部出现的小黑条消息,一会就没了的那种)
- from Virgo.android.core_components.Activity import ControlActivity # Activity对象
- from Virgo.android.core_components.View_Listeners import VOnClickListener # 按钮回调监听器
- from Virgo.android.ui.Element.AlertDialog import Builder # 导入信息对话框控件
- from Virgo.android.core_components.Bundle import VBundle # 导入Bundle,用于传输数据
如果你用Java写过Android,那你肯定对这些东西很熟悉!因为这根本就是Java的Python绑定诶!
2. 写自己的Activity类,继承ControlActivity类,用于定义不同页面的逻辑
第一个Activity
- class MyActivity(ControlActivity): # 首先,写一个自己的Activity,名字随便,但要继承ControlActivity
- def __init__(self, APP):
- super().__init__("MainActivity", APP) # 注意,父类的构造方法有两个参数,第一个为Activity名(必填,以后有用),第二个是APP对象,下面会实例化,不要着急
- self.weights = {} # 这个是自己定义的,用来存控件的
-
- def check_password(self): # 自己定义的函数,用来检测账号密码
- usr = self.weights["usr_input"].getText().toString() # 这个字典对象后文会赋值,获取目标控件并获取内部文字
- pwd = self.weights["pwd_input"].getText().toString()
- if usr == "cemeye" and pwd == "pwd": # 检测是否是对应的账号密码
- toast(self, "账户密码正确!") # 用吐司消息显示提示信息
- else:
- # 如果账号密码错误,显示提示对话框
- # Builder需要传入Activity对象,这里用self传入即可(对应代码:Builder(self))
- # set_title("来自开发者的提醒~")设置消息题目
- # set_message("..")设置信息内容
- # set_positive_button("确定", None) 设置按钮参数,如果需要,第二个参数的None可改为VOnClickListener并绑定回调函数,None表示按下按钮什么都不做
- # showMessage()显示消息,进入消息循环,此时这个Activity会进入Pause状态,会调用onPause()函数,如果你有需要,可以重写这个类的onPause()函数来执行对应功能
- # 类似的,还有onCreate等方法,在外部列出
- Builder(self).set_title("来自开发者的提醒~").set_message(
- "您输入的账号或密码有误。对了,这个库是我开发出来玩的哦~我觉得Java太麻烦,还是咱Python最好用对吧?所以我写了这个库").set_positive_button(
- "确定", None).showMessage()
- # 为输入框设置文字
- self.weights["usr_input"].setText("")
- self.weights["pwd_input"].setText("")
-
- def change_activity_to(self):
- # 又是自定义函数,用于切换Activity
- data = self.weights["usr_input"].getText().toString()
- # bundle用于在不同的Activity间传递额外的消息,这个是安卓必须得写法
- bundle = VBundle().putString("name", data) # 添加字符串类型的键值对,"name"为键,data为值,可在后文第二个Activity中通过键获取值
- self.APP.change_activity("Activity2",
- self_defined_bundle=bundle) # 切换Activity到"Activity2",这里是前面那个【Activity名(必填,以后有用)】的伏笔
- # self.finish() 这个是结束当前Activity的方法,也就是说你调用这个方法后再按返回键返回不了前一个页面了,而是直接关闭了
-
- def onCreate(self, savedInstanceState):
- # 重写的方法,在Activity创建时会调用基本上所有Activity都会重写这个方法
- # 题外话:在安卓的Java中也是这种方式,也需要重写Activity的这个方法!
-
- # 设置Activity的XML布局文件,为其设置布局
- self.setContentView("activity_main")
- # 用你在XML文件中填写的ID,获取对应的控件,并保存在self.weights这个字典里
- self.weights["usr_input"] = self.findViewById("usr_input")
- self.weights["pwd_input"] = self.findViewById("pwd_input")
- self.weights["login_btn"] = self.findViewById("login_btn")
- self.weights["rgr_btn"] = self.findViewById("register_btn")
- # 设置按钮回调函数的方法,挺麻烦的,不过这是安卓的写法移植,也就是说,安卓Java中也是这样,甚至要更麻烦
- self.weights["login_btn"].setOnClickListener(VOnClickListener().register_onClick(
- lambda view: self.check_password()
- ))
- self.weights["rgr_btn"].setOnClickListener(VOnClickListener().register_onClick(
- # lambda view: self.APP.startActivityForResult("Activity2", 100)
-
- # 这里用来开启第二个Activity,这就是为什么点击了第二个按钮后会跳转
- lambda view: self.change_activity_to()
- ))
-
- def onActivityResult(self, requestCode, resultCode, data):
- """
- 当目的Activity结束并且返回结果时调用
- :param requestCode: 期望的状态码(用于和resultCode做比较,判断结果是不是自己想要的)
- :param resultCode: 返回的状态码
- :param data: 数据Intent对象
- :return: None
- """
- # 这个是一个挺高级的用法了,如果前文采用注释中的【self.APP.startActivityForResult("Activity2", 100)】取代【lambda view: self.change_activity_to()】
- # 则第二个Activity的启动是带有目的性的,是为了返回结果给这个Activity的,所以当第二个Activity被销毁或暂停时,会返回结果给这个函数,也会执行这个函数
- # 别急,这也是Android Java的标准写法,都得这么写,Virgo只不过是移植了这种写法,或者说封装了这种写法?也就是你学过Android开发的话,开发Virgo就跟玩一样,
- # 里面的方法基本上都是一样的,流程也是一样的
- try:
- # Intent获取数据的方法,返回的是个Bundle对象,她有个方法是get,可以通过键获取值,前文也提到过
- data = data.getExtras()
- # 吐司消息显示~
- toast(self, str(data.get("data")))
- except Exception as e:
- ...
第二个Activity(我就略写了哈)
- class Activity2(ControlActivity):
- def __init__(self, APP):
- # 一样的,前面
- super().__init__("Activity2", APP)
- # 设置是否调用onBackPressed的super方法?
- # 这里的原因是,如果想实现【再按一次退出】的效果,必须要取缔原来onBackPressed的父类方法
- # 因为她父类的方法是点一次就退出当前Activity,不调用父类方法的效果就是,不管你按多少次返回键都退不出页面,只能自己写处理逻辑
- # 相应地,其他的可重写函数也可以通过这种方式不调用super,但不建议这样做
- self.if_call_super["onBackPressed"] = False
-
- # 数一下按了几次?
- self.press_times = 0
-
- def try_me(self):
- # 瞎起的方法名,self.finish()用于结束当前Activity
- self.finish()
-
- def onCreate(self, savedInstanceState):
- # 也是重写onCreate方法,不解释
- # 这个savedInstanceState,就是上文提到的传家宝,你可以通过 self.getBeforeData("name")的方法获取值
- self.setContentView("activity2")
- self.btn = self.findViewById("button")
- self.btn.setOnClickListener(VOnClickListener().register_onClick(
- lambda view: self.try_me()
- ))
- # 传家宝写法
- data = self.getBeforeData("name")
- print("!来自二号Activity!数据传过来的是:", data)
- self.findViewById("textView3").setText("你好!亲爱的[" + str(data) + "]")
-
- def onBackPressed(self):
- # 诺,重写的BackPressed方法,用来执行【按下返回键后的操作】
- self.press_times += 1
- # 恶搞一下,必须按够3次才能退出页面。你要是设置成100我也不拦着你(笑)
- if self.press_times >= 3:
- # 设置结果(如果当前Activity是以【目的性Activity】的方式启动【即前文startActivityForResult】)
- # 一般用Bundle设置结果
- self.set_result(100, bundle=VBundle().putString("data", "这是来自第二个Activity的数据!"))
- # 结束当前Activity
- self.finish()
额,说是略写,写着写着突然注释写多了
- class MainAPP(AndroidAPP):
- def __init__(self):
- super().__init__()
- self.append_activities([
- MyActivity(self),
- Activity2(self)
- ])
- # 绑定主Activity
- self.bind_activity("MainActivity")
这个,顾函数名思义就理解了,不写注释了
重要提醒:
当前项目是无法直接执行的,必须要经过Virgo打包APK才能执行,所以你直接右键【运行】10000%会报一个ModuleNotFoundError: No module named 'android',因为这个模块是在Android环境中动态赋予的!
emm,Virgo写的还是太简陋,所以不大好意思上传到pip上(笑)
下一步打算开发传感器支持!
附录,Activity的可重写函数与对应作用(有删减)
1. onCreate():当Activity被创建时调用,通常在此方法中进行布局的初始化和数据的加载。
2. onStart():当Activity可见但未获得焦点时调用。
3. onResume():当Activity获得焦点并开始活动时调用。
4. onPause():当Activity失去焦点但仍可见时调用,通常在此方法中保存数据和释放资源。
5. onStop():当Activity不再可见时调用,通常在此方法中释放资源。
6. onDestroy():当Activity被销毁时调用,通常在此方法中释放所有资源。
7. onRestart():当Activity从停止状态重新启动时调用。
10. onActivityResult():当Activity启动的子Activity返回结果时调用,用于处理返回的结果。
11. onBackPressed():当用户按下返回键时调用,通常在此方法中处理返回键的逻辑。
14. onConfigurationChanged():当设备配置发生改变时调用,例如旋转屏幕或改变语言设置。
作者最近才开始用CSDN,给个赞再走吧
(有问题或建议欢迎提出!不过我可能只有周末才能回复)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。