开发Hybrid App的技术选型

欢迎点击「算法与编程之美」↑关注我们!

本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列文章。

作者|王小强

来源|https://my.oschina.net/wxqdoit

一、前言

如果我们把Hybrid App理解为运行在android或者ios以及其他移动终端设备上的应用,也可以叫做H5 APP,这种开发应用的模式结合web开发技术与Native开发的部分技术,通常也被称为混合开发模式。

二、移动应用开发的三种方式

  • Native App:原生应用,在android端通常使用Java或Kotlin开发,ios端使用OC或者Swift开发
  • Hybrid App:混合应用,结合Web与Native技术开发
  • Web App:web应用,网页三剑客html+css+js

Native App开发依旧是移动应用的主导,但如今的Native App或多或少会嵌入一些web页面,诸如淘宝、京东等APP,所以如今真正意义上的原生应用又该如何去定义呢?Hybrid App受到越来越多开发者的追捧与其开发周期短,开发难度小,跨平台离不开,当然APP的效果也成为大家诟病的话题,如首屏打开缓慢,动画效果不够流畅等。

三种方式的技术比较(图片来自网络)

三、Hybrid App开发的核心

毫无疑问,webview是Hybrid App开发的核心。webview可以简单的理解为一个浏览器。webview 使用的是手机自带的浏览器内核,一般来说,手机厂家在内置浏览器的时候都会对其内核做一定的修改,所以在webview渲染的内容可能或有些差异,但是这基本上不影响APP的开发。绝大部分手机都使用的是WebKit作为webview的渲染引擎。关于WebKit以及其他的浏览器内核知识可以查看这里。

关于webview的知识点非常多,如在android上常用的属性:WebSettings、WebViewClient,与JavaScript的交互,js注入漏洞,jsBridge等等,在此以Kotlin为例实现一个简单的例子,项目源码点击这里。

MAinActivity.kt

package com.example.wxqdoit.kotlintest
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.design.widget.BottomNavigationView
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bottom_navigation.setOnNavigationItemSelectedListener(mBottomNavigationView)
        val fragments: MutableList<Fragment> = ArrayList<Fragment>()
        fragments.add(Fragment1("https://www.baidu.com"))
        fragments.add(Fragment1("https://www.jianshu.com"))
        fragments.add(Fragment1("https://aidn.jp/mikutap/"))
        vp_main.adapter = ViewPagerAdapter(supportFragmentManager,fragments)
    }
    private val mBottomNavigationView = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.word -> {
                //vp_main.currentItem = 0
                Toast.makeText(this,"点击了",Toast.LENGTH_SHORT).show()
                return@OnNavigationItemSelectedListener true
            }
            R.id.pic -> {
                //vp_main.currentItem = 1
                return@OnNavigationItemSelectedListener true
            }
            R.id.me -> {
                //vp_main.currentItem = 2
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }
}

ViewPagerAdapter.kt

package com.example.wxqdoit.kotlintest
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentPagerAdapter
//继承 FragmentPagerAdapter 创建适配器
class ViewPagerAdapter(fm: FragmentManager?, var list: List<Fragment>) : FragmentPagerAdapter(fm) {
    override fun getItem(position: Int): Fragment {
        return list.get(position)
    }
    override fun getCount(): Int {
        return list.size
    }
}

Fragment1.kt

package com.example.wxqdoit.kotlintest
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import kotlinx.android.synthetic.main.fragment1.view.*
@SuppressLint("ValidFragment")
class Fragment1(private val url: String):Fragment() {
    @SuppressLint("SetJavaScriptEnabled")
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment1, container, false)
        val webView = view.testWebView
        webView!!.webViewClient = webClient
        webView.settings.javaScriptEnabled = true
        webView.settings.allowContentAccess = true
        webView.settings.layoutAlgorithm
        webView.loadUrl(url)
        return view
    }
    private val webClient = object : WebViewClient() {
        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun shouldOverrideUrlLoading(view: WebView, request:WebResourceRequest ): Boolean {
            view.loadUrl(request.url.toString())
            return true
        }
    }
}

效果如下

四、移动APP适配

做过原生开发的都知道美术需要出几套图以适配不同的分辨率,720×1280、750×1334、1080×1920、1242×2208,更大或者更小的屏在市面上也常见,android上字体大小通常以dp、sp作为单位。Hybrid App在只有一套美术UI的情况下应当如何处理以适配不同的机型呢?

媒体查询、百分比,或是直接使用web端常用的单位px、em、rem以及vh、vw,都是常用的适配方案。在设计稿给到固定宽度的情况下,相对而言,使用rem作为单位是比较合理的选择,至于移动设备适配,屏幕宽度,逻辑像素、物理像素、dpi等知识就不在此处赘述,分享优秀的博客:

移动设备适配基础知识速成:weibo.com/p/1001603933391216084991

五、flexible.js

flexible是淘宝使用的移动端适配方案,原理即根据机型分辨率进行适配,设置根font-size,使用相对单位rem。其核心功能如下:

  • 判断meta标签,动态改写标签
  • 给html标签添加data-dpr属性
  • 给html标签添加font-size属性

根font-size(即html标签上的)属性值即为一个单位的rem,在chrome浏览器上,默认的font-size值为16px:即16px=1rem

getComputedStyle(document.getElementsByTagName("html")[0])["font-size"];
//16px

flexible.js核心代码:

    function refreshRem(){
        var width = docEl.getBoundingClientRect().width;
        if (width / dpr > 540) {
            width = 540 * dpr;
        }
        var rem = width / 10;
        docEl.style.fontSize = rem + 'px';
        docEl.setAttribute("font-size",rem + 'px');
        flexible.rem = win.rem = rem;
    }
    win.addEventListener('resize', function() {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 200);
    }, false);
    win.addEventListener('pageshow', function(e) {
        if (e.persisted) {
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 200);
        }
    }, false);

将设备宽度分为10份,而每一份视作一个单位,再将html的font-size设置为这个单位即:

font-size = 750/10 = 75px;

1rem = 75px;

如果在宽度为750px的设备上,完全符合上述换算。如果在iphone6/7/8上,宽度为375,那么:

font-size = 375/10 = 37.5px;

1rem = 37.5px;

现在以宽为750的设计稿为例,有一个款750px高75px的按钮 ;在iphone6/7/8上(实际宽375px)我们实际看到的宽高是多少呢?

width = 750/75 = 10rem ---> 10rem(在iphone6/7/8上) = 37.5px*10 = 375px;

height = 75/75 = 1rem --->1rem(在iphone6/7/8上) = 37.5px;

不难看出,只需要将这个单位作为除数即可计算出所需要的值;

六、打包工具

开发完成之后使用hbuilder或cordova打包成为android APK或者ios IPA。我更偏向于cordova,插件相对更多,社区更加活跃,稳定的更新维护。当然,孰优孰劣各自体会。下文会具体介绍cordova的相关知识。

Hbuild:http://www.dcloud.io/

cordova:https://cordova.apache.org/

七、UI框架

开发框架常用的有ionic,mui,jQuery Mobile,weui等等。就事实而论,当设计稿给到开发者时,或者这些都用不到,全套UI自己写也是常见的。

八、angular、react还是vue?

angular、react相对而言比较重,vue显得轻量一些,当开发大型SPA应用时,前两者是不错的选择,而vue完整的工具链以及活跃的社区也适应绝大部分的开发场景。

九、jQuery还用吗?

对于事件的封装,DOM操作的实现都是毫无疑问需要使用的,同时集成的ajax封装也必不可少,不过不论是Web APP的开发还是 Hybrid App的开发,jq都是不二之选。但当我们在对请求过滤的处理时,这些get,post方法基本上不能满足我们的需求,所以需要对请求进行二次封装。

/**
     * 通用请求数据接口
     * @param reqUrl
     * @param reqType
     * @param data
     * @param fn
     */
    commonRequest: function commonRequest(reqUrl, reqType, data, fn) {
        $.ajax({
            url: reqUrl,
            type: reqType,
            data: data,
            async: false,
            success: function success(data) {
                fn(data);
            },
            error: function error(data) {
                $.lightTip.error("网络请求错误-" + data.status, 2000);
            }
        });
    },
    /**
     * 二次封装get请求
     * @param reqUrl
     * @param data
     * @param fn
     */
    get: function get(reqUrl, data, fn) {
        $.ajax({
            url: reqUrl,
            type: 'GET',
            data: data,
            async: false,
            success: function success(data) {
                if (data.meta.code === 200) {
                    fn(data);
                } else {
                    alert(data.meta.message);
                    throw new Error(data.meta.message);
                }
            },
            error: function error(data) {
                $.lightTip.error("网络请求错误-" + data.status, 2000);
            }
        });
    },
    /**
     * 二次封装post请求
     * @param reqUrl
     * @param data
     * @param fn
     */
    post: function post(reqUrl, data, fn) {
        $.ajax({
            url: reqUrl,
            type: 'POST',
            data: data,
            async: false,
            success: function success(data) {
                if (data.meta.code === 200) {
                    fn(data);
                } else {
                    alert(data.meta.message);
                    throw new Error(data.meta.message);
                }
            },
            error: function error(data) {
                $.lightTip.error("网络请求错误-" + data.status, 2000);
            }
        });
    },
    /**
     * formData上传数据
     * @param reqUrl
     * @param reqType
     * @param data
     * @param fn
     */
    formDataReq: function formDataReq(reqUrl, reqType, data, fn) {
        $.ajax({
            url: reqUrl,
            data: data,
            type: reqType,
            processData: false,
            contentType: false,
            success: function success(data) {
                if (data.meta.code === 200) {
                    fn(data);
                } else {
                    alert(data.meta.message);
                    throw new Error(data.meta.message);
                }
            },
            error: function error(data) {
                $.lightTip.error("网络请求错误-" + data.status, 2000);
            }
        });
    },

这样封装一次,当用户没有登录时,可以根据返回的数据进行过滤处理。

当然如果你不用jq也可以选择其他的类库如封装ajax请求的axios!

十、swiper是个好东西

swiper常用于移动端网站的内容触摸滑动,是纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端,Swiper能实现触屏焦点图、触屏Tab切换、触屏多图切换等常用效。这个插件功能确实强大,官网惊艳,api文档走心,性能不错。用作APP开发的主容器亦有一战之力。

十一、cordova开发详解

1、cordova开发环境搭建,以android为例

1、安装jdk(建议jdk8+),配置环境变量;

2、安装android sdk(至少到27,也可以安装android studio依赖安装),配置环境变量;

3、安装node(npm,也可以选择使用yarn)(node建议8+),如果不是默认安装,请配置环境变量;

4、安装bower,用以下载各种前端类库;

5、使用npm install -g cordova全局安装cordova,如果安装过慢或失败请科学上网;

2、创建一个app并运行起来

1、cordova create [文件夹名] [包名] [app名]

$ cordova create hello com.example.hello HelloWorld

2、添加平台(以android为例)

$ cd hello

$ cordova platform add android

3、build项目

cordova build android
......
.....
...
.
BUILD SUCCESSFUL in 7s
47 actionable tasks: 1 executed, 46 up-to-date
Built the following apk(s):
        D:\dev\****\****\****\platforms\android\app\build\outputs\apk\debug\app-debug.apk

在路径:\platforms\android\app\build\outputs\apk\debug\app-debug.apk下可以找到apk文件。

3、常用命令

Global Commands
    create ............................. 创建项目
    help ............................... 获取帮助
    telemetry .......................... 开启或关闭遥测采集
    config ............................. 全局配置
Project Commands
    info ............................... 项目基本信息
    requirements ....................... 检查并打印出指定平台的所有要求
    platform ........................... 管理项目平台
    plugin ............................. 管理插件
    compile ............................ 编译项目
    clean .............................. 清除项目

更多命令可以到官网查看。

4、项目结构

如果项目成功运行,您看到的项目结构应该如下:

www文件夹作为开发主文件夹;

res文件夹存放app的闪屏图片和icon;

plugins文件夹存放插件;

plaatforms文件夹存放诸如android、ios等各端的文件;

node-modules文件夹自然是依赖的各个模块

config.xml是项目的配置文件,你添加的插件将会在里面显示,如状态栏插件,你可以添加更多插件。

<plugin name="cordova-plugin-statusbar" spec="^2.4.2" />

5、常用插件收集:

phonegap-plugin-barcodescanner : 二维码扫描

cordova-plugin-statusbar:状态栏

cordova-plugin-inappbrowser: 内置浏览

cordova-plugin-camera:照相机

插件太多可查看这里

官方的插件搜索地址点击这里

6、插件使用,以imagePicker为例子

cordova plugin add cordova-plugin-imagepicker

使用:

document.addEventListener("deviceready",onDeviceReady);
        function onDeviceReady(){
            console.log("onDeviceReady");
            $(document).on("tap","#getPictures",()=>{
                window.imagePicker.getPictures(
                    function(results) {
                        for (let i = 0; i < results.length; i++) {
                            console.log('Image URI: ' + results[i]);
                            alert('Image URI: ' + results[i]);
                        }
                    }, function (error) {
                        console.log('Error: ' + error);
                        alert('Error: ' + error);
                    },{
                        maxImages: 9,
                        width: 500,
                        height: 500,
                        quality:60
                    }
                );
            })
        }

当然要实现QQ登录、微信登录等功能也是完全没有问题的,只需要添加对应的插件使用就可以了。

7、构建release版本以及签名

构建release版本 cordova build android --release

对 APK 签名 jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore release-key.keystore platforms/android/build/outputs/apk/android-release-unsigned.apk [秘钥]apk签名的相关知识比较复杂,可以阅读这一篇:Cordova 打包 Android release app 过程详解

更多精彩文章:

算法|从阶乘计算看递归算法

算法|字符串匹配(查找)-KMP算法

JavaScript|脚本岂能随意放置

开发|优秀的Java工程师的“对象”一定不错

谈一谈|2019蓝桥杯回顾与分享

where2go 团队


微信号:算法与编程之美

温馨提示:点击页面右下角“写留言”发表评论,期待您的参与!期待您的转发!

原文发布于微信公众号 - 算法与编程之美(algo_coding)

原文发表时间:2019-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券