当前位置:   article > 正文

Python开发Android新选择--Virgo库开发日记(1)_beeware调用chaquopy package repository

beeware调用chaquopy package repository

因为我们学校被用作高考考场了,所以终于有10天时间发篇博客了(笑)

咳咳,进入正题

01 引入

众所周知,Python开发安卓好像就三个选择:Kivy,BeeWare,p4a(Python-for-android)。这三个,我体验了前两个,发现几个问题:

1.Kivy是采用自研的UI和设计,导致效率低不说,很多Python第三方库都不能用...

2.BeeWare也有这个问题,很多Python第三方库都不能用,并且虽然是原生UI,但是很多安卓中的特性都不支持

3.p4a的问题就更大了,前提就得是有台Linux机器,打包环境配置及其费劲,还极大概率会出错,捣鼓半天都不一定有效,这把我们宝贵的时间全都浪费了....

下面给大家稍稍看一下页面区别(部分图片来自网络)

 1.Kivy与原生开发的页面效果对比

Kivy的页面效果

2.BeeWare(图片来自网络)

 可以看到,Beeware页面确实好了不少,但是据我所知好像它是不支持安卓传感器的,也不适合开发大型项目,只适合开发小工具,效果也不佳

当然,也不能光说人家的缺点,它们对于跨平台都有着较好的支持,如果你需要一次编写,处处运行的效果,就可以试试这两个库

02 尝试

“Python开发安卓应用怎么这么费劲,我想写点东西方便移动使用都没办法....”

"网上也没有对应的支持,那怎么办....."

"要不,自己写一个库来实现Python跨平台-安卓开发?"

说干就干,还真让我找到了方法:chaquopy

这是一个开源的,免费的Android插件(虽然最近才刚开源),可以帮你在安卓中打包进Python环境,再利用JNI技术实现调用Python;理论上只要没有平台依赖的第三方库(比如pywin32肯定不行)都可以被调用,诸如requests,tensorflow,pytorch,numpy好像都可以!但是这毕竟只是插件,没办法完全使用Python开发,还要写Java代码,好麻烦的说

网上chaquopy资料很少,基本上都是复制粘贴官网的极个别样例,只有一份全英文的文档摆在那..

没办法,百度翻译+文档生啃了一星期终于学明白了,(吐槽:它的类继承写的真有够麻烦的,还有什么动态代理和静态代理,头大)

03 Virgo库

先看效果(原本速度不是这么慢的,因为转换为GIF导致速度变慢了。原本的速度和普通APP完全无区别)

此APP除去Virgo库的代码,页面的控制逻辑完全采用Python构建

"Virgo库是什么?"

"为什么我pip安装不了?"

额,这是我正在写的一个项目,还没有完全囊括安卓的方方面面,所以先不上传pip了

(这个名字翻译过来是处女座,我觉得挺好听的(笑))

 

所以这个是什么?为什么Python可以写出这么好的页面?

原因是:这些页面完全不是用Python构建的,而是用安卓本身提供的控件构建的!也就是说,你可以用这个库做到与其他大型软件完全没有区别的UI设计,只不过后台逻辑语言由Java换成了Python而已!所有的元素,诸如按钮,输入框,信息对话框等等全都是Android提供的!甚至,你可以自由使用网上其他人写的安卓页面拓展!

03-1代码分析

让我们一部分一部分地看代码

.0 前提条件,准备两个XML文件用于写页面,完全使用Android开发者的方法写页面(后期我打算用我自制的一门标记语言来简化这个过程,已经写好了),这里只放出一小部分,大家看看就好

第一个页面的XML

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical"
  7. android:paddingLeft="5dp"
  8. android:paddingRight="5dp"
  9. android:id="@+id/mylayout">
  10. <TextView
  11. android:layout_width="match_parent"
  12. android:layout_height="wrap_content"
  13. android:gravity="center"
  14. android:paddingTop="60dp"
  15. android:paddingBottom="40dp"
  16. android:text="@string/login"
  17. android:textColor="#E6941A"
  18. android:textSize="30sp" />
  19. <EditText
  20. android:id="@+id/usr_input"
  21. android:layout_width="match_parent"
  22. android:layout_height="wrap_content"
  23. android:autofillHints="username"
  24. android:background="@drawable/et_style"
  25. android:drawableStart="@mipmap/usr"
  26. android:drawablePadding="10dp"
  27. android:hint="@string/usr_hint"
  28. android:minLines="1"
  29. android:paddingLeft="10dp"
  30. android:paddingTop="10dp"
  31. android:paddingRight="10dp"
  32. android:paddingBottom="10dp"
  33. android:textColor="#2BD5B3"
  34. tools:ignore="InvalidId" />
  35. <TextView
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content" />
  38. <EditText
  39. android:id="@+id/pwd_input"
  40. android:layout_width="match_parent"
  41. android:layout_height="wrap_content"
  42. android:autofillHints="username"
  43. android:background="@drawable/et_style"
  44. android:drawableStart="@mipmap/password"
  45. android:drawablePadding="10dp"
  46. android:hint="@string/pwd_hint"
  47. android:minLines="1"
  48. android:paddingLeft="10dp"
  49. android:paddingTop="10dp"
  50. android:paddingRight="10dp"
  51. android:paddingBottom="10dp"
  52. android:selectAllOnFocus="true"
  53. android:textColor="#2BD5B3" />
  54. <TextView
  55. android:layout_width="wrap_content"
  56. android:layout_height="wrap_content" />
  57. <Button
  58. android:id="@+id/login_btn"
  59. android:layout_width="match_parent"
  60. android:layout_height="wrap_content"
  61. android:background="@drawable/btu"
  62. android:paddingLeft="10dp"
  63. android:paddingRight="10dp"
  64. android:text="@string/login_btn"
  65. android:textColor="#FFFFFFFF" />
  66. <TextView
  67. android:layout_width="wrap_content"
  68. android:layout_height="wrap_content" />
  69. <Button
  70. android:id="@+id/register_btn"
  71. android:layout_width="match_parent"
  72. android:layout_height="wrap_content"
  73. android:background="@drawable/register_btn"
  74. android:paddingLeft="10dp"
  75. android:paddingRight="10dp"
  76. android:text="@string/register_btn"
  77. android:textColor="#FFFFFFFF" />
  78. </LinearLayout>

第二个页面的XML

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:id="@+id/mylayout2"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:orientation="vertical"
  8. android:paddingLeft="5dp"
  9. android:paddingTop="20dp"
  10. android:paddingRight="5dp"
  11. android:paddingBottom="20dp">
  12. <TextView
  13. android:id="@+id/textView3"
  14. android:layout_width="match_parent"
  15. android:layout_height="wrap_content"
  16. android:text="欢迎来到用于测试的第二页!" />
  17. <Button
  18. android:id="@+id/button"
  19. android:layout_width="match_parent"
  20. android:layout_height="wrap_content"
  21. android:text="切回到上一页" />
  22. <Switch
  23. android:id="@+id/switch1"
  24. android:layout_width="match_parent"
  25. android:layout_height="wrap_content"
  26. android:text="开启测试开关1"
  27. tools:ignore="UseSwitchCompatOrMaterialXml" />
  28. <Switch
  29. android:id="@+id/switch2"
  30. android:layout_width="match_parent"
  31. android:layout_height="wrap_content"
  32. android:text="开启测试开关2"
  33. tools:ignore="UseSwitchCompatOrMaterialXml" />
  34. <TextView
  35. android:id="@+id/textView4"
  36. android:layout_width="match_parent"
  37. android:layout_height="wrap_content"
  38. android:text="感谢" />
  39. </LinearLayout>

大家看看就好,关键的是类似 android:id="@+id/.."的代码,里面的【..】对应ID名称我们后面在Python代码中要用到;是不是这种构造方法挺费劲的,还需要写一大堆没啥意义的属性,很麻烦,所以我正准备移植自制的标记语言

1. 导入必要的模块和类,我都已经写注释了,大家可以自己看看

  1. from Virgo.android.APP.app import AndroidAPP # 导入AndroidAPP类,用于调控和管理各个Activity和切换
  2. from Virgo.android.ui.Element.Toast import toast # 导入吐司消息条(额,就是那个底部出现的小黑条消息,一会就没了的那种)
  3. from Virgo.android.core_components.Activity import ControlActivity # Activity对象
  4. from Virgo.android.core_components.View_Listeners import VOnClickListener # 按钮回调监听器
  5. from Virgo.android.ui.Element.AlertDialog import Builder # 导入信息对话框控件
  6. from Virgo.android.core_components.Bundle import VBundle # 导入Bundle,用于传输数据

 如果你用Java写过Android,那你肯定对这些东西很熟悉!因为这根本就是Java的Python绑定诶!

 2. 写自己的Activity类,继承ControlActivity类,用于定义不同页面的逻辑

第一个Activity

  1. class MyActivity(ControlActivity): # 首先,写一个自己的Activity,名字随便,但要继承ControlActivity
  2. def __init__(self, APP):
  3. super().__init__("MainActivity", APP) # 注意,父类的构造方法有两个参数,第一个为Activity名(必填,以后有用),第二个是APP对象,下面会实例化,不要着急
  4. self.weights = {} # 这个是自己定义的,用来存控件的
  5. def check_password(self): # 自己定义的函数,用来检测账号密码
  6. usr = self.weights["usr_input"].getText().toString() # 这个字典对象后文会赋值,获取目标控件并获取内部文字
  7. pwd = self.weights["pwd_input"].getText().toString()
  8. if usr == "cemeye" and pwd == "pwd": # 检测是否是对应的账号密码
  9. toast(self, "账户密码正确!") # 用吐司消息显示提示信息
  10. else:
  11. # 如果账号密码错误,显示提示对话框
  12. # Builder需要传入Activity对象,这里用self传入即可(对应代码:Builder(self))
  13. # set_title("来自开发者的提醒~")设置消息题目
  14. # set_message("..")设置信息内容
  15. # set_positive_button("确定", None) 设置按钮参数,如果需要,第二个参数的None可改为VOnClickListener并绑定回调函数,None表示按下按钮什么都不做
  16. # showMessage()显示消息,进入消息循环,此时这个Activity会进入Pause状态,会调用onPause()函数,如果你有需要,可以重写这个类的onPause()函数来执行对应功能
  17. # 类似的,还有onCreate等方法,在外部列出
  18. Builder(self).set_title("来自开发者的提醒~").set_message(
  19. "您输入的账号或密码有误。对了,这个库是我开发出来玩的哦~我觉得Java太麻烦,还是咱Python最好用对吧?所以我写了这个库").set_positive_button(
  20. "确定", None).showMessage()
  21. # 为输入框设置文字
  22. self.weights["usr_input"].setText("")
  23. self.weights["pwd_input"].setText("")
  24. def change_activity_to(self):
  25. # 又是自定义函数,用于切换Activity
  26. data = self.weights["usr_input"].getText().toString()
  27. # bundle用于在不同的Activity间传递额外的消息,这个是安卓必须得写法
  28. bundle = VBundle().putString("name", data) # 添加字符串类型的键值对,"name"为键,data为值,可在后文第二个Activity中通过键获取值
  29. self.APP.change_activity("Activity2",
  30. self_defined_bundle=bundle) # 切换Activity到"Activity2",这里是前面那个【Activity名(必填,以后有用)】的伏笔
  31. # self.finish() 这个是结束当前Activity的方法,也就是说你调用这个方法后再按返回键返回不了前一个页面了,而是直接关闭了
  32. def onCreate(self, savedInstanceState):
  33. # 重写的方法,在Activity创建时会调用基本上所有Activity都会重写这个方法
  34. # 题外话:在安卓的Java中也是这种方式,也需要重写Activity的这个方法!
  35. # 设置Activity的XML布局文件,为其设置布局
  36. self.setContentView("activity_main")
  37. # 用你在XML文件中填写的ID,获取对应的控件,并保存在self.weights这个字典里
  38. self.weights["usr_input"] = self.findViewById("usr_input")
  39. self.weights["pwd_input"] = self.findViewById("pwd_input")
  40. self.weights["login_btn"] = self.findViewById("login_btn")
  41. self.weights["rgr_btn"] = self.findViewById("register_btn")
  42. # 设置按钮回调函数的方法,挺麻烦的,不过这是安卓的写法移植,也就是说,安卓Java中也是这样,甚至要更麻烦
  43. self.weights["login_btn"].setOnClickListener(VOnClickListener().register_onClick(
  44. lambda view: self.check_password()
  45. ))
  46. self.weights["rgr_btn"].setOnClickListener(VOnClickListener().register_onClick(
  47. # lambda view: self.APP.startActivityForResult("Activity2", 100)
  48. # 这里用来开启第二个Activity,这就是为什么点击了第二个按钮后会跳转
  49. lambda view: self.change_activity_to()
  50. ))
  51. def onActivityResult(self, requestCode, resultCode, data):
  52. """
  53. 当目的Activity结束并且返回结果时调用
  54. :param requestCode: 期望的状态码(用于和resultCode做比较,判断结果是不是自己想要的)
  55. :param resultCode: 返回的状态码
  56. :param data: 数据Intent对象
  57. :return: None
  58. """
  59. # 这个是一个挺高级的用法了,如果前文采用注释中的【self.APP.startActivityForResult("Activity2", 100)】取代【lambda view: self.change_activity_to()】
  60. # 则第二个Activity的启动是带有目的性的,是为了返回结果给这个Activity的,所以当第二个Activity被销毁或暂停时,会返回结果给这个函数,也会执行这个函数
  61. # 别急,这也是Android Java的标准写法,都得这么写,Virgo只不过是移植了这种写法,或者说封装了这种写法?也就是你学过Android开发的话,开发Virgo就跟玩一样,
  62. # 里面的方法基本上都是一样的,流程也是一样的
  63. try:
  64. # Intent获取数据的方法,返回的是个Bundle对象,她有个方法是get,可以通过键获取值,前文也提到过
  65. data = data.getExtras()
  66. # 吐司消息显示~
  67. toast(self, str(data.get("data")))
  68. except Exception as e:
  69. ...

第二个Activity(我就略写了哈)

  1. class Activity2(ControlActivity):
  2. def __init__(self, APP):
  3. # 一样的,前面
  4. super().__init__("Activity2", APP)
  5. # 设置是否调用onBackPressed的super方法?
  6. # 这里的原因是,如果想实现【再按一次退出】的效果,必须要取缔原来onBackPressed的父类方法
  7. # 因为她父类的方法是点一次就退出当前Activity,不调用父类方法的效果就是,不管你按多少次返回键都退不出页面,只能自己写处理逻辑
  8. # 相应地,其他的可重写函数也可以通过这种方式不调用super,但不建议这样做
  9. self.if_call_super["onBackPressed"] = False
  10. # 数一下按了几次?
  11. self.press_times = 0
  12. def try_me(self):
  13. # 瞎起的方法名,self.finish()用于结束当前Activity
  14. self.finish()
  15. def onCreate(self, savedInstanceState):
  16. # 也是重写onCreate方法,不解释
  17. # 这个savedInstanceState,就是上文提到的传家宝,你可以通过 self.getBeforeData("name")的方法获取值
  18. self.setContentView("activity2")
  19. self.btn = self.findViewById("button")
  20. self.btn.setOnClickListener(VOnClickListener().register_onClick(
  21. lambda view: self.try_me()
  22. ))
  23. # 传家宝写法
  24. data = self.getBeforeData("name")
  25. print("!来自二号Activity!数据传过来的是:", data)
  26. self.findViewById("textView3").setText("你好!亲爱的[" + str(data) + "]")
  27. def onBackPressed(self):
  28. # 诺,重写的BackPressed方法,用来执行【按下返回键后的操作】
  29. self.press_times += 1
  30. # 恶搞一下,必须按够3次才能退出页面。你要是设置成100我也不拦着你(笑)
  31. if self.press_times >= 3:
  32. # 设置结果(如果当前Activity是以【目的性Activity】的方式启动【即前文startActivityForResult】)
  33. # 一般用Bundle设置结果
  34. self.set_result(100, bundle=VBundle().putString("data", "这是来自第二个Activity的数据!"))
  35. # 结束当前Activity
  36. self.finish()

额,说是略写,写着写着突然注释写多了

3.创建AndroidAPP对象,添加你创建的Activity

  1. class MainAPP(AndroidAPP):
  2. def __init__(self):
  3. super().__init__()
  4. self.append_activities([
  5. MyActivity(self),
  6. Activity2(self)
  7. ])
  8. # 绑定主Activity
  9. 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,给个赞再走吧

(有问题或建议欢迎提出!不过我可能只有周末才能回复)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/537921
推荐阅读
相关标签
  

闽ICP备14008679号