Skip to content

Cordova 开发日记 08:与原生 Android 交互

以下是记录 JS 调用 Native 的全过程

定义 Java Class

java
package com.junl.cordova;

import android.widget.Toast;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;

public class NUI extends CordovaPlugin {
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {

        // 前端回调的方法名
        if ("showToast".equals(action)) {
            return executeShowToast(args, callbackContext);
        } else {
            callbackContext.error("发生异常,请检查API使用是否正确");
            return false;
        }
    }

    /**
     * 显示toast的原生方法
     */
    private boolean executeShowToast(JSONArray args, CallbackContext callbackContext) {
        try {
            CordovaArgs cordovaArgs = new CordovaArgs(args);
            String text = cordovaArgs.getJSONObject(0).getString("text");
            android.widget.Toast.makeText(cordova.getActivity(), text, Toast.LENGTH_LONG).show();
            callbackContext.success();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            callbackContext.error("显示toast异常");
            return false;
        }
    }
}

声明对外暴露的配置

config.xml 中:

xml
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.hello" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <feature name="Whitelist">
        <param name="android-package" value="org.apache.cordova.whitelist.WhitelistPlugin" />
        <param name="onload" value="true" />
    </feature>

    <feature name="NUI">
        <param name="android-package" value="com.junl.cordova.NUI"/>
    </feature>

    <name>HelloWorld</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="[email protected]" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <allow-intent href="market:*" />
    <preference name="loglevel" value="DEBUG" />
</widget>

JS 调用示例

原生方法的调用方式:exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);

html
<script src="cordova.js"></script>
javascript
function success(msg) {}
function error(msg) {}
function onDeviceReady() {
  const params = [
    {
      text: "你好,我是 Toast",
    },
  ];
  cordova.exec(success, error, "NUI", "showToast", params);
}
document.addEventListener("deviceready", onDeviceReady, false);

问题:webview 中进行跳转时打开的是外部浏览器

希望直接替换 webview 的页面,这个需要在 config.xml 配置一下白名单

xml
<widget id="com.example.hello"
    version="1.0.0"
    xmlns="http://www.w3.org/ns/widgets">
    <!--允许加载所有url-->
    <allow-navigation href="http://*/*" />
    <allow-navigation href="https://*/*" />
</widget>

问题:android 9.0 webview 不能使用 http 协议打开页面

增加 android:usesCleartextTraffictrue

xml
<?xml version='1.0' encoding='utf-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hello"
    android:hardwareAccelerated="true"
    android:versionCode="10000"
    android:versionName="1.0.0">
    <!--...-->
    <application
        android:hardwareAccelerated="true"
        android:usesCleartextTraffic="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true">
        <!--...-->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
</manifest>

问题:如何避免启动页白屏

我的解决思路是把启动图设置为第一个 activity 的背景。这样启动时会立即生效。

res/values/styles.xml 中定义样式:

xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 应用启动页(StartingWindow)的theme -->
    <style name="AppTheme.StartingWindowTheme" parent="Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@drawable/img_welcome</item>
    </style>
    <style name="AppTheme.MainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar"></style>
</resources>

AndroidManifest.xml 中应用样式:

xml
<activity
    android:name="MainActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
    android:label="@string/activity_name"
    android:launchMode="singleTop"
    android:theme="@style/AppTheme.StartingWindowTheme"
    android:windowSoftInputMode="adjustResize">
    <intent-filter android:label="@string/launcher_name">
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

MainActivity.java 中还原样式:

java
package com.example.hello;

import android.os.Bundle;

import com.junl.cordova.utils.PermissionUtil;

import org.apache.cordova.CordovaActivity;

public class MainActivity extends CordovaActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(R.style.AppTheme_MainTheme); // 恢复原有的样式
        // 允许 Cordova 后台运行
        Bundle extras = getIntent().getExtras();
        if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
            moveTaskToBack(true);
        }

        /* 申请程序所需权限 */
        if (!PermissionUtil.hasPermission(this)) {
            PermissionUtil.requestPermission(this);
        }

        loadUrl(launchUrl);
    }
}

Version 4.1 (framework-1.1.4)