自定义Capacitor插件

2,063 阅读3分钟

2.png

上一篇讲了如何使用Capacitor打包安卓APK,在使用Capacitor开发的时候,我们会经常使用到它的内置插件,但,肯定会有插件不满足我们使用的时候。 这时候我们需要自定义一个我们自己的插件。

Capacitor原生给我们提供了很多插件,有官方插件和社区插件。

详细插件信息可以到官方查看capacitorjs.com/docs/plugin…

整体插件的封装还比较简单,比Uniapp原生插件容易多了,Uniapp原生插件 + 引用第三方SDK,那是一个恶心。

image.png

先描述一下,我想要写的插件

我在打包HTML的时候,遇到超过4G的资源,那么就会无法打包,因为安卓本身限制了最大只能打包4G,并且安卓设备无法安装超过4G的APK.

这时候,就需要在线去下载资源到本地,类似于王者荣耀的下载资源包功能。

具体功能如下:

  1. 先下载一个空壳的APK
  2. 进入程序后,请求版本更新
  3. 返回下载文件的JSON,跟本地保存的JSON进行diff对比,
  4. 增量更新,下载文件到本地目录,保存新版本信息到本地
  5. 新建一个Webview,展示读取下载的HTML

请求、文件下载、读取都有官方插件可以使用,唯独没有可以动态创建一个Webview去加载我下载下来的HTML。

那么,我需要写的插件就是去新建一个Webview加载本地HTML。

插件命名为:LoadLocalHtml

创建插件

创建一个class

打开Android studio,来到src main下面的 example app 下面创建一个Java Class。

在这个Class中会写我们主要的业务逻辑。

image.png

我创建了一个Class:LoadLocalHtml

image.png

LoadLocalHtml.java 实现

package com.example.app;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

// 插件名称,在web端注册的时候会使用到
@CapacitorPlugin(name = "LoadLocalHtml")
public class LoadLocalHtmlPlugin extends Plugin {

    // 插件方法,通过这个注解暴露方法,到时候web端就可以去调用这个方法
    @PluginMethod
    public void loadWebPage(PluginCall call) {
        // 获取web端传入的参数,此处传入了一个{ uri: string }
        String contentUriString = call.getString("uri");

        // 这一段是我的业务逻辑,大家看的时候可以适当忽略
        if (contentUriString != null) {
            Uri contentUri = Uri.parse(contentUriString);
            String fileContent = readFileContent(contentUri);
            String baseUrl = contentUriString.substring(0, contentUriString.lastIndexOf("/") + 1);

            if (fileContent != null) {
                Context context = getContext();
                getActivity().runOnUiThread(new Runnable() {
                    @SuppressLint("SetJavaScriptEnabled")
                    @Override
                    public void run() {
                        // 创建一个 FrameLayout 作为容器
                        FrameLayout layout = new FrameLayout(context);

                        // 创建Webview并且设置相关权限
                        WebView webView = new WebView(context);
                        webView.getSettings().setJavaScriptEnabled(true);
                        webView.getSettings().setAllowFileAccess(true);
                        webView.getSettings().setAllowFileAccessFromFileURLs(true);
                        webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
                        webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

                        // 加载页面
                        webView.loadDataWithBaseURL(baseUrl, fileContent, "text/html", "UTF-8", null);

                        // 监测Webview错误
                        webView.setWebViewClient(new WebViewClient() {
                            @Override
                            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                                Log.e("WebView Error", "Error: " + description);
                            }
                        });

                        // 将 WebView 添加到 FrameLayout 中
                        layout.addView(webView);

                        // 获取当前 Activity 的根视图
                        ViewGroup rootView = (ViewGroup) getActivity().getWindow().getDecorView().getRootView();

                        // 将 FrameLayout 添加到根视图中
                        rootView.addView(layout);

                        JSObject ret = new JSObject();
                        ret.put("message", "Web page loaded successfully");
                        
                        // 返回结果给web端
                        call.resolve(ret);
                    }
                });
            } else {
                call.reject("Failed to read file content");
            }
        } else {
            call.reject("Content URI is null");
        }
    }

    private String readFileContent(Uri contentUri) {
        try {
            ContentResolver contentResolver = getContext().getContentResolver();
            InputStream inputStream = contentResolver.openInputStream(contentUri);
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
            inputStream.close();
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

注册插件

在MainActivity.java文件中

public class MainActivity extends BridgeActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        // 注册插件
        registerPlugin(LoadLocalHtmlPlugin.class);
        super.onCreate(savedInstanceState);
    }
}

在web端

新建一个js或者ts文件,用于注册插件

LoadLocalhtmlPlugin.ts

import { registerPlugin } from '@capacitor/core'

export interface LoadLocalHtml {
   // 这是我们在插件中定义的方法,接收一个对象,里面有个uri属性
  loadWebPage(options: { uri: string }): Promise<{ value: string }>
}

// LoadLocalHtml 为我们刚刚创建的插件名称
const LoadLocalHtml = registerPlugin<LoadLocalHtml>('LoadLocalHtml')

export default LoadLocalHtml

插件的使用

<script setup lang="ts">
import LoadLocalHtml from './utils/LoadLocalhtmlPlugin'

onMounted(() => {
    const path = 'xxxx'
    LoadLocalHtml.loadWebPage({ uri: path })
})
</script>