• Android H5混合开发(5):封装Cordova View, 让Fragment、弹框、Activity自由使用Cordova


    近期,有同事咨询如何在Fragment中使用Cordova,看了下Cordova源码,官方并没有提供包含Cordova Webview的Fragment,以供我们继承。

    上网查询了一下,也有几篇文章讲解Fragment中如何使用Cordova,不过Cordova逻辑与Fragment逻辑耦合太深,不太适用于常规项目开发。

    通过分析CordovaActivity的源码实现,我们只需要将Cordova封装成自定义View就可以了。后面的演示,咱们还是基于之前的工程吧,代码会在后面分享给大家的。

    CordovaView实现的目标:

    应像系统Webview一样,与页面逻辑解耦,且方便使用

    1、CordovaView的逻辑应独立;
    2、能在Fragment中使用;
    3、能在Activity中使用;
    4、能在弹框中使用;
    

    CordovaView封装

    因我这边时间比较紧张,所以不带领大家去分析CordovaActivity的实现原理了,此处直接贴出自定义CordovaView的源码吧:

    自定义控件内容,主要包含:
    1、CordovaWebView的初始化、UI、URL加载、配合Activity、Fragment生命周期等;
    2、读取Cordova配置文件,设置页面相关属性;
    3、Crodova接口实现(无需自己写,直接使用Cordova自带的CordovaInterfaceImpl)
    4、异常消息回调,以便于使用方自己控制错误提示等;
    5、返回真实Webview控件,以便于使用方自行控制goback、reload、websetting等;
    6、其他...
    
    CordovaView.java实现 (重要的注释都已加上了):
    package com.ccc.ddd;
    
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.content.res.Configuration;
    import android.graphics.Color;
    import android.os.Bundle;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.WindowManager;
    import android.webkit.WebView;
    import android.widget.FrameLayout;
    import android.widget.RelativeLayout;
    
    import org.apache.cordova.Config;
    import org.apache.cordova.ConfigXmlParser;
    import org.apache.cordova.CordovaInterfaceImpl;
    import org.apache.cordova.CordovaPlugin;
    import org.apache.cordova.CordovaPreferences;
    import org.apache.cordova.CordovaWebView;
    import org.apache.cordova.CordovaWebViewEngine;
    import org.apache.cordova.CordovaWebViewImpl;
    import org.apache.cordova.PluginEntry;
    import org.apache.cordova.PluginManager;
    import org.apache.cordova.engine.SystemWebView;
    import org.apache.cordova.engine.SystemWebViewEngine;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    
    /**
     * 自定义Cordova控件
     * 1、可用于Activity、Fragment集成
     * 2、可在布局xml文件中引入
     * 3、可在代码中使用new关键字创建实例
     *
     * <p>
     * 使用示例:
     * String launchUrl = "file:///android_asset/www/index.html";
     * CordovaView cordovaView = view.findViewById(R.id.cv);
     * cordovaView.initCordova(getActivity());
     * cordovaView.loadUrl(launchUrl);
     * <p>
     * 
     * 作者:齐xc
     * 日期:2019.09.15
     */
    public class CordovaView extends RelativeLayout {
        //页面对象
        private Activity activity;
        //Cordova浏览器对象: 初始化、UI布局控制、url加载、生命周期(开始、暂停、销毁...)
        protected CordovaWebView appView;
        //Cordova配置对象: 各类配置信息读取、设置、使用
        protected CordovaPreferences preferences;
        //Cordova接口实现对象: 消息处理(页面跳转、页面数据存取、权限申请...)
        protected CordovaInterfaceImpl cordovaInterface;
        //是否保持运行
        protected boolean keepRunning = true;
        //是否沉浸式
        protected boolean immersiveMode;
        //默认启动url
        protected String launchUrl;
        //插件实体类集合
        protected ArrayList<PluginEntry> pluginEntries;
        //接收错误的监听器(用于回调页面加载错误,如:页面未找到等等。使用方需先调用方法:setOnReceivedErrorListener())
        private OnReceivedErrorListener errorListener;
    
        /**
         * 构造函数
         *
         * @param context 上下文
         */
        public CordovaView(Context context) {
            super(context);
        }
    
        /**
         * 构造函数
         *
         * @param context 上下文
         * @param attrs   属性
         */
        public CordovaView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        /**
         * 初始化Cordova
         *
         * @param activity 页面
         */
        public void initCordova(Activity activity) {
            this.activity = activity;
    
            //加载配置信息
            loadConfig();
    
            //设置页面是否全屏
            if (preferences.getBoolean("SetFullscreen", false)) {
                preferences.set("Fullscreen", true);
            }
            if (preferences.getBoolean("Fullscreen", false)) {
                if (!preferences.getBoolean("FullscreenNotImmersive", false)) {
                    immersiveMode = true;
                } else {
                    activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                            WindowManager.LayoutParams.FLAG_FULLSCREEN);
                }
            } else {
                activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
                        WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            }
    
            //实例化接口实现
            cordovaInterface = makeCordovaInterface();
    
            //设置背景为白色
            activity.getWindow().getDecorView().setBackgroundColor(Color.WHITE);
    
            //初始化
            initCordova();
        }
    
        /**
         * 加载配置信息
         * 1.读取默认启动的url  file:///android_asset/www/index.html
         * 2.读取res/xml/config.xml文件,获得插件集合 pluginEntries
         */
        private void loadConfig() {
            ConfigXmlParser parser = new ConfigXmlParser();
            parser.parse(activity);
            preferences = parser.getPreferences();
            preferences.setPreferencesBundle(activity.getIntent().getExtras());
            launchUrl = parser.getLaunchUrl();
            pluginEntries = parser.getPluginEntries();
            try {
                //通过反射处理
                Field field = Config.class.getDeclaredField("parser");
                field.setAccessible(true);
                field.set(null, parser);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        /**
         * 初始化
         */
        private void initCordova() {
            //实例化webview对象
            appView = makeWebView();
            //将webview加载到页面中,并根据参数配置其属性
            createViews();
            //如果"实例化接口"为空
            if (!appView.isInitialized()) {
                //appview初始化
                //初始化插件管理
                //初始化消息队列
                //初始化桥模块
                //......
                appView.init(cordovaInterface, pluginEntries, preferences);
            }
            //设置插件管理器
            //设置onActivityResult消息回调
            //设置Activity销毁处理
            cordovaInterface.onCordovaInit(appView.getPluginManager());
        }
    
    
        /**
         * 创建views
         */
        @SuppressWarnings({"deprecation", "ResourceType"})
        private void createViews() {
            //appView.getView()指SystemWebViewEngine的SystemWebView,继承自android.webkit.WebView
            appView.getView().setId(100);
            appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));
    
            //设置当前视图为SystemWebViewEngine的SystemWebView
            //setContentView(appView.getView());
            this.removeAllViews();
            this.addView(appView.getView(), new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    
            //如果preferences有配置背景色,则设置webview背景色
            if (preferences.contains("BackgroundColor")) {
                try {
                    int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
                    // Background of activity:
                    appView.getView().setBackgroundColor(backgroundColor);
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                }
            }
    
            //webview获得焦点(不受touch限制)
            appView.getView().requestFocusFromTouch();
        }
    
        /**
         * 构建CordovaWebView
         *
         * @return CordovaWebView
         */
        private CordovaWebView makeWebView() {
            //1.通过preferences配置信息构建CordovaWebViewEngine
            //2.通过CordovaWebViewEngine构建CordovaWebView
            return new CordovaWebViewImpl(makeWebViewEngine());
        }
    
        /**
         * 通过preferences配置信息构建CordovaWebViewEngine
         *
         * @return CordovaWebViewEngine
         */
        private CordovaWebViewEngine makeWebViewEngine() {
            return CordovaWebViewImpl.createEngine(activity, preferences);
        }
    
        /**
         * 构建接口实现类,接收消息
         *
         * @return CordovaInterfaceImpl
         */
        private CordovaInterfaceImpl makeCordovaInterface() {
            return new CordovaInterfaceImpl(activity) {
                @Override
                public Object onMessage(String id, Object data) {
                    return CordovaView.this.onMessage(id, data);
                }
            };
        }
    
        /**
         * 处理消息
         *
         * @param id   消息id
         * @param data 消息数据
         * @return 处理结果
         */
        public Object onMessage(String id, Object data) {
            try {
                if ("onReceivedError".equals(id)) {
                    JSONObject d = (JSONObject) data;
                    try {
                        //将消息透传给客户端
                        if (errorListener != null) {
                            int errorCode = d.getInt("errorCode");
                            String description = d.getString("description");
                            String failingUrl = d.getString("url");
                            errorListener.onReceivedError(errorCode, description, failingUrl);
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }
    
    
        /**
         * 获得Webview组件,供客户端使用
         *
         * @return Webview组件
         */
        public SystemWebView getWebview() {
            if (appView != null && appView.getView() instanceof WebView) {
                SystemWebView webView = (SystemWebView) appView.getView();
                return webView;
            }
            return null;
        }
    
        /**
         * 获得系统webview引擎
         * @return 系统webview引擎
         */
        public SystemWebViewEngine getSystemWebViewEngine(){
            return (SystemWebViewEngine) appView.getEngine();
        }
    
        /**
         * 加载url
         *
         * @param url 地址,默认应是:file:///android_asset/www/index.html
         */
        public void loadUrl(String url) {
            if (appView == null) {
                initCordova();
            }
    
            // If keepRunning
            // 如果preferences配置了KeepRunning,则页面置于后台时,仍可见
            this.keepRunning = preferences.getBoolean("KeepRunning", true);
    
            //加载url
            //第2个参数表示重新初始化插件管理器、插件集合等
            appView.loadUrlIntoView(url, true);
        }
    
        /**
         * 当页面执行onPause方法时,可调用
         */
        public void onPause() {
            if (this.appView != null) {
                CordovaPlugin activityResultCallback = null;
                try {
                    Field field = CordovaInterfaceImpl.class.getDeclaredField("activityResultCallback");
                    field.setAccessible(true);
                    activityResultCallback = (CordovaPlugin) field.get(this.cordovaInterface);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                boolean keepRunning = this.keepRunning || activityResultCallback != null;
                this.appView.handlePause(keepRunning);
            }
        }
    
        /**
         * 当页面执行onNewIntent方法时,可调用
         */
        public void onNewIntent(Intent intent) {
            if (this.appView != null)
                this.appView.onNewIntent(intent);
        }
    
        /**
         * 当页面执行onResume方法时,可调用
         */
        public void onResume() {
            if (this.appView == null) {
                return;
            }
            activity.getWindow().getDecorView().requestFocus();
            this.appView.handleResume(this.keepRunning);
        }
    
        /**
         * 当页面执行onStop方法时,可调用
         */
        public void onStop() {
            if (this.appView == null) {
                return;
            }
            this.appView.handleStop();
        }
    
        /**
         * 当页面执行onStart方法时,可调用
         */
        public void onStart() {
            if (this.appView == null) {
                return;
            }
            this.appView.handleStart();
        }
    
        /**
         * 当页面执行onDestroy方法时,可调用
         */
        public void onDestroy() {
            if (this.appView != null) {
                appView.handleDestroy();
            }
        }
    
        /**
         * 当页面执行onWindowFocusChanged方法时,可调用
         */
        @SuppressLint("InlinedApi")
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            //设置沉浸式与全屏
            if (hasFocus && immersiveMode) {
                final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
                activity.getWindow().getDecorView().setSystemUiVisibility(uiOptions);
            }
        }
    
        /**
         * 当页面执行startActivityForResult方法时,可调用
         */
        @SuppressLint({"NewApi", "RestrictedApi"})
        public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
            // Capture requestCode here so that it is captured in the setActivityResultCallback() case.
            cordovaInterface.setActivityResultRequestCode(requestCode);
            //super.startActivityForResult(intent, requestCode, options);
        }
    
        /**
         * 当页面执行onActivityResult方法时,可调用
         */
        public void onActivityResult(int requestCode, int resultCode, Intent intent) {
            //super.onActivityResult(requestCode, resultCode, intent);
            cordovaInterface.onActivityResult(requestCode, resultCode, intent);
        }
    
        /**
         * 当页面执行onSaveInstanceState方法时,可调用
         */
        public void onSaveInstanceState(Bundle outState) {
            cordovaInterface.onSaveInstanceState(outState);
        }
    
        /**
         * 当页面执行onConfigurationChanged方法时,可调用
         */
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (this.appView == null) {
                return;
            }
            PluginManager pm = this.appView.getPluginManager();
            if (pm != null) {
                pm.onConfigurationChanged(newConfig);
            }
        }
    
        /**
         * 接口:接收到错误时的回调
         */
        public interface OnReceivedErrorListener {
            /**
             * 接收到错误
             *
             * @param errorCode   错误码(请参考Cordova官方定义)
             * @param description 错误描述
             * @param failingUrl  发生异常的url
             */
            void onReceivedError(int errorCode, String description, String failingUrl);
        }
    
        /**
         * 设置监听
         *
         * @param listener 监听器
         */
        public void setOnReceivedErrorListener(OnReceivedErrorListener listener) {
            this.errorListener = listener;
        }
    }
    

    如何使用

    1、Fragment中(灰常简单)

    布局文件:fragment_test.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffff00"
        tools:context=".TestFragment">
    
        <com.ccc.ddd.CordovaView
            android:id="@+id/cv"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </com.ccc.ddd.CordovaView>
    
    </FrameLayout>
    
    

    代码:TestFragment.java

    package com.ccc.ddd;
    
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.webkit.WebChromeClient;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    
    public class TestFragment extends Fragment {
        public TestFragment() {
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_test, container, false);
            initView(view);
            return view;
        }
    
        private void initView(View view) {
            String launchUrl = "file:///android_asset/www/index1.html";
            final CordovaView cordovaView = view.findViewById(R.id.cv);
            cordovaView.initCordova(getActivity());
            cordovaView.loadUrl(launchUrl);
            //如果需要处理异常,设置此回调即可;
            cordovaView.setOnReceivedErrorListener(new CordovaView.OnReceivedErrorListener() {
                @Override
                public void onReceivedError(int errorCode, String description, String failingUrl) {
                    Log.i("onReceivedError", "errorCode:" + errorCode + "     description:" + description + "     failingUrl:" + failingUrl);
                }
            });
            //获得Cordova的Webview控件,执行操作,如:reload、goback、设置缓存、获得进度条等等
            //cordovaView.getWebview().reload();
            //cordovaView.getWebview().goBack();
            /*cordovaView.getWebview().setWebChromeClient(new SystemWebChromeClient(cordovaView.getSystemWebViewEngine()) {
                //监听进度
                @Override
                public void onProgressChanged(WebView view, int newProgress) {
                    super.onProgressChanged(view, newProgress);
                    //设置页面加载进度
                    Log.i("newProgress","newProgress: "+newProgress);
                }
    
                @Override
                public void onReceivedTitle(WebView view, String title) {
                    super.onReceivedTitle(view, title);
                    //设置标题
                }
            });*/
        }
    }
    
    

    2、Activity中(同样灰常简单)

    布局文件:activity_test_cordova_view.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".TestCordovaViewActivity">
    
        <com.ccc.ddd.CordovaView
            android:id="@+id/cv"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </com.ccc.ddd.CordovaView>
    
    </android.support.constraint.ConstraintLayout>
    

    代码实现:TestCordovaViewActivity.java

    package com.ccc.ddd;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    
    public class TestCordovaViewActivity extends AppCompatActivity {
    
        private CordovaView cordovaView;
        private String launchUrl = "file:///android_asset/www/index.html";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test_cordova_view);
    
            cordovaView = findViewById(R.id.cv);
            cordovaView.initCordova(this);
            cordovaView.loadUrl(launchUrl);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            cordovaView.onDestroy();
        }
    }
    

    其他用法

    在弹框中实现,也是类似的,具体不再演示了。

    1)生命周期

    如果想关联Activity或Frament的生命周期,CordovaView中都已经预留了生命周期方法,只需在页面生命周期方法中,关联对应的方法即可,如:

    @Override
        protected void onDestroy() {
            super.onDestroy();
            cordovaView.onDestroy();
        }
    ...其他生命周期函数写法类似...
    
    2)异常消息处理

    CordovaWebview加载过程中如果遇到问题,会将错误信息回传,如果开发者需要处理其错误信息,只需设置监听即可:

    cordovaView.setOnReceivedErrorListener(new CordovaView.OnReceivedErrorListener() {
                @Override
                public void onReceivedError(int errorCode, String description, String failingUrl) {
                    Log.i("onReceivedError", "errorCode:" + errorCode + "     description:" + description + "     failingUrl:" + failingUrl);
                }
            });
    
    3)Webview控件

    通过cordovaView.getWebview()方法获得Webview控件,可用于配置WebSetting、设置goback、设置reload、监听加载进度等。

    cordovaView.getWebview().reload();
    cordovaView.getWebview().goBack();
    cordovaView.getWebview().setWebChromeClient(new SystemWebChromeClient(cordovaView.getSystemWebViewEngine()) {
                //监听进度
                @Override
                public void onProgressChanged(WebView view, int newProgress) {
                    super.onProgressChanged(view, newProgress);
                    //设置页面加载进度
                    Log.i("newProgress","newProgress: "+newProgress);
                }
    
                @Override
                public void onReceivedTitle(WebView view, String title) {
                    super.onReceivedTitle(view, title);
                    //设置标题
                }
            });
    

    Fragment中运行效果

    Demo源码下载

    https://pan.baidu.com/s/1_6Ms-K_Wj5EjCp01LVZHhg
    https://github.com/qxcwanxss/CordovaDemo.git

    如有不明白的地方,建议多看下CordovaActivity的源码实现 ,也欢迎留言,就是不一定有时间解答,哈哈。。。


    Android H5混合开发(1):构建Cordova 项目
    https://www.cnblogs.com/qixingchao/p/11654454.html

    Android H5混合开发(2):自定义Cordova插件
    https://www.cnblogs.com/qixingchao/p/11652418.html

    Android H5混合开发(3):原生Android项目里嵌入Cordova
    https://www.cnblogs.com/qixingchao/p/11652424.html

    Android H5混合开发(4):构建Cordova Jar包
    https://www.cnblogs.com/qixingchao/p/11652431.html

    Android H5混合开发(5):封装Cordova View, 让Fragment、弹框、Activity自由使用Cordova
    https://www.cnblogs.com/qixingchao/p/11652438.html

  • 相关阅读:
    linux多线程学习笔记五--线程安全【转】
    linux多线程学习笔记六--一次性初始化和线程私有数据【转】
    【Linux】可重入函数和线程安全的区别与联系【转】
    【Linux】自主实现my_sleep【转】
    Linux/Unix编程中的线程安全问题【转】
    C语言字符串操作总结大全(超详细)【转】
    linux中的strip命令简介------给文件脱衣服【转】
    FTK应用程序编程接口(API)手册-1【转】
    python编程(python开发的三种运行模式)【转】
    ftk学习记(label篇)【转】
  • 原文地址:https://www.cnblogs.com/qixingchao/p/11652438.html
Copyright © 2020-2023  润新知