Cordova:安卓WebView和App交互的一个轮子

Cordova生态中有很多插件,插件是通过Web页面中的js脚本调用App实现两者的数据交互。如果有一些定制化的数据交互,我们可以通过实现自定义插件的方式来实现,对于一些轻量的数据交互,这里提供一种非插件方式的轮子:通过在App中开启一个本地的HttpServer来响应html页面的请求。下面记录一下实现步骤。

1 添加依赖

添加nanohttpd依赖包,用于实现轻量级http服务。另外,为了便于操作,添加阿里巴巴的fastjson包。如下:

注意:添加外部引用包,不能添加到build.gradle文件中,因为该文件在Cordova框架构建过程中会重新生成。这里需要按照以下格式将外部依赖添加到安卓平台项目根目录下的project.properties文件中,并标定依赖包的顺序编号

cordova.system.library.1=org.nanohttpd:nanohttpd:2.3.1
cordova.system.library.2=com.alibaba:fastjson:1.2.68

2 构造相关类

这里我们实现一个通过Html页面的JS发送Post请求获取本机网络、存储信息的功能。

2.1 创建全局上下文类

为便于在全局使用,创建上下文类CordovaContext,使当前上下文在全局可用,代码如下:

public final class CordovaContext {
    private static Activity context ;

    public static Activity getContext() {
        return context;
    }

    public static void setContext(Activity context) {
        CordovaContext.context = context;
    }
}

2.2 创建网络工具类

需要在AndroidManifest.xml文件中manifest节点下指定获取网络链接和网络状态的权限如下

<!--允许程序访问网络连接,可能产生GPRS流量-->
<uses-permission android:name="android.permission.INTERNET" />
<!--允许程序获取网络信息状态,如当前的网络连接是否有效-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

创建类NetworkUtil用于获取网络相关信息,代码如下:


public class NetworkUtil {
    //没有网络连接
    public static final int NETWORK_NONE = 0;
    //wifi连接
    public static final int NETWORK_WIFI = 1;
    //手机网络数据连接类型
    public static final int NETWORK_2G = 2;
    public static final int NETWORK_3G = 3;
    public static final int NETWORK_4G = 4;
    public static final int NETWORK_MOBILE = 5;

    public static String getNetworkType(Context context) {
        int type = getNetworkState(context);
        switch (type) {
            case 0:
                return "无网络连接";
            case 1:
                return "Wifi网络";
            default:
                return "移动网络";
        }
    }

    /**
     * 获取当前网络连接类型
     *
     * @param context
     * @return
     */
    private static int getNetworkState(Context context) {
        //获取系统的网络服务
        ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        //如果当前没有网络
        if (null == connManager)
            return NETWORK_NONE;

        //获取当前网络类型,如果为空,返回无网络
        NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo();
        if (activeNetInfo == null || !activeNetInfo.isAvailable()) {
            return NETWORK_NONE;
        }

        // 判断是不是连接的是不是wifi
        NetworkInfo wifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
        if (null != wifiInfo) {
            NetworkInfo.State state = wifiInfo.getState();
            if (null != state)
                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
                    return NETWORK_WIFI;
                }
        }

        // 如果不是wifi,则判断当前连接的是运营商的哪种网络2g、3g、4g等
        NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);

        if (null != networkInfo) {
            NetworkInfo.State state = networkInfo.getState();
            String strSubTypeName = networkInfo.getSubtypeName();
            if (null != state)
                if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
                    switch (activeNetInfo.getSubtype()) {
                        //如果是2g类型
                        case TelephonyManager.NETWORK_TYPE_GPRS: // 联通2g
                        case TelephonyManager.NETWORK_TYPE_CDMA: // 电信2g
                        case TelephonyManager.NETWORK_TYPE_EDGE: // 移动2g
                        case TelephonyManager.NETWORK_TYPE_1xRTT:
                        case TelephonyManager.NETWORK_TYPE_IDEN:
                            return NETWORK_2G;
                        //如果是3g类型
                        case TelephonyManager.NETWORK_TYPE_EVDO_A: // 电信3g
                        case TelephonyManager.NETWORK_TYPE_UMTS:
                        case TelephonyManager.NETWORK_TYPE_EVDO_0:
                        case TelephonyManager.NETWORK_TYPE_HSDPA:
                        case TelephonyManager.NETWORK_TYPE_HSUPA:
                        case TelephonyManager.NETWORK_TYPE_HSPA:
                        case TelephonyManager.NETWORK_TYPE_EVDO_B:
                        case TelephonyManager.NETWORK_TYPE_EHRPD:
                        case TelephonyManager.NETWORK_TYPE_HSPAP:
                            return NETWORK_3G;
                        //如果是4g类型
                        case TelephonyManager.NETWORK_TYPE_LTE:
                            return NETWORK_4G;
                        default:
                            //中国移动 联通 电信 三种3G制式
                            if (strSubTypeName.equalsIgnoreCase("TD-SCDMA") || strSubTypeName.equalsIgnoreCase("WCDMA") || strSubTypeName.equalsIgnoreCase("CDMA2000")) {
                                return NETWORK_3G;
                            } else {
                                return NETWORK_MOBILE;
                            }
                    }
                }
        }
        return NETWORK_NONE;
    }
}

2.3 创建存储工具类

需要在AndroidManifest.xml文件中manifest节点下指定获取读写存储的权限,如下

<!--允许程序写入外部存储,如SD卡上读写文件-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

创建类StorageUtil用户获取Android机存储信息,代码如下。


public final class StorageUtil {
    /**
     * 根据路径获取内存状态
     *
     * @param path
     * @return
     */
    public static Map<String, String> getMemoryInfo(File path) {
        // 获得一个磁盘状态对象
        StatFs stat = new StatFs(path.getPath());
        long blockSize = stat.getBlockSizeLong();    // 获得一个扇区的大小
        long totalBlocks = stat.getBlockCountLong();    // 获得扇区的总数
        long availableBlocks = stat.getAvailableBlocksLong();    // 获得可用的扇区数量
        // 总空间
        String totalMemory = Formatter.formatFileSize(CordovaContext.getContext(), totalBlocks * blockSize);
        // 可用空间
        String availableMemory = Formatter.formatFileSize(CordovaContext.getContext(), availableBlocks * blockSize);
        Map<String, String> mInfo = new HashMap<>();
        mInfo.put("total", totalMemory);
        mInfo.put("available", availableMemory);
        return mInfo;
    }

    public static Map<String, String> getRamInfo() {
        ActivityManager manager = (ActivityManager) CordovaContext.getContext().getSystemService(CordovaContext.getContext().ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
        manager.getMemoryInfo(info);
        Map<String, String> mInfo = new HashMap<>();
        mInfo.put("total", Formatter.formatFileSize(CordovaContext.getContext(), info.totalMem));
        mInfo.put("available", Formatter.formatFileSize(CordovaContext.getContext(), info.availMem));
        return mInfo;
    }

    public static Map<String, String> getRomInfo() {
        return getMemoryInfo(Environment.getDataDirectory());
    }
}

2.4 创建服务请求处理类

构造类LocalHttpServer开启安卓机本机Server,实现nanohttpd包中的NanoHTTPD类,用于响应Html页面的请求。默认使用端口9090,绑定本机所有地址(可通过父类的另外一个构造函数,限定绑定的IP,例如仅绑定本机回送地址127.0.0.1,就不能响应其它IP发来的请求)。

在下面例子中,响应请求功能主要在serve方法中,这里通过请求中op参数的值判断需要做什么类型的操作,例如sysInfo表示获取系统信息。

完整代码如下:


public final class LocalHttpServer extends NanoHTTPD {
    private static final String TAG = "LocalHttpServer";
    private static boolean started = false;
    private static final int port = 9090;
    private static LocalHttpServer instance;

    static {
        try {
            instance = new LocalHttpServer();
        } catch (Exception e) {
            org.apache.cordova.LOG.e(TAG, "初始化本地服务异常!", e);
        }
    }

    private LocalHttpServer() {
        super(port);//绑定所有地址
    }

    public static LocalHttpServer getInstance() {
        return instance;
    }

    public void startServer() {
        if (started) {
            org.apache.cordova.LOG.w(TAG, "本地服务已启动," + port);
            return;
        }
        if (instance == null) {
            try {
                instance = new LocalHttpServer();
            } catch (Exception e) {
                org.apache.cordova.LOG.e(TAG, "初始化启动本地服务异常!", e);
                return;
            }
        }
        try {
            //super("127.0.0.1", 9090);//仅绑定本地回送地址 只能响应本机发的请求
            start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
            started = true;
        } catch (Exception e) {
            started = false;
        }
    }


    @Override
    public Response serve(IHTTPSession session) {
        if (session.getMethod().name().equals("OPTIONS")) {
            Response response = newFixedLengthResponse("");
            setDefaultHeader(response);
            return response;
        }
        if (session.getMethod().equals(Method.POST)) {
            try {
                session.parseBody(new HashMap<>());
            } catch (IOException | ResponseException e) {
                org.apache.cordova.LOG.e(TAG, "post 参数转化错误", e);
            }
        }
        Map<String, List<String>> params = session.getParameters();
        String opType = this.getOneParam("op", params);
        Response response;
        if (TextUtils.isEmpty(opType)) {
            response = newFixedLengthResponse(new JSONObject().fluentPut("success", false).fluentPut("data", "").fluentPut("message", "parameter op is required").toString());
            response.setStatus(Response.Status.BAD_REQUEST);
            setDefaultHeader(response);
            return response;
        }
        switch (opType) {
            case "sysInfo":
                // 获得sd卡的内存状态
                File sdcardFileDir = Environment.getExternalStorageDirectory();
                // 获得手机内部存储控件的状态
                File dataFileDir = Environment.getDataDirectory();
                JSONObject res = new JSONObject().fluentPut("success", true)
                        .fluentPut("data",
                                new JSONObject()
                                        .fluentPut("Ram", StorageUtil.getRamInfo())
                                        .fluentPut("Rom", StorageUtil.getRomInfo())
                                        .fluentPut("Sdcard", StorageUtil.getMemoryInfo(sdcardFileDir))
                                        .fluentPut("Internal", StorageUtil.getMemoryInfo(dataFileDir))
                                        .fluentPut("Network", NetworkUtil.getNetworkType(CordovaContext.getContext()))
                                        .fluentPut("Release", Build.VERSION.RELEASE)
                                        .fluentPut("BaseOs", Build.VERSION.BASE_OS)
                                        .fluentPut("Version", Build.ID + "_" + Build.VERSION.INCREMENTAL)
                                        .fluentPut("Model", Build.MODEL)
                                        .fluentPut("Serial", Build.SERIAL)
                        ).fluentPut("message", "指令执行完成");
                response = newFixedLengthResponse(res.toString());
                response.setStatus(Response.Status.OK);
                setDefaultHeader(response);
                return response;
            default:
                response = newFixedLengthResponse(new JSONObject().fluentPut("success", false).fluentPut("data", "").fluentPut("message", "invalid operation").toString());
                response.setStatus(Response.Status.BAD_REQUEST);
                setDefaultHeader(response);
                break;
        }
        return response;
    }

    private void setDefaultHeader(Response response) {
        response.addHeader("Content-Type", "application/json");
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Allow-Headers", "Hlh-Code, Authorization");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
    }

    /**
     * 获取参数
     *
     * @param key    参数名
     * @param params 参数列表
     * @return
     */
    private String getOneParam(String key, Map<String, List<String>> params) {
        if (TextUtils.isEmpty(key)) {
            return "";
        }
        if (params == null || params.size() <= 0) {
            return "";
        }
        List<String> tmps = params.get(key);
        if (tmps == null || tmps.size() <= 0) {
            return "";
        }
        String p = tmps.get(0);
        return TextUtils.isEmpty(p) ? "" : p;
    }
}

3 使用

在入口类MainActivityonCreate方法中,启用本地Server,如下:

LocalHttpServer.getInstance().startServer();

在这里插入图片描述

4 模拟请求

完成上面步骤的操作后,生成新的Apk安装包并安装启动,然后使用连接在同一个网络的电脑用Postman发送请求模拟效果如下,可正常响应请求。如果有其他类型的请求,在LocalHttpServer类中的serve方法中通过op参数判断实现即可。

注意,如果是在不同机器发送请求,LocalHttpServer类中不能只绑定本机回送地址,否则通过IP无法访问。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值