Hybrid App移动应用开发初探

一、移动App类型及其优缺点

1.1 Native App

  Native App(原生App)是用原生语言(Object-C/Java/C#/....)开发,用户需要下载安装的手机应用。

  优点是 可以完全利用系统的 API 和平台特性,在性能上也是最好的。

  缺点是 由于开发技术不同,如果你要覆盖多个平台,则要针对每个平台独立开发,无跨平台特性。

1.2 Web App

  Web App主要是采用统一的标准的HTML、JavaScript与CSS 等 Web 技术开发。

  优点是 用户无需下载,通过不同平台的浏览器访问即可实现跨平台,同时可以通过浏览器支持充分使用 HTML5 特性。

  缺点是 这些基于浏览器的应用无法调用系统 API 来实现一些高级功能(例如拍照、GPS、存储等),也不适合高性能要求的场合。

1.3 Hybrid App

  Hybrid App(混合式App)中和了Native App和Web App各自的优势。 我们可以用 HTML + CSS + JS 开发,兼容多个平台。用户也要下载安装,并能调用手机的摄像头、通讯录等功能, Hybrid App的静态资源也在手机本地。

  优点是 相同的代码只需针对不同平台进行编译就能实现在多平台的分发,大大提高了多平台开发的效率;而相较于 Web App,开发者可以通过包装好的接口,调用大 部分常用的系统 API。

二、Hybird App开发平台介绍

2.1 PhoneGap

  PhoneGap是一个用基于HTML,CSS和JavaScript的,创建移动跨平台移动应用程序的快速开发平台。它使开发者能够利用iPhone,Android,Palm,Symbian,WP7,WP8,Bada和Blackberry智能手机的核心功能——包括地理定位,加速器,联系人,声音和振动等,此外PhoneGap拥有丰富的插件,可以调用。

  业界很多主流的移动开发框架均源于PhoneGap。较著名的有Worklight、appMobi、WeX5等;其中WeX5为国内打造,完全Apache开源,在融合Phonegap的基础上,做了深度优化,具备接近Native app的性能,同时开发便捷性也较好。

2.2 Cordova

  Cordova是贡献给Apache后的开源项目,是从PhoneGap中抽出的核心代码,是驱动PhoneGap的核心引擎。你可以把它们的关系想象成类似于Webkit和Google Chrome的关系。

  Cordova提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头、麦克风等。

  Cordova还提供了一组统一的JavaScript类库,以及为这些类库所用的设备相关的原生后台代码。

  Cordova支持如下移动操作系统:iOS, Android,ubuntu phone os, Blackberry, Windows Phone, Palm WebOS, Bada 和 Symbian。

2.3 Hybrid App程序结构

  为了了解清楚Hybrid App的程序结构,我们先来复习一下普通的ASP.NET Web网站应用的结构:

  最底层当然是CLR提供的运行时环境,这是所有.NET应用程序都必须赖以生存的条件。在CLR之上是.NET Framework提供的一些基类库BCL,包括了IO、String、Thread等常用的类型。在BCL之上是一些常用的Framework,例如B/S模式的ASP.NET WebForm和ASP.NET MVC,C/S模式的Windows Form或WPF等。最上层才是我们得应用程序,它是基于下面的基础环境来构建的,一层接一层,每一层都对下层有依赖。

  现在我们再来看下面一张图,它展示了一个Hybird App的结构:

  与Web网站结构图相对应,Hybrid App结构图的最底层是Native Code(本地代码),这里列举了三种主要操作系统iOS、Android以及Windows Phone的对应开发语言Object-C、Java和C#,在Native App的开发中我们直接使用这几种语言开发对应操作系统的App。在Native Code之上的是Cordova/PhoneGap这样的平台,这些平台提供了JavaScript执行平台和Native API,上层通过传递JS代码,由JS执行平台进行解释,再调用对应的Native API,由Native API去调用对应操作系统的Native Code。换句话说,Cordova/PhoneGap这一层所做的就是对Native Code层面的包装。在平台层之上是一些Plugins(插件),它是一堆手机的硬件组件接口,可以方便地使用JS代码调用相机、文件、网络等硬件资源。最上层是一些Web前端的展示层框架,借助这些框架可以方便地开发出适合Hybird App的Web页面。

  一个典型的Hybrid App的调用手机硬件的Camera相机功能的层次顺序如下图所示:

三、Cordova平台环境配置

3.1 配置JDK环境

  下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

  安装jdk-8u71-windows-x64.exe,配置环境变量:

JAVA_HOME = C:\Program Files\Java\jdk1.8.0_71 Path += %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; CLASSPATH += %JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

  验证:java -version

3.2 配置Android-SDK环境

  下载地址1:http://developer.android.com/design/downloads/index.html

  下载地址2(通过Android Studio安装Android SDK):https://dl.google.com/dl/android/studio/install/1.2.1.1/android-studio-bundle-141.1903250-windows.exe

  解压android-sdk.rar到D:\Develop\Android\sdk,配置环境变量:

ADT_HOME = D:\Develop\Android\sdk Path += %ADT_HOME%\platform-tools;%ADT_HOME%\tools;

  验证:adb -version

3.3 配置Apache-Ant环境

  下载地址:http://ant.apache.org/bindownload.cgi

  解压apache-ant-1.9.6.rar,配置环境变量:

ANT_HOME = D:\Develop\ApacheAnt Path += %ANT_HOME%\bin;

  验证:ant -versioin

3.4 配置GIT环境

  下载地址:http://msysgit.github.io/

  安装Git_V2.5.1_64_bit_setup.1441791170.exe,配置环境变量:

GIT_HOME = C:\Program Files\Git Path += %GIT_HOME%\bin;

  验证:git --version

3.5 配置Node环境

  下载地址:http://nodejs.org/download/

  安装node-v0.10.29-x64.msi,配置环境变量:

NODE_HOME = C:\Program Files\Nodejs\ Path += %NODE_HOME%;

  验证:node -v,npm -v

3.6 配置Cordova环境

  在cmd中运行npm install -g cordova(在线安装)

  或者将cordova.rar解压到C:\Users\YourName\AppData\Roaming\npm\node_modules

PS:cordova最新版本匹配android 6.0,因此你的Android SDK也要下载6.0的包,如果你只有5.x的,那么可以指定cordova的版本进行安装,例如安装cordova 5.1.1 :npm install -g cordova@5.1.1

四、第一个移动App:简单登录Demo

4.1 开发流程概述

  首先,使用Visual Studio或Sublime Text等IDE开发Web网页,然后使用Cordova平台进行打包生成Android项目文件,最后调整配置文件和发布成apk。

4.2 使用Visual Studio开发Web网站

  这里只开发一个简单的login页面,因此只有一个HTML文件:login.html,借助于bootstrap和zeptojs。login.html的代码如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>MyHybirdApp</title>
    <link href="assets/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <link href="assets/css/style.css" rel="stylesheet" />
</head>
<body>
    <!-- 表单区域 -->
    <form id="form_login" class="form-main" method="post" autocomplete="off">
        <h2>Sign in</h2>
        <hr />
        <div class="avator">
            <img id="myavator" src="assets/img/avatar.png" />
        </div>
        <div class="message-box"></div>
        <label for="username" class="sr-only">Username</label>
        <input id="username" type="text" class="form-control input-lg input-group-top" placeholder="Input your Username" required autofocus>
        <label for="password" class="sr-only">Password</label>
        <input id="password" type="password" class="form-control input-lg input-group-bottom" placeholder="Input your Password" required>
        <div class="checkbox">
            <input id="remember" type="checkbox" value="remember-me" checked>
            <label for="remember">Remember me</label>
            <a class="link" href="register.html">Sign up</a>
        </div>
        <input type="hidden" name="redirect" value="/" />
        <button id="btn_login" class="btn btn-lg btn-info btn-block">Sign in</button>
    </form>
    <!-- 脚本区域 -->
    <script type="text/javascript" src="assets/lib/zepto/zepto.min.js"></script>
    <script type="text/javascript" src="cordova.js"></script>
    <script type="text/javascript">
        $(function () {
            // 调用手机硬件拍照
            $('#myavator').on('click', function () {
                navigator.camera.getPicture(onSuccess, onFail, {
                    quality: 50,
                    destinationType: Camera.DestinationType.DATA_URL
                });

                function onSuccess(imageData) {
                    var image = document.getElementById('myavator');
                    image.src = "data:image/jpeg;base64," + imageData;
                }

                function onFail(message) {
                    alert('Failed because: ' + message);
                }
            });
            // 访问服务端服务
            $('#btn_login').on('click', function () {
                var userName = $('#username').val();
                var password = $('#password').val();
                if (userName == "") {
                    alert('Please input your user name!');
                }
                else if (password == "") {
                    alert('Please input your password!');
                }
                else {
                    // 在PC浏览器端的话下面的ajax请求就涉及到跨域,而在Cordova中我们不需要考虑么么哒
                    $.get('http://www.edisonchou.cn/AccountHandler.ashx?action=Login&username=' + userName + '&password=' + password + '', {
                    }, function (data) {
                        alert(data.Message);
                    });
                }

                return false;
            });
        });
    </script>
</body>
</html>

  这里需要注意的有以下几点:

  (1)访问服务端的服务

    // 访问服务端服务
    $('#btn_login').on('click', function () {
        var userName = $('#username').val();
        var password = $('#password').val();
        if (userName == "") {
            alert('Please input your user name!');
        }
        else if (password == "") {
            alert('Please input your password!');
        }
        else {
            // 在PC浏览器端的话下面的ajax请求就涉及到跨域,而在Cordova中我们不需要考虑么么哒
            $.get('http://www.edisonchou.cn/AccountHandler.ashx?action=Login&username=' + userName + '&password=' + password + '', {
            }, function (data) {
                alert(data.Message);
            });
        }

        return false;
    });

  我们知道在传统PC 浏览器端中,ajax请求受限于XMLHttpRequest无法进行跨域请求,我们可能需要借助JSONP一类的帮手帮我们解决,而在Cordova生成的Hybird App中不需要考虑这个问题。在上面的代码中,get请求访问的是一个位于远端服务器中的一个服务(可以是ashx一般处理程序,也可以是一个MVC应用的action)。

  (2)访问Android手机的硬件

    // 调用手机硬件拍照
    $('#myavator').on('click', function () {
        navigator.camera.getPicture(onSuccess, onFail, {
            quality: 50,
            destinationType: Camera.DestinationType.DATA_URL
        });

        function onSuccess(imageData) {
            var image = document.getElementById('myavator');
            image.src = "data:image/jpeg;base64," + imageData;
        }

        function onFail(message) {
            alert('Failed because: ' + message);
        }
    });

  参考Cordova的API文档,我们可以通过如上所示的JS代码访问Camera相机,并调用相机进行拍照。两个事件onSuccess和OnFail则是拍照成功或失败后的处理逻辑。这里成功后,我们将新拍的照片放到头像Image位置。

  另外,我们还需要一个服务端,提供登录验证的接口供App客户端调用,这里我们简单地做一个ashx一般处理程序来进行处理,并将其发布到阿里云的虚拟机中以便手机可以随时访问,其处理逻辑代码如下:

    public class AccountHandler : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            if (string.IsNullOrEmpty(context.Request["action"]))
            {
                return;
            }

            string action = context.Request["action"];
            JsonObject jsonObject = new JsonObject();
            switch (action)
            {
                case "Login":
                    string userName = context.Request["username"];
                    string password = context.Request["password"];
                    jsonObject.UserId = userName;

                    bool result = CheckAccount(userName, password);
                    if (result)
                    {
                        jsonObject.Message = "Success";
                    }
                    else
                    {
                        jsonObject.Message = "Failed";
                    }

                    JavaScriptSerializer serializer = new JavaScriptSerializer();
                    string jsonResult = serializer.Serialize(jsonObject);
                    context.Response.ContentType = "application/json";
                    context.Response.Write(jsonResult);
                    break;
            }
        }

        private bool CheckAccount(string userName, string password)
        {
            bool result = false;
            List<Account> accountList = GetAccountList();
            foreach (var account in accountList)
            {
                if (account.UserName.Equals(userName) && account.Password.Equals(password))
                {
                    result = true;
                }
            }

            return result;
        }

        private List<Account> GetAccountList()
        {
            return new List<Account>()
            {
                new Account() { UserName="edisonchou",Password="123456" },
                new Account() { UserName="zhouxulong",Password="654321"}
            };
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

  这里没有进行数据库的操作,而是简单得模拟了一个含有两条用户信息的List集合。

4.3 使用Cordova打包apk文件

  1.新建一个项目文件夹

  有了Web网站,我们可以进行App的准备工作了,首先新建一个Cordova项目文件夹:

  这里我们给app取名为约吗,PS:今天情人节,你约了吗?

  然后将www文件里面的内容全部删除,将我们开发的web网页以及依赖的资源(图片、css、js等)拷贝到此目录下:

  2.增加android platform支持

  有了一个Cordova的项目文件夹,我们需要增加一个android的platform,因为我们要做的是一个基于android的app。进入yuema文件夹,然后输入以下命令:

  3.增加android plugin支持

  由于我们的app使用了硬件,所以需要增加针对相对应硬件的plugin支持,前面提到我们需要调用对应的plugin,由plugin去调用JS解释器生成对应Native API。这里我们增加camera的plugin:

4.4 调整配置文件和发布应用

  在cordova生成的项目文件夹中,最顶层有一个config.xml,这个就是我们需要编辑的配置文件。

  1.设置app的起始页面

    <!-- 应用程序入口 -->
    <content src="login.html" />

  2.设置app的Icon以及SplashScreen

    <!-- 针对不同平台单独的设置选项 -->
    <platform name="android">
        <allow-intent href="market:*" />
        <!-- app icon image -->
        <icon src="www/assets/img/avatar.png" density="ldpi" />
        <icon src="www/assets/img/avatar.png" density="mdpi" />
        <icon src="www/assets/img/avatar.png" density="hdpi" />
        <icon src="www/assets/img/avatar.png" density="xdpi" />
        <!-- splash screen image -->
        <splash src="www/assets/img/splashscreen.png" density="land-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-xdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-xdpi" />
    </platform>

  完整的config.xml如下:

<?xml version='1.0' encoding='utf-8'?>
<widget id="cn.edisonchou.app" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>约吗</name>
    <description>
        Edison Chou的第一个Cordova应用.
    </description>
    <author email="edisonchou@hotmail.com" href="http://www.edisonchou.cn">
        Edison Chou
    </author>
    <!-- 应用程序入口 -->
    <content src="login.html" />
    <!-- 插件依赖项 -->
    <plugin name="cordova-plugin-whitelist" version="1" />
    <!-- 网络类型配置 *代表所有地址均可访问-->
    <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:*" />
    <!-- 针对不同平台单独的设置选项 -->
    <platform name="android">
        <allow-intent href="market:*" />
        <!-- app icon image -->
        <icon src="www/assets/img/avatar.png" density="ldpi" />
        <icon src="www/assets/img/avatar.png" density="mdpi" />
        <icon src="www/assets/img/avatar.png" density="hdpi" />
        <icon src="www/assets/img/avatar.png" density="xdpi" />
        <!-- splash screen image -->
        <splash src="www/assets/img/splashscreen.png" density="land-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-xdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-xdpi" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
</widget>

  3.编译发布app成apk文件

  通过命令:cordova build android 来生成最后的apk文件

  生成的apk文件位于:YourDirectory\yuema\platforms\android\build\outputs\apk 中

4.5 预览我们的app

  将生成的apk放到我们得android手机中并进行安装,然后点击进入,下面是演示图片(演示手机:Smartisan T1)。

  (1)安装app

  (2)点击进入登录页面

  以下两个gif图片受限于gif制作软件,效果较差,但是功能已经演示了出来:

  (3)调用服务端进行验证

  (4)调用相机进行拍照

附件下载

1.整个项目的源代码:http://pan.baidu.com/s/1gdVIurx

2.生成后的apk文件:http://pan.baidu.com/s/1bfvVWE

参考资料

1.汪磊,《Hybird App 基础入门公开课

2.Apache,《Apache Cordova API Document

3.李秉骏,《Hybrid App 开发实战

4.FreeZinG,《使用HTML和JS开发移动App-部署Cordova配套开发环境

5.周金根,《新手的第一个PhoneGap Android应用

6.zythy,《跨平台框架Cordova命令行CLI简介

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我的安全视界观

[一起玩蛇】Python代码审计中的器II

31470
来自专栏全栈开发

webpack构建优化:bundle体积从3M到400k之路

在CQM平台开发时,把demo网站给同事体验,都纷纷反馈第一次打开页面的时候需要等待很久,页面一直在转菊花。作为一个为韩国头部厂商提供优质服务的网站,接到这种反...

73840
来自专栏程序你好

SignalR介绍简单示例教程入门版

18140
来自专栏北京马哥教育

用 Python 搞定正方教务系统之抢课篇

最近学校开始选课,但是如果选课时间与自己的事情冲突,这时候就可以使用Python脚本自助抢课,抢课的第一步即是模拟登录,需要模拟登录后保存登录信息然后再进行操作...

51800
来自专栏逸鹏说道

用Python、NetCore、Shell分别开发一个Ubuntu版的定时提醒

用Python、NetCore、Shell分别开发一个Ubuntu版的定时提醒(附NetCore跨平台的两种发布方式) 

23590
来自专栏玄魂工作室

[实战]如何在Kali Linux中进行WIFI钓鱼?

文中提及的部分技术可能带有一定攻击性,仅供安全学习和教学用途,禁止非法使用! ? 0x00 实验环境 操作系统:Kali 1.0 (VM) FackAP: ea...

53560
来自专栏FreeBuf

打开文件夹就运行?COM劫持利用新姿势

*本文原创作者:菠菜,本文属FreeBuf原创奖励计划,未经许可禁止转载 打开文件夹就能运行指定的程序?这不是天方夜谭,而是在现实世界中确实存在的。利用本文探讨...

26480
来自专栏阿杜的世界

前端开发:工具和流程

在dailyReport项目中,我通过SpringBoot + Mongodb + Redis构建了后端RESTful接口,现在需要客户端展现了,但是我的web...

15020
来自专栏macOS 开发学习

cocos2d-objc 3.0+ 游戏开发学习手册(一): 简介与安装

目前网络中关于cocos2d-iphone 方面的资料,大部分都是基于c++ 语言跨平台的cocos2d-x,偶尔搜到一些cocos2d方面的也由于版本比较早(...

12030
来自专栏JackeyGao的博客

Scrapy抓取简书热门生成电子书发送到Kindle

简书是个学习的好网站, 我大多只关注首页上的人们文章, 但是最近因为忙错过了很多首页上的文章,所以有了想法把每天的热门top生成mobi推送到kindle。这样...

9310

扫码关注云+社区

领取腾讯云代金券