
推送开发指南
简介
推送服务是一项移动应用消息推送服务解决方案,支持Android和iOS两大平台,开发者可以借助该服务,快速构建稳定高效的消息推送系统,及时有效地将服务端消息推送到客户端上,为实时业务需求和产品运营提供技术支持,从而积极地保持与用户的连接,并提高用户活跃度和留存率。本文档帮助开发者理解推送服务的工作流程,手把手介绍所需的各项集成步骤,同时介绍了消息的不同类型特点,供应用开发者根据实际业务需求进行选择。
推送消息
推送消息总共分为通知消息和透传消息两大类。通知消息一般会在系统消息中心通知显示,满足日常的运营需求,而通知消息按照后续动作分类,又可分为启动应用消息(即普通通知消息),打开网页消息,下载链接消息三种;透传消息则会将消息下发到app,后续动作由app进行定制,从而满足业务相关的特定功能需求,注意透传消息需要满足json格式,并包含key分别为title,content,payload的键值对内容,三者缺一不可,如:
透传消息的格式为{title:"通知标题",content:"通知内容",payload:"通知去干嘛这里可以自定义"}
另外5+API提供了推送消息事件监听机制,app接收到消息的“receive”事件和用户点击消息的“click”事件,如当用户点击消息中心里的消息时会启动应用,并且在监听push事件的页面触发“click”事件。API详细介绍移步这里事件监听,并且两个事件根据推送消息类型和格式,手机操作系统,手机网络连接情况的不同,产生不同的触发情况。现总结如下:

推送配置
开发者向个推开放平台进行应用注册,得到appid,appkey,appsecret的值,然后在HBuilder中的项目进行配置,配置步骤参考如下文档:
HBuilder中推送消息模块配置
服务端也需要下载并集成个推相应服务器端SDK下载地址,同时我们提供用php写的服务端开源项目,仅供参考。
推送消息服务器开源项目Github地址
简介
推送服务是一项移动应用消息推送服务解决方案,支持Android和iOS两大平台,开发者可以借助该服务,快速构建稳定高效的消息推送系统,及时有效地将服务端消息推送到客户端上,为实时业务需求和产品运营提供技术支持,从而积极地保持与用户的连接,并提高用户活跃度和留存率。本文档帮助开发者理解推送服务的工作流程,手把手介绍所需的各项集成步骤,同时介绍了消息的不同类型特点,供应用开发者根据实际业务需求进行选择。
推送消息
推送消息总共分为通知消息和透传消息两大类。通知消息一般会在系统消息中心通知显示,满足日常的运营需求,而通知消息按照后续动作分类,又可分为启动应用消息(即普通通知消息),打开网页消息,下载链接消息三种;透传消息则会将消息下发到app,后续动作由app进行定制,从而满足业务相关的特定功能需求,注意透传消息需要满足json格式,并包含key分别为title,content,payload的键值对内容,三者缺一不可,如:
透传消息的格式为{title:"通知标题",content:"通知内容",payload:"通知去干嘛这里可以自定义"}
另外5+API提供了推送消息事件监听机制,app接收到消息的“receive”事件和用户点击消息的“click”事件,如当用户点击消息中心里的消息时会启动应用,并且在监听push事件的页面触发“click”事件。API详细介绍移步这里事件监听,并且两个事件根据推送消息类型和格式,手机操作系统,手机网络连接情况的不同,产生不同的触发情况。现总结如下:
推送配置
开发者向个推开放平台进行应用注册,得到appid,appkey,appsecret的值,然后在HBuilder中的项目进行配置,配置步骤参考如下文档:
HBuilder中推送消息模块配置
服务端也需要下载并集成个推相应服务器端SDK下载地址,同时我们提供用php写的服务端开源项目,仅供参考。
推送消息服务器开源项目Github地址

Android Studio环境进行离线打包
1) 导入项目官网下载的HBuilder离线打包Android版SDK(5+ SDK下载)中项目\Android-SDK@1.9.9.38184_20171023\HBuilder-Hello
1-1) 出现“manifest merger failed with multiple errors,see logs”错误提示
则删除Hbuilder-Hello这个目录里将AndroidManIfest.xml配置了小米推送和小米登录的信息,这个不删掉一定会导致运行报错
1-2) 在真机运行,运行不了,但也不报错,可以考虑离线打包测试(这个错误在离线打包时才显示)
Error:(2) Error: "app_name" is not translated in "zh" (Chinese) [MissingTranslation]
答: 在build.gradle文件中添加:
android{
...
lintOptions{
checkReleaseBuilds false
abortOnError false
}
}
此时,真机运行成功,替换HBuilder中的前端项目到As环境中
2) 替换\Android-SDK@1.9.9.38184_20171023\HBuilder-gm\app\src\main\assets\apps\HelloH5\www目录下内容为新项目NewProject前端内容
直接运行查看效果
3) 如果再次运行时会报错,此时将HBuilder-Hello下\assets\apps\HelloH5\www中的manifest.json替换成原项目中的manifest.json内容,再次运行查看
4) 修改启动图片和应用图标: \Android-SDK@1.9.9.38184_20171023\HBuilder-gm\app\build\intermediates\res\merged\release\
可通过drawable-XXXhdpi文件名查询对应尺寸(可参考: http://blog.csdn.net/gf771115/article/details/50323635)
5) 修改应用名称: \Android-SDK@1.9.9.38184_20171023\HBuilder-gm\app\src\main\res\values\strings.html
结束。
1) 导入项目官网下载的HBuilder离线打包Android版SDK(5+ SDK下载)中项目\Android-SDK@1.9.9.38184_20171023\HBuilder-Hello
1-1) 出现“manifest merger failed with multiple errors,see logs”错误提示
则删除Hbuilder-Hello这个目录里将AndroidManIfest.xml配置了小米推送和小米登录的信息,这个不删掉一定会导致运行报错
1-2) 在真机运行,运行不了,但也不报错,可以考虑离线打包测试(这个错误在离线打包时才显示)
Error:(2) Error: "app_name" is not translated in "zh" (Chinese) [MissingTranslation]
答: 在build.gradle文件中添加:
android{
...
lintOptions{
checkReleaseBuilds false
abortOnError false
}
}
此时,真机运行成功,替换HBuilder中的前端项目到As环境中
2) 替换\Android-SDK@1.9.9.38184_20171023\HBuilder-gm\app\src\main\assets\apps\HelloH5\www目录下内容为新项目NewProject前端内容
直接运行查看效果
3) 如果再次运行时会报错,此时将HBuilder-Hello下\assets\apps\HelloH5\www中的manifest.json替换成原项目中的manifest.json内容,再次运行查看
4) 修改启动图片和应用图标: \Android-SDK@1.9.9.38184_20171023\HBuilder-gm\app\build\intermediates\res\merged\release\
可通过drawable-XXXhdpi文件名查询对应尺寸(可参考: http://blog.csdn.net/gf771115/article/details/50323635)
5) 修改应用名称: \Android-SDK@1.9.9.38184_20171023\HBuilder-gm\app\src\main\res\values\strings.html
结束。

Android7解决plus.runtime.openFile方法打开文件无响应问题(需本地打包并修改SDK)
需修改的类:pdr.jar\io\dcloud\common\adapter\util\PlatformUtil.class
具体方法请参见:解决 Android N 7.0 上 报错:android.os.FileUriExposedException
另外调试中还发现一个问题:
** PLEASE READ ****
- New versions of the Android SDK no longer support the Crypto provider.
- If your app was relying on setSeed() to derive keys from strings, you
- should switch to using SecretKeySpec to load raw key bytes directly OR
- use a real key derivation function (KDF). See advice here :
-
http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html
此问题的解决请参见:Android:7.0 后加密库 Crypto 被废弃后的爬坑指南
需修改的类:pdr.jar\io\dcloud\common\adapter\util\DCloudTrustManager.class
希望dcloud团队能更新此类问题
需修改的类:pdr.jar\io\dcloud\common\adapter\util\PlatformUtil.class
具体方法请参见:解决 Android N 7.0 上 报错:android.os.FileUriExposedException
另外调试中还发现一个问题:
** PLEASE READ ****
- New versions of the Android SDK no longer support the Crypto provider.
- If your app was relying on setSeed() to derive keys from strings, you
- should switch to using SecretKeySpec to load raw key bytes directly OR
- use a real key derivation function (KDF). See advice here :
-
http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html
此问题的解决请参见:Android:7.0 后加密库 Crypto 被废弃后的爬坑指南
需修改的类:pdr.jar\io\dcloud\common\adapter\util\DCloudTrustManager.class
希望dcloud团队能更新此类问题
收起阅读 »
有偿找一个会做前端加PHP写后台外包
来一个会做前端加PHP写后台外包
一个前端加 PHP后台!联系QQ 1120094610
速度要快的,有经验的,
来一个会做前端加PHP写后台外包
一个前端加 PHP后台!联系QQ 1120094610
速度要快的,有经验的,

消息推送 - wap2app教程
wap2app应用目前支持集成个推平台,可以向用户发送推送消息。
wap2app的推送开发,分为三个部分:
- 个推平台申请账号及应用登记,获取应用的appid、appkey、appsecret参数;
- 本地manifest.json中配置推送权限及个推参数
- 本地app.js中编写推送消息监听代码
其中,前两项和5+ App的配置方法相同,参考推送插件开发指南即可,其中有推送原理的详细说明;为了便于理解,这里再次简述一下推送的相关概念。
推送概念简述
推送消息分为普通推送和透传推送,区别在于:
- 普通推送只有消息标题和消息内容;
- 透传消息除了消息标题、消息内容外,还有payload字段,payload是一个json对象,可以包含业务自定义参数,比如新闻ID等。
wap2app应用支持click、receive两种事件监听推送消息,主要区别在于:
- 进入手机消息中心的推送,用户点击后触发click事件
- 透传消息不符合规范或iOS应用正处于前台运行时收到推送,此时消息不会进入手机消息中心,而会直接触发receive事件。
Tips:Android平台的普通消息会进入消息中心,但用户点击后仅激活应用,不会触发click事件。
为了完整实现,需要在代码中同时监听click事件和receive事件。
wap2app中的推送监听
wap2app应用需要在app.js的onLaunch事件中监听推送消息,本示例接收透传消息,透传消息中包含新闻ID,用户点击推送消息后,直接打开新闻详情,实现消息直达的需求;推送内容如下:
{title:"推送标题",content:"推送内容",payload:{id:1001}}
示例代码如下:
/**
* 当wap2app初始化完成时,会触发 onLaunch
* @param {Object} options
*/
onLaunch: function (options) {
//应用初始化
/******推送消息监听代码开始******/
//监听click事件,用户从消息中心点击触发的
plus.push.addEventListener("click", function (msg) {
console.log("You clicked: " + msg.title); //推送消息标题
console.log("You clicked: " + msg.content); //推送消息内容
//根据payload传递过来的数据,打开一个详情
var payload = msg.payload;
if (payload) {
// payload 按照规范是 Object,但实际推送过来有可能是 String,需要多一步处理;
if (typeof payload === 'string') {
payload = JSON.parse(payload);
}
if (typeof payload === 'object') {
//payload是一个json对象,可以传递业务数据,开发者可以根据实际需求自定义参数
//本示例在payload中传入新闻id,wap2app接收到推送后,直接打开新闻详情
var detailId = payload.id;
//wap2app.open(url)可以直接打开对应的webview
//这里是示例,实际项目中开发者需根据M站的url拼接页面地址
wap2app.open('https://m.example.com/detial/' + detailId + '.html');
}
}
}, false);
//监听receive事件
plus.push.addEventListener("receive", function (msg) {
console.log("recieve title: " + msg.title); //推送消息标题
console.log("recieve content: " + msg.content); //推送消息内容
//根据payload传递过来的数据,打开一个详情
var payload;
if (msg.payload) {
//如透传消息不符合格式,则“payload”属性为string类型
//这里的示例以json字符串去解析,实际上也可以做字符串匹配
if (typeof (msg.payload) == "string") {
try {
payload = JSON.parse(msg.payload);
} catch (error) {
console.log(error);
}
} else if (typeof (msg.payload) == "object") {
//iOS应用正处于前台运行时收到推送,也触发receive事件,此时payload为json对象
payload = msg.payload;
}
if (payload) {
//本示例在payload中传入新闻id,wap2app接收到推送后,直接打开新闻详情
var detailId = payload.id;
//wap2app.open(url)可以直接打开对应的webview
//这里是示例,实际项目中开发者需根据M站的url拼接页面地址
wap2app.open('https://m.example.com/detial/' + detailId + '.html');
}
}
}, false);
/******推送消息监听代码结束******/
}
wap2app应用目前支持集成个推平台,可以向用户发送推送消息。
wap2app的推送开发,分为三个部分:
- 个推平台申请账号及应用登记,获取应用的appid、appkey、appsecret参数;
- 本地manifest.json中配置推送权限及个推参数
- 本地app.js中编写推送消息监听代码
其中,前两项和5+ App的配置方法相同,参考推送插件开发指南即可,其中有推送原理的详细说明;为了便于理解,这里再次简述一下推送的相关概念。
推送概念简述
推送消息分为普通推送和透传推送,区别在于:
- 普通推送只有消息标题和消息内容;
- 透传消息除了消息标题、消息内容外,还有payload字段,payload是一个json对象,可以包含业务自定义参数,比如新闻ID等。
wap2app应用支持click、receive两种事件监听推送消息,主要区别在于:
- 进入手机消息中心的推送,用户点击后触发click事件
- 透传消息不符合规范或iOS应用正处于前台运行时收到推送,此时消息不会进入手机消息中心,而会直接触发receive事件。
Tips:Android平台的普通消息会进入消息中心,但用户点击后仅激活应用,不会触发click事件。
为了完整实现,需要在代码中同时监听click事件和receive事件。
wap2app中的推送监听
wap2app应用需要在app.js的onLaunch事件中监听推送消息,本示例接收透传消息,透传消息中包含新闻ID,用户点击推送消息后,直接打开新闻详情,实现消息直达的需求;推送内容如下:
{title:"推送标题",content:"推送内容",payload:{id:1001}}
示例代码如下:
/**
* 当wap2app初始化完成时,会触发 onLaunch
* @param {Object} options
*/
onLaunch: function (options) {
//应用初始化
/******推送消息监听代码开始******/
//监听click事件,用户从消息中心点击触发的
plus.push.addEventListener("click", function (msg) {
console.log("You clicked: " + msg.title); //推送消息标题
console.log("You clicked: " + msg.content); //推送消息内容
//根据payload传递过来的数据,打开一个详情
var payload = msg.payload;
if (payload) {
// payload 按照规范是 Object,但实际推送过来有可能是 String,需要多一步处理;
if (typeof payload === 'string') {
payload = JSON.parse(payload);
}
if (typeof payload === 'object') {
//payload是一个json对象,可以传递业务数据,开发者可以根据实际需求自定义参数
//本示例在payload中传入新闻id,wap2app接收到推送后,直接打开新闻详情
var detailId = payload.id;
//wap2app.open(url)可以直接打开对应的webview
//这里是示例,实际项目中开发者需根据M站的url拼接页面地址
wap2app.open('https://m.example.com/detial/' + detailId + '.html');
}
}
}, false);
//监听receive事件
plus.push.addEventListener("receive", function (msg) {
console.log("recieve title: " + msg.title); //推送消息标题
console.log("recieve content: " + msg.content); //推送消息内容
//根据payload传递过来的数据,打开一个详情
var payload;
if (msg.payload) {
//如透传消息不符合格式,则“payload”属性为string类型
//这里的示例以json字符串去解析,实际上也可以做字符串匹配
if (typeof (msg.payload) == "string") {
try {
payload = JSON.parse(msg.payload);
} catch (error) {
console.log(error);
}
} else if (typeof (msg.payload) == "object") {
//iOS应用正处于前台运行时收到推送,也触发receive事件,此时payload为json对象
payload = msg.payload;
}
if (payload) {
//本示例在payload中传入新闻id,wap2app接收到推送后,直接打开新闻详情
var detailId = payload.id;
//wap2app.open(url)可以直接打开对应的webview
//这里是示例,实际项目中开发者需根据M站的url拼接页面地址
wap2app.open('https://m.example.com/detial/' + detailId + '.html');
}
}
}, false);
/******推送消息监听代码结束******/
}
收起阅读 »

android 指纹识别插件 离线打包版
更新:HTML5+已经自带指纹识别。http://www.html5plus.org/doc/zh_cn/fingerprint.html。以下文档已过期。
安卓问识别插件,需要离线打包才能使用,下面贴上代码:
AndroidManifest.xml 文件
<!--指纹识别权限-->
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
package com.mall.trade.util;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.os.Build;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import com.mall.trade.activity.MainActivity;
/**
* Created by Alen on 2017/11/15 0015.
* 指纹识别模块
*/
public class FingerprintUtil {
public static CancellationSignal cancellationSignal;
public static final int DEVICENOTSUPPORTED = 1011;//设备不支持
public static final int DEVICENOTSAFEGUARD = 1012;//设备未处于安全保护中
public static final int DEVICENOTREGUIDFIN = 1013;//设备没有注册过指纹
public static final int SUCCESS = 1000;//支持指纹识别
public static final int FINGETSUCCESS = 1002;//指纹识别成功
public static final int ERROR = 1001;//指纹识别失败
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static void callFingerPrint(final OnCallBackListenr listener){
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(MainActivity.getInstanse());
ResultObj res = checkFingerPrint(managerCompat);//检查指纹识别
if(res.getCode()!=SUCCESS){
if (listener != null){
if(res.getCode()==DEVICENOTSUPPORTED){//判断设备是否支持
listener.onSupportFailed(res);
}else if(res.getCode()==DEVICENOTSAFEGUARD){//判断设备是否处于安全保护中
listener.onInsecurity(res);
}else if(res.getCode()==DEVICENOTSAFEGUARD){//设备没有注册过指纹
listener.onEnrollFailed(res);
}
}
return;
}
if (listener != null)
listener.onAuthenticationStart(res); //开始指纹识别
cancellationSignal = new CancellationSignal(); //必须重新实例化,否则cancel 过一次就不能再使用了
managerCompat.authenticate(null,0,cancellationSignal,new FingerprintManagerCompat.AuthenticationCallback(){
// 当出现错误的时候回调此函数,比如多次尝试都失败了的时候,errString是错误信息,比如华为的提示就是:尝试次数过多,请稍后再试。
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (listener != null)
listener.onAuthenticationError(errMsgId ,errString );
}
// 当指纹验证失败的时候会回调此函数,失败之后允许多次尝试,失败次数过多会停止响应一段时间然后再停止sensor的工作
@Override
public void onAuthenticationFailed() {
if (listener != null)
listener.onAuthenticationFailed(new ResultObj(ERROR,"验证失败"));
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
if (listener != null)
listener.onAuthenticationHelp(helpMsgId,helpString);
}
// 当验证的指纹成功时会回调此函数,然后不再监听指纹sensor
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
if (listener != null)
listener.onAuthenticationSucceeded(result);
}
;
},null);
}
/**
* 取消指纹识别
*/
public static void cancelFingerPrint(){
if(cancellationSignal!=null){
cancellationSignal.cancel();
}
}
/**
* 判断当前设备是否支持指纹识别
* @return
*/
public static ResultObj checkFingerPrint(){
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(MainActivity.getInstanse());
return FingerprintUtil.checkFingerPrint(managerCompat);
}
/**
* 判断当前设备是否支持指纹识别
* @param managerCompat
* @return
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static ResultObj checkFingerPrint(FingerprintManagerCompat managerCompat){
if (!managerCompat.isHardwareDetected()){ //判断设备是否支持
return new ResultObj(FingerprintUtil.DEVICENOTSUPPORTED,"当前设备不支持指纹");
}
KeyguardManager keyguardManager =(KeyguardManager)MainActivity.getInstanse().getSystemService(MainActivity.getInstanse().KEYGUARD_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {//判断设备是否处于安全保护中
return new ResultObj(FingerprintUtil.DEVICENOTSAFEGUARD,"当前设备没有设置密码保护");
}
if (!managerCompat.hasEnrolledFingerprints()){ //判断设备是否已经注册过指纹
return new ResultObj(FingerprintUtil.DEVICENOTREGUIDFIN,"还没有设置指纹");
}
return new ResultObj(FingerprintUtil.SUCCESS,"支持指纹识别");
}
public interface OnCallBackListenr{
void onSupportFailed(ResultObj error);
void onInsecurity(ResultObj error);
void onEnrollFailed(ResultObj error);
void onAuthenticationStart(ResultObj error);
void onAuthenticationError(int errMsgId, CharSequence errString);
void onAuthenticationFailed(ResultObj error);
void onAuthenticationHelp(int helpMsgId, CharSequence helpString);
void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result);
}
public static class ResultObj{
private int code;
private String msg;
public ResultObj(int code,String msg){
this.code = code;
this.msg = msg;
}
public ResultObj getResult(){
return this;
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
public static void cancel(){
if (cancellationSignal != null)
cancellationSignal.cancel();
}
}
下面是调用代码:
/**
* 检查系统是否支持指纹识别
* @param pWebview
* @param array
*/
public void checkFingerPrint(IWebview pWebview, JSONArray array){
String callbackId = array.optString(0);
FingerprintUtil.ResultObj res = FingerprintUtil.checkFingerPrint();
if(res.getCode()==FingerprintUtil.SUCCESS){
String msg = toJSON(res.getCode(), res.getMsg());
Log.d("haiji", "FingerPrint " + res.getCode() + " " + res.getMsg());
JSUtil.execCallback(pWebview,callbackId,msg, JSUtil.OK, false);//成功回调
}else{
Log.d("haiji", "FingerPrint "+res.getMsg());
String error = toJSON(res.getCode(), res.getMsg());
JSUtil.execCallback(pWebview,callbackId,error, JSUtil.ERROR, false);//失败回调
}
}
/**
* 开始进行指纹识别
* @param pWebview
* @param array
*/
public void callFingerPrint(final IWebview pWebview, JSONArray array){
final String callbackId = array.optString(0);
//开始指纹识别
FingerprintUtil.callFingerPrint(new FingerprintUtil.OnCallBackListenr() {
@Override
public void onSupportFailed(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
// JsObjectCommonMethod.showToast("当前设备不支持指纹");
}
@Override
public void onInsecurity(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
// JsObjectCommonMethod.showToast("当前设备没有设置密码保护");
}
@Override
public void onEnrollFailed(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationStart(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.OK, true);//成功回调
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
String error = toJSON(errMsgId, errString.toString());
Log.d("haiji", "FingerPrint "+error.toString());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationFailed(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+error);
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
String error = toJSON(helpMsgId, helpString.toString());
Log.d("haiji", "FingerPrint "+error);
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
String error = toJSON(FingerprintUtil.FINGETSUCCESS, "指纹识别成功");
Log.d("haiji", "FingerPrint "+error);
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.OK, false);//成功回调
}
});
}
/**
* 取消指纹识别
* @param pWebview
* @param array
*/
public void calcelFingerPrint(final IWebview pWebview, JSONArray array){
Log.d("haiji", "FingerPrint 取消指纹识别");
FingerprintUtil.cancelFingerPrint();
}
下面是js中的代码
/**
* 指纹识别检测
* @param param
*/
checkFingerPrint:function(successCbk,errorCbk){
var success = typeof successCbk !== 'function'? null:function(args){
successCbk(args);
}
var fail = typeof errorCbk !== 'function'?null:function(code){
errorCbk(code);
}
callbackID = BRIGE.callbackId(success,fail);
BRIGE.exec(_CLASS_NAME, "checkFingerPrint", [callbackID,""]);
},
/**
* 开始进行指纹识别
* @param param
*/
callFingerPrint:function(successCbk,errorCbk){
var success = typeof successCbk !== 'function'? null:function(args){
successCbk(args);
}
var fail = typeof errorCbk !== 'function'?null:function(code){
errorCbk(code);
}
callbackID = BRIGE.callbackId(success,fail);
BRIGE.exec(_CLASS_NAME, "callFingerPrint", [callbackID,""]);
},
/**
* 取消指纹识别
* @param param
*/
calcelFingerPrint:function(){
BRIGE.exec(_CLASS_NAME, "calcelFingerPrint", []);
},
更新:HTML5+已经自带指纹识别。http://www.html5plus.org/doc/zh_cn/fingerprint.html。以下文档已过期。
安卓问识别插件,需要离线打包才能使用,下面贴上代码:
AndroidManifest.xml 文件
<!--指纹识别权限-->
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
package com.mall.trade.util;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.os.Build;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import com.mall.trade.activity.MainActivity;
/**
* Created by Alen on 2017/11/15 0015.
* 指纹识别模块
*/
public class FingerprintUtil {
public static CancellationSignal cancellationSignal;
public static final int DEVICENOTSUPPORTED = 1011;//设备不支持
public static final int DEVICENOTSAFEGUARD = 1012;//设备未处于安全保护中
public static final int DEVICENOTREGUIDFIN = 1013;//设备没有注册过指纹
public static final int SUCCESS = 1000;//支持指纹识别
public static final int FINGETSUCCESS = 1002;//指纹识别成功
public static final int ERROR = 1001;//指纹识别失败
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static void callFingerPrint(final OnCallBackListenr listener){
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(MainActivity.getInstanse());
ResultObj res = checkFingerPrint(managerCompat);//检查指纹识别
if(res.getCode()!=SUCCESS){
if (listener != null){
if(res.getCode()==DEVICENOTSUPPORTED){//判断设备是否支持
listener.onSupportFailed(res);
}else if(res.getCode()==DEVICENOTSAFEGUARD){//判断设备是否处于安全保护中
listener.onInsecurity(res);
}else if(res.getCode()==DEVICENOTSAFEGUARD){//设备没有注册过指纹
listener.onEnrollFailed(res);
}
}
return;
}
if (listener != null)
listener.onAuthenticationStart(res); //开始指纹识别
cancellationSignal = new CancellationSignal(); //必须重新实例化,否则cancel 过一次就不能再使用了
managerCompat.authenticate(null,0,cancellationSignal,new FingerprintManagerCompat.AuthenticationCallback(){
// 当出现错误的时候回调此函数,比如多次尝试都失败了的时候,errString是错误信息,比如华为的提示就是:尝试次数过多,请稍后再试。
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (listener != null)
listener.onAuthenticationError(errMsgId ,errString );
}
// 当指纹验证失败的时候会回调此函数,失败之后允许多次尝试,失败次数过多会停止响应一段时间然后再停止sensor的工作
@Override
public void onAuthenticationFailed() {
if (listener != null)
listener.onAuthenticationFailed(new ResultObj(ERROR,"验证失败"));
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
if (listener != null)
listener.onAuthenticationHelp(helpMsgId,helpString);
}
// 当验证的指纹成功时会回调此函数,然后不再监听指纹sensor
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
if (listener != null)
listener.onAuthenticationSucceeded(result);
}
;
},null);
}
/**
* 取消指纹识别
*/
public static void cancelFingerPrint(){
if(cancellationSignal!=null){
cancellationSignal.cancel();
}
}
/**
* 判断当前设备是否支持指纹识别
* @return
*/
public static ResultObj checkFingerPrint(){
FingerprintManagerCompat managerCompat = FingerprintManagerCompat.from(MainActivity.getInstanse());
return FingerprintUtil.checkFingerPrint(managerCompat);
}
/**
* 判断当前设备是否支持指纹识别
* @param managerCompat
* @return
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public static ResultObj checkFingerPrint(FingerprintManagerCompat managerCompat){
if (!managerCompat.isHardwareDetected()){ //判断设备是否支持
return new ResultObj(FingerprintUtil.DEVICENOTSUPPORTED,"当前设备不支持指纹");
}
KeyguardManager keyguardManager =(KeyguardManager)MainActivity.getInstanse().getSystemService(MainActivity.getInstanse().KEYGUARD_SERVICE);
if (!keyguardManager.isKeyguardSecure()) {//判断设备是否处于安全保护中
return new ResultObj(FingerprintUtil.DEVICENOTSAFEGUARD,"当前设备没有设置密码保护");
}
if (!managerCompat.hasEnrolledFingerprints()){ //判断设备是否已经注册过指纹
return new ResultObj(FingerprintUtil.DEVICENOTREGUIDFIN,"还没有设置指纹");
}
return new ResultObj(FingerprintUtil.SUCCESS,"支持指纹识别");
}
public interface OnCallBackListenr{
void onSupportFailed(ResultObj error);
void onInsecurity(ResultObj error);
void onEnrollFailed(ResultObj error);
void onAuthenticationStart(ResultObj error);
void onAuthenticationError(int errMsgId, CharSequence errString);
void onAuthenticationFailed(ResultObj error);
void onAuthenticationHelp(int helpMsgId, CharSequence helpString);
void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result);
}
public static class ResultObj{
private int code;
private String msg;
public ResultObj(int code,String msg){
this.code = code;
this.msg = msg;
}
public ResultObj getResult(){
return this;
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
public static void cancel(){
if (cancellationSignal != null)
cancellationSignal.cancel();
}
}
下面是调用代码:
/**
* 检查系统是否支持指纹识别
* @param pWebview
* @param array
*/
public void checkFingerPrint(IWebview pWebview, JSONArray array){
String callbackId = array.optString(0);
FingerprintUtil.ResultObj res = FingerprintUtil.checkFingerPrint();
if(res.getCode()==FingerprintUtil.SUCCESS){
String msg = toJSON(res.getCode(), res.getMsg());
Log.d("haiji", "FingerPrint " + res.getCode() + " " + res.getMsg());
JSUtil.execCallback(pWebview,callbackId,msg, JSUtil.OK, false);//成功回调
}else{
Log.d("haiji", "FingerPrint "+res.getMsg());
String error = toJSON(res.getCode(), res.getMsg());
JSUtil.execCallback(pWebview,callbackId,error, JSUtil.ERROR, false);//失败回调
}
}
/**
* 开始进行指纹识别
* @param pWebview
* @param array
*/
public void callFingerPrint(final IWebview pWebview, JSONArray array){
final String callbackId = array.optString(0);
//开始指纹识别
FingerprintUtil.callFingerPrint(new FingerprintUtil.OnCallBackListenr() {
@Override
public void onSupportFailed(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
// JsObjectCommonMethod.showToast("当前设备不支持指纹");
}
@Override
public void onInsecurity(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
// JsObjectCommonMethod.showToast("当前设备没有设置密码保护");
}
@Override
public void onEnrollFailed(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationStart(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+code.getMsg());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.OK, true);//成功回调
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
String error = toJSON(errMsgId, errString.toString());
Log.d("haiji", "FingerPrint "+error.toString());
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationFailed(FingerprintUtil.ResultObj code) {
String error = toJSON(code.getCode(), code.getMsg());
Log.d("haiji", "FingerPrint "+error);
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
String error = toJSON(helpMsgId, helpString.toString());
Log.d("haiji", "FingerPrint "+error);
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.ERROR, false);//失败回调
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
String error = toJSON(FingerprintUtil.FINGETSUCCESS, "指纹识别成功");
Log.d("haiji", "FingerPrint "+error);
JSUtil.execCallback(pWebview, callbackId, error, JSUtil.OK, false);//成功回调
}
});
}
/**
* 取消指纹识别
* @param pWebview
* @param array
*/
public void calcelFingerPrint(final IWebview pWebview, JSONArray array){
Log.d("haiji", "FingerPrint 取消指纹识别");
FingerprintUtil.cancelFingerPrint();
}
下面是js中的代码
/**
* 指纹识别检测
* @param param
*/
checkFingerPrint:function(successCbk,errorCbk){
var success = typeof successCbk !== 'function'? null:function(args){
successCbk(args);
}
var fail = typeof errorCbk !== 'function'?null:function(code){
errorCbk(code);
}
callbackID = BRIGE.callbackId(success,fail);
BRIGE.exec(_CLASS_NAME, "checkFingerPrint", [callbackID,""]);
},
/**
* 开始进行指纹识别
* @param param
*/
callFingerPrint:function(successCbk,errorCbk){
var success = typeof successCbk !== 'function'? null:function(args){
successCbk(args);
}
var fail = typeof errorCbk !== 'function'?null:function(code){
errorCbk(code);
}
callbackID = BRIGE.callbackId(success,fail);
BRIGE.exec(_CLASS_NAME, "callFingerPrint", [callbackID,""]);
},
/**
* 取消指纹识别
* @param param
*/
calcelFingerPrint:function(){
BRIGE.exec(_CLASS_NAME, "calcelFingerPrint", []);
},
收起阅读 »

【分享】【开源】canvas图像裁剪、压缩、旋转
前言
前段时间遇到了一个移动端对图像进行裁剪、压缩、旋转的需求。
考虑到已有各轮子的契合度都不高,于是自己重新造了一个轮子。
关于图像裁剪、压缩
在HTML5时代,canvas的功能已经非常强大了,可以进行像素级的操作。像图像裁剪、压缩就都是基于canvas来实现的。
关于其中原理,无非就是利用canvas自带的API,复杂一点的就是裁剪框以及旋转后的坐标计算,因此不再赘述。
本文中的图像裁剪、压缩都是基于canvas完成的。
图像裁剪
功能包括:
-
canvas绘制图片
-
裁剪框选择裁剪大小
-
旋转功能
-
放大镜(方便旋转)
-
裁剪功能
-
缩放、压缩功能(通过参数控制)
示例
https://dailc.github.io/image-process/examples/clip.html
效果
[图片上传失败...(image-c687d7-1510800462845)]
使用
引入
dist/image-clip.css
dist/image-clip.js
全局变量
ImageClip
调用方法
var cropImage = new ImageClip(options);
cropImage.method()
API
resetClipRect
重置裁剪框,重新变为最大
cropImage.resetClipRect();
clip
裁剪图像,根据当前的裁剪框进行裁剪
cropImage.clip();
getClipImgData
获取已裁剪的图像
var base64 = cropImage.getClipImgData();
rotate
旋转图片
cropImage.clip(isClockWise);
destroy
销毁当前的裁剪对象
如果一个容器需要重新生成裁剪对象,一定要先销毁以前的
cropImage.destroy();
更多
关于详细参数说明与更多使用
请参考源码
图像缩放
上述的图片裁剪中其实已经附带缩放功能,但是鉴于那是基于整套裁剪流程的,不满足一些场景(譬如只要针对图片压缩的)。
因此,单独又将图像缩放提取成一个模块,以适用于此类场景。
功能包括:
-
图像的缩放、压缩
-
一些常用的缩放算法(双立方,双线性,近邻)
示例
https://dailc.github.io/image-process/examples/scale.html
https://dailc.github.io/image-process/examples/scale_compress.html
效果
示例较为粗糙
使用
引入
dist/image-scale.js
全局变量
ImageScale
调用方法
ImageScale.method()
API
scaleImageData
对ImageData
类型的数据进行缩放,将数据放入新的ImageData
中
ImageScale.scaleImageData(imageData, newImageData, {
// 0: nearestNeighbor
// 1: bilinearInterpolation
// 2: bicubicInterpolation
// 3: bicubicInterpolation2
processType: 0,
});
scaleImage
对Image
类型的对象进行缩放,返回一个base64
字符串
var base64 = ImageScale.scaleImage(image, {
width: 80,
height: 80,
mime: 'image/png',
// 0: nearestNeighbor
// 1: bilinearInterpolation
// 2: bicubicInterpolation
// 3: bicubicInterpolation2
processType: 0,
});
compressImage
compressImage,返回一个base64
字符串
与scale的区别是这用的是canvas自动缩放,并且有很多参数可控
var base64 = ImageScale.compressImage(image, {
// 压缩质量
quality: 0.92,
mime: 'image/jpeg',
// 压缩时的放大系数,默认为1,如果增大,代表图像的尺寸会变大(最大不会超过原图)
compressScaleRatio: 1,
// ios的iPhone下主动放大一定系数以解决分辨率过小的模糊问题
iphoneFixedRatio: 2,
// 是否采用原图像素(不会改变大小)
isUseOriginSize: false,
// 增加最大宽度,增加后最大不会超过这个宽度
maxWidth: 0,
// 使用强制的宽度,如果使用,其它宽高比系数都会失效,默认整图使用这个宽度
forceWidth: 0,
// 同上,但是一般不建议设置,因为很可能会改变宽高比导致拉升,特殊场景下使用
forceHeight: 0,
});
更多
关于详细参数说明与更多使用
请参考源码
完善与不足
虽然说一些注意的功能都已经实现,但是从细节角度考虑,还是有很多有待完善的地方的。
譬如,裁剪框的实现方式不优雅。
譬如,旋转不支持其它角度。
譬如,内部源码有待优化。
...
虽然说有计划未来某段时间重构,但考虑到实际的时间安排,可能得等到很后了。
源码
图像裁剪:
https://github.com/dailc/image-process/blob/master/src/clip/README.md
图像缩放:
https://github.com/dailc/image-process/blob/master/src/scale/README.md
前言
前段时间遇到了一个移动端对图像进行裁剪、压缩、旋转的需求。
考虑到已有各轮子的契合度都不高,于是自己重新造了一个轮子。
关于图像裁剪、压缩
在HTML5时代,canvas的功能已经非常强大了,可以进行像素级的操作。像图像裁剪、压缩就都是基于canvas来实现的。
关于其中原理,无非就是利用canvas自带的API,复杂一点的就是裁剪框以及旋转后的坐标计算,因此不再赘述。
本文中的图像裁剪、压缩都是基于canvas完成的。
图像裁剪
功能包括:
-
canvas绘制图片
-
裁剪框选择裁剪大小
-
旋转功能
-
放大镜(方便旋转)
-
裁剪功能
-
缩放、压缩功能(通过参数控制)
示例
https://dailc.github.io/image-process/examples/clip.html
效果
[图片上传失败...(image-c687d7-1510800462845)]
使用
引入
dist/image-clip.css
dist/image-clip.js
全局变量
ImageClip
调用方法
var cropImage = new ImageClip(options);
cropImage.method()
API
resetClipRect
重置裁剪框,重新变为最大
cropImage.resetClipRect();
clip
裁剪图像,根据当前的裁剪框进行裁剪
cropImage.clip();
getClipImgData
获取已裁剪的图像
var base64 = cropImage.getClipImgData();
rotate
旋转图片
cropImage.clip(isClockWise);
destroy
销毁当前的裁剪对象
如果一个容器需要重新生成裁剪对象,一定要先销毁以前的
cropImage.destroy();
更多
关于详细参数说明与更多使用
请参考源码
图像缩放
上述的图片裁剪中其实已经附带缩放功能,但是鉴于那是基于整套裁剪流程的,不满足一些场景(譬如只要针对图片压缩的)。
因此,单独又将图像缩放提取成一个模块,以适用于此类场景。
功能包括:
-
图像的缩放、压缩
-
一些常用的缩放算法(双立方,双线性,近邻)
示例
https://dailc.github.io/image-process/examples/scale.html
https://dailc.github.io/image-process/examples/scale_compress.html
效果
示例较为粗糙
使用
引入
dist/image-scale.js
全局变量
ImageScale
调用方法
ImageScale.method()
API
scaleImageData
对ImageData
类型的数据进行缩放,将数据放入新的ImageData
中
ImageScale.scaleImageData(imageData, newImageData, {
// 0: nearestNeighbor
// 1: bilinearInterpolation
// 2: bicubicInterpolation
// 3: bicubicInterpolation2
processType: 0,
});
scaleImage
对Image
类型的对象进行缩放,返回一个base64
字符串
var base64 = ImageScale.scaleImage(image, {
width: 80,
height: 80,
mime: 'image/png',
// 0: nearestNeighbor
// 1: bilinearInterpolation
// 2: bicubicInterpolation
// 3: bicubicInterpolation2
processType: 0,
});
compressImage
compressImage,返回一个base64
字符串
与scale的区别是这用的是canvas自动缩放,并且有很多参数可控
var base64 = ImageScale.compressImage(image, {
// 压缩质量
quality: 0.92,
mime: 'image/jpeg',
// 压缩时的放大系数,默认为1,如果增大,代表图像的尺寸会变大(最大不会超过原图)
compressScaleRatio: 1,
// ios的iPhone下主动放大一定系数以解决分辨率过小的模糊问题
iphoneFixedRatio: 2,
// 是否采用原图像素(不会改变大小)
isUseOriginSize: false,
// 增加最大宽度,增加后最大不会超过这个宽度
maxWidth: 0,
// 使用强制的宽度,如果使用,其它宽高比系数都会失效,默认整图使用这个宽度
forceWidth: 0,
// 同上,但是一般不建议设置,因为很可能会改变宽高比导致拉升,特殊场景下使用
forceHeight: 0,
});
更多
关于详细参数说明与更多使用
请参考源码
完善与不足
虽然说一些注意的功能都已经实现,但是从细节角度考虑,还是有很多有待完善的地方的。
譬如,裁剪框的实现方式不优雅。
譬如,旋转不支持其它角度。
譬如,内部源码有待优化。
...
虽然说有计划未来某段时间重构,但考虑到实际的时间安排,可能得等到很后了。
源码
图像裁剪:
https://github.com/dailc/image-process/blob/master/src/clip/README.md
图像缩放:
https://github.com/dailc/image-process/blob/master/src/scale/README.md
收起阅读 »
那些年绕不过的苹果上架审核
这个分类。。。。不太恰当
在分享我和苹果审核纠葛史前,还是有必要铺垫一下的。
我,大城市里平民阶层,工作时间是再不能更标准的朝九晚五,公司距离租的房子乘以6倍差不多才一站地铁的路,不刻意改变一下出门时间,甚至每天早上在路上看到的脸都是那几张。日复一日的两点一线,反倒是把心挠的痒痒。众所周知,豆瓣里人才济济,幽默风趣,针砭时事,侃侃而谈之士大有所在,头脑不太简单但生活太过简单的我极其轻易地被几篇打着“专治无聊”帖子吸引了去。最终,用一句话描述——我个人做起了APP。
非技术出身,没经验,海阔天空畅想后还是老实巴交照着豆瓣经验贴讲的去做了。挑了某某免费在线制作APP平台,研究了大半个月,搞清了平台APP制作逻辑,用了大半年去搭建界面,填充数据。因为APP没有涉及电商、医疗等特殊行业类别,所以没有选择代上架服务,而是自己花点钱申请了苹果个人开发者账号走了自助上架流程。
那么,激动人心又饱含心酸的旅程就此开始。
APP制作完备,资料准备充分,先是过了平台的自助上架审核然后自行上架到App Store。
由于关闭了启动图,我的第一次申请,就这么被App Store拒绝了。百度一下相似情况还真不少——为了增强应用程序启动时的用户体验,您应该提供一个启动图像。启动图像并不是为您提供机会进行艺术展示;它完全是为了增强用户对应用程序能够快速启动并立即投入使用的感知度,巴拉巴拉的。在制作台开启启动页后,重新打包,满腔自信再次提交上架申请。由于是社区类型App,版块还挺多,其中一个版块我加了不少链接,我的上架申请如愿以偿又被驳回了。苹果告诉我有链接堆砌的嫌疑,这种界面不适宜放在APP里,居然中肯建议我使用H5制作。足足用了两天改好了问题板块,对自己APP的内容审视再三,希望的火苗又一次点燃。
两天过后,我拿起了好几个月前放在枕头下的《金刚经》。我以为拥护苹果是一种美德,实则不然,APP中某一tab栏的图标风格带有浓郁ios icon风情,又一次,被拒绝了。第四次,也是最后一次。事实证明苹果审核人员真的十分认真负责,最后一次被拒绝也确实是我制作过程中疏忽了一个页面的顶栏返回按钮,点击过后的背景色被设置成了和白色页面背景形成强烈明暗对比的黑色,用户体验不友好,这个我完全认同。被苹果的火眼金睛发现了这一漏洞,调整好心态,改正,重新打包,提交,漫长而又让人心焦的等待......
最终,我的APP通过了苹果的审核。绿色的圆点+可供销售四个大字,对于每一位苹果开发者来说,视觉快感约等于通关游戏后的醒目弹窗。
一刹那,我很爽。
四次交手,内心吐槽苹果多次,更多却是欣赏它的严谨和求实,对每一位苹果开发者负责,更是对每一位ios用户负责。这一过程即便繁琐,也确实丰富了一名普通群众的业余生活,接触了曾经未曾涉及的领域。上架App Store是第一步,如何运营好自己的App是下一阶段该着重思考的问题了。
以上这些文字,能提炼出被App Store打回的原因有:启动页黑屏、APP制作不完善、链接堆砌、涉及苹果专有图标或文字。也可以总结出一条通用的“通过苹果ios上架审核的诀窍”——耐心。将心比心,你是iOS用户希望在App Store下载到什么样的应用,那就请以什么样的标准去制作和改进自己的App。制作App过程中遇到问题求助所用平台的专员或求助万能的搜索引擎,上架过程遇到阻碍,首选求助搜索引擎,其次是找不到根据的一些可以试着和苹果联系,他们的审核人员一般都会给出非常周到和中肯的建议。
这个分类。。。。不太恰当
在分享我和苹果审核纠葛史前,还是有必要铺垫一下的。
我,大城市里平民阶层,工作时间是再不能更标准的朝九晚五,公司距离租的房子乘以6倍差不多才一站地铁的路,不刻意改变一下出门时间,甚至每天早上在路上看到的脸都是那几张。日复一日的两点一线,反倒是把心挠的痒痒。众所周知,豆瓣里人才济济,幽默风趣,针砭时事,侃侃而谈之士大有所在,头脑不太简单但生活太过简单的我极其轻易地被几篇打着“专治无聊”帖子吸引了去。最终,用一句话描述——我个人做起了APP。
非技术出身,没经验,海阔天空畅想后还是老实巴交照着豆瓣经验贴讲的去做了。挑了某某免费在线制作APP平台,研究了大半个月,搞清了平台APP制作逻辑,用了大半年去搭建界面,填充数据。因为APP没有涉及电商、医疗等特殊行业类别,所以没有选择代上架服务,而是自己花点钱申请了苹果个人开发者账号走了自助上架流程。
那么,激动人心又饱含心酸的旅程就此开始。
APP制作完备,资料准备充分,先是过了平台的自助上架审核然后自行上架到App Store。
由于关闭了启动图,我的第一次申请,就这么被App Store拒绝了。百度一下相似情况还真不少——为了增强应用程序启动时的用户体验,您应该提供一个启动图像。启动图像并不是为您提供机会进行艺术展示;它完全是为了增强用户对应用程序能够快速启动并立即投入使用的感知度,巴拉巴拉的。在制作台开启启动页后,重新打包,满腔自信再次提交上架申请。由于是社区类型App,版块还挺多,其中一个版块我加了不少链接,我的上架申请如愿以偿又被驳回了。苹果告诉我有链接堆砌的嫌疑,这种界面不适宜放在APP里,居然中肯建议我使用H5制作。足足用了两天改好了问题板块,对自己APP的内容审视再三,希望的火苗又一次点燃。
两天过后,我拿起了好几个月前放在枕头下的《金刚经》。我以为拥护苹果是一种美德,实则不然,APP中某一tab栏的图标风格带有浓郁ios icon风情,又一次,被拒绝了。第四次,也是最后一次。事实证明苹果审核人员真的十分认真负责,最后一次被拒绝也确实是我制作过程中疏忽了一个页面的顶栏返回按钮,点击过后的背景色被设置成了和白色页面背景形成强烈明暗对比的黑色,用户体验不友好,这个我完全认同。被苹果的火眼金睛发现了这一漏洞,调整好心态,改正,重新打包,提交,漫长而又让人心焦的等待......
最终,我的APP通过了苹果的审核。绿色的圆点+可供销售四个大字,对于每一位苹果开发者来说,视觉快感约等于通关游戏后的醒目弹窗。
一刹那,我很爽。
四次交手,内心吐槽苹果多次,更多却是欣赏它的严谨和求实,对每一位苹果开发者负责,更是对每一位ios用户负责。这一过程即便繁琐,也确实丰富了一名普通群众的业余生活,接触了曾经未曾涉及的领域。上架App Store是第一步,如何运营好自己的App是下一阶段该着重思考的问题了。
以上这些文字,能提炼出被App Store打回的原因有:启动页黑屏、APP制作不完善、链接堆砌、涉及苹果专有图标或文字。也可以总结出一条通用的“通过苹果ios上架审核的诀窍”——耐心。将心比心,你是iOS用户希望在App Store下载到什么样的应用,那就请以什么样的标准去制作和改进自己的App。制作App过程中遇到问题求助所用平台的专员或求助万能的搜索引擎,上架过程遇到阻碍,首选求助搜索引擎,其次是找不到根据的一些可以试着和苹果联系,他们的审核人员一般都会给出非常周到和中肯的建议。

Android平台以WebView方式集成HTML5+SDK 播放视频全屏时白屏
Android平台以WebView方式集成HTML5+SDK 播放视频全屏时白屏,视频播放有声音.
这个问题困扰了好久.后面跟踪代码发现在WebJsEvent.java文件中655行有问题,取不到IActivityHandler对象,导致代码不往下执行了
解决办法:在activity中实现IActivityHandler接口,并且在obtainActivityContentView方法中返回你当前的布局对象就可以了!
PS:官方的文档没说要实现这个接口
@Override
public FrameLayout obtainActivityContentView() {
return obtainActivityContentView;
}
Android平台以WebView方式集成HTML5+SDK 播放视频全屏时白屏,视频播放有声音.
这个问题困扰了好久.后面跟踪代码发现在WebJsEvent.java文件中655行有问题,取不到IActivityHandler对象,导致代码不往下执行了
解决办法:在activity中实现IActivityHandler接口,并且在obtainActivityContentView方法中返回你当前的布局对象就可以了!
PS:官方的文档没说要实现这个接口
@Override
public FrameLayout obtainActivityContentView() {
return obtainActivityContentView;
}
收起阅读 »

升级检测 - wap2app教程
wap2app项目的发行方式及对应的升级模式:
- 发行成原生安装包,类似5+ App安装包,需开发者自己处理升级更新;
- 发行到流应用平台,,流应用有内置的升级逻辑,开发者只需要将最新版本提交到流应用平台即可,无需单独开发.
本文主要讲解发行成原生安装包的升级检测实现,给出一种实现示例(仅供参考,开发者可以自定义逻辑实现),主要分为两个部分:
- 客户端:App启动时,向服务器上报当前版本号,根据服务器响应结果决定是否提醒用户升级
- 服务端:保存App最新版本号,根据客户端上报的版本号,比对后决定是否需要升级,若需升级则返回升级信息(rease notes、更新包地址等);
接口约定
如下数据接口约定仅为示例,开发者可以自定义接口参数。
请求地址:http://www.example.com/check/update
请求方法:GET
请求数据:
{
"appid": plus.runtime.appid,
"version": plus.runtime.version
}
响应数据:
{
"status":1,//升级标志,1:需要升级;0:无需升级
"title": "wap2app版本更新",
"note": "修复bug1;\n修复bug2;",//release notes
"url": "http://www.example.com/wap2app.apk" //更新包下载地址
}
Tips:若应用已经是最新版本,无需升级,则服务端仅需返回status字段(并将值设为0),其它字段无需返回;
客户端实现
在app.js的onLaunch事件中,发起升级检测请求,如下:
/**
* 当wap2app初始化完成时,会触发 onLaunch(全局只触发一次)
* @param {Object} options
*/
onLaunch: function(options) {
//TODO wap2app其它初始化代码
/************升级检测代码开始********** */
var ua = navigator.userAgent;
//Html5Plus环境,但不是流应用环境
if(ua.indexOf('Html5Plus')>-1 && ua.indexOf('StreamApp')==-1){
var url = "http://www.example.com/check/update";//检查更新地址
var req = {//升级检测数据
"appid": plus.runtime.appid,
"version": plus.runtime.version
};
wap2app.ajax.get(url, req, function(rsp) {
if(rsp && rsp.status){
//需要更新,提示用户
plus.nativeUI.confirm(rsp.note, function(event) {
if(0 == event.index) {//用户点击了“立即更新”按钮
plus.runtime.openURL(rsp.url);
}
}, rsp.title, ["立即更新", "取 消"]);
}
});
}
/************升级检测代码结束********** */
}
服务端实现
推荐使用通过uniCloud
的云函数模板,快速实现升级检查逻辑,详情参考:https://ext.dcloud.net.cn/plugin?id=2226
开发者也可以根据根据M站的开发语言,自己实现升级检测逻辑,如下是一个php示例代码:
$appid = $_GET['appid'];
$version = $_GET['version'];//客户端版本号
$rsp = array('status' => 0);//默认返回值,不需要升级
if (isset($appid) && isset($version)) {
if($appid=="__W2A__m.example.com"){//校验appid
//这里是示例代码,真实业务上,最新版本号及relase notes可以存储在数据库或文件中
if($version !== "1.0.1"){
$rsp['status'] = 1;
$rsp['title'] = "应用更新";
$rsp['note'] = "修复bug1;\n修复bug2;";//release notes,支持换行
$rsp['url'] = "http://www.example.com/wap2app.apk";//应用升级包下载地址
}
}
}
exit(json_encode($rsp));
wap2app项目的发行方式及对应的升级模式:
- 发行成原生安装包,类似5+ App安装包,需开发者自己处理升级更新;
- 发行到流应用平台,,流应用有内置的升级逻辑,开发者只需要将最新版本提交到流应用平台即可,无需单独开发.
本文主要讲解发行成原生安装包的升级检测实现,给出一种实现示例(仅供参考,开发者可以自定义逻辑实现),主要分为两个部分:
- 客户端:App启动时,向服务器上报当前版本号,根据服务器响应结果决定是否提醒用户升级
- 服务端:保存App最新版本号,根据客户端上报的版本号,比对后决定是否需要升级,若需升级则返回升级信息(rease notes、更新包地址等);
接口约定
如下数据接口约定仅为示例,开发者可以自定义接口参数。
请求地址:http://www.example.com/check/update
请求方法:GET
请求数据:
{
"appid": plus.runtime.appid,
"version": plus.runtime.version
}
响应数据:
{
"status":1,//升级标志,1:需要升级;0:无需升级
"title": "wap2app版本更新",
"note": "修复bug1;\n修复bug2;",//release notes
"url": "http://www.example.com/wap2app.apk" //更新包下载地址
}
Tips:若应用已经是最新版本,无需升级,则服务端仅需返回status字段(并将值设为0),其它字段无需返回;
客户端实现
在app.js的onLaunch事件中,发起升级检测请求,如下:
/**
* 当wap2app初始化完成时,会触发 onLaunch(全局只触发一次)
* @param {Object} options
*/
onLaunch: function(options) {
//TODO wap2app其它初始化代码
/************升级检测代码开始********** */
var ua = navigator.userAgent;
//Html5Plus环境,但不是流应用环境
if(ua.indexOf('Html5Plus')>-1 && ua.indexOf('StreamApp')==-1){
var url = "http://www.example.com/check/update";//检查更新地址
var req = {//升级检测数据
"appid": plus.runtime.appid,
"version": plus.runtime.version
};
wap2app.ajax.get(url, req, function(rsp) {
if(rsp && rsp.status){
//需要更新,提示用户
plus.nativeUI.confirm(rsp.note, function(event) {
if(0 == event.index) {//用户点击了“立即更新”按钮
plus.runtime.openURL(rsp.url);
}
}, rsp.title, ["立即更新", "取 消"]);
}
});
}
/************升级检测代码结束********** */
}
服务端实现
推荐使用通过uniCloud
的云函数模板,快速实现升级检查逻辑,详情参考:https://ext.dcloud.net.cn/plugin?id=2226
开发者也可以根据根据M站的开发语言,自己实现升级检测逻辑,如下是一个php示例代码:
$appid = $_GET['appid'];
$version = $_GET['version'];//客户端版本号
$rsp = array('status' => 0);//默认返回值,不需要升级
if (isset($appid) && isset($version)) {
if($appid=="__W2A__m.example.com"){//校验appid
//这里是示例代码,真实业务上,最新版本号及relase notes可以存储在数据库或文件中
if($version !== "1.0.1"){
$rsp['status'] = 1;
$rsp['title'] = "应用更新";
$rsp['note'] = "修复bug1;\n修复bug2;";//release notes,支持换行
$rsp['url'] = "http://www.example.com/wap2app.apk";//应用升级包下载地址
}
}
}
exit(json_encode($rsp));
收起阅读 »