HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

从网上下载的mui包放入xcode本地打包的程序中时出错

从网上下载的mui包,包括从gethub上,dcloud中建立的mui等

放入xcode中的本地打包程序中时,再按照官网的本地打包流程修改后,运行会出错,

解决方法虽然很简单但是,真是不合乎常理啊,望给新人提供点经验

解决方法:将manifest.json中的注释删除掉便可

继续阅读 »

从网上下载的mui包,包括从gethub上,dcloud中建立的mui等

放入xcode中的本地打包程序中时,再按照官网的本地打包流程修改后,运行会出错,

解决方法虽然很简单但是,真是不合乎常理啊,望给新人提供点经验

解决方法:将manifest.json中的注释删除掉便可

收起阅读 »

TabBar中使用WebView模式子页面中返回主页面时遇到的问题

在TabBar中使用WebView模式子页面中,有这样的一个流程,首页是index.html页面,能实现四个tab之间的切换


此时,tabbar已经不再显示了,然后再页面A里面再mui.open打开页面B,页面B有一个按钮,点击返回主页,我用的也是mui.openWindow()打开index.html,结果返回到我的index.html时候,无论我点击哪一个tab时候只显示第一个页面,这是什么原因呢?

继续阅读 »

在TabBar中使用WebView模式子页面中,有这样的一个流程,首页是index.html页面,能实现四个tab之间的切换


此时,tabbar已经不再显示了,然后再页面A里面再mui.open打开页面B,页面B有一个按钮,点击返回主页,我用的也是mui.openWindow()打开index.html,结果返回到我的index.html时候,无论我点击哪一个tab时候只显示第一个页面,这是什么原因呢?

收起阅读 »

插件开发示例汇总

5+App开发 插件开发

Android平台插件

调用系统通讯录选择手机号

http://ask.dcloud.net.cn/article/153

系统通知栏显示进度条

http://ask.dcloud.net.cn/article/155

iOS平台插件

IOS指纹识别插件开发方法

http://ask.dcloud.net.cn/article/1348

继续阅读 »

Android平台插件

调用系统通讯录选择手机号

http://ask.dcloud.net.cn/article/153

系统通知栏显示进度条

http://ask.dcloud.net.cn/article/155

iOS平台插件

IOS指纹识别插件开发方法

http://ask.dcloud.net.cn/article/1348

收起阅读 »

iOS平台Webview窗口侧滑返回滚动问题

5+App开发

从HBuilder5.2.2以后版本修复了Webview窗口在侧滑返回时页面仍然可以上下滚动的问题,在大多数情况侧滑返回时Webview窗口不可上下滚动,但是由于系统机制原因目前还存在以下缺陷:

  • 如果Webview窗口页面存在横向滚动条,那么侧滑返回功能失效
    在HelloH5应用中"Webview" -> "窗口嵌套",在地址栏输入“http://www.dcloud.io/”可重现,此时无法通过侧滑返回关闭Webview窗口

    暂时只能避免出现横向滚动条的出现来解决此问题

  • 如果Webview窗口通过监听touch*事件来实现div的滚动,依然会出现侧滑返回是可滚动div
    在HelloMUI应用中"pull to refresh(下拉刷新和上拉加载更多)"可重现,侧滑返回时仍然可以滚动下拉刷新

    这种情况可通过监听popGesture事件来动态开关div的滚动

    function plusReady(){  
    var wvs=plus.webview.currentWebview();  
    wvs.addEventListener( "popGesture", function(e){  
        if(e.type=="start"){  
            //关闭div滚动  
        }else if(type="end"){  
            //开启div滚动  
        }  
    }, false );  
    }  
    document.addEventListener("plusready",plusReady,false);
继续阅读 »

从HBuilder5.2.2以后版本修复了Webview窗口在侧滑返回时页面仍然可以上下滚动的问题,在大多数情况侧滑返回时Webview窗口不可上下滚动,但是由于系统机制原因目前还存在以下缺陷:

  • 如果Webview窗口页面存在横向滚动条,那么侧滑返回功能失效
    在HelloH5应用中"Webview" -> "窗口嵌套",在地址栏输入“http://www.dcloud.io/”可重现,此时无法通过侧滑返回关闭Webview窗口

    暂时只能避免出现横向滚动条的出现来解决此问题

  • 如果Webview窗口通过监听touch*事件来实现div的滚动,依然会出现侧滑返回是可滚动div
    在HelloMUI应用中"pull to refresh(下拉刷新和上拉加载更多)"可重现,侧滑返回时仍然可以滚动下拉刷新

    这种情况可通过监听popGesture事件来动态开关div的滚动

    function plusReady(){  
    var wvs=plus.webview.currentWebview();  
    wvs.addEventListener( "popGesture", function(e){  
        if(e.type=="start"){  
            //关闭div滚动  
        }else if(type="end"){  
            //开启div滚动  
        }  
    }, false );  
    }  
    document.addEventListener("plusready",plusReady,false);
收起阅读 »

设计基于HTML5的APP登录功能及安全调用接口的方式(原理篇)

登录 保存密码 安全 加密

最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间。

你是否真的需要登录功能?

把这个问题放在最前面并不是灌水,而是真的见过很多并不需要登录的APP去做了登录功能,或者是并不需要强制登录的APP把登录作为启动页。
用户对你的APP一无所知,你就要求对方注册并登录,除非APP本身已经很有名气或者是用户有强需求,否则正常人应该会直接把它删掉。
比较温和的方式是将一些并不需要登录,但可以给用户带来帮助的东西,第一时间展现给他们,让他们产生兴趣,再在合适的时机引导他们注册(比如使用需要使用更高级的功能,或用户需要收藏某个喜欢的信息时)。

登录和注册要足够简单

这是小小的手机端,用再好的输入法,打字也是不方便的,所以别把登录页设计得需要填很多东西。如果有可能的话,只填手机号,让用户收到短信验证码就完成注册是最好不过的了。想获得更多信息?想想大公司的APP是怎么做的,他们会告诉用户,现在的个人资料完善程度是30%,如果想获得更多积分,你需要填完。
tips:如果你想发布在Appstore并且同时包含注册功能,那么注册页面必须做一个用户许可协议的链接,否则有可能通不过审核。

实现登录后的session有几种方式?

APP当浏览器用,直接载入远程页面

这种情况是很多偷懒的程序员或者傻X的老板选择的方式,因为做起来实在太快。如果本身网站是响应式布局,那么很有可能不需要做什么更改,就只要在开发时打开首页就好了,这样Hybird的APP外壳就纯粹成为了一个浏览器。
但比起这样做带来的无数缺点来,开发速度快的优点几乎可以忽略不计。
首先,在网络环境不佳时,纯大白页,用户体验0;
然后,CSS和JS等资源不在本地,需要远程载入,如果使用了bootstrap之类的框架,那用户为了开一下APP而耗费的流量真是令人感动;
再然后,网页里常用的jquery,在手机的webview里速度并不理想,而如果是非ajax的网页那就更糟心了,每次操作都要跳转和页面渲染,要让人把它当成APP那实在是笑话。
再再然后,这样的所谓APP,要通过Appstore的审查,那是做梦的(除非审核员当天闹肚子严重,拿着纸巾奔向厕所前误点了通过……),苹果的要求是,这得是APP,而不能是某个网站做成APP的样子,那样的情况适合做Web APP。而据我所知,国内几个较大的Android市场,这样的APP也是无法通过审核的。

调用后端接口

这是个很好的时代,因为无论后端你是用Java、PHP,还是node.js,都可以通过xml、json来和APP通讯。遥想当年写服务端要自己写包结构,然后为了解决并发问题还折腾了半年IOCP模型,真心觉得现在太幸福了。
把刚才那个用APP当浏览器使的案例的所有缺点反过来看,就是这样做的优点,在优化完善的情况下体验接近原生,而且通讯流量极少,通过各种审核也是妥妥的。
tips:通过plus对象中的XMLHttpRequest来Get、Post远程的后端接口,或者使用Mui中封装好的AJAX相关函数

插一段代码,我把mui的ajax又做了进一步的封装,对超时进行了自动重试,而对invalid_token等情况也做相应处理:

;mui.web_query = function(func_url, params, onSuccess, onError, retry){  
    var onSuccess = arguments[2]?arguments[2]:function(){};  
    var onError = arguments[3]?arguments[3]:function(){};  
    var retry = arguments[4]?arguments[4]:3;  
    func_url = 'http://www.xxxxxx.com/ajax/?fn=' + func_url;  
    mui.ajax(func_url, {  
        data:params,  
        dataType:'json',  
        type:'post',  
        timeout:3000,  
        success:function(data){  
            if(data.err === 'ok'){  
                onSuccess(data);  
            }  
            else{  
                onError(data.code);  
            }  
        },  
        error:function(xhr,type,errorThrown){  
            retry--;  
            if(retry > 0) return mui.web_query(func_url, params, onSuccess, onError, retry);  
            onError('FAILED_NETWORK');  
        }  
    })  
};
var onError = function(errcode){  
    switch(errcode){  
    case 'FAILED_NETWORK':  
        mui.toast('网络不佳');  
        break;  
    case 'INVALID_TOKEN':  
        wv_login.show();  
        break;  
    default:  
        console.log(errcode);  
    }  
};  
var params = {per:10, pageno:coms_current_pageno};  
mui.web_query('get_com_list', params, onSuccess, onError, 3);

调用后端接口怎么样才安全?

在APP中保存登录数据,每次调用接口时传输

程序员总能给自己找到偷懒的方法,有的程序为了省事,会在用户登录后,直接把用户名和密码保存在本地,然后每次调用后端接口时作为参数传递。真省事儿啊!可这种方法简单就像拿着一袋子钱在路上边走边喊“快来抢我呀!快来抢我呀!”,一个小小的嗅探器就能把用户的密码拿到手,如果用户习惯在所有地方用一个密码,那么你闯大祸了,黑客通过撞库的方法能把用户的所有信息一锅端。

登录时请求一次token,之后用token调用接口

这是比较安全的方式,用户在登录时,APP调用获取token的接口(比如http://api.abc.com/get_token/),用post将用户名和密码的摘要传递给服务器,然后服务器比对数据库中的用户信息,匹配则返回绑定该用户的token(这一般翻译为令牌,很直观的名字,一看就知道是有了这玩意,就会对你放行),而数据库中,在用户的token表中也同时插入了这个token相关的数据:这个token属于谁?这个token的有效期是多久?这个token当前登录的ip地址是?这个token对应的deviceid是?……
这样即便token被有心人截获,也不会造成太大的安全风险。因为没有用户名和密码,然后如果黑客通过这个token伪造用户请求,我们在服务器端接口被调用时就可以对发起请求的ip地址、user-agent之类的信息作比对,以防止伪造。再然后,如果token的有效期设得小,过一会儿它就过期了,除非黑客可以持续截获你的token,否则他只能干瞪眼。(插一句题外话:看到这里,是不是明白为什么不推荐在外面随便接入来历不明的wifi热点了?)
tips:token如何生成? 可以根据用户的信息及一些随机信息(比如时间戳)再通过hash编码(比如md5、sha1等)生成唯一的编码。
tips:token的安全级别,取决于你的实际需求,所以如果不是涉及财产安全的领域,并不建议太严格(比如用户走着走着,3G换了个基站,闪断了一下IP地址变了,尼玛token过期了,这就属于为了不必要的安全丢了用户体验,当然如果变换的IP地址跨省的话还是应该验证一下的,想想QQ有时候会让填验证码就明白了)。
tips:接口在返回信息时,可以包含本次请求的状态,比如成功调用,那么result['status']可能就是'success',而反之则是'error',而如果是'error',则result['errcode']中就可以包含错误的原因,比如errcode中是'invalid_token'就可以告诉APP这个token过期或无效,这时APP应弹出登录框或者用本地存储的用户名或密码再次请求token(用户选择“记住密码”,就应该在本地保存用户名和密码的摘要,方法见plus.storage的文档)。

再插点代码,基于plus.storage的用户信息类,注意:需要在plusReady之后再使用。

;function UserInfo(){  
};  

//清除登录信息  
UserInfo.clear = function(){  
    plus.storage.removeItem('username');  
    plus.storage.removeItem('password');  
    plus.storage.removeItem('token');  
}  

//检查是否包含自动登录的信息  
UserInfo.auto_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    if(!username || !pwd){  
        return false;  
    }  
    return true;  
}  

//检查是否已登录  
UserInfo.has_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    var token = UserInfo.token();  
    if(!username || !pwd || !token){  
        return false;  
    }  
    return true;  
};  

UserInfo.username = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('username');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('username');  
        return;  
    }  
    plus.storage.setItem('username', arguments[0]);  
};  

UserInfo.password = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('password');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('password');  
        return;  
    }  
    plus.storage.setItem('password', arguments[0]);  
};  

UserInfo.token = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('token');         
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('token');  
        return;  
    }  
    plus.storage.setItem('token', arguments[0]);  
};

这样当用户启动APP或使用了需要登录才能使用的功能时,就可以使用UserInfo.has_login()来判断是否已经登录,如果已登录,则使用UserInfo.token()来获取到token数据,作为参数调用远程的后端接口。

if(UserInfo.has_login()){  
    //打开需要展示给用户的页面,或者是调用远端接口  
}  
else{  
    wv_login.show('slide-in-up');   //从底部向上滑出登录页面  
}

在登录页面中,用户输入了用户名和密码后,并点击了”登录“按钮,我们下一步做什么?再插段代码(注意:此处使用的是我刚才代码中扩展的web_query函数,你也可以直接使用mui的ajax):


function get_pwd_hash(pwd){  
    var salt = 'hbuilder';  //此处的salt是为了避免黑客撞库,而在md5之前对原文做一定的变形,可以设为自己喜欢的,只要和服务器验证时的salt一致即可。  
    return md5(salt + pwd); //此处假设你已经引用了md5相关的库,比如github上的JavaScript-MD5  
}  

//这里假设你已经通过DOM操作获取到了用户名和密码,分别保存在username和password变量中。  
var username = xxx;  
var password = xxx;  
var pwd_hash = get_pwd_hash(password);  

var onSuccess = function(data){  
    UserInfo.username(username);  
    UserInfo.password(pwd_hash);  
    UserInfo.token(data.token); //把获取到的token保存到storage中  
    var wc = plus.webview.currentWebview();  
    wc.hide('slide-out-bottom');    //此处假设是隐藏登录页回到之前的页面,实际你也可以干点儿别的  
}  

var onError = function(errcode){  
    switch(errcode){  
    case 'INCORRECT_PASSWORD':  
        mui.toast('密码不正确');  
        break;  
    case 'USER_NOT_EXISTS':  
        mui.toast('用户尚未注册');  
        break;  
    }  
}  

mui.web_query('get_token', {username:username,password:pwd_hash}, onSuccess, onError, 3);

更安全一点,获取token通过SSL

刚才的方法,机智一点儿的读者大概会心存疑虑:那获取token时不还是得明文传输一次密码吗?
是的,你可以将这个获取token的地址,用SSL来保护(比如https://api.abc.com/get_token/),这样黑客即使截了包,一时半会儿也解不出什么信息。
SSL证书的获取渠道很多,我相信你总有办法查到,所以不废话了。不过话说namecheap上的SSL证书比godaddy的要便宜得多……(这是吐槽)
tips:前段时间OpenSSL漏洞让很多服务器遭殃,所以如果自己搭服务器,一定记得装补丁。
tips:可以把所有接口都弄成SSL的吗?可以。但会拖慢服务器,如果是配置并不自信的VPS,建议不折腾。

还要更更安全(这标题真省事)

还记得刚才APP向服务器请求token时,可以加入的用户信息吗?比如用户的设备deviceid。
如果我们在调用接口时,还附带一个当前时间戳参数timestamp,同时,用deviceid和这个时间戳再生成一个参数sign,比如 md5(deviceid timestamp token)这样的形式。而服务端首先验证一下参数中的时间戳与当前服务器时间是否一致(误差保持在合理范围内即可,比如5分钟),然后根据用户保存在服务器中的deviceid来对参数中的时间戳进行相同的变形,验证是否匹配,那便自然“更更安全”了。
tips:如果对整个调用请求中的参数进行排序,再以deviceid和timestamp加上排序后的参数来对整个调用生成1个sign,黑客即使截获sign,不同的时间点、参数请求所使用的sign也是不同的,难以伪造,自然会更安全。当然,写起来也更费事。
tips:明白了原理,整个验证过程是可以根据自己的需求改造的。

----------------------------------------------------------华丽的分割线-------------------------------------------------------

如果整篇文章中有不明白的名词,欢迎询问?不,不欢迎询问,因为搜索引擎都能找到。国内这方面的资料较少,但国外非常丰富,正所谓外事问google,内事问百度,程序员的第一个门槛就是要学会搜索。

什么?google上不去?开什么玩笑,你可以选择折腾点儿地天天找代理,也可以选择花一点点钱用红杏或类似的科学上网工具。
这里有福利→ 用我的邀请来注册红杏,购买30天以上的套餐,你我都可以各多10天

管理员,要求大大的加分!

继续阅读 »

最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间。

你是否真的需要登录功能?

把这个问题放在最前面并不是灌水,而是真的见过很多并不需要登录的APP去做了登录功能,或者是并不需要强制登录的APP把登录作为启动页。
用户对你的APP一无所知,你就要求对方注册并登录,除非APP本身已经很有名气或者是用户有强需求,否则正常人应该会直接把它删掉。
比较温和的方式是将一些并不需要登录,但可以给用户带来帮助的东西,第一时间展现给他们,让他们产生兴趣,再在合适的时机引导他们注册(比如使用需要使用更高级的功能,或用户需要收藏某个喜欢的信息时)。

登录和注册要足够简单

这是小小的手机端,用再好的输入法,打字也是不方便的,所以别把登录页设计得需要填很多东西。如果有可能的话,只填手机号,让用户收到短信验证码就完成注册是最好不过的了。想获得更多信息?想想大公司的APP是怎么做的,他们会告诉用户,现在的个人资料完善程度是30%,如果想获得更多积分,你需要填完。
tips:如果你想发布在Appstore并且同时包含注册功能,那么注册页面必须做一个用户许可协议的链接,否则有可能通不过审核。

实现登录后的session有几种方式?

APP当浏览器用,直接载入远程页面

这种情况是很多偷懒的程序员或者傻X的老板选择的方式,因为做起来实在太快。如果本身网站是响应式布局,那么很有可能不需要做什么更改,就只要在开发时打开首页就好了,这样Hybird的APP外壳就纯粹成为了一个浏览器。
但比起这样做带来的无数缺点来,开发速度快的优点几乎可以忽略不计。
首先,在网络环境不佳时,纯大白页,用户体验0;
然后,CSS和JS等资源不在本地,需要远程载入,如果使用了bootstrap之类的框架,那用户为了开一下APP而耗费的流量真是令人感动;
再然后,网页里常用的jquery,在手机的webview里速度并不理想,而如果是非ajax的网页那就更糟心了,每次操作都要跳转和页面渲染,要让人把它当成APP那实在是笑话。
再再然后,这样的所谓APP,要通过Appstore的审查,那是做梦的(除非审核员当天闹肚子严重,拿着纸巾奔向厕所前误点了通过……),苹果的要求是,这得是APP,而不能是某个网站做成APP的样子,那样的情况适合做Web APP。而据我所知,国内几个较大的Android市场,这样的APP也是无法通过审核的。

调用后端接口

这是个很好的时代,因为无论后端你是用Java、PHP,还是node.js,都可以通过xml、json来和APP通讯。遥想当年写服务端要自己写包结构,然后为了解决并发问题还折腾了半年IOCP模型,真心觉得现在太幸福了。
把刚才那个用APP当浏览器使的案例的所有缺点反过来看,就是这样做的优点,在优化完善的情况下体验接近原生,而且通讯流量极少,通过各种审核也是妥妥的。
tips:通过plus对象中的XMLHttpRequest来Get、Post远程的后端接口,或者使用Mui中封装好的AJAX相关函数

插一段代码,我把mui的ajax又做了进一步的封装,对超时进行了自动重试,而对invalid_token等情况也做相应处理:

;mui.web_query = function(func_url, params, onSuccess, onError, retry){  
    var onSuccess = arguments[2]?arguments[2]:function(){};  
    var onError = arguments[3]?arguments[3]:function(){};  
    var retry = arguments[4]?arguments[4]:3;  
    func_url = 'http://www.xxxxxx.com/ajax/?fn=' + func_url;  
    mui.ajax(func_url, {  
        data:params,  
        dataType:'json',  
        type:'post',  
        timeout:3000,  
        success:function(data){  
            if(data.err === 'ok'){  
                onSuccess(data);  
            }  
            else{  
                onError(data.code);  
            }  
        },  
        error:function(xhr,type,errorThrown){  
            retry--;  
            if(retry > 0) return mui.web_query(func_url, params, onSuccess, onError, retry);  
            onError('FAILED_NETWORK');  
        }  
    })  
};
var onError = function(errcode){  
    switch(errcode){  
    case 'FAILED_NETWORK':  
        mui.toast('网络不佳');  
        break;  
    case 'INVALID_TOKEN':  
        wv_login.show();  
        break;  
    default:  
        console.log(errcode);  
    }  
};  
var params = {per:10, pageno:coms_current_pageno};  
mui.web_query('get_com_list', params, onSuccess, onError, 3);

调用后端接口怎么样才安全?

在APP中保存登录数据,每次调用接口时传输

程序员总能给自己找到偷懒的方法,有的程序为了省事,会在用户登录后,直接把用户名和密码保存在本地,然后每次调用后端接口时作为参数传递。真省事儿啊!可这种方法简单就像拿着一袋子钱在路上边走边喊“快来抢我呀!快来抢我呀!”,一个小小的嗅探器就能把用户的密码拿到手,如果用户习惯在所有地方用一个密码,那么你闯大祸了,黑客通过撞库的方法能把用户的所有信息一锅端。

登录时请求一次token,之后用token调用接口

这是比较安全的方式,用户在登录时,APP调用获取token的接口(比如http://api.abc.com/get_token/),用post将用户名和密码的摘要传递给服务器,然后服务器比对数据库中的用户信息,匹配则返回绑定该用户的token(这一般翻译为令牌,很直观的名字,一看就知道是有了这玩意,就会对你放行),而数据库中,在用户的token表中也同时插入了这个token相关的数据:这个token属于谁?这个token的有效期是多久?这个token当前登录的ip地址是?这个token对应的deviceid是?……
这样即便token被有心人截获,也不会造成太大的安全风险。因为没有用户名和密码,然后如果黑客通过这个token伪造用户请求,我们在服务器端接口被调用时就可以对发起请求的ip地址、user-agent之类的信息作比对,以防止伪造。再然后,如果token的有效期设得小,过一会儿它就过期了,除非黑客可以持续截获你的token,否则他只能干瞪眼。(插一句题外话:看到这里,是不是明白为什么不推荐在外面随便接入来历不明的wifi热点了?)
tips:token如何生成? 可以根据用户的信息及一些随机信息(比如时间戳)再通过hash编码(比如md5、sha1等)生成唯一的编码。
tips:token的安全级别,取决于你的实际需求,所以如果不是涉及财产安全的领域,并不建议太严格(比如用户走着走着,3G换了个基站,闪断了一下IP地址变了,尼玛token过期了,这就属于为了不必要的安全丢了用户体验,当然如果变换的IP地址跨省的话还是应该验证一下的,想想QQ有时候会让填验证码就明白了)。
tips:接口在返回信息时,可以包含本次请求的状态,比如成功调用,那么result['status']可能就是'success',而反之则是'error',而如果是'error',则result['errcode']中就可以包含错误的原因,比如errcode中是'invalid_token'就可以告诉APP这个token过期或无效,这时APP应弹出登录框或者用本地存储的用户名或密码再次请求token(用户选择“记住密码”,就应该在本地保存用户名和密码的摘要,方法见plus.storage的文档)。

再插点代码,基于plus.storage的用户信息类,注意:需要在plusReady之后再使用。

;function UserInfo(){  
};  

//清除登录信息  
UserInfo.clear = function(){  
    plus.storage.removeItem('username');  
    plus.storage.removeItem('password');  
    plus.storage.removeItem('token');  
}  

//检查是否包含自动登录的信息  
UserInfo.auto_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    if(!username || !pwd){  
        return false;  
    }  
    return true;  
}  

//检查是否已登录  
UserInfo.has_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    var token = UserInfo.token();  
    if(!username || !pwd || !token){  
        return false;  
    }  
    return true;  
};  

UserInfo.username = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('username');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('username');  
        return;  
    }  
    plus.storage.setItem('username', arguments[0]);  
};  

UserInfo.password = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('password');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('password');  
        return;  
    }  
    plus.storage.setItem('password', arguments[0]);  
};  

UserInfo.token = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('token');         
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('token');  
        return;  
    }  
    plus.storage.setItem('token', arguments[0]);  
};

这样当用户启动APP或使用了需要登录才能使用的功能时,就可以使用UserInfo.has_login()来判断是否已经登录,如果已登录,则使用UserInfo.token()来获取到token数据,作为参数调用远程的后端接口。

if(UserInfo.has_login()){  
    //打开需要展示给用户的页面,或者是调用远端接口  
}  
else{  
    wv_login.show('slide-in-up');   //从底部向上滑出登录页面  
}

在登录页面中,用户输入了用户名和密码后,并点击了”登录“按钮,我们下一步做什么?再插段代码(注意:此处使用的是我刚才代码中扩展的web_query函数,你也可以直接使用mui的ajax):


function get_pwd_hash(pwd){  
    var salt = 'hbuilder';  //此处的salt是为了避免黑客撞库,而在md5之前对原文做一定的变形,可以设为自己喜欢的,只要和服务器验证时的salt一致即可。  
    return md5(salt + pwd); //此处假设你已经引用了md5相关的库,比如github上的JavaScript-MD5  
}  

//这里假设你已经通过DOM操作获取到了用户名和密码,分别保存在username和password变量中。  
var username = xxx;  
var password = xxx;  
var pwd_hash = get_pwd_hash(password);  

var onSuccess = function(data){  
    UserInfo.username(username);  
    UserInfo.password(pwd_hash);  
    UserInfo.token(data.token); //把获取到的token保存到storage中  
    var wc = plus.webview.currentWebview();  
    wc.hide('slide-out-bottom');    //此处假设是隐藏登录页回到之前的页面,实际你也可以干点儿别的  
}  

var onError = function(errcode){  
    switch(errcode){  
    case 'INCORRECT_PASSWORD':  
        mui.toast('密码不正确');  
        break;  
    case 'USER_NOT_EXISTS':  
        mui.toast('用户尚未注册');  
        break;  
    }  
}  

mui.web_query('get_token', {username:username,password:pwd_hash}, onSuccess, onError, 3);

更安全一点,获取token通过SSL

刚才的方法,机智一点儿的读者大概会心存疑虑:那获取token时不还是得明文传输一次密码吗?
是的,你可以将这个获取token的地址,用SSL来保护(比如https://api.abc.com/get_token/),这样黑客即使截了包,一时半会儿也解不出什么信息。
SSL证书的获取渠道很多,我相信你总有办法查到,所以不废话了。不过话说namecheap上的SSL证书比godaddy的要便宜得多……(这是吐槽)
tips:前段时间OpenSSL漏洞让很多服务器遭殃,所以如果自己搭服务器,一定记得装补丁。
tips:可以把所有接口都弄成SSL的吗?可以。但会拖慢服务器,如果是配置并不自信的VPS,建议不折腾。

还要更更安全(这标题真省事)

还记得刚才APP向服务器请求token时,可以加入的用户信息吗?比如用户的设备deviceid。
如果我们在调用接口时,还附带一个当前时间戳参数timestamp,同时,用deviceid和这个时间戳再生成一个参数sign,比如 md5(deviceid timestamp token)这样的形式。而服务端首先验证一下参数中的时间戳与当前服务器时间是否一致(误差保持在合理范围内即可,比如5分钟),然后根据用户保存在服务器中的deviceid来对参数中的时间戳进行相同的变形,验证是否匹配,那便自然“更更安全”了。
tips:如果对整个调用请求中的参数进行排序,再以deviceid和timestamp加上排序后的参数来对整个调用生成1个sign,黑客即使截获sign,不同的时间点、参数请求所使用的sign也是不同的,难以伪造,自然会更安全。当然,写起来也更费事。
tips:明白了原理,整个验证过程是可以根据自己的需求改造的。

----------------------------------------------------------华丽的分割线-------------------------------------------------------

如果整篇文章中有不明白的名词,欢迎询问?不,不欢迎询问,因为搜索引擎都能找到。国内这方面的资料较少,但国外非常丰富,正所谓外事问google,内事问百度,程序员的第一个门槛就是要学会搜索。

什么?google上不去?开什么玩笑,你可以选择折腾点儿地天天找代理,也可以选择花一点点钱用红杏或类似的科学上网工具。
这里有福利→ 用我的邀请来注册红杏,购买30天以上的套餐,你我都可以各多10天

管理员,要求大大的加分!

收起阅读 »

如何安装sexftp插件

FTP 插件 HBuilder sexftp

说明

有用户反馈目前集成的ftp插件不是很好用,而且在某些情况下目前集成的ftp插件会出现问题。有用户推荐了sexftp,但是由于sexftp插件依赖JDT,导致暂时无法集成该插件,如有用户需要安装sexftp可按以下步骤安装

安装JDT插件

安装步骤参见http://ask.dcloud.net.cn/article/146中JDT安装部分

下载sexftp

sexftp_2012.0.0.201202120942.jar
下载完毕后把这个jar包放到HBuilder根目录下的dropins目录下

让HBuilder加载sexftp

启动HBuilder,此时HBuilder会扫描dropins目录下的插件,扫描到后静默进行插件的配置(此过程非常快),然后重启HBuilder即可。(如果你是在HBuilder运行期间拷贝到dropins目录的,只需要重启即可)

sexftp简单说明

安装完毕后,在toolbar最右边会出现一个小鸟图标,点击即可使用sexftp

继续阅读 »

说明

有用户反馈目前集成的ftp插件不是很好用,而且在某些情况下目前集成的ftp插件会出现问题。有用户推荐了sexftp,但是由于sexftp插件依赖JDT,导致暂时无法集成该插件,如有用户需要安装sexftp可按以下步骤安装

安装JDT插件

安装步骤参见http://ask.dcloud.net.cn/article/146中JDT安装部分

下载sexftp

sexftp_2012.0.0.201202120942.jar
下载完毕后把这个jar包放到HBuilder根目录下的dropins目录下

让HBuilder加载sexftp

启动HBuilder,此时HBuilder会扫描dropins目录下的插件,扫描到后静默进行插件的配置(此过程非常快),然后重启HBuilder即可。(如果你是在HBuilder运行期间拷贝到dropins目录的,只需要重启即可)

sexftp简单说明

安装完毕后,在toolbar最右边会出现一个小鸟图标,点击即可使用sexftp

收起阅读 »

系统通知栏显示进度条Android插件

插件开发 通知栏显示进度

app新版更新时,在系统通知栏显示下载进度条,代码还有地方要改进。
使用:

        var url = "";  
    var options = {method:"GET"};  
    dtask = plus.downloader.createDownload( url, options );  
    plus.notification.setNotification("新版下载", "开始下载");//插件调用  
    dtask.addEventListener( "statechanged", function(task,status){  
        switch(task.state) {  
            case 1: // 开始  
                break;  
            case 2: //已连接到服务器  
                 break;  
            case 3: // 已接收到数据  
                var current = parseInt(100*task.downloadedSize/task.totalSize);  
                plus.notification.setProgress(current);//插件调用  
                 break;  
            case 4: // 下载完成           
                plus.notification.compProgressNotification("下载完成");//插件调用  
                plus.runtime.install(plus.io.convertLocalFileSystemURL(task.filename),//安装APP  
                               {force:true},function(){  

                },function(){  
                    mui.toast('安装失败');  
                });  
                break;  
        }  
    } );

添加权限:

"notification":{  
            "description": "通知栏"  
        }

plugin.js:

document.addEventListener("plusready",  function()  
{  
    var B = window.plus.bridge;  
    var notification =   
    {  
        "setProgress":function(incr){  
            return B.exec("notification", "setProgress", [incr]);  
        },  
        "setNotification":function(contentTitle, ticker){  
            return B.exec("notification", "setProgressNotification", [contentTitle, ticker]);  
        },  
        "compProgressNotification":function(contentTitle){  
            return B.exec("notification", "compProgressNotification", [contentTitle]);  
        }  
    };  
    window.plus.notification = notification;  
}, true);

Notify.java:

import io.dcloud.DHInterface.AbsMgr;  
import io.dcloud.DHInterface.IFeature;  
import io.dcloud.DHInterface.IWebview;  
import io.dcloud.util.JSUtil;  
import io.dcloud.adapter.util.AndroidResources;  
import android.R.integer;  
import android.R.string;  
import android.app.Activity;  
import android.app.Notification;  
import android.app.NotificationManager;  
import android.content.Context;  
import android.os.Bundle;  
import android.util.Log;  

public class Notify implements IFeature{  

    //IWebview webview;  
    //Context context;  
    NotificationManager manager;  
    Notification.Builder builder;  
    Activity activity;  

    @Override  
    public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
        activity = pWebview.getActivity();  
        manager = (NotificationManager)activity.getSystemService(Activity.NOTIFICATION_SERVICE);  
        builder = new Notification.Builder(activity);  
        builder.setWhen(System.currentTimeMillis())  
            .setPriority(Notification.PRIORITY_DEFAULT)  
            .setContentTitle("新版下载")  
            .setTicker("开始下载")  
            .setSmallIcon(R.drawable.icon)  
            .setVibrate(null);  

        activity.runOnUiThread(new Runnable() {  
            @Override  
            public void run() {  
                if("setNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    String ticker = pArgs[1];  
                    builder.setContentTitle(title).setTicker(ticker);  
                    manager.notify(1000, builder.build());  
                }  
                else if("setProgress".equals(action))  
                {  

                    int incr = Integer.parseInt(pArgs[0]);  
                    builder.setProgress(100, incr, false);  
                    manager.notify(1000, builder.build());  
                }  
                else if("compProgressNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    builder.setContentTitle(title).setProgress(0, 0, false);  
                    manager.notify(1000, builder.build());  
                }  
            }  
        });  
        return null;  
    }  

    @Override  
    public void init(AbsMgr arg0, String arg1) {  

    }  
    @Override  
    public void dispose(String arg0) {  
    }  
}
继续阅读 »

app新版更新时,在系统通知栏显示下载进度条,代码还有地方要改进。
使用:

        var url = "";  
    var options = {method:"GET"};  
    dtask = plus.downloader.createDownload( url, options );  
    plus.notification.setNotification("新版下载", "开始下载");//插件调用  
    dtask.addEventListener( "statechanged", function(task,status){  
        switch(task.state) {  
            case 1: // 开始  
                break;  
            case 2: //已连接到服务器  
                 break;  
            case 3: // 已接收到数据  
                var current = parseInt(100*task.downloadedSize/task.totalSize);  
                plus.notification.setProgress(current);//插件调用  
                 break;  
            case 4: // 下载完成           
                plus.notification.compProgressNotification("下载完成");//插件调用  
                plus.runtime.install(plus.io.convertLocalFileSystemURL(task.filename),//安装APP  
                               {force:true},function(){  

                },function(){  
                    mui.toast('安装失败');  
                });  
                break;  
        }  
    } );

添加权限:

"notification":{  
            "description": "通知栏"  
        }

plugin.js:

document.addEventListener("plusready",  function()  
{  
    var B = window.plus.bridge;  
    var notification =   
    {  
        "setProgress":function(incr){  
            return B.exec("notification", "setProgress", [incr]);  
        },  
        "setNotification":function(contentTitle, ticker){  
            return B.exec("notification", "setProgressNotification", [contentTitle, ticker]);  
        },  
        "compProgressNotification":function(contentTitle){  
            return B.exec("notification", "compProgressNotification", [contentTitle]);  
        }  
    };  
    window.plus.notification = notification;  
}, true);

Notify.java:

import io.dcloud.DHInterface.AbsMgr;  
import io.dcloud.DHInterface.IFeature;  
import io.dcloud.DHInterface.IWebview;  
import io.dcloud.util.JSUtil;  
import io.dcloud.adapter.util.AndroidResources;  
import android.R.integer;  
import android.R.string;  
import android.app.Activity;  
import android.app.Notification;  
import android.app.NotificationManager;  
import android.content.Context;  
import android.os.Bundle;  
import android.util.Log;  

public class Notify implements IFeature{  

    //IWebview webview;  
    //Context context;  
    NotificationManager manager;  
    Notification.Builder builder;  
    Activity activity;  

    @Override  
    public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
        activity = pWebview.getActivity();  
        manager = (NotificationManager)activity.getSystemService(Activity.NOTIFICATION_SERVICE);  
        builder = new Notification.Builder(activity);  
        builder.setWhen(System.currentTimeMillis())  
            .setPriority(Notification.PRIORITY_DEFAULT)  
            .setContentTitle("新版下载")  
            .setTicker("开始下载")  
            .setSmallIcon(R.drawable.icon)  
            .setVibrate(null);  

        activity.runOnUiThread(new Runnable() {  
            @Override  
            public void run() {  
                if("setNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    String ticker = pArgs[1];  
                    builder.setContentTitle(title).setTicker(ticker);  
                    manager.notify(1000, builder.build());  
                }  
                else if("setProgress".equals(action))  
                {  

                    int incr = Integer.parseInt(pArgs[0]);  
                    builder.setProgress(100, incr, false);  
                    manager.notify(1000, builder.build());  
                }  
                else if("compProgressNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    builder.setContentTitle(title).setProgress(0, 0, false);  
                    manager.notify(1000, builder.build());  
                }  
            }  
        });  
        return null;  
    }  

    @Override  
    public void init(AbsMgr arg0, String arg1) {  

    }  
    @Override  
    public void dispose(String arg0) {  
    }  
}
收起阅读 »

提升HTML5的性能体验系列之四 使用原生UI

nativeUI 体验 性能优化

系列文章目录导航:

【本文更新时间2017-5-8】

原生UI的设计目的

HTML和css有一个优势就是灵活的样式设计。
在大多数情况下,我们都应该使用HTML+css来负责UI。但是有些情况下,我们发现HTML+css的UI不满足需求。

  1. 绝对置顶
    HTML的video等元素,以及5+的titleNView、subnview、map、二维码扫描等原生元素,这些原生控件的层级高于div。
    对于一些弹出的需要置顶的控件,会造成div模式的控件无法绝对置顶,就像在web开发里弹出的div被flash遮住一样。
    使用div方式开发的如下弹出控件:alert、confirm、actionSheet、waiting、date、time、prompt、toast,都存在这个问题。
    为此,HTML5+扩展了上述native级别的UI控件,保证可以绝对置顶。
  2. 全屏遮罩
    弹出控件时,需要对整个屏幕的其他部分做阴影遮罩。
    使用div遮罩,同样无法蒙住titleNView、video、map等原生控件,也无法跨webview。
    而且原生的遮罩还可以蒙住手机顶部状态条,这都是div遮罩做不到的。
    HTML5+扩展的nativeUI控件,保证可以全屏遮罩。
  3. 跨webview
    如果界面使用webview方式的tab选项卡,但想从底部弹出一个菜单actionsheet,此时使用div方式的弹出菜单由于无法跨webview,只能看到一部分。
    此时就需要使用nativeUI里的actionsheet才能跨webview。
    注意:从HBuilder8起,官方推荐使用nview替代父子Webview,并计划在以后废除对同屏显示多个Webview的支持。
    title部分,请使用webview里的titleNView,参考http://www.html5plus.org/doc/zh_cn/webview.html
    tab部分,参考此文实现原生tab:http://ask.dcloud.net.cn/article/12602

原生UI的特点

为解决上述问题,HTML5+提供了原生ui,分别在

  1. plus.nativeUI规范。
    plus.nativeUI对原生的常用弹出型UI控件做了封装,包括警告框、确认框、弹出输入框、弹出底部菜单、等待框、可自动消失的提示条等。参考:http://www.html5plus.org/#specification#/specification/nativeUI.html
  2. plus.nativeobj规范。
    与nativeUI不同plus.nativeobj提供的是底层的绘图和写字功能, 参考:http://html5plus.org/doc/zh_cn/nativeobj.html
    http://ask.dcloud.net.cn/article/665
    (抱歉在nativeUI的名称上没有强调弹出式的特征,导致开发者可能误以为所有原生ui控件都在nativeUI下,其实nativeUI只负责弹出式控件)

原生UI的特点有:

  1. 绝对置顶,不担心被其他原生控件遮挡
  2. 可以跨webview显示
  3. 全屏遮罩,保证手机屏幕其他部分处于蒙灰状态
  4. 原生样式及高性能体验
    nativeUI的特点是,调用OS的UI控件,确保和本机体验一致。
    在iOS6、iOS7+、Android2.3、Android4.x、Android5上,OS是不同的设计风格,但nativeUI的弹出框和当前OS风格一致。

nativeUI的局限性

相比div+css,plus.nativeUI也有一个缺点就是可定制性差。
开发者无法使用css修饰这些弹出控件的样式,它们长的、且只能长得和本机OS的风格一样。
所以在需要个性化设计控件且不在意绝对置顶、跨webview、全屏遮罩这些问题时,也可以使用div+css方式制作弹出控件。
plus.nativeobj的view由于可以自己贴图写字,定制性强,但使用略复杂。

利用plus.nativeobj的view来扩展原生控件的UI

5+引擎提供了原生的扫描、地图控件,想修改这些控件的UI,则需使用nativeObj.view。
这个不是指弹出一个原生框盖住地图那么简单。是可以侵入控件的UI的。
比如在plus.map的原生地图控件上,要绘制一些map控件无法支持的内容,则可以使用nview来处理。
比如在扫码控件上,可以使用nview在摄像头区域中画图标、写字。
也可以把摄像头区域扩大至全屏大,上面的按钮都使用nview绘制。

mui框架的封装

与nativeUI不同,mui同时也补充封装了一些div方式的弹出控件。
包括div方式的alert、confirm、actionSheet、popover...,可以在nativeUI遇到限制时使用。
另外如果要同时多端发布到非5+环境下比如微信公众号里时,则使用mui.alert会自动判断切换,在5+环境下调用plus.nativeUI.alert,在非5+环境下则使用div模式。

继续阅读 »

系列文章目录导航:

【本文更新时间2017-5-8】

原生UI的设计目的

HTML和css有一个优势就是灵活的样式设计。
在大多数情况下,我们都应该使用HTML+css来负责UI。但是有些情况下,我们发现HTML+css的UI不满足需求。

  1. 绝对置顶
    HTML的video等元素,以及5+的titleNView、subnview、map、二维码扫描等原生元素,这些原生控件的层级高于div。
    对于一些弹出的需要置顶的控件,会造成div模式的控件无法绝对置顶,就像在web开发里弹出的div被flash遮住一样。
    使用div方式开发的如下弹出控件:alert、confirm、actionSheet、waiting、date、time、prompt、toast,都存在这个问题。
    为此,HTML5+扩展了上述native级别的UI控件,保证可以绝对置顶。
  2. 全屏遮罩
    弹出控件时,需要对整个屏幕的其他部分做阴影遮罩。
    使用div遮罩,同样无法蒙住titleNView、video、map等原生控件,也无法跨webview。
    而且原生的遮罩还可以蒙住手机顶部状态条,这都是div遮罩做不到的。
    HTML5+扩展的nativeUI控件,保证可以全屏遮罩。
  3. 跨webview
    如果界面使用webview方式的tab选项卡,但想从底部弹出一个菜单actionsheet,此时使用div方式的弹出菜单由于无法跨webview,只能看到一部分。
    此时就需要使用nativeUI里的actionsheet才能跨webview。
    注意:从HBuilder8起,官方推荐使用nview替代父子Webview,并计划在以后废除对同屏显示多个Webview的支持。
    title部分,请使用webview里的titleNView,参考http://www.html5plus.org/doc/zh_cn/webview.html
    tab部分,参考此文实现原生tab:http://ask.dcloud.net.cn/article/12602

原生UI的特点

为解决上述问题,HTML5+提供了原生ui,分别在

  1. plus.nativeUI规范。
    plus.nativeUI对原生的常用弹出型UI控件做了封装,包括警告框、确认框、弹出输入框、弹出底部菜单、等待框、可自动消失的提示条等。参考:http://www.html5plus.org/#specification#/specification/nativeUI.html
  2. plus.nativeobj规范。
    与nativeUI不同plus.nativeobj提供的是底层的绘图和写字功能, 参考:http://html5plus.org/doc/zh_cn/nativeobj.html
    http://ask.dcloud.net.cn/article/665
    (抱歉在nativeUI的名称上没有强调弹出式的特征,导致开发者可能误以为所有原生ui控件都在nativeUI下,其实nativeUI只负责弹出式控件)

原生UI的特点有:

  1. 绝对置顶,不担心被其他原生控件遮挡
  2. 可以跨webview显示
  3. 全屏遮罩,保证手机屏幕其他部分处于蒙灰状态
  4. 原生样式及高性能体验
    nativeUI的特点是,调用OS的UI控件,确保和本机体验一致。
    在iOS6、iOS7+、Android2.3、Android4.x、Android5上,OS是不同的设计风格,但nativeUI的弹出框和当前OS风格一致。

nativeUI的局限性

相比div+css,plus.nativeUI也有一个缺点就是可定制性差。
开发者无法使用css修饰这些弹出控件的样式,它们长的、且只能长得和本机OS的风格一样。
所以在需要个性化设计控件且不在意绝对置顶、跨webview、全屏遮罩这些问题时,也可以使用div+css方式制作弹出控件。
plus.nativeobj的view由于可以自己贴图写字,定制性强,但使用略复杂。

利用plus.nativeobj的view来扩展原生控件的UI

5+引擎提供了原生的扫描、地图控件,想修改这些控件的UI,则需使用nativeObj.view。
这个不是指弹出一个原生框盖住地图那么简单。是可以侵入控件的UI的。
比如在plus.map的原生地图控件上,要绘制一些map控件无法支持的内容,则可以使用nview来处理。
比如在扫码控件上,可以使用nview在摄像头区域中画图标、写字。
也可以把摄像头区域扩大至全屏大,上面的按钮都使用nview绘制。

mui框架的封装

与nativeUI不同,mui同时也补充封装了一些div方式的弹出控件。
包括div方式的alert、confirm、actionSheet、popover...,可以在nativeUI遇到限制时使用。
另外如果要同时多端发布到非5+环境下比如微信公众号里时,则使用mui.alert会自动判断切换,在5+环境下调用plus.nativeUI.alert,在非5+环境下则使用div模式。

收起阅读 »

调用系统通讯录选择手机号 Android插件

插件开发 通讯录

plus.contacts加载全部联系人,只为选择一个联系人时,联系人较多情况下,速度很慢。搞了个插件来调用系统通讯录,只返回选择的一个联系人。

使用:

plus.nativecontact.pick(function(contact){//成功  
           alert(contact.numbers[0].value);//所选择联系人的第一个电话号码  
    }, function(status){//失败  

    });  
//contact为联系人json对象,形式:{"givenName":"张三","numbers":[{"value":"123"},{"value":"456"}]}

添加权限:

"nativecontact":{  
            "description": "系统通讯录"  
        }

plugin.js:

document.addEventListener("plusready",  function()  
{  
    var B = window.plus.bridge;  
    var nativecontact =   
    {  
        "pick":function(successCallback, errorCallback){  
            var success = typeof successCallback !== 'function' ? null : function(args) {  
                        successCallback(args);  
                    },  
                fail = typeof errorCallback !== 'function' ? null : function(code) {  
                        errorCallback(code);  
                    },  
                callbackID = B.callbackId(success, fail);  
            return B.exec("nativecontact", "pick", [callbackID]);  
        }  
    };  
    window.plus.nativecontact = nativecontact;  
}, true);

NativeContact.java:

import android.app.Activity;  
import android.content.Context;  
import android.content.Intent;  
import android.database.Cursor;  
import android.net.Uri;  
import android.provider.ContactsContract;  
import io.dcloud.DHInterface.AbsMgr;  
import io.dcloud.DHInterface.IApp;  
import io.dcloud.DHInterface.IFeature;  
import io.dcloud.DHInterface.ISysEventListener;  
import io.dcloud.DHInterface.ISysEventListener.SysEventType;  
import io.dcloud.DHInterface.IWebview;  
import io.dcloud.util.JSONUtil;  
import io.dcloud.util.JSUtil;  

public class NativeContact implements IFeature{  
    private final static int REQUESTCODE = 1;  

    @Override  
    public void dispose(String arg0) {  
        // TODO Auto-generated method stub  

    }  

    @Override  
    public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
        if("pick".equals(action))  
        {  
            final IApp _app = pWebview.obtainFrameView().obtainApp();  
            final String callBackId = pArgs[0];  
            _app.registerSysEventListener(new ISysEventListener(){  
                @Override  
                public boolean onExecute(SysEventType pEventType, Object pArgs) {  
                    Object[] _args = (Object[])pArgs;  
                    int requestCode = (Integer)_args[0];  
                    int resultCode = (Integer)_args[1];  
                    Intent data = (Intent)_args[2];  
                    if(pEventType == SysEventType.OnActivityResult){  
                        _app.unregisterSysEventListener(this, SysEventType.OnActivityResult);  
                        if (requestCode == REQUESTCODE) {  
                            if(resultCode == Activity.RESULT_OK){  
                                String phoneNumber = null;  
                                String resultString = "";  
                                Context context = pWebview.getContext();  
                                Uri contactData = data.getData();  
                                Cursor cursor = context.getContentResolver().query(contactData, null, null, null, null);  
                                cursor.moveToFirst();  
                                String givenName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));  
                                resultString += "{\"givenName\":\"" + givenName + "\",\"numbers\":[";  
                                String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));  
                                Cursor pCursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,   
                                        null,   
                                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId,   
                                        null,   
                                        null);  
                                while (pCursor.moveToNext()) {  
                                    phoneNumber = pCursor.getString(pCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));  
                                    resultString += "{\"value\":\"" + phoneNumber + "\"},";  
                                }  
                                resultString = resultString.substring(0, resultString.length()-1);  
                                resultString += "]}";  
                                cursor.close();  
                                pCursor.close();  
                                JSUtil.execCallback(pWebview, callBackId, JSONUtil.createJSONObject(resultString), JSUtil.OK, false);  
                            }  
                        }  
                    }  
                    return false;  
                }  

            }, SysEventType.OnActivityResult);  
            Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);  
            pWebview.getActivity().startActivityForResult(intent, REQUESTCODE);  
        }  
        return null;  
    }  

    @Override  
    public void init(AbsMgr arg0, String arg1) {  
        // TODO Auto-generated method stub  
    }  
}
继续阅读 »

plus.contacts加载全部联系人,只为选择一个联系人时,联系人较多情况下,速度很慢。搞了个插件来调用系统通讯录,只返回选择的一个联系人。

使用:

plus.nativecontact.pick(function(contact){//成功  
           alert(contact.numbers[0].value);//所选择联系人的第一个电话号码  
    }, function(status){//失败  

    });  
//contact为联系人json对象,形式:{"givenName":"张三","numbers":[{"value":"123"},{"value":"456"}]}

添加权限:

"nativecontact":{  
            "description": "系统通讯录"  
        }

plugin.js:

document.addEventListener("plusready",  function()  
{  
    var B = window.plus.bridge;  
    var nativecontact =   
    {  
        "pick":function(successCallback, errorCallback){  
            var success = typeof successCallback !== 'function' ? null : function(args) {  
                        successCallback(args);  
                    },  
                fail = typeof errorCallback !== 'function' ? null : function(code) {  
                        errorCallback(code);  
                    },  
                callbackID = B.callbackId(success, fail);  
            return B.exec("nativecontact", "pick", [callbackID]);  
        }  
    };  
    window.plus.nativecontact = nativecontact;  
}, true);

NativeContact.java:

import android.app.Activity;  
import android.content.Context;  
import android.content.Intent;  
import android.database.Cursor;  
import android.net.Uri;  
import android.provider.ContactsContract;  
import io.dcloud.DHInterface.AbsMgr;  
import io.dcloud.DHInterface.IApp;  
import io.dcloud.DHInterface.IFeature;  
import io.dcloud.DHInterface.ISysEventListener;  
import io.dcloud.DHInterface.ISysEventListener.SysEventType;  
import io.dcloud.DHInterface.IWebview;  
import io.dcloud.util.JSONUtil;  
import io.dcloud.util.JSUtil;  

public class NativeContact implements IFeature{  
    private final static int REQUESTCODE = 1;  

    @Override  
    public void dispose(String arg0) {  
        // TODO Auto-generated method stub  

    }  

    @Override  
    public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
        if("pick".equals(action))  
        {  
            final IApp _app = pWebview.obtainFrameView().obtainApp();  
            final String callBackId = pArgs[0];  
            _app.registerSysEventListener(new ISysEventListener(){  
                @Override  
                public boolean onExecute(SysEventType pEventType, Object pArgs) {  
                    Object[] _args = (Object[])pArgs;  
                    int requestCode = (Integer)_args[0];  
                    int resultCode = (Integer)_args[1];  
                    Intent data = (Intent)_args[2];  
                    if(pEventType == SysEventType.OnActivityResult){  
                        _app.unregisterSysEventListener(this, SysEventType.OnActivityResult);  
                        if (requestCode == REQUESTCODE) {  
                            if(resultCode == Activity.RESULT_OK){  
                                String phoneNumber = null;  
                                String resultString = "";  
                                Context context = pWebview.getContext();  
                                Uri contactData = data.getData();  
                                Cursor cursor = context.getContentResolver().query(contactData, null, null, null, null);  
                                cursor.moveToFirst();  
                                String givenName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));  
                                resultString += "{\"givenName\":\"" + givenName + "\",\"numbers\":[";  
                                String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));  
                                Cursor pCursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,   
                                        null,   
                                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId,   
                                        null,   
                                        null);  
                                while (pCursor.moveToNext()) {  
                                    phoneNumber = pCursor.getString(pCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));  
                                    resultString += "{\"value\":\"" + phoneNumber + "\"},";  
                                }  
                                resultString = resultString.substring(0, resultString.length()-1);  
                                resultString += "]}";  
                                cursor.close();  
                                pCursor.close();  
                                JSUtil.execCallback(pWebview, callBackId, JSONUtil.createJSONObject(resultString), JSUtil.OK, false);  
                            }  
                        }  
                    }  
                    return false;  
                }  

            }, SysEventType.OnActivityResult);  
            Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);  
            pWebview.getActivity().startActivityForResult(intent, REQUESTCODE);  
        }  
        return null;  
    }  

    @Override  
    public void init(AbsMgr arg0, String arg1) {  
        // TODO Auto-generated method stub  
    }  
}
收起阅读 »

iOS证书(.p12)和描述文件(.mobileprovision)申请

5+App开发 Apple证书 iOS证书

iOS有两种证书和描述文件:

证书类型 使用场景
开发(Development)证书和描述文件 用于开发测试,在 HBuilderX 中打包后可在真机环境通过Safari调试
发布(Distribution)证书和描述文件 用于提交 AppStore,在 HBuilderX 中提交云打包后提交到 AppStore 审核发布

准备环境

  • 必需要有苹果开发者账号,并且加入了 “iOS Developer Program”
  • Mac OS 10.9以上系统(如果已经申请p12证书则不需要)

登录 iOS Dev Center

打开网站 iOS Dev Center
使用苹果开发者账号登录 iOS Dev Center:


登录成功后在页面左侧选择 “Certificates,IDs & Profiles” 进入证书管理页面:

在证书管理页面,可以看到所有已经申请的证书及描述文件:

下面我们从头开始学习一下如何申请开发证书、发布证书及相对应的描述文件。

首先需要申请苹果 App ID (App的唯一标识)

> 如果已经申请,可跳过此节

选择页面的 “Identifiers" 可查看到已申请的所有 App 应用标识,点击页面上的加号来创建一个新的应用标识:


选择标识类型为 “App IDs”,然后点击 “Continue”

平台选择 “iOS,tvOS,watchOS”,Bundle ID 选择 “Explicit”,在 Description 中填写描述,然后填写 Bundle ID,Bundle ID 要保持唯一性,建议填写反域名加应用标识的格式 如:“io.dcloud.hellouniapp”, 然后点击 “Continue”
注意:在 HBuilderX 中 App 提交云端打包时界面上的 AppID 栏填写的就是这个 Bundle ID

接下来需要选择应用需要使用的服务(如需要使用到消息推送功能,则选择“Push Notifications”),然后点击 “Continue”
注意:如果App用不到的服务一定不要勾选,以免响应审核


确认后选择提交,回到 identifiers 页面即可看到刚创建的App ID:

至此,App ID 已经创建完毕,接下来开始创建开发证书,在创建开发证书前,需要先生成证书请求文件

生成证书请求文件

> 不管是申请开发 (Development) 证书还是发布 (Distribution) 证书,都需要使用证书请求 (.certSigningRequest) 文件,证书请求文件需在Mac OS上使用 “钥匙串访问” 工具生成。

在“Spltlight Search”中搜索“钥匙串”并打开 “钥匙串访问” 工具:



打开菜单 “钥匙串访问”->“证书助理”,选择“从证书颁发机构请求证书...”:

打开创建请求证书页面,在页面中输入用户邮件地址、常用名称,选择存储到磁盘,点击 “继续” :

文件名称为“CertificateSigningRequest.certSigningRequest”,选择保存位置,点击 “存储” 将证书请求文件保存到指定路径下,后面申请开发(Development)证书和发布(Production)证书时需要用到

申请开发(Development)证书和描述文件

> 开发(Development)证书及对应的描述文件用于开发阶段使用,可以直接将 App 安装到手机上,一个描述文件最多绑定100台测试设备(开发证书不能用于发布应用到 App Store)。

申请开发(Development)证书

在证书管理页面选择 “Certificates" 可查看到已申请的所有证书(TYPE:Development 为开发证书,Distribution为发布证书),点击页面的加号来创建一个新的证书:


在 “Software” 栏下选中 “iOS App Development” 然后点击 “Continue”:

接下来需要用到刚刚生成的证书请求文件,点击“Choose File...”选择刚刚保存到本地的 “CertificateSigningRequest.certSigningRequest”文件,点击 “Continue” 生成证书文件:

生成证书后选择 “Download” 将证书下到本地 (ios_development.cer):

双击保存到本地的 ios_development.cer 文件,会自动打开 “钥匙串访问” 工具说明导入证书成功,可以在证书列表中看到刚刚导入的证书,接下来需要导出 .p12 证书文件,选中导入的证书,右键选择 “导出...”:

输入文件名、选择路径后点击 “存储”:

输入密码及确认密码后点击 “好”:

至此,我们已经完成了开发证书的制作(得到了 xxx.p12 证书文件),接下来,继续生成开发阶段所需的描述文件,在生成描述文件之前,需要先添加调试设备(iPhone 、iPad)

添加调试设备

> 开发描述文件必须绑定调试设备,只有授权的设备才可以直接安装 App,所以在申请开发描述文件之前,先添加调试的设备。
(如果已经添加设备,可跳过此节)

在证书管理页面选择 “Devices”,可查看到已添加的所有设备信息,点击页面上的加号来添加一个新设备:


填写设备名称 和 UDID(设备标识):

获取设备UDID方法,将设备连接到电脑,启动 iTunes,点击此区域可切换显示设备的 UDID,右键选择复制

输入完成后,点击“Continue” 继续完成添加即可;
接下来继续申请描述文件

申请开发 (Development) 描述文件

在证书管理页面选择 “Profiles”,可查看到已申请的所有描述文件,点击页面上的加号来添加一个新的描述文件:


在 “Development” 栏下选中 “iOS App Development”,点击“Continue”按钮:

这里要选择之前创建的 “App ID” (这里是“io.dcloud.hellouniapp”),点击“Continue”:

接下来选择需要绑定的证书,这里建议直接勾选 “Select All”,点击“Continue”:

选择授权调试设备,这里建议直接勾选 “Select All”,点击 “Continue”:

输入描述文件的名称(如“HelloUniAppProfile”), 点击 “Generate” 生成描述文件:

点击“Download”下载保存开发描述文件(文件后缀为 .mobileprovision)

至此,我们已经得到了开发证书(.p12)及对应的描述文件(.mobileprovision),接下看一下如何制作发布证书及发布描述文件

申请发布(Distribution)证书和描述文件

> 发布 (Production) 证书用于正式发布环境下使用,用于提交到Appstore审核发布。发布证书打包的 ipa,不可以直接安装到手机上

申请发布(Production)证书

在证书管理页面选择 “Certificates" 可查看到已申请的所有证书(TYPE:Development 为开发证书,Distribution为发布证书),点击页面的加号来创建一个新的证书:


在 “Software” 栏下选中 “App Store and Ad Hoc”,点击 “Continue”:

接下来同样需要用到之前生成的证书请求文件,点击“Choose File...”选择刚刚保存到本地的 “CertificateSigningRequest.certSigningRequest”文件,点击 “Continue” 生成证书文件:

生成证书成功,选择“Download” 将证书下载到本地 (ios_production.cer):

同样双击保存到本地的 ios_production.cer 文件将证书导入到 “钥匙串访问”工具中,可以在证书列表中看到刚刚导入的证书,接下来需要导出 .p12 证书文件,选中导入的证书,右键选择 “导出...”:

输入文件名、选择路径后点击 “存储”:

输入密码及确认密码后点击 “好”:

至此,我们已经完成了发布证书的制作(得到了 xxx.p12 证书文件),接下来,继续生成发布描述文件

申请发布 (Distribution) 描述文件

在证书管理页面选择 “Profiles”,可查看到已申请的所有描述文件,点击页面上的加号来添加一个新的描述文件:


在 “Distribution” 栏下选中 “App Store”,点击“Continue”按钮:

这里要选择之前创建的 “App ID” (这里是“io.dcloud.hellouniapp”),点击“Continue”:

接下来选择需要绑定的发布证书(iOS Distribution),这里勾选刚刚生成的发布证书”,点击“Continue”:

接下来输入描述文件的名称(如“HelloUniAppProfileDistribution”), 点击 “Generate” 生成描述文件:

然后点击 “Download” 将描述文件下载到本地(文件后缀为 .mobileprovision)

至此,我们已经得到了发布证书(.p12)及对应的发布描述文件(.mobileprovision)

继续阅读 »

iOS有两种证书和描述文件:

证书类型 使用场景
开发(Development)证书和描述文件 用于开发测试,在 HBuilderX 中打包后可在真机环境通过Safari调试
发布(Distribution)证书和描述文件 用于提交 AppStore,在 HBuilderX 中提交云打包后提交到 AppStore 审核发布

准备环境

  • 必需要有苹果开发者账号,并且加入了 “iOS Developer Program”
  • Mac OS 10.9以上系统(如果已经申请p12证书则不需要)

登录 iOS Dev Center

打开网站 iOS Dev Center
使用苹果开发者账号登录 iOS Dev Center:


登录成功后在页面左侧选择 “Certificates,IDs & Profiles” 进入证书管理页面:

在证书管理页面,可以看到所有已经申请的证书及描述文件:

下面我们从头开始学习一下如何申请开发证书、发布证书及相对应的描述文件。

首先需要申请苹果 App ID (App的唯一标识)

> 如果已经申请,可跳过此节

选择页面的 “Identifiers" 可查看到已申请的所有 App 应用标识,点击页面上的加号来创建一个新的应用标识:


选择标识类型为 “App IDs”,然后点击 “Continue”

平台选择 “iOS,tvOS,watchOS”,Bundle ID 选择 “Explicit”,在 Description 中填写描述,然后填写 Bundle ID,Bundle ID 要保持唯一性,建议填写反域名加应用标识的格式 如:“io.dcloud.hellouniapp”, 然后点击 “Continue”
注意:在 HBuilderX 中 App 提交云端打包时界面上的 AppID 栏填写的就是这个 Bundle ID

接下来需要选择应用需要使用的服务(如需要使用到消息推送功能,则选择“Push Notifications”),然后点击 “Continue”
注意:如果App用不到的服务一定不要勾选,以免响应审核


确认后选择提交,回到 identifiers 页面即可看到刚创建的App ID:

至此,App ID 已经创建完毕,接下来开始创建开发证书,在创建开发证书前,需要先生成证书请求文件

生成证书请求文件

> 不管是申请开发 (Development) 证书还是发布 (Distribution) 证书,都需要使用证书请求 (.certSigningRequest) 文件,证书请求文件需在Mac OS上使用 “钥匙串访问” 工具生成。

在“Spltlight Search”中搜索“钥匙串”并打开 “钥匙串访问” 工具:



打开菜单 “钥匙串访问”->“证书助理”,选择“从证书颁发机构请求证书...”:

打开创建请求证书页面,在页面中输入用户邮件地址、常用名称,选择存储到磁盘,点击 “继续” :

文件名称为“CertificateSigningRequest.certSigningRequest”,选择保存位置,点击 “存储” 将证书请求文件保存到指定路径下,后面申请开发(Development)证书和发布(Production)证书时需要用到

申请开发(Development)证书和描述文件

> 开发(Development)证书及对应的描述文件用于开发阶段使用,可以直接将 App 安装到手机上,一个描述文件最多绑定100台测试设备(开发证书不能用于发布应用到 App Store)。

申请开发(Development)证书

在证书管理页面选择 “Certificates" 可查看到已申请的所有证书(TYPE:Development 为开发证书,Distribution为发布证书),点击页面的加号来创建一个新的证书:


在 “Software” 栏下选中 “iOS App Development” 然后点击 “Continue”:

接下来需要用到刚刚生成的证书请求文件,点击“Choose File...”选择刚刚保存到本地的 “CertificateSigningRequest.certSigningRequest”文件,点击 “Continue” 生成证书文件:

生成证书后选择 “Download” 将证书下到本地 (ios_development.cer):

双击保存到本地的 ios_development.cer 文件,会自动打开 “钥匙串访问” 工具说明导入证书成功,可以在证书列表中看到刚刚导入的证书,接下来需要导出 .p12 证书文件,选中导入的证书,右键选择 “导出...”:

输入文件名、选择路径后点击 “存储”:

输入密码及确认密码后点击 “好”:

至此,我们已经完成了开发证书的制作(得到了 xxx.p12 证书文件),接下来,继续生成开发阶段所需的描述文件,在生成描述文件之前,需要先添加调试设备(iPhone 、iPad)

添加调试设备

> 开发描述文件必须绑定调试设备,只有授权的设备才可以直接安装 App,所以在申请开发描述文件之前,先添加调试的设备。
(如果已经添加设备,可跳过此节)

在证书管理页面选择 “Devices”,可查看到已添加的所有设备信息,点击页面上的加号来添加一个新设备:


填写设备名称 和 UDID(设备标识):

获取设备UDID方法,将设备连接到电脑,启动 iTunes,点击此区域可切换显示设备的 UDID,右键选择复制

输入完成后,点击“Continue” 继续完成添加即可;
接下来继续申请描述文件

申请开发 (Development) 描述文件

在证书管理页面选择 “Profiles”,可查看到已申请的所有描述文件,点击页面上的加号来添加一个新的描述文件:


在 “Development” 栏下选中 “iOS App Development”,点击“Continue”按钮:

这里要选择之前创建的 “App ID” (这里是“io.dcloud.hellouniapp”),点击“Continue”:

接下来选择需要绑定的证书,这里建议直接勾选 “Select All”,点击“Continue”:

选择授权调试设备,这里建议直接勾选 “Select All”,点击 “Continue”:

输入描述文件的名称(如“HelloUniAppProfile”), 点击 “Generate” 生成描述文件:

点击“Download”下载保存开发描述文件(文件后缀为 .mobileprovision)

至此,我们已经得到了开发证书(.p12)及对应的描述文件(.mobileprovision),接下看一下如何制作发布证书及发布描述文件

申请发布(Distribution)证书和描述文件

> 发布 (Production) 证书用于正式发布环境下使用,用于提交到Appstore审核发布。发布证书打包的 ipa,不可以直接安装到手机上

申请发布(Production)证书

在证书管理页面选择 “Certificates" 可查看到已申请的所有证书(TYPE:Development 为开发证书,Distribution为发布证书),点击页面的加号来创建一个新的证书:


在 “Software” 栏下选中 “App Store and Ad Hoc”,点击 “Continue”:

接下来同样需要用到之前生成的证书请求文件,点击“Choose File...”选择刚刚保存到本地的 “CertificateSigningRequest.certSigningRequest”文件,点击 “Continue” 生成证书文件:

生成证书成功,选择“Download” 将证书下载到本地 (ios_production.cer):

同样双击保存到本地的 ios_production.cer 文件将证书导入到 “钥匙串访问”工具中,可以在证书列表中看到刚刚导入的证书,接下来需要导出 .p12 证书文件,选中导入的证书,右键选择 “导出...”:

输入文件名、选择路径后点击 “存储”:

输入密码及确认密码后点击 “好”:

至此,我们已经完成了发布证书的制作(得到了 xxx.p12 证书文件),接下来,继续生成发布描述文件

申请发布 (Distribution) 描述文件

在证书管理页面选择 “Profiles”,可查看到已申请的所有描述文件,点击页面上的加号来添加一个新的描述文件:


在 “Distribution” 栏下选中 “App Store”,点击“Continue”按钮:

这里要选择之前创建的 “App ID” (这里是“io.dcloud.hellouniapp”),点击“Continue”:

接下来选择需要绑定的发布证书(iOS Distribution),这里勾选刚刚生成的发布证书”,点击“Continue”:

接下来输入描述文件的名称(如“HelloUniAppProfileDistribution”), 点击 “Generate” 生成描述文件:

然后点击 “Download” 将描述文件下载到本地(文件后缀为 .mobileprovision)

至此,我们已经得到了发布证书(.p12)及对应的发布描述文件(.mobileprovision)

收起阅读 »

如何安装配置手机模拟器

真机运行 真机联调 HBuilder 模拟器

Android和iOS都有模拟器。其中iOS模拟器只有MAC OSX版本。

iOS模拟器

准备环境

  • Mac OS 10.9以上系统
  • XCode6.0以上程序

安装XCode程序

在Safari中打开XCode下载页面
也可以通过App Store搜索xcode安装:

或者可以下载dmg文件,在Finder中双击dmg文件按提示一步步安装:

HBuilder中启动模拟器运行

HBuilder中选中要调试的应用,在Mac OS上安装好XCode后会自动检测支持的iOS模拟器。
在菜单中选择“运行”->"手机运行"->"iOS模拟器运行":

或者通过“真机运行”图标打开:

在列表中选择要运行的模拟器后,会自动启动iOS模拟器并运行要调试的应用:


Android模拟器

Google官方提供了模拟器,也有三方的Android模拟器。
Android模拟器比较慢,如果有真机就不要用模拟器了。
但如果没有Android4.4或以上版本的手机,那么也有必要装一个模拟器,因为Android4.4起可以通过chrom控制台debug webapp,比较方便。
使用官方Android模拟器,一定要安装intel优化插件,使用Intel HAXM技术来加速,使得模拟器运行速度有大幅的提升。
硬件要求

  • CPU支持Intel VT技术(AMD CPU无法使用HAXM加速);
  • 内存推荐4G;
  • Window XP/Vista/7/8(32/64-bit),推荐Windows 7/8(64-bit)。

ADT工具

配置模拟器调试环境需要安装ADT工具,如果已经配置过android开发环境,则可跳过此章节。

下载ADT工具

注意开始继续前请先自备梯子翻墙。
Android模拟器开发环境需通过ADT工具进行安装,这里不需要下载完整的ADT Bundle,使用独立ADT工具即可:

  • 进入官网下载Android SDK Tools

    选择windows平台下的独立ADT插件安装包。

  • 同意使用条款和条件

    点击下载按钮。

  • 保存安装包
    目前版本为22.6.2,保存到本地为:installer_r22.6.2-windows.exe。

安装ADT工具

  • 双击运行保存的ADT安装文件(install_r22.6.2-windows.exe),开始安装

    选择下一步“Next”。

  • 配置JDK环境
    如果已经安装了JDK环境,则提示确认:

    直接选择下一步“Next”。
    如果没有安装过JDK,则会提示没有找到:

    需安装JDK,并设置JAVA_HOME环境变量,完成后重新运行ADT安装文件。

  • 选择用户

    选择默认值,仅当前用户使用即可,选择下一步“Next”。

  • 选择安装目录

    选择非系统盘目录(如“D:\AndroidSDK”),确保有足够的磁盘空间安装SDK及模拟器文件(至少需要1G的空间)。选择下一步“Next”。

  • 选择开始菜单目录

    保持默认值,选择下一步“Next”。

  • 解压安装

    完成后,选择下一步“Next”:

  • 完成安装

    选择立即启动SDK管理器(Android SDK Manager),选择下一步“Next”,完成ADT工具的安装。
    如未立即启动,可在Android SDK根目录(如“D:\AndroidSDK”),双击运行“SDK Manager.exe”程序。

下载SDK和模拟器

安装ADT工具后,启动SDK管理程序对各版本编译工具、SDK、模拟器、插件进行管理,如升级、安装、卸载等。如果已经下载了SDK和模拟器,可跳过此章节。

  • 更新SDK列表
    启动SDK管理程序后会自动获取最新的工具、SDK、模拟器及扩展插件列表。

  • 列表更新完成
    更新完成后,显示日志:

  • 选择下载项
    使用Intel HAXM加速模拟器,必须选择以下项下载:

目录 用途
Tools Android SDK Platform-tools Android平台工具,基础组件
Android 4.4.2(API19) SDK Platform Android4.4.2 SDK,模拟器基础组件
Android 4.4.2(API19) Intel x86 Atom System Image Inter x86平台的Android4.4.2模拟器镜像文件
Extras Intel x86 Emulator Accelerator (HAXM installer) Inter x86平台Android模拟器硬件加速程序

选择好下载项后,如下图所示:

选择“Intall 4 Packages...”,开始下载安装。

  • 接受许可协议

    分别选择右侧packages列表中的项后,选中“Accept License”接受许可协议。然后点击“Install”,开始下载。

  • 开始下载

    由于SDK和模拟器镜像文件比较大,下载时间会比较长,而且国内访问google官方网站不太稳定,经常提示下载失败:

    关闭提示对话框,重新下载,或者翻墙后再尝试下载

安装Intel X86HAXM

下载Intel X86 HAXM插件后,需要到下载目录运行安装程序进行安装,目录为: “%ADT安装目录%\extras\intel\Hardware_Accelerated_Execution_Manager\”。双击运行intelhaxm.exe进行安装,目前新版本为1.0.8,如果已经安装过低版本,建议升级。

  • 开始安装

    选择下一步“Next”。

  • 配置HAXM使用最大内存

    安装程序会自动计算推荐值,使用默认值,选择下一步“Next”。

  • 确认配置

    选择下一步“Next”。

  • 完成安装

创建模拟器

ADT工具带Android模拟器管理程序(Android Virtual Device Manager),可在Android SDK根目录(如“D:\AndroidSDK”),双击运行“AVD Manager.exe”程序。

由于没有创建过模拟器,在列表中显示无可用模拟器,点击“New...”开始新建模拟器。

  • 新建模拟器
说明
AVD Name 模拟器名称,根据爱好输入
Device 模拟设备,根据爱好选择,建议根据显示器分辨率来选择,大显示器选择则高分比率模拟器
Target 选择“Android 4.4.2 - API Level 19”
CPU/ABI 选择“Intel Atom (x86)”
Skin 模拟器皮肤,根据爱好选择,推荐选择WVGA800
Front Camera 前置摄像头,用不到就选“None”,模拟摄像头就选“Emulated”,使用PC的摄像头就选“WebCam0”
Back Camera 后置摄像头,与前置摄像头选择类似
Memory Options 内存大小,根据PC内存大小设置,推荐RAM:512;Heap:64
Internal Storage 内部存储器大小,根据PC系统盘空间大小设置,推荐200M
SD Card SD卡存储器大小,根据PC系统盘空间大小设置,推荐200M

设置完成后,点击“OK”。

  • 确认配置

  • 创建完成后在模拟器列表中显示

启动模拟器

创建完模拟器,每次启动Android模拟器管理程序都能在列表中显示:

  • 启动模拟器

    选择“Android4.4.2”模拟器,点击“Start...”启动。

  • 设置启动配置信息

说明
Scale Display to real size 是否缩放到设置的模拟器分辨率,在PC分辨率低时使用
Wipe user data 是否擦除用户数据,重置模拟器时使用

点击“Launch”启动。

  • 等待加载模拟器

    PC的配置决定速度,耐心等待模拟器的启动。

  • 完成启动模拟器

    模拟器配置完毕,这时可通过HBuilder的真机运行功能进行连接

HBuilder中启动模拟器运行

Android模拟器启动后,HBuilder会将其识别为名称为emulator-xxxx的Android手机,其中的xxxx为模拟器的id如下图

另外还有三方的Android模拟器,如Genymotion,请自行搜索其用法。

继续阅读 »

Android和iOS都有模拟器。其中iOS模拟器只有MAC OSX版本。

iOS模拟器

准备环境

  • Mac OS 10.9以上系统
  • XCode6.0以上程序

安装XCode程序

在Safari中打开XCode下载页面
也可以通过App Store搜索xcode安装:

或者可以下载dmg文件,在Finder中双击dmg文件按提示一步步安装:

HBuilder中启动模拟器运行

HBuilder中选中要调试的应用,在Mac OS上安装好XCode后会自动检测支持的iOS模拟器。
在菜单中选择“运行”->"手机运行"->"iOS模拟器运行":

或者通过“真机运行”图标打开:

在列表中选择要运行的模拟器后,会自动启动iOS模拟器并运行要调试的应用:


Android模拟器

Google官方提供了模拟器,也有三方的Android模拟器。
Android模拟器比较慢,如果有真机就不要用模拟器了。
但如果没有Android4.4或以上版本的手机,那么也有必要装一个模拟器,因为Android4.4起可以通过chrom控制台debug webapp,比较方便。
使用官方Android模拟器,一定要安装intel优化插件,使用Intel HAXM技术来加速,使得模拟器运行速度有大幅的提升。
硬件要求

  • CPU支持Intel VT技术(AMD CPU无法使用HAXM加速);
  • 内存推荐4G;
  • Window XP/Vista/7/8(32/64-bit),推荐Windows 7/8(64-bit)。

ADT工具

配置模拟器调试环境需要安装ADT工具,如果已经配置过android开发环境,则可跳过此章节。

下载ADT工具

注意开始继续前请先自备梯子翻墙。
Android模拟器开发环境需通过ADT工具进行安装,这里不需要下载完整的ADT Bundle,使用独立ADT工具即可:

  • 进入官网下载Android SDK Tools

    选择windows平台下的独立ADT插件安装包。

  • 同意使用条款和条件

    点击下载按钮。

  • 保存安装包
    目前版本为22.6.2,保存到本地为:installer_r22.6.2-windows.exe。

安装ADT工具

  • 双击运行保存的ADT安装文件(install_r22.6.2-windows.exe),开始安装

    选择下一步“Next”。

  • 配置JDK环境
    如果已经安装了JDK环境,则提示确认:

    直接选择下一步“Next”。
    如果没有安装过JDK,则会提示没有找到:

    需安装JDK,并设置JAVA_HOME环境变量,完成后重新运行ADT安装文件。

  • 选择用户

    选择默认值,仅当前用户使用即可,选择下一步“Next”。

  • 选择安装目录

    选择非系统盘目录(如“D:\AndroidSDK”),确保有足够的磁盘空间安装SDK及模拟器文件(至少需要1G的空间)。选择下一步“Next”。

  • 选择开始菜单目录

    保持默认值,选择下一步“Next”。

  • 解压安装

    完成后,选择下一步“Next”:

  • 完成安装

    选择立即启动SDK管理器(Android SDK Manager),选择下一步“Next”,完成ADT工具的安装。
    如未立即启动,可在Android SDK根目录(如“D:\AndroidSDK”),双击运行“SDK Manager.exe”程序。

下载SDK和模拟器

安装ADT工具后,启动SDK管理程序对各版本编译工具、SDK、模拟器、插件进行管理,如升级、安装、卸载等。如果已经下载了SDK和模拟器,可跳过此章节。

  • 更新SDK列表
    启动SDK管理程序后会自动获取最新的工具、SDK、模拟器及扩展插件列表。

  • 列表更新完成
    更新完成后,显示日志:

  • 选择下载项
    使用Intel HAXM加速模拟器,必须选择以下项下载:

目录 用途
Tools Android SDK Platform-tools Android平台工具,基础组件
Android 4.4.2(API19) SDK Platform Android4.4.2 SDK,模拟器基础组件
Android 4.4.2(API19) Intel x86 Atom System Image Inter x86平台的Android4.4.2模拟器镜像文件
Extras Intel x86 Emulator Accelerator (HAXM installer) Inter x86平台Android模拟器硬件加速程序

选择好下载项后,如下图所示:

选择“Intall 4 Packages...”,开始下载安装。

  • 接受许可协议

    分别选择右侧packages列表中的项后,选中“Accept License”接受许可协议。然后点击“Install”,开始下载。

  • 开始下载

    由于SDK和模拟器镜像文件比较大,下载时间会比较长,而且国内访问google官方网站不太稳定,经常提示下载失败:

    关闭提示对话框,重新下载,或者翻墙后再尝试下载

安装Intel X86HAXM

下载Intel X86 HAXM插件后,需要到下载目录运行安装程序进行安装,目录为: “%ADT安装目录%\extras\intel\Hardware_Accelerated_Execution_Manager\”。双击运行intelhaxm.exe进行安装,目前新版本为1.0.8,如果已经安装过低版本,建议升级。

  • 开始安装

    选择下一步“Next”。

  • 配置HAXM使用最大内存

    安装程序会自动计算推荐值,使用默认值,选择下一步“Next”。

  • 确认配置

    选择下一步“Next”。

  • 完成安装

创建模拟器

ADT工具带Android模拟器管理程序(Android Virtual Device Manager),可在Android SDK根目录(如“D:\AndroidSDK”),双击运行“AVD Manager.exe”程序。

由于没有创建过模拟器,在列表中显示无可用模拟器,点击“New...”开始新建模拟器。

  • 新建模拟器
说明
AVD Name 模拟器名称,根据爱好输入
Device 模拟设备,根据爱好选择,建议根据显示器分辨率来选择,大显示器选择则高分比率模拟器
Target 选择“Android 4.4.2 - API Level 19”
CPU/ABI 选择“Intel Atom (x86)”
Skin 模拟器皮肤,根据爱好选择,推荐选择WVGA800
Front Camera 前置摄像头,用不到就选“None”,模拟摄像头就选“Emulated”,使用PC的摄像头就选“WebCam0”
Back Camera 后置摄像头,与前置摄像头选择类似
Memory Options 内存大小,根据PC内存大小设置,推荐RAM:512;Heap:64
Internal Storage 内部存储器大小,根据PC系统盘空间大小设置,推荐200M
SD Card SD卡存储器大小,根据PC系统盘空间大小设置,推荐200M

设置完成后,点击“OK”。

  • 确认配置

  • 创建完成后在模拟器列表中显示

启动模拟器

创建完模拟器,每次启动Android模拟器管理程序都能在列表中显示:

  • 启动模拟器

    选择“Android4.4.2”模拟器,点击“Start...”启动。

  • 设置启动配置信息

说明
Scale Display to real size 是否缩放到设置的模拟器分辨率,在PC分辨率低时使用
Wipe user data 是否擦除用户数据,重置模拟器时使用

点击“Launch”启动。

  • 等待加载模拟器

    PC的配置决定速度,耐心等待模拟器的启动。

  • 完成启动模拟器

    模拟器配置完毕,这时可通过HBuilder的真机运行功能进行连接

HBuilder中启动模拟器运行

Android模拟器启动后,HBuilder会将其识别为名称为emulator-xxxx的Android手机,其中的xxxx为模拟器的id如下图

另外还有三方的Android模拟器,如Genymotion,请自行搜索其用法。

收起阅读 »

感谢Hbuider+mui

5年前,一直是一个B/S软件开发者。近几年进入了政府机关,从事了不同行业,不再做开发,可一直都放不下曾经辛苦学来的技术。曾经想学Android开发,但由于平常工作忙,学习成本太高太复杂,望而却步。直至10天前发现Hbuider,被它的易用性所深深吸引!
10多天来,白天上班,晚上学习,终于克服诸多困难,成功做出了自己的第一个软件,把单位的网站app化,使用MUI框架+ajax技术+个推平台,没有想到Hbuider+MUI+HTML5这么强大,效果不亚于原生!
感谢提供如此方便的人们!

简单分享一下我的架构(因为是初学者,不一定优化,也不一定科学,仅供参考)

  1. 根页面( index):带侧滑菜单页,侧滑菜单设置有三个一级菜单。点击后,自定义函数修改index页的二级菜单,设置index的subpage为该一级菜单下的默认页template,同时关闭侧滑菜单页。
    index页:预读index-menu,预读显示详细内容的showContent页

  1. 列表模板页面(template):点击二级菜单,经ajax调取不同数据显示列表,点击列表,打开showContent页,传入参数,ajax调取详细内容显示。
  2. 详细内容显示( showContent):是一个带subpage的模板页,重写MUI.back,返回后恢复其subpage为一个空页面(否则点其他列表再次打开的瞬间还是上一次内容的缓存。)
  3. 绑定个推插件,测试推送成功,可以为单消息,也可以是链接,或者启动软件。

真心感受,MUI这个框架给我们省了太多太多的事!

继续阅读 »

5年前,一直是一个B/S软件开发者。近几年进入了政府机关,从事了不同行业,不再做开发,可一直都放不下曾经辛苦学来的技术。曾经想学Android开发,但由于平常工作忙,学习成本太高太复杂,望而却步。直至10天前发现Hbuider,被它的易用性所深深吸引!
10多天来,白天上班,晚上学习,终于克服诸多困难,成功做出了自己的第一个软件,把单位的网站app化,使用MUI框架+ajax技术+个推平台,没有想到Hbuider+MUI+HTML5这么强大,效果不亚于原生!
感谢提供如此方便的人们!

简单分享一下我的架构(因为是初学者,不一定优化,也不一定科学,仅供参考)

  1. 根页面( index):带侧滑菜单页,侧滑菜单设置有三个一级菜单。点击后,自定义函数修改index页的二级菜单,设置index的subpage为该一级菜单下的默认页template,同时关闭侧滑菜单页。
    index页:预读index-menu,预读显示详细内容的showContent页

  1. 列表模板页面(template):点击二级菜单,经ajax调取不同数据显示列表,点击列表,打开showContent页,传入参数,ajax调取详细内容显示。
  2. 详细内容显示( showContent):是一个带subpage的模板页,重写MUI.back,返回后恢复其subpage为一个空页面(否则点其他列表再次打开的瞬间还是上一次内容的缓存。)
  3. 绑定个推插件,测试推送成功,可以为单消息,也可以是链接,或者启动软件。

真心感受,MUI这个框架给我们省了太多太多的事!

收起阅读 »