BeeWare 开发安卓程序 OpenCV,文件读写,基于安卓14

        前几天需要快速开发一个小程序,开始使用android_studio开发,奈何学的时候已经是4年多之前,一看现在的开发完全变成了陌生的样子。

        先用deepseek在python代码上修改,快速搭了一个基于compose的框架,能运行,但是生成的图片在python验证会出错。故想着使用python来开发,不想在kotlin去引入opencv库(教训:早知道还是应该使用Android_studio进行原生开发,起码资料多)

        开发流程

  1. 选择开发框架,百度了一下,发现有kivy,beeware,flet等,当时看见beeware有中文文档就选择beeware,哪知道这是炼狱的开始。
  2. 按照官网教程快速搭建了框架BeeWare教程,来到教程五,稍微花了一点时间去部署环境,这里需要科学上网。完成此步骤并将apk安装到真机上
  3. 界面好做,功能难写,官网基本没有安卓开发的参考,这是我的界面代码,没有参考意义
        def startup(self):
            # 主窗口
            main_box = toga.Box(style=Pack(direction=COLUMN, padding=10))
    
            # UUID 输入
            uuid_label = toga.Label('UUID:', style=Pack(padding=(0, 5)))
            self.uuid_input = toga.TextInput(placeholder='请输入UUID', style=Pack(flex=1))
            uuid_box = toga.Box(style=Pack(direction=ROW, padding=5))
            uuid_box.add(uuid_label)
            uuid_box.add(self.uuid_input)
            main_box.add(uuid_box)
    
            # 生成随机UUID按钮
            self.random_uuid_button = toga.Button(
                '生成随机UUID',
                on_press=self.generate_random_uuid,
                style=Pack(padding=5)
            )
            main_box.add(self.random_uuid_button)
    
            # 日期选择
            date_label = toga.Label('截止日期:', style=Pack(padding=(0, 5)))
            self.date_input = toga.DateInput(style=Pack(flex=1))
            date_box = toga.Box(style=Pack(direction=ROW, padding=5))
            date_box.add(date_label)
            date_box.add(self.date_input)
            main_box.add(date_box)
    
            # 时间选择
            time_label = toga.Label('截止时间:', style=Pack(padding=(0, 5)))
            self.time_input = toga.TimeInput(style=Pack(flex=1))
            time_box = toga.Box(style=Pack(direction=ROW, padding=5))
            time_box.add(time_label)
            time_box.add(self.time_input)
            main_box.add(time_box)
    
            # 时间快捷按钮
            self.create_time_shortcuts(main_box)
    
            # 输出路径选择按钮
            self.output_button = toga.Button(
                '选择输出路径',
                on_press=self.select_output_path,
                style=Pack(padding=5)
            )
            main_box.add(self.output_button)
    
            # 生成按钮
            self.generate_button = toga.Button(
                '生成密钥文件',
                on_press=self.generate_key_file,
                style=Pack(padding=5)
            )
            main_box.add(self.generate_button)
    
            # 状态标签
            self.status_label = toga.Label('准备就绪', style=Pack(padding=5))
            main_box.add(self.status_label)
    
            # 设置主窗口内容
            self.main_window = toga.MainWindow(title=self.formal_name)
            self.main_window.content = main_box
            self.main_window.show()

  4. 这个天杀的Google把安卓13之后文件读写权限搞得特别复杂,难以申请到写文件权限,而Beeware也没有示例可以参考,在安卓上完全不像windows上能容易的获取文件路径。通过我不懈查找,找到了能快速写文件的方法,通过Intent来开启,这是Google的java代码,

    // Request code for creating a PDF document.
    private static final int CREATE_FILE = 1;
    
    private void createFile(Uri pickerInitialUri) {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("application/pdf");
        intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf");
    
        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker when your app creates the document.
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
    
        startActivityForResult(intent, CREATE_FILE);
    }

    但是beeware该怎么获取?摸索了半天找到一个开启电话的intent示例,but这个android.content是那里的python库。我找了一会也没有查找到,有大佬知道的可以说一下

    from android.content import Intent
    from android.net import Uri
    
    intent = Intent(Intent.ACTION_DIAL)
    intent.setData(Uri.parse("tel:0123456789"))
    
    def number_dialed(result, data):
        # result is the status code (e.g., Activity.RESULT_OK)
        # data is the value returned by the activity.
        ...
    
    # Assuming your toga.App app instance is called `app`
    app._impl.start_activity(intent, on_complete=number_dialed)

    那么我就尝试修改相关intent参数,构造了请求,我需要写入的是图片,所有Type设置png,设置了一下Flag,但是好像不需要,因为我设置的是读权限(懒得改了)在回调中拿捏住文件的uri,可以理解是句柄,用于后续写入。我们需要在那个文件夹写文件,就设置文件名intent.putExtra(Intent.EXTRA_TITLE, "xxx.png")和文件夹intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);如果只设置文件名的话默认在Download文件夹下面。

    intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
    intent.setType("image/png")
    intent.addCategory(Intent.CATEGORY_OPENABLE)
    intent.putExtra(Intent.EXTRA_TITLE, "license.png")
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    def get_fileuri(result, data):
        self.file_uri=data.getData()
    self._impl.start_activity(intent, on_complete=get_fileuri)

  5. 但是这样写的文件只是一个空文件,我们需要处理好数据后再写入,通过在Toga-android库里面摸索,我找到了from java.io import FileOutputStream,ByteArrayOutputStream这些写文件的库。第一行pfd的获取是我翻源码然后找的,对于普通的字符串写入就很简单,data_bytes = f"Overwritten at {current_time_ms}\n".encode('utf-8'),然后fileOutputStream.write(data_bytes ),我的png_bytes是图片,处理不太一样

    pfd = self._impl.native.getApplicationContext().getContentResolver().openFileDescriptor(self.file_uri, "w")
            fileOutputStream = FileOutputStream(pfd.getFileDescriptor())
            fileOutputStream.write(png_bytes)
            fileOutputStream.close()
            pfd.close()

    可以看见这个nvtive基本就是java的mainactivaty,那么我们可以做的事情就多了,这也是前面能获取pfd的前提。

        def _native_checkSelfPermission(self, permission):  # pragma: no cover
            # A wrapper around the native method so that it can be mocked during testing.
            return ContextCompat.checkSelfPermission(
                self.native.getApplicationContext(), permission
            )
    
        def _native_requestPermissions(self, permissions, code):  # pragma: no cover
            # A wrapper around the native method so that it can be mocked during testing.
            self.native.requestPermissions(permissions, code)

    最后是相关依赖库,如opencv,numpy的打包,这个在pyproject.toml写入就好,

    requires = [
        "numpy",
        "opencv-python",
        "pycryptodome",
    ]

    总结,没有参考,可恶的google权限,我真的会谢,目前尽量别用python来开发安卓,老老实实原生开发,除非你有不错的python和安卓基础。

  6. 动态权限申请,手动修改了beeware的安卓框架jave代码去动态权限申请,代码中就是注释的地方,在低版本安卓能正常申请,高了就不太行

        protected void onCreate(Bundle savedInstanceState) {
            Log.d(TAG, "onCreate() start");
            // Change away from the splash screen theme to the app theme.
            setTheme(R.style.AppTheme);
            super.onCreate(savedInstanceState);
            LinearLayout layout = new LinearLayout(this);
            this.setContentView(layout);
            singletonThis = this;
    
            Python py;
            if (Python.isStarted()) {
                Log.d(TAG, "Python already started");
                py = Python.getInstance();
            } else {
                Log.d(TAG, "Starting Python");
                AndroidPlatform platform = new AndroidPlatform(this);
                platform.redirectStdioToLogcat();
                Python.start(platform);
                py = Python.getInstance();
    
                String argvStr = getIntent().getStringExtra("org.beeware.ARGV");
                if (argvStr != null) {
                    try {
                        JSONArray argvJson = new JSONArray(argvStr);
                        List<PyObject> sysArgv = py.getModule("sys").get("argv").asList();
                        for (int i = 0; i < argvJson.length(); i++) {
                            sysArgv.add(PyObject.fromJava(argvJson.getString(i)));
                        }
                    } catch (JSONException e) {
                        throw new RuntimeException(e);
                    }
                }
                Log.d(TAG, "Running main module " + argvStr);
            }
            
            Log.d(TAG, "Running main module " + getString(R.string.main_module));
            py.getModule("runpy").callAttr(
                "run_module",
                getString(R.string.main_module),
                new Kwarg("run_name", "__main__"),
                new Kwarg("alter_sys", true)
            );
    
            // if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            //     ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, WRITE_EXTERNAL_STORAGE);
            //     Log.d("Request Permission", "request permission");
            // } else {
            //     Log.d("Request Permission", "request permission ok");
            // }
            userCode("onCreate");
    
            Log.d(TAG, "onCreate() complete");
        }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值