Android混合开发全解析

2023-11-02

     现在的app都开始流行混合开发了,这是一个app开发的新技术,作为android程序猿的我们也应该要了解并且掌握他。那么在使用之前,我们一定要搞清楚,我们的哪些场景使用混合开发好一些呢?这个问题一定要搞清楚,因为现在的混合开发还不成熟,Web页面的渲染效率目前还无法和Native的体验相比,而大家如果只是为了采用新技术就盲目的使用混合开发,最后遇到一些体验问题的话,肯定会得不偿失。

     那么什么情况适合Html 5开发呢?像一些活动页面,比如秒杀、团购等适合做Html 5,因为这些页面可能涉及的非常炫而且复杂,Html 5开发或许会简单点,关键是这些页面时效性短,更新更快,因为一个活动说不定就一周时间,下周换活动,如果这样的话,你还做Native是肯定不行的,这些场景就需要使用混合开发了。以下是网上能找到的一些比较好的入门介绍,大家可以学习一下。

     谈谈Android App混合开发

     Android 混合开发 的一些心得

     混合开发的实质就是在JS和Native之间相互调用,其中的第一篇博客中也提到了,实现混合开发的方式主要的有两种:1、js调用Native中的代码;2、WebView拦截页面跳转。第二种方式因为在Android 4.2(API 17)一下存在高危的漏洞,漏洞的原理就是Android系统通过 WebView.addJavascriptInterface(Object o, String interface) 方法注册可供js调用的Java对象,但是系统并没有对注册的Java对象方法调用做限制。导致攻击者可以利用反射调用未注册的其他任何Java对象,攻击者可以根据客户端的能力做任何事情,如下的文章详细讲解了漏洞产生的根本原因:

     WebView 远程代码执行漏洞浅析

     上面的博主使用的是别人封装好的一个js框架,git地址如下:

     safe-java-js-webview-bridge

     而现在介绍较多的还有Facebook的混合开发框架React Native,大家也可以去看一下:

     Use React Native

     react-native

     我们本节要分析的就是safe-java-js-webview-bridge框架了,这里的代码也非常简洁,主界面是WebActivity,看了下我这里的项目源码,有两个WebView类,一个是在frameworks/base/tools/layoutlib/bridge/src/android/webkit路径下的WebView,它是继承MockView的,还有一个是在vendor/letv/webview/base/core/java/android/webkit路径下,开始看到vendor目录,还以为把原生的东西重写的,后来问了下浏览器模块的同事,才知道这不是重写,而是把原生的移动了个目录而已,我们后面的分析也都是在这个包下面的类。


     我们从断点可以看到,获取回来的WebSettings的实现类是一个名称为ContentSettingsAdapter的对象,整个源码搜遍,找不到任何相关的东西,看来还是没有源码。这些可能也是谷歌Chrom浏览器的一些核心技术了,如果有哪位精通的,请指点我一下。


     我们先来看一下整个代码的执行逻辑:


     重点的地方我也标红出来了,整个WebView上的事件响应、界面显示都是在WebViewChromiumFactoryProvider类中处理的,WebViewChromiumFactoryProvider类是通过反射生成的,后边分析的过程中,大家会看到它的产生过程,这些没有源码,我们也无从得知它的处理逻辑;还有一个重点的地方,就是最后标红的那块,就是在构造JsCallJava对象时,通过StringBuilder拼接一个javascript的角本出来,拼接过程当中,就会通过调用genJavaMethodSign方法,把我们要回调的类的所有方法连接成string字符串注入到javascript角本当中,这里也就是为什么WebView浏览器能回调我们java代码,并且我们可以通过返回来的参数知道是要调用哪个方法的原因了。

     好了,下面我们就一起来看一下整个代码的执行过程。

     首先,通过new构造一个WebView对象,WebView的构造方法不断的转调,最终调用了protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, Map<String, Object> javaScriptInterfaces, boolean privateBrowsing)构造方法,我们来看一下它的实现:

    /**
     * @hide
     */
    @SuppressWarnings("deprecation")  // for super() call into deprecated base class constructor.
    protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
            Map
    
    
     
      javaScriptInterfaces, boolean privateBrowsing) {
        super(context, attrs, defStyleAttr, defStyleRes);
        if (context == null) {
            throw new IllegalArgumentException("Invalid context argument");
        }
        sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
                Build.VERSION_CODES.JELLY_BEAN_MR2;
        checkThread();
        if (TRACE) Log.d(LOGTAG, "WebView
     
     
      
      ");

        ensureProviderCreated();
        mProvider.init(javaScriptInterfaces, privateBrowsing);
        // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
        CookieSyncManager.setGetInstanceIsAllowed();
    }
     
     
    
    
     调用super父类的构造方法来初始化一些成员变量,这个过程我们就不分析了,跟之前动画全解析中的初始化的道理基本是一样的。最重要的就是ensureProviderCreated()这句了,它是对成员变量mProvider进行赋值的,这个mProvider也就是WebView最核心的东西了。ensureProviderCreated方法当中先通过调用getFactory()来获取一个WebViewFactoryProvider对象,然后再用它的createWebView方法为给成员变量mProvider赋值,getFactory方法中的实现又是调用WebViewFactory.getProvider()来完成的,我们来看一下这个方法的执行过程:

    static WebViewFactoryProvider getProvider() {
        synchronized (sProviderLock) {
            // For now the main purpose of this function (and the factory abstraction) is to keep
            // us honest and minimize usage of WebView internals when binding the proxy.
            if (sProviderInstance != null) return sProviderInstance;

            final int uid = android.os.Process.myUid();
            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
                throw new UnsupportedOperationException(
                        "For security reasons, WebView is not allowed in privileged processes");
            }

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
            try {
                Class
    
    
     
      providerClass = getProviderClass();

                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
                Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
                try {
                    sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
                            .newInstance(new WebViewDelegate());
                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                    return sProviderInstance;
                } catch (Exception e) {
                    Log.e(LOGTAG, "error instantiating provider", e);
                    throw new AndroidRuntimeException(e);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
        }
    }
    
    
     这个方法的返回值是WebViewFactoryProvider,它是一个接口,那么在这个过程中,肯定是生成了一个实体对象的,可以看到try代码块中的逻辑,调用getProviderClass()方法获取到一个providerClass,然后通过反射调用providerClass.getConstructor(WebViewDelegate.class).newInstance(new WebViewDelegate())来生成一个该类的对象sProviderInstance返回给调用者。那我们接下来就看一下getProviderClass方法的实现:

    private static Class
    
    
     
      getProviderClass() {
        try {
            // First fetch the package info so we can log the webview package version.
            sPackageInfo = fetchPackageInfo();
            Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
                sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
            loadNativeLibrary();
            Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);

            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
            try {
                return getChromiumProviderClass();
            } catch (ClassNotFoundException e) {
                Log.e(LOGTAG, "error loading provider", e);
                throw new AndroidRuntimeException(e);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
        } catch (MissingWebViewPackageException e) {
            // If the package doesn't exist, then try loading the null WebView instead.
            // If that succeeds, then this is a device without WebView support; if it fails then
            // swallow the failure, complain that the real WebView is missing and rethrow the
            // original exception.
            try {
                return (Class
     
     
      
      ) Class.forName(NULL_WEBVIEW_FACTORY);
            } catch (ClassNotFoundException e2) {
                // Ignore.
            }
            Log.e(LOGTAG, "Chromium WebView package does not exist", e);
            throw new AndroidRuntimeException(e);
        }
    }
     
     
    
    
     首先调用loadNativeLibrary()加载动态库,然后调用getChromiumProviderClass方法去生成我们要的class对象,那么继续跟踪看一下这个方法的实现:

    private static final String CHROMIUM_WEBVIEW_FACTORY =
            "com.android.webview.chromium.WebViewChromiumFactoryProvider";

    private static Class
    
    
     
      getChromiumProviderClass()
            throws ClassNotFoundException {
        Application initialApplication = AppGlobals.getInitialApplication();
        try {
            // Construct a package context to load the Java code into the current app.
            Context webViewContext = initialApplication.createPackageContext(
                    sPackageInfo.packageName,
                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
            initialApplication.getAssets().addAssetPath(
                    webViewContext.getApplicationInfo().sourceDir);
            ClassLoader clazzLoader = webViewContext.getClassLoader();
            Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
            try {
                return (Class
     
     
      
      ) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
                                                                     clazzLoader);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new MissingWebViewPackageException(e);
        }
    }
     
     
    
    
     在这个方法中,我们就看到要加载的目标类CHROMIUM_WEBVIEW_FACTORY,也就是我们流程图中红色标注的重点,生成这个类是用的当前webViewContext的类加载器来生成的,在Java的双亲委派模型中,大家可以知道,使用instanceof判断两个类是否相同时,相同的条件有两个:1、类的路径完全相同;2、加载这个类的加载器完全相同,只有这两个条件同时满足,instanceof判断才会为true,而最终的重量级类WebViewChromiumFactoryProvider没有源码,这样我们后边的很多分析过程也就无从得知了。

     上面的过程为我们创建了最重要的处理类,后边的逻辑基本就很简单了,继续调用createWebView生成一个WebViewProvider对象,并赋值给成员变量mProvider,然后调用init方法初始化,这样WebView对象就创建好了。然后调用wv.getSettings()获取一个WebSettings对象,并通过setJavaScriptEnabled方法设置它支持javascript,然后调用setWebChromeClient将我们的回调注入进去,最后设置WebView要加载的url地址。

     我们主要来看一下setWebChromeClient将我们的回调类注入进去的过程。CustomChromeClient的构造方法中直接调用父类InjectedChromeClient的构造方法,传入的两个参数"HostApp", HostJsScope.class,分别就是注入到H5页面中的名称和回调Native的Java类,在这里就利用这两个参数构造一个JsCallJava对象,我们再来看一下JsCallJava类的构造方法的实现:

    public JsCallJava (String injectedName, Class injectedCls) {
        try {
            if (TextUtils.isEmpty(injectedName)) {
                throw new Exception("injected name can not be null");
            }
            mInjectedName = injectedName;
            mMethodsMap = new HashMap
    
    
     
     ();
            //获取自身声明的所有方法(包括public private protected), getMethods会获得所有继承与非继承的方法
            Method[] methods = injectedCls.getDeclaredMethods();
            StringBuilder sb = new StringBuilder("javascript:(function(b){console.log(\"");
            sb.append(mInjectedName);
            sb.append(" initialization begin\");var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};");
            for (Method method : methods) {
                String sign;
                if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(method)) == null) {
                    continue;
                }
                mMethodsMap.put(sign, method);
                sb.append(String.format("a.%s=", method.getName()));
            }

            sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\"");
            sb.append(mInjectedName);
            sb.append(" call error, message:miss method name\"}var e=[];for(var h=1;h
     
     
    
    
     在这里我们可以看到,首先根据传进来的Class,利用反射获取到它的所有可执行方法,将这些方法保存在成员变量mMethodsMap当中,后边H5页面回调回来了,我们就根据传回来的参数对比一下,就知道需要执行哪个方法了,获取到的所有的方法也会通过string字符串的形式拼接成一个完整的javascript角本注入到WebView当中,这里执行完,那么所有的环境都准备好了,在WebView加载过程中,会回调onProgressChanged方法,也就是在这里,把我们拼接好的javascript角本注入进去的。我们把拼接完成的javascript角本字符串打印出来,用HBuilder格式化整齐看一下,如下图:


     看到javascript的角本看是头大了,语法好乱,可能也是自己不懂吧,以后还得好好学习。那么当我们在H5页面上点击的时候,就会通过javascript角本处理,然后回调WebView的onJsPrompt方法,也就是InjectedChromeClient类的onJsPrompt方法了,相应的参数都会通过这个方法中的message参数以string字符串的形式传过来,我们可以看一下获取IMSI方法的参数:


    这样就能保证Native和H5沟通无阻了,既然已经调用回来了,参数也都给我们了,那么接下来在native中执行就简单了,根据我们之前保存好的方法去匹配,找到目标后就直接执行,最后把结果返回给H5,返回数据给H5当然也是系统已经给我们把框架搭建好了的,我们只需要把数据传进去就OK了,真是太妙了!!!

     好了,理解完整个过程,那我们要自己去实现一个也就就简单了,最后我们来把要点总结一下:

     1:一定要支持javascript,可以通过ws.setJavaScriptEnabled(true)来设置

     2:要设置一个H5回调Native的ChromeClient对象,可以通过wv.setWebChromeClient来完成

     3:实现好你的Native回调类,也就是我要在这个类中干些什么事情,比如我要打开Activity,显示对话框,或者获取手机信息返回给H5等等

     4:要将你的回调类的所有方法通过javascript角本注入到ChromeClient当中,注入是在ChromeClient的onProgressChanged方法中完成的,注入的数据是以string拼接出来的

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android混合开发全解析 的相关文章

  • 判断这个数能被4整除,但是不能被100整除

    需求 用户输入一个 判断这个数能被4整除 但是不能被100整除 分析 1 用户输入 2 控制台 是否能被4整除并且100整除 let num prompt 请输入一个数 num num 0 防止用户输入空字符若是空字符就为0 let re

随机推荐

  • kdj超卖_kdj超卖是什么意思?kdj超买超卖区别是什么

    kdj超卖是什么意思 kdj超买超卖区别是什么 对于大多数股民来说 指标应用是股市投资必不可少的操作系统 指标的主要作用就是用来作参考 辅助自己进行股票投资 今天 在这里我们所要谈论的是kdj指标 kdj超卖是什么意思 如何判断kdj超买超
  • angular4学习指南,环境搭建,基础概念解析(一)

    一 Angular是什么 Angular是由google开发维护的一个开发跨平台应用的框架 同时适应PC端和移动端 两个大版本 1 5 和4 0 4 0完全重写 1 5之前的叫angularJS 4 0叫angular 二 Angular开
  • Netty02-入门

    二 Netty 入门 1 概述 1 1 Netty 是什么 Netty is an asynchronous event driven network application framework for rapid development
  • dlna 斐讯r1怎么用_斐讯R1智能(蓝牙)音箱固件升级教程

    斐讯R1智能 蓝牙 音箱固件升级教程 2019 07 05 17 46 00 55点赞 459收藏 112评论 儿子现在突然爱上听歌写作业 难得是听歌品味居然跟我8分像 书桌空间有限 看来一圈蓝牙音箱 发现斐讯遗产R1 哈曼认证单元 但据说
  • Python 频繁请求问题: [Errno 104] Connection reset by peer

    记遇到的一个问题 Errno 104 Connection reset by peer 今天工作上有个需求 数据库有个表有将近3万条url记录 每条记录都是一个图片 我需要请求他们拿到每个图片存到本地 一开始我是这么写的 伪代码 impor
  • Java学习笔记16——抽象类

    抽象类 抽象类 什么是抽象类 抽象的关键字 抽象类的特点 抽象类的成员特点 抽象类 什么是抽象类 在Java中 一个没有方法体的方法 应该被定义为抽象方法 而类中如果有抽象方法 该类被定义为抽象类 抽象的关键字 abstract 抽象类的特
  • Spring 的基本用法之另外一种装配方式

    一 目的 了解 Spring 环境配置 掌握定义应用程序类 掌握编辑配置文件创建 bean 实例实现依赖注入 掌握编辑测试类创建 IoC 容器 通过容器获取 bean 实例 二 内容 创建一个Spring项目 完成主题为 订单信息通知 项目
  • 一些比较不错的资源网站

    阮一峰日志地址 Github上值得推荐的开源电子书
  • QT 5.15 源码windows下 msvc编译

    目录 1 下载 Qt 源代码 2 安装依赖项 3 配置命令行环境变量 4 构建 5 使用 1 下载 Qt 源代码 Index of archive qt 5 15 5 15 8 singlehttps download qt io arch
  • STM32F4XX/APM32F4XX USB OTA升级

    近期在研究USB CDC协议 使用USB Virtual Port Com功能与上位机通讯做了OTA功能 开发平台 MDK529开发硬件 APM32F411首先介绍一下程序执行逻辑 程序由两部分组成 Boot APP Flash由三部分组成
  • 485串口服务器协议,10/100M TCP/IP转1口RS-232/485/422串口服务器 - 宇泰(UTEK) - 全球领先智能通讯解决方案提供商!...

    gt gt 硬件特性 菜单配置界面 操作模式丰富 满足不同行业的应用 提供Windows 虚拟COM 驱动软件 灵活合理的UNIX 下Fixedtty 工作机制 具有1个串行端口 可以连接终端 Modem 条码机 收款机 ISDN 终端适配
  • 《网页设计基础——CSS的四种引入方式详解》

    网页设计基础 CSS的四种引入方式详解 一 行内式 规则 1 行内式是所有样式方法中最为直接的一种 它直接对HTML的标记使用style属性 然后将CSS代码直接写在其中 格式 p style color FF0000 font size
  • 用「渣男」心态去面试,爽翻!

    转载自博学谷公众号 面试是一场 销售 自己的考试 很多程序员掌握的技术 知识都非常过关 但在面试中却不能完全展现出来 屡屡发挥失常 同样是天选打工人 别人的offer 像猴子摘苞谷 应接不暇 而你的offer 像肉包子打狗 有去无回 究其根
  • python的环境变量配置

    一般会自己在安装时配置环境变量 但是也有小失误忘了打勾勾 这时我们则需要自己配置环境变量 Python的环境变量配置很简单 1 1 Windows配置环境变量 右击点击 我的电脑 点击 属性 2在弹出的界面中点击 高级系统设置 不同的win
  • 《MySQL实战45讲》——学习笔记33 MySQL Server查询结果的发送流程 / 一次查询大量数据对innoDB bufferPool的影响 / 内存淘汰算法LRU与innoDB改进的LRU

    本篇通过 大查询会不会把内存用光 这个问题 介绍了MySQL 的查询结果发送给客户端的过程 涉及的知识点包括 MySQL Server查询结果的发送流程 边读边发 MySQL线程状态Sending to client Sending dat
  • 面试题:软件测试工程师工作职责?

    软件测试工程师工作职责 工作职责 测试人员有不同的级别 或者说有不同的职位称呼 都有相对应不同的工作职责 如果你想了解现在企业里面最新的职位需求的话 最简单的一个方式就是直接去大型招聘网站上BOSS直聘或者前程无忧 赶集网去搜索软件测试工程
  • 前端表单验证 for循环验证 自定义表单验证

  • 关于Java的那些安全框架

    前言 在Java开发中 安全是一项至关重要的特性 不仅仅是因为它保护我们的数据和系统免受恶意攻击 还因为它保护着我们和我们的用户的隐私 因此 Java安全框架的选择至关重要 在本篇博客中 我们将探讨一些常见的Java安全框架 以及如何使用它
  • 微服务发展趋势

    目录 云原生网关逐步成型 服务网格回归理性 微服务架构分层逐渐清晰 微服务技术标准逐步形成 数据面 SidecarProxy 与 Proxyless 模式的融合 服务治理数据面透明化 控制面标准化 分布式事务从多样化到标准化 多语言解决方案
  • Android混合开发全解析

    现在的app都开始流行混合开发了 这是一个app开发的新技术 作为android程序猿的我们也应该要了解并且掌握他 那么在使用之前 我们一定要搞清楚 我们的哪些场景使用混合开发好一些呢 这个问题一定要搞清楚 因为现在的混合开发还不成熟 We