微信公众号利用JSSDK选择图片并上传的分享
uni.chooseImage这个api利用的浏览器本身的能力,基于浏览器本身的规范。在微信浏览器内经常会出现无法选择图片的情况,并且对于多图选择支持很不友好。
对于H5开发很多情况都是在公众号这个场景,因此我们可以利用jssdk里的chooseImage能力来进行实现。
基础设施参考了 这篇文章 谢谢作者的支持。
首要要在jsApiList选项中补充“chooseImage”和“getLocalImgData”。
其余的直接贴代码
chooseImage: async function(data) {
if (!this.isWechat()) {
return;
}
if (!data)
data = {};
if (!data.count)
data.count = 9;
if (!data.sizeType)
data.sizeType = ['original', 'compressed'];
if (!data.sourceType)
data.sourceType = ['album', 'camera'];
var that = this;
return new Promise((resolve, reject) => {
jweixin.chooseImage({
count: data.count,
sizeType: data.sizeType,
sourceType: data.sourceType,
success: async function(res) {
var localDatas = await that.handlerLoacalImgData(res.localIds);
resolve(localDatas)
}
})
})
},
handlerLoacalImgData: async function(localIds) {
var localDatas = new Array();
for (let index in localIds) {
var item = localIds[index];
var localData = await this.getLocalImgData(item);
localDatas.push(localData);
}
return localDatas;
},
getLocalImgData: function(item) {
var isIOS = uni.getSystemInfoSync().platform == 'ios';
return new Promise((resolve, reject) => {
jweixin.getLocalImgData({
localId: item,
success: function(res) {
var localData = res.localData;
if (localData.indexOf("base64,") > -1) {
localData = localData.substr(localData.indexOf("base64,") + "base64,".length);
}
if (isIOS) {
resolve({
localData: localData,
src: res.localData
})
} else {
resolve({
localData: localData,
src: item
})
}
}
})
})
}
localData字段为base64上传的值,src为image显示的值。
localData在Ios和安卓上有不同的处理,具体原因参见 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#17
在上传的时候目前我没有想到可以直传阿里云OSS的方法 因此先用base64绕到服务端再上传到OSS。
如需前端压缩,可自行接入相关插件。提供一个自己在用的 https://github.com/brunobar79/J-I-C/
以上。
uni.chooseImage这个api利用的浏览器本身的能力,基于浏览器本身的规范。在微信浏览器内经常会出现无法选择图片的情况,并且对于多图选择支持很不友好。
对于H5开发很多情况都是在公众号这个场景,因此我们可以利用jssdk里的chooseImage能力来进行实现。
基础设施参考了 这篇文章 谢谢作者的支持。
首要要在jsApiList选项中补充“chooseImage”和“getLocalImgData”。
其余的直接贴代码
chooseImage: async function(data) {
if (!this.isWechat()) {
return;
}
if (!data)
data = {};
if (!data.count)
data.count = 9;
if (!data.sizeType)
data.sizeType = ['original', 'compressed'];
if (!data.sourceType)
data.sourceType = ['album', 'camera'];
var that = this;
return new Promise((resolve, reject) => {
jweixin.chooseImage({
count: data.count,
sizeType: data.sizeType,
sourceType: data.sourceType,
success: async function(res) {
var localDatas = await that.handlerLoacalImgData(res.localIds);
resolve(localDatas)
}
})
})
},
handlerLoacalImgData: async function(localIds) {
var localDatas = new Array();
for (let index in localIds) {
var item = localIds[index];
var localData = await this.getLocalImgData(item);
localDatas.push(localData);
}
return localDatas;
},
getLocalImgData: function(item) {
var isIOS = uni.getSystemInfoSync().platform == 'ios';
return new Promise((resolve, reject) => {
jweixin.getLocalImgData({
localId: item,
success: function(res) {
var localData = res.localData;
if (localData.indexOf("base64,") > -1) {
localData = localData.substr(localData.indexOf("base64,") + "base64,".length);
}
if (isIOS) {
resolve({
localData: localData,
src: res.localData
})
} else {
resolve({
localData: localData,
src: item
})
}
}
})
})
}
localData字段为base64上传的值,src为image显示的值。
localData在Ios和安卓上有不同的处理,具体原因参见 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#17
在上传的时候目前我没有想到可以直传阿里云OSS的方法 因此先用base64绕到服务端再上传到OSS。
如需前端压缩,可自行接入相关插件。提供一个自己在用的 https://github.com/brunobar79/J-I-C/
以上。
收起阅读 »后台(或锁屏)持续/实时定位的实现方案
注意,本问题的解决方案需要对原生打包熟练,并且能编写简单原生代码(如有过编写原生插件经验)
另,ios只要正确设置Background Modes和对应权限申请描述即可,本文只关心安卓端
问题描述:
watchPosition监听位置信息,每间隔一段时间获取位置信息上传,实现后台实时定位,当app退到后台或锁屏一段时间(通常几分钟)后,便获取不到位置信息了。
解决方案:(以百度SDK为例)
第一种:
根据百度官方建议:http://lbsyun.baidu.com/index.php?title=android-locsdk/guide/addition-func/android8-notice,我们可以自行编写一个原生插件,如:
package com.XX.XXX.H5PlusPlugin;
import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import io.dcloud.common.DHInterface.IWebview;
import io.dcloud.common.DHInterface.StandardFeature;
import io.dcloud.common.util.JSUtil;
import com.XX.XXX.util.NotificationUtils;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.text.ParsePosition;
public class BaiduLocation extends StandardFeature {
public String callBackID = "";
public Activity curActivity;
public IWebview curWebview;
private LocationClient mClient;
private NotificationUtils mNotificationUtils;
private Notification notification;
public void onStart(Context pContext, Bundle pSavedInstanceState, String[] pRuntimeArgs) {
/**
* 如果需要在应用启动时进行初始化,可以继承这个方法,并在properties.xml文件的service节点添加扩展插件的注册即可触发onStart方法
* */
}
public void watchPosition(IWebview pWebview, JSONArray array) {
curWebview = pWebview;
curActivity = pWebview.getActivity();
// 原生代码中获取JS层传递的参数,
// 参数的获取顺序与JS层传递的顺序一致
callBackID = array.optString(0);
//String verifyToken = array.optString(1);
// 定位初始化
mClient = new LocationClient(pWebview.getContext());
LocationClientOption mOption = new LocationClientOption();
mOption.setScanSpan(10000);
mOption.setCoorType("bd09ll");
mOption.setIsNeedAddress(true);
mOption.setOpenGps(true);
mClient.setLocOption(mOption);
//mClient.registerLocationListener(myLocationListener);
mClient.registerLocationListener(new BDAbstractLocationListener() {
public void onReceiveLocation(BDLocation bdLocation) {
JSONObject json=makeJSON(bdLocation,"bd09ll");
//结果返回给js层
JSUtil.execCallback(curWebview, callBackID, json.toString(), JSUtil.OK, true,true);
}
});
//设置后台定位
//android8.0及以上使用NotificationUtils
if (Build.VERSION.SDK_INT >= 26) {
mNotificationUtils = new NotificationUtils(pWebview.getContext());
Notification.Builder builder2 = mNotificationUtils.getAndroidChannelNotification
("适配android 8限制后台定位功能", "正在后台定位");
notification = builder2.build();
} else {
//获取一个Notification构造器
Notification.Builder builder = new Notification.Builder(curActivity);
Intent nfIntent = new Intent(curActivity, curActivity.getClass());
builder.setContentIntent(PendingIntent.
getActivity(curActivity, 0, nfIntent, 0)) // 设置PendingIntent
.setContentTitle("适配android 8限制后台定位功能") // 设置下拉列表里的标题
.setSmallIcon(android.R.drawable.btn_star) // 设置状态栏内的小图标
.setContentText("正在后台定位") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
notification = builder.build(); // 获取构建好的Notification
}
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
mClient.enableLocInForeground(1, notification);
mClient.start();
}
private JSONObject makeJSON(BDLocation pLoc, String coordsType) {
JSONObject json = null;
try {
json = new JSONObject();
json.put("latitude", pLoc.getLatitude());
json.put("longitude", pLoc.getLongitude());
json.put("altitude", pLoc.getAltitude());
json.put("accuracy", pLoc.getRadius());
json.put("altitudeAccuracy", 0);
json.put("heading", pLoc.getDirection());
json.put("velocity", pLoc.getSpeed());
json.put("coordsType", coordsType);
try {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParsePosition pos = new ParsePosition(0);
Date strtodate = formatter.parse(pLoc.getTime(), pos);
json.put("timestamp", strtodate.getTime());
} catch (Exception e) {
e.printStackTrace();
json.put("timestamp", pLoc.getTime());
}
json.put("address", pLoc.getAddrStr());
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
}
其中com.XX.XXX.util.NotificationUtils是自行编写的一个生成通知的类,在百度定位sdk的demo工程中可以拷贝。
由于dcloud已经引入过百度SDK(baidu-libs-release.aar),我们不需要再添加引用。
按原生插件配置文档配置好之后,编写好js插件,然后在html页面调用即可,如
plus.BaiduLocation.watchPosition(function(p){
$('#div_position').prepend('<p>'+moment().format('HH:mm:ss')+'</p><p>'+JSON.stringify(p)+'</p>');
});
第二种:
直接修改DCloud官方H5+的watchPosition方法。
该方法在geolocation-baidu-release.aar中,利用jd-ui等工具反编译导出java文件,然后新建工程,修改代码重新编译替换.class即可。(注意新建工程需要引用依赖的包lib.5plus.base-release.aar、baidu-libs-release.aar)
修改的代码就是按百度官方建议的,加上前台服务通知,调用enableLocInForeground开启前台定位,如:
package io.dcloud.js.geolocation.baidu;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Build;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import io.dcloud.common.DHInterface.FeatureMessageDispatcher;
import io.dcloud.common.DHInterface.IEventCallback;
import io.dcloud.common.DHInterface.IWebview;
import io.dcloud.common.adapter.ui.AdaFrameView;
import io.dcloud.common.adapter.util.AndroidResources;
import io.dcloud.common.adapter.util.Logger;
import io.dcloud.common.adapter.util.SP;
import io.dcloud.common.util.JSUtil;
import io.dcloud.common.util.NetTool;
import io.dcloud.common.util.PdrUtil;
import io.dcloud.common.util.StringUtil;
import io.dcloud.js.geolocation.GeoManagerBase;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
public class BaiduGeoManager
extends GeoManagerBase
{
public static final String TAG = BaiduGeoManager.class.getSimpleName();
boolean hasAppkey = false;
boolean isGeocode = true;
boolean isStreamApp = false;
static BaiduGeoManager mInstance;
LocationClient mClient = null;
LocationClientOption mOption = null;
HashMap<String, LocationClient> mContinuousMap = new HashMap<>();
HashMap<String, LocationClient> mSingleTimeMap = new HashMap<>();
private NotificationManager mManager;
public static final String ANDROID_CHANNEL_NAME = "ANDROID CHANNEL";
private Notification notification;
public Activity curActivity;
public BaiduGeoManager(Context pContext) {
super(pContext);
this.hasAppkey = !PdrUtil.isEmpty(AndroidResources.getMetaValue("com.baidu.lbsapi.API_KEY"));
}
public static BaiduGeoManager getInstance(Context pContext) {
pContext = pContext.getApplicationContext();
if (mInstance != null) {
return mInstance;
}
mInstance = new BaiduGeoManager(pContext);
return mInstance;
}
public String execute(IWebview pWebViewImpl, String pActionName, String[] pJsArgs) {
curActivity=pWebViewImpl.getActivity();
String result = "";
try {
this.isStreamApp = pWebViewImpl.obtainApp().isStreamApp();
String t = (pJsArgs.length > 7) ? pJsArgs[6] : "null";
int timeout = Integer.MAX_VALUE;
if (!"null".equals(t)) {
timeout = Integer.parseInt(t);
}
String intervals = (pJsArgs.length > 8) ? pJsArgs[7] : "5000";
int interval = 5000;
if (!intervals.equals("null")) {
interval = Integer.parseInt(intervals);
if (interval < 1000) {
interval = 1000;
}
}
if (pActionName.startsWith("getCurrentPosition")) {
this.isGeocode = Boolean.parseBoolean(pJsArgs[5]);
boolean _enableHighAccuracy = Boolean.parseBoolean(pJsArgs[1]);
boolean isNotWgs84 = !PdrUtil.isEquals("wgs84", pJsArgs[3]);
if (isNotWgs84) {
startLocating(pWebViewImpl, pJsArgs[0], null, _enableHighAccuracy, timeout, -1, pActionName.endsWith("DLGEO"), pJsArgs[3], false);
} else {
String _json = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(17), isNotWgs84 ? "指定的provider不存在或无效" : "only support gcj02|bd09|bd09ll" });
JSUtil.execCallback(pWebViewImpl, pJsArgs[0], _json, JSUtil.ERROR, true, false);
}
} else if (pActionName.startsWith("watchPosition")) {
this.isGeocode = Boolean.parseBoolean(pJsArgs[5]);
boolean _enableHighAccuracy = Boolean.parseBoolean(pJsArgs[2]);
pWebViewImpl.obtainFrameView().addFrameViewListener(new IEventCallback()
{
public Object onCallBack(String pEventType, Object pArgs) {
if ((PdrUtil.isEquals(pEventType, "window_close") || PdrUtil.isEquals(pEventType, "close")) && pArgs instanceof IWebview) {
BaiduGeoManager.this.stopContinuousLocating();
((AdaFrameView)((IWebview)pArgs).obtainFrameView()).removeFrameViewListener(this);
}
return null;
}
});
boolean isNotWgs84 = !PdrUtil.isEquals("wgs84", pJsArgs[3]);
if (isNotWgs84) {
startLocating(pWebViewImpl, pJsArgs[0], pJsArgs[1], _enableHighAccuracy, timeout, interval, pActionName.endsWith("DLGEO"), pJsArgs[3], true);
} else {
String _json = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(17), isNotWgs84 ? "指定的provider不存在或无效" : "only support gcj02|bd09|bd09ll" });
JSUtil.execCallback(pWebViewImpl, pJsArgs[0], _json, JSUtil.ERROR, true, false);
}
} else if (pActionName.startsWith("clearWatch")) {
this.keySet.remove(pJsArgs[0]);
LocationClient tClient=(LocationClient)this.mContinuousMap.remove(pJsArgs[0]);
tClient.disableLocInForeground(true);
tClient.stop();
}
return result;
} catch (Exception e) {
Logger.e(TAG, "e.getMessage()==" + e.getMessage());
return result;
}
}
public void startLocating(final IWebview pWebViewImpl, final String pCallbackId, String key, boolean enableHighAccuracy, int timeOut, int intervals, final boolean isDLGeo, final String coordsType, final boolean continuous) {
if (this.hasAppkey) {
this.mClient = new LocationClient(pWebViewImpl.getContext());
this.mOption = new LocationClientOption();
if (PdrUtil.isEmpty(key)) {
this.mOption.setScanSpan(0);
this.mSingleTimeMap.put(pCallbackId, this.mClient);
} else {
this.mOption.setScanSpan(intervals);
this.mOption.setLocationNotify(true);
this.keySet.add(key);
this.mContinuousMap.put(key, this.mClient);
}
if (NetTool.isNetworkAvailable(this.mContext)) {
if (enableHighAccuracy) {
this.mOption.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);
} else {
this.mOption.setLocationMode(LocationClientOption.LocationMode.Battery_Saving);
}
this.mOption.setTimeOut(timeOut);
} else {
this.mOption.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
if (Integer.MAX_VALUE == timeOut) {
this.mOption.setTimeOut(3000);
} else {
this.mOption.setTimeOut(timeOut);
}
}
this.mOption.setIsNeedAddress(this.isGeocode);
this.mOption.setCoorType(getCoorType(coordsType));
this.mClient.setLocOption(this.mOption);
this.mClient.registerLocationListener(new BDAbstractLocationListener()
{
public void onReceiveLocation(BDLocation bdLocation) {
if (bdLocation.getAddress() != null) {
FeatureMessageDispatcher.dispatchMessage("record_address", (bdLocation.getAddress() != null) ? (bdLocation.getAddress()).address : null);
}
Logger.e(BaiduGeoManager.TAG, "onReceiveLocation bdLocation==" + bdLocation.toString());
BaiduGeoManager.this.callBack2Front(pWebViewImpl, pCallbackId, bdLocation, BaiduGeoManager.this.getCoorType(coordsType), isDLGeo, continuous);
}
});
if(continuous) {
//设置后台定位
if (Build.VERSION.SDK_INT >= 26) {
createChannels();
Notification.Builder builder2 = getAndroidChannelNotification("适配android 8限制后台定位功能", "正在后台定位");
notification = builder2.build();
} else {
//获取一个Notification构造器
Notification.Builder builder = new Notification.Builder(curActivity);
Intent nfIntent = new Intent(curActivity, curActivity.getClass());
builder.setContentIntent(PendingIntent.
getActivity(curActivity, 0, nfIntent, 0)) // 设置PendingIntent
.setContentTitle("适配android 8限制后台定位功能") // 设置下拉列表里的标题
.setSmallIcon(android.R.drawable.btn_star) // 设置状态栏内的小图标
.setContentText("正在后台定位") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
notification = builder.build(); // 获取构建好的Notification
}
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
this.mClient.enableLocInForeground(1,notification);
}
this.mClient.start();
} else {
String _json = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(16), "has not baidu appkey" });
JSUtil.execCallback(pWebViewImpl, pCallbackId, _json, JSUtil.ERROR, true, false);
}
}
public void createChannels() {
// create android channel
NotificationChannel androidChannel = new NotificationChannel(curActivity.getPackageName()+".BDLocation",
ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
// Sets whether notifications posted to this channel should display notification lights
androidChannel.enableLights(true);
// Sets whether notification posted to this channel should vibrate.
androidChannel.enableVibration(true);
// Sets the notification light color for notifications posted to this channel
androidChannel.setLightColor(Color.GREEN);
// Sets whether notifications posted to this channel appear on the lockscreen or not
androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
getManager().createNotificationChannel(androidChannel);
}
private NotificationManager getManager() {
if (mManager == null) {
mManager = (NotificationManager) curActivity.getSystemService(Context.NOTIFICATION_SERVICE);
}
return mManager;
}
public Notification.Builder getAndroidChannelNotification(String title, String body) {
return new Notification.Builder(curActivity.getApplicationContext(), curActivity.getPackageName()+".BDLocation")
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.btn_star)
.setAutoCancel(true);
}
private void stopContinuousLocating() {
for (Map.Entry<String, LocationClient> entry : this.mContinuousMap.entrySet()) {
System.out.println("key= " + (String)entry.getKey() + " and value= " + entry.getValue());
if (!PdrUtil.isEmpty(entry.getValue())) {
LocationClient tClient=(LocationClient)entry.getValue();
tClient.disableLocInForeground(true);
tClient.stop();
}
}
}
private void callBack2Front(IWebview mWebview, String mCallbackId, BDLocation location, String CoordsType, boolean isDLGeo, boolean continuous) {
if (!continuous &&
!PdrUtil.isEmpty(this.mSingleTimeMap.get(mCallbackId))) {
((LocationClient)this.mSingleTimeMap.get(mCallbackId)).stop();
}
JSONObject _json = makeJSON(location, CoordsType);
if (_json == null) {
geoDataError(mWebview, mCallbackId, isDLGeo, continuous);
} else {
callback(mWebview, mCallbackId, _json.toString(), JSUtil.OK, true, isDLGeo, continuous);
}
}
public void callback(IWebview webview, String callId, String json, int code, boolean isJson, boolean isDLGeo, boolean continuous) {
if (isDLGeo) {
JSUtil.execGEOCallback(webview, callId, json, code, isJson, continuous);
} else {
JSUtil.execCallback(webview, callId, json, code, isJson, continuous);
}
}
private JSONObject makeJSON(BDLocation pLoc, String coordsType) {
JSONObject json = null;
try {
json = new JSONObject();
json.put("latitude", pLoc.getLatitude());
json.put("longitude", pLoc.getLongitude());
json.put("altitude", pLoc.getAltitude());
json.put("accuracy", pLoc.getRadius());
json.put("altitudeAccuracy", 0);
json.put("heading", pLoc.getDirection());
json.put("velocity", pLoc.getSpeed());
json.put("coordsType", coordsType);
try {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParsePosition pos = new ParsePosition(0);
Date strtodate = formatter.parse(pLoc.getTime(), pos);
json.put("timestamp", strtodate.getTime());
} catch (Exception e) {
e.printStackTrace();
json.put("timestamp", pLoc.getTime());
}
if (this.isGeocode) {
JSONObject address = new JSONObject();
json.put("address", address);
address.put("country", pLoc.getCountry());
address.put("province", pLoc.getProvince());
address.put("city", pLoc.getCity());
address.put("district", pLoc.getDistrict());
address.put("street", pLoc.getStreet());
address.put("streetNum", pLoc.getStreetNumber());
address.put("poiName", (pLoc.getPoiList() != null && pLoc.getPoiList().size() > 0) ? pLoc.getPoiList().get(0) : null);
address.put("postalCode", null);
address.put("cityCode", pLoc.getCityCode());
json.put("addresses", pLoc.getAddrStr());
}
} catch (JSONException e) {
e.printStackTrace();
}
saveGeoData(pLoc, coordsType);
return json;
}
private String getCoorType(String coorType) {
if (PdrUtil.isEquals(coorType, "bd09ll"))
return "bd09ll";
if (PdrUtil.isEquals(coorType, "bd09")) {
return "bd09";
}
return "gcj02";
}
public void onDestroy() {
for (Map.Entry<String, LocationClient> entry : this.mContinuousMap.entrySet()) {
if (!PdrUtil.isEmpty(entry.getValue())) {
((LocationClient)entry.getValue()).stop();
}
}
this.mContinuousMap.clear();
for (Map.Entry<String, LocationClient> entry : this.mSingleTimeMap.entrySet()) {
if (!PdrUtil.isEmpty(entry.getValue())) {
((LocationClient)entry.getValue()).stop();
}
}
this.mSingleTimeMap.clear();
}
private void saveGeoData(BDLocation pLoc, String coordsType) {
if (!this.isStreamApp) {
JSONObject jsonObject = new JSONObject();
JSONObject coordsJson = new JSONObject();
try {
coordsJson.put("latitude", pLoc.getLatitude());
coordsJson.put("longitude", pLoc.getLongitude());
jsonObject.put("coords", coordsJson);
jsonObject.put("coordsType", coordsType);
if (this.isGeocode) {
jsonObject.put("addresses", pLoc.getAddrStr());
}
SharedPreferences startSp = SP.getOrCreateBundle("start_statistics_data");
SP.setBundleData(startSp, "geo_data", jsonObject.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void geoDataError(IWebview pWebViewImpl, String pCallbackId, boolean isDLGeo, boolean continuous) {
String err = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(40), "定位异常"});
if (isDLGeo) {
JSUtil.execGEOCallback(pWebViewImpl, pCallbackId, err, JSUtil.ERROR, true, continuous);
} else {
JSUtil.execCallback(pWebViewImpl, pCallbackId, err, JSUtil.ERROR, true, continuous);
}
}
}
总结:
以上代码只是测试功能,写得简单且不规范。其实都只是加入百度SDK已经实现的功能代码。
经打包安装到手机(目前只在华为Novaz5)测试,退到后台锁屏几十分钟再打开查看记录,一直能连续获取到位置信息。
另外,要将手机管家中对该app去掉“自动管理”,并打开“允许后台活动”开关
最后,强烈建议官方修改watchPosition方法(如加个生成前台服务通知的参数?)!!
注意,本问题的解决方案需要对原生打包熟练,并且能编写简单原生代码(如有过编写原生插件经验)
另,ios只要正确设置Background Modes和对应权限申请描述即可,本文只关心安卓端
问题描述:
watchPosition监听位置信息,每间隔一段时间获取位置信息上传,实现后台实时定位,当app退到后台或锁屏一段时间(通常几分钟)后,便获取不到位置信息了。
解决方案:(以百度SDK为例)
第一种:
根据百度官方建议:http://lbsyun.baidu.com/index.php?title=android-locsdk/guide/addition-func/android8-notice,我们可以自行编写一个原生插件,如:
package com.XX.XXX.H5PlusPlugin;
import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import io.dcloud.common.DHInterface.IWebview;
import io.dcloud.common.DHInterface.StandardFeature;
import io.dcloud.common.util.JSUtil;
import com.XX.XXX.util.NotificationUtils;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.text.ParsePosition;
public class BaiduLocation extends StandardFeature {
public String callBackID = "";
public Activity curActivity;
public IWebview curWebview;
private LocationClient mClient;
private NotificationUtils mNotificationUtils;
private Notification notification;
public void onStart(Context pContext, Bundle pSavedInstanceState, String[] pRuntimeArgs) {
/**
* 如果需要在应用启动时进行初始化,可以继承这个方法,并在properties.xml文件的service节点添加扩展插件的注册即可触发onStart方法
* */
}
public void watchPosition(IWebview pWebview, JSONArray array) {
curWebview = pWebview;
curActivity = pWebview.getActivity();
// 原生代码中获取JS层传递的参数,
// 参数的获取顺序与JS层传递的顺序一致
callBackID = array.optString(0);
//String verifyToken = array.optString(1);
// 定位初始化
mClient = new LocationClient(pWebview.getContext());
LocationClientOption mOption = new LocationClientOption();
mOption.setScanSpan(10000);
mOption.setCoorType("bd09ll");
mOption.setIsNeedAddress(true);
mOption.setOpenGps(true);
mClient.setLocOption(mOption);
//mClient.registerLocationListener(myLocationListener);
mClient.registerLocationListener(new BDAbstractLocationListener() {
public void onReceiveLocation(BDLocation bdLocation) {
JSONObject json=makeJSON(bdLocation,"bd09ll");
//结果返回给js层
JSUtil.execCallback(curWebview, callBackID, json.toString(), JSUtil.OK, true,true);
}
});
//设置后台定位
//android8.0及以上使用NotificationUtils
if (Build.VERSION.SDK_INT >= 26) {
mNotificationUtils = new NotificationUtils(pWebview.getContext());
Notification.Builder builder2 = mNotificationUtils.getAndroidChannelNotification
("适配android 8限制后台定位功能", "正在后台定位");
notification = builder2.build();
} else {
//获取一个Notification构造器
Notification.Builder builder = new Notification.Builder(curActivity);
Intent nfIntent = new Intent(curActivity, curActivity.getClass());
builder.setContentIntent(PendingIntent.
getActivity(curActivity, 0, nfIntent, 0)) // 设置PendingIntent
.setContentTitle("适配android 8限制后台定位功能") // 设置下拉列表里的标题
.setSmallIcon(android.R.drawable.btn_star) // 设置状态栏内的小图标
.setContentText("正在后台定位") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
notification = builder.build(); // 获取构建好的Notification
}
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
mClient.enableLocInForeground(1, notification);
mClient.start();
}
private JSONObject makeJSON(BDLocation pLoc, String coordsType) {
JSONObject json = null;
try {
json = new JSONObject();
json.put("latitude", pLoc.getLatitude());
json.put("longitude", pLoc.getLongitude());
json.put("altitude", pLoc.getAltitude());
json.put("accuracy", pLoc.getRadius());
json.put("altitudeAccuracy", 0);
json.put("heading", pLoc.getDirection());
json.put("velocity", pLoc.getSpeed());
json.put("coordsType", coordsType);
try {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParsePosition pos = new ParsePosition(0);
Date strtodate = formatter.parse(pLoc.getTime(), pos);
json.put("timestamp", strtodate.getTime());
} catch (Exception e) {
e.printStackTrace();
json.put("timestamp", pLoc.getTime());
}
json.put("address", pLoc.getAddrStr());
} catch (JSONException e) {
e.printStackTrace();
}
return json;
}
}
其中com.XX.XXX.util.NotificationUtils是自行编写的一个生成通知的类,在百度定位sdk的demo工程中可以拷贝。
由于dcloud已经引入过百度SDK(baidu-libs-release.aar),我们不需要再添加引用。
按原生插件配置文档配置好之后,编写好js插件,然后在html页面调用即可,如
plus.BaiduLocation.watchPosition(function(p){
$('#div_position').prepend('<p>'+moment().format('HH:mm:ss')+'</p><p>'+JSON.stringify(p)+'</p>');
});
第二种:
直接修改DCloud官方H5+的watchPosition方法。
该方法在geolocation-baidu-release.aar中,利用jd-ui等工具反编译导出java文件,然后新建工程,修改代码重新编译替换.class即可。(注意新建工程需要引用依赖的包lib.5plus.base-release.aar、baidu-libs-release.aar)
修改的代码就是按百度官方建议的,加上前台服务通知,调用enableLocInForeground开启前台定位,如:
package io.dcloud.js.geolocation.baidu;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Build;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import io.dcloud.common.DHInterface.FeatureMessageDispatcher;
import io.dcloud.common.DHInterface.IEventCallback;
import io.dcloud.common.DHInterface.IWebview;
import io.dcloud.common.adapter.ui.AdaFrameView;
import io.dcloud.common.adapter.util.AndroidResources;
import io.dcloud.common.adapter.util.Logger;
import io.dcloud.common.adapter.util.SP;
import io.dcloud.common.util.JSUtil;
import io.dcloud.common.util.NetTool;
import io.dcloud.common.util.PdrUtil;
import io.dcloud.common.util.StringUtil;
import io.dcloud.js.geolocation.GeoManagerBase;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;
public class BaiduGeoManager
extends GeoManagerBase
{
public static final String TAG = BaiduGeoManager.class.getSimpleName();
boolean hasAppkey = false;
boolean isGeocode = true;
boolean isStreamApp = false;
static BaiduGeoManager mInstance;
LocationClient mClient = null;
LocationClientOption mOption = null;
HashMap<String, LocationClient> mContinuousMap = new HashMap<>();
HashMap<String, LocationClient> mSingleTimeMap = new HashMap<>();
private NotificationManager mManager;
public static final String ANDROID_CHANNEL_NAME = "ANDROID CHANNEL";
private Notification notification;
public Activity curActivity;
public BaiduGeoManager(Context pContext) {
super(pContext);
this.hasAppkey = !PdrUtil.isEmpty(AndroidResources.getMetaValue("com.baidu.lbsapi.API_KEY"));
}
public static BaiduGeoManager getInstance(Context pContext) {
pContext = pContext.getApplicationContext();
if (mInstance != null) {
return mInstance;
}
mInstance = new BaiduGeoManager(pContext);
return mInstance;
}
public String execute(IWebview pWebViewImpl, String pActionName, String[] pJsArgs) {
curActivity=pWebViewImpl.getActivity();
String result = "";
try {
this.isStreamApp = pWebViewImpl.obtainApp().isStreamApp();
String t = (pJsArgs.length > 7) ? pJsArgs[6] : "null";
int timeout = Integer.MAX_VALUE;
if (!"null".equals(t)) {
timeout = Integer.parseInt(t);
}
String intervals = (pJsArgs.length > 8) ? pJsArgs[7] : "5000";
int interval = 5000;
if (!intervals.equals("null")) {
interval = Integer.parseInt(intervals);
if (interval < 1000) {
interval = 1000;
}
}
if (pActionName.startsWith("getCurrentPosition")) {
this.isGeocode = Boolean.parseBoolean(pJsArgs[5]);
boolean _enableHighAccuracy = Boolean.parseBoolean(pJsArgs[1]);
boolean isNotWgs84 = !PdrUtil.isEquals("wgs84", pJsArgs[3]);
if (isNotWgs84) {
startLocating(pWebViewImpl, pJsArgs[0], null, _enableHighAccuracy, timeout, -1, pActionName.endsWith("DLGEO"), pJsArgs[3], false);
} else {
String _json = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(17), isNotWgs84 ? "指定的provider不存在或无效" : "only support gcj02|bd09|bd09ll" });
JSUtil.execCallback(pWebViewImpl, pJsArgs[0], _json, JSUtil.ERROR, true, false);
}
} else if (pActionName.startsWith("watchPosition")) {
this.isGeocode = Boolean.parseBoolean(pJsArgs[5]);
boolean _enableHighAccuracy = Boolean.parseBoolean(pJsArgs[2]);
pWebViewImpl.obtainFrameView().addFrameViewListener(new IEventCallback()
{
public Object onCallBack(String pEventType, Object pArgs) {
if ((PdrUtil.isEquals(pEventType, "window_close") || PdrUtil.isEquals(pEventType, "close")) && pArgs instanceof IWebview) {
BaiduGeoManager.this.stopContinuousLocating();
((AdaFrameView)((IWebview)pArgs).obtainFrameView()).removeFrameViewListener(this);
}
return null;
}
});
boolean isNotWgs84 = !PdrUtil.isEquals("wgs84", pJsArgs[3]);
if (isNotWgs84) {
startLocating(pWebViewImpl, pJsArgs[0], pJsArgs[1], _enableHighAccuracy, timeout, interval, pActionName.endsWith("DLGEO"), pJsArgs[3], true);
} else {
String _json = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(17), isNotWgs84 ? "指定的provider不存在或无效" : "only support gcj02|bd09|bd09ll" });
JSUtil.execCallback(pWebViewImpl, pJsArgs[0], _json, JSUtil.ERROR, true, false);
}
} else if (pActionName.startsWith("clearWatch")) {
this.keySet.remove(pJsArgs[0]);
LocationClient tClient=(LocationClient)this.mContinuousMap.remove(pJsArgs[0]);
tClient.disableLocInForeground(true);
tClient.stop();
}
return result;
} catch (Exception e) {
Logger.e(TAG, "e.getMessage()==" + e.getMessage());
return result;
}
}
public void startLocating(final IWebview pWebViewImpl, final String pCallbackId, String key, boolean enableHighAccuracy, int timeOut, int intervals, final boolean isDLGeo, final String coordsType, final boolean continuous) {
if (this.hasAppkey) {
this.mClient = new LocationClient(pWebViewImpl.getContext());
this.mOption = new LocationClientOption();
if (PdrUtil.isEmpty(key)) {
this.mOption.setScanSpan(0);
this.mSingleTimeMap.put(pCallbackId, this.mClient);
} else {
this.mOption.setScanSpan(intervals);
this.mOption.setLocationNotify(true);
this.keySet.add(key);
this.mContinuousMap.put(key, this.mClient);
}
if (NetTool.isNetworkAvailable(this.mContext)) {
if (enableHighAccuracy) {
this.mOption.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);
} else {
this.mOption.setLocationMode(LocationClientOption.LocationMode.Battery_Saving);
}
this.mOption.setTimeOut(timeOut);
} else {
this.mOption.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
if (Integer.MAX_VALUE == timeOut) {
this.mOption.setTimeOut(3000);
} else {
this.mOption.setTimeOut(timeOut);
}
}
this.mOption.setIsNeedAddress(this.isGeocode);
this.mOption.setCoorType(getCoorType(coordsType));
this.mClient.setLocOption(this.mOption);
this.mClient.registerLocationListener(new BDAbstractLocationListener()
{
public void onReceiveLocation(BDLocation bdLocation) {
if (bdLocation.getAddress() != null) {
FeatureMessageDispatcher.dispatchMessage("record_address", (bdLocation.getAddress() != null) ? (bdLocation.getAddress()).address : null);
}
Logger.e(BaiduGeoManager.TAG, "onReceiveLocation bdLocation==" + bdLocation.toString());
BaiduGeoManager.this.callBack2Front(pWebViewImpl, pCallbackId, bdLocation, BaiduGeoManager.this.getCoorType(coordsType), isDLGeo, continuous);
}
});
if(continuous) {
//设置后台定位
if (Build.VERSION.SDK_INT >= 26) {
createChannels();
Notification.Builder builder2 = getAndroidChannelNotification("适配android 8限制后台定位功能", "正在后台定位");
notification = builder2.build();
} else {
//获取一个Notification构造器
Notification.Builder builder = new Notification.Builder(curActivity);
Intent nfIntent = new Intent(curActivity, curActivity.getClass());
builder.setContentIntent(PendingIntent.
getActivity(curActivity, 0, nfIntent, 0)) // 设置PendingIntent
.setContentTitle("适配android 8限制后台定位功能") // 设置下拉列表里的标题
.setSmallIcon(android.R.drawable.btn_star) // 设置状态栏内的小图标
.setContentText("正在后台定位") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
notification = builder.build(); // 获取构建好的Notification
}
notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
this.mClient.enableLocInForeground(1,notification);
}
this.mClient.start();
} else {
String _json = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(16), "has not baidu appkey" });
JSUtil.execCallback(pWebViewImpl, pCallbackId, _json, JSUtil.ERROR, true, false);
}
}
public void createChannels() {
// create android channel
NotificationChannel androidChannel = new NotificationChannel(curActivity.getPackageName()+".BDLocation",
ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
// Sets whether notifications posted to this channel should display notification lights
androidChannel.enableLights(true);
// Sets whether notification posted to this channel should vibrate.
androidChannel.enableVibration(true);
// Sets the notification light color for notifications posted to this channel
androidChannel.setLightColor(Color.GREEN);
// Sets whether notifications posted to this channel appear on the lockscreen or not
androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
getManager().createNotificationChannel(androidChannel);
}
private NotificationManager getManager() {
if (mManager == null) {
mManager = (NotificationManager) curActivity.getSystemService(Context.NOTIFICATION_SERVICE);
}
return mManager;
}
public Notification.Builder getAndroidChannelNotification(String title, String body) {
return new Notification.Builder(curActivity.getApplicationContext(), curActivity.getPackageName()+".BDLocation")
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(android.R.drawable.btn_star)
.setAutoCancel(true);
}
private void stopContinuousLocating() {
for (Map.Entry<String, LocationClient> entry : this.mContinuousMap.entrySet()) {
System.out.println("key= " + (String)entry.getKey() + " and value= " + entry.getValue());
if (!PdrUtil.isEmpty(entry.getValue())) {
LocationClient tClient=(LocationClient)entry.getValue();
tClient.disableLocInForeground(true);
tClient.stop();
}
}
}
private void callBack2Front(IWebview mWebview, String mCallbackId, BDLocation location, String CoordsType, boolean isDLGeo, boolean continuous) {
if (!continuous &&
!PdrUtil.isEmpty(this.mSingleTimeMap.get(mCallbackId))) {
((LocationClient)this.mSingleTimeMap.get(mCallbackId)).stop();
}
JSONObject _json = makeJSON(location, CoordsType);
if (_json == null) {
geoDataError(mWebview, mCallbackId, isDLGeo, continuous);
} else {
callback(mWebview, mCallbackId, _json.toString(), JSUtil.OK, true, isDLGeo, continuous);
}
}
public void callback(IWebview webview, String callId, String json, int code, boolean isJson, boolean isDLGeo, boolean continuous) {
if (isDLGeo) {
JSUtil.execGEOCallback(webview, callId, json, code, isJson, continuous);
} else {
JSUtil.execCallback(webview, callId, json, code, isJson, continuous);
}
}
private JSONObject makeJSON(BDLocation pLoc, String coordsType) {
JSONObject json = null;
try {
json = new JSONObject();
json.put("latitude", pLoc.getLatitude());
json.put("longitude", pLoc.getLongitude());
json.put("altitude", pLoc.getAltitude());
json.put("accuracy", pLoc.getRadius());
json.put("altitudeAccuracy", 0);
json.put("heading", pLoc.getDirection());
json.put("velocity", pLoc.getSpeed());
json.put("coordsType", coordsType);
try {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ParsePosition pos = new ParsePosition(0);
Date strtodate = formatter.parse(pLoc.getTime(), pos);
json.put("timestamp", strtodate.getTime());
} catch (Exception e) {
e.printStackTrace();
json.put("timestamp", pLoc.getTime());
}
if (this.isGeocode) {
JSONObject address = new JSONObject();
json.put("address", address);
address.put("country", pLoc.getCountry());
address.put("province", pLoc.getProvince());
address.put("city", pLoc.getCity());
address.put("district", pLoc.getDistrict());
address.put("street", pLoc.getStreet());
address.put("streetNum", pLoc.getStreetNumber());
address.put("poiName", (pLoc.getPoiList() != null && pLoc.getPoiList().size() > 0) ? pLoc.getPoiList().get(0) : null);
address.put("postalCode", null);
address.put("cityCode", pLoc.getCityCode());
json.put("addresses", pLoc.getAddrStr());
}
} catch (JSONException e) {
e.printStackTrace();
}
saveGeoData(pLoc, coordsType);
return json;
}
private String getCoorType(String coorType) {
if (PdrUtil.isEquals(coorType, "bd09ll"))
return "bd09ll";
if (PdrUtil.isEquals(coorType, "bd09")) {
return "bd09";
}
return "gcj02";
}
public void onDestroy() {
for (Map.Entry<String, LocationClient> entry : this.mContinuousMap.entrySet()) {
if (!PdrUtil.isEmpty(entry.getValue())) {
((LocationClient)entry.getValue()).stop();
}
}
this.mContinuousMap.clear();
for (Map.Entry<String, LocationClient> entry : this.mSingleTimeMap.entrySet()) {
if (!PdrUtil.isEmpty(entry.getValue())) {
((LocationClient)entry.getValue()).stop();
}
}
this.mSingleTimeMap.clear();
}
private void saveGeoData(BDLocation pLoc, String coordsType) {
if (!this.isStreamApp) {
JSONObject jsonObject = new JSONObject();
JSONObject coordsJson = new JSONObject();
try {
coordsJson.put("latitude", pLoc.getLatitude());
coordsJson.put("longitude", pLoc.getLongitude());
jsonObject.put("coords", coordsJson);
jsonObject.put("coordsType", coordsType);
if (this.isGeocode) {
jsonObject.put("addresses", pLoc.getAddrStr());
}
SharedPreferences startSp = SP.getOrCreateBundle("start_statistics_data");
SP.setBundleData(startSp, "geo_data", jsonObject.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void geoDataError(IWebview pWebViewImpl, String pCallbackId, boolean isDLGeo, boolean continuous) {
String err = StringUtil.format("{code:%d,message:'%s'}", new Object[] { Integer.valueOf(40), "定位异常"});
if (isDLGeo) {
JSUtil.execGEOCallback(pWebViewImpl, pCallbackId, err, JSUtil.ERROR, true, continuous);
} else {
JSUtil.execCallback(pWebViewImpl, pCallbackId, err, JSUtil.ERROR, true, continuous);
}
}
}
总结:
以上代码只是测试功能,写得简单且不规范。其实都只是加入百度SDK已经实现的功能代码。
经打包安装到手机(目前只在华为Novaz5)测试,退到后台锁屏几十分钟再打开查看记录,一直能连续获取到位置信息。
另外,要将手机管家中对该app去掉“自动管理”,并打开“允许后台活动”开关
最后,强烈建议官方修改watchPosition方法(如加个生成前台服务通知的参数?)!!
收起阅读 »iOS 集成使用 uni小程序SDK 原生功能模块
此文档以过期,请访问 新的文档
uni小程序SDK 提供了丰富的原生能力,如果您在小程序中调用了相关模块的 API 但是原生工程没有添加相关依赖的时候会出现如下提示
按照下面的教程将依赖库添加到原生工程中即可
您可根据需求自行添加功能模块,各功能模块所需的依赖库及资源文件,在
UniMPSDK/Features
目录中
目录结构
|-- UniMPSDK/Features
|-- Feature-iOS.xls // 功能模块配置表
|-- inc // 框架使用的第三库 .h 头文件
|-- Libs // 各功能模块的依赖库存放位置
|-- Resources // 资源文件
请参考 Feature-iOS.xls 配置表,添加模块所需依赖库及资源文件即可;
集成方式
下面以 Gallery 模块为例
首先查看配置文件,需要添加 liblibCamera.a
、AssetsLibrary.framework
依赖库,及 TZImagePickerController.bundle
资源文件
添加依赖库
将 UniMPSDK/Features/Lib 中的 liblibCamera.a
库及AssetsLibrary.framework
系统库,添加到工程的 TARGETS->Build Phases-> Link Binary With Libaries
中;
添加依赖资源文件
然后将 UniMPSDK/Features/Resources 中的TZImagePickerController.bundle
资源文件添加到工程中
然后即可调用 Gallery 相关功能;
功能模块与 API 对应关系
此文档以过期,请访问 新的文档
uni小程序SDK 提供了丰富的原生能力,如果您在小程序中调用了相关模块的 API 但是原生工程没有添加相关依赖的时候会出现如下提示
按照下面的教程将依赖库添加到原生工程中即可
您可根据需求自行添加功能模块,各功能模块所需的依赖库及资源文件,在
UniMPSDK/Features
目录中
目录结构
|-- UniMPSDK/Features
|-- Feature-iOS.xls // 功能模块配置表
|-- inc // 框架使用的第三库 .h 头文件
|-- Libs // 各功能模块的依赖库存放位置
|-- Resources // 资源文件
请参考 Feature-iOS.xls 配置表,添加模块所需依赖库及资源文件即可;
集成方式
下面以 Gallery 模块为例
首先查看配置文件,需要添加 liblibCamera.a
、AssetsLibrary.framework
依赖库,及 TZImagePickerController.bundle
资源文件
添加依赖库
将 UniMPSDK/Features/Lib 中的 liblibCamera.a
库及AssetsLibrary.framework
系统库,添加到工程的 TARGETS->Build Phases-> Link Binary With Libaries
中;
添加依赖资源文件
然后将 UniMPSDK/Features/Resources 中的TZImagePickerController.bundle
资源文件添加到工程中
然后即可调用 Gallery 相关功能;
功能模块与 API 对应关系
收起阅读 »uni小程序SDK 概述
uni小程序SDK原生开发者交流QQ群: 892918401
请移步查看 新的文档,此文档已过期
uni小程序SDK
概述
uni小程序SDK,是一个用于原生App中集成的SDK,它可以帮助原生App快速实现小程序的能力。
效果如下:
基本概念
- 宿主:想要构建小程序的原生App,集成uni小程序SDK的宿主
- 小程序:运行在宿主中的前端代码编写的小程序,使用uni-app框架开发
注意:- uni小程序SDK仅支持使用uni-app开发的小程序,不支持纯wxml微信小程序运行。但uni-app支持使用wxml格式的小程序组件。
- uni小程序SDK仅支持uni-app的v3编译器。使用老版的开发者,需首先保障应用可以运行在v3编译模式下。
集成流程
- 宿主开发者下载uni小程序sdk,根据文档集成到宿主App中
- 小程序开发者使用uni-app框架开发,通过运行到手机App进行测试,开发完毕后在发行菜单导出wgt包
- 宿主开发者将wgt集成到宿主App中,可离线集成直接打包进去,也可以在线下载wgt
- 通过uni小程序sdk的api,可以调起wgt,执行为一个小程序
- uni小程序支持wgt升级,可在线更新wgt应用,即热更新
原生工程集成 uni小程序sdk 文档
特色
uni-app在app端是双渲染引擎,可以像微信那样使用webview渲染,也可以使用改造版的weex进行原生渲染。包括webview渲染在Android上也同时支持系统webview渲染和x5渲染。
- 性能
uni小程序sdk的性能,与uni-app的app端v3编译器相同,性能超过市面其他小程序引擎。在启动速度、页面加载速度、逻辑层与视图层通信优化等方面均优有更优秀的表现。 - 功能
uni小程序sdk的功能,与uni-app的app端功能相同,所有原生能力均可调用。不同于小程序的限制和有限API,uni小程序sdk的功能更加强大。 - 插件生态
uni-app插件市场有大量丰富的插件。
应用场景
- 宿主App构建自己的应用生态,可以面向广泛开发者,也可以定向部分开发者
- 原生App利用小程序模式对模块解耦,让不同模块的开发团队各自独立发版,灵活更新
- 原生App中部分功能使用uni-app实现,降低开发成本、提升发布效率
注意
- 目前uni小程序sdk仅支持同时运行一个小程序实例,启动下一个小程序会关闭上一个小程序
- uni小程序sdk无法使用插件市场中付费的原生插件,需自己开发
FAQ
Q:uni小程序sdk与5+ sdk的差别是什么?
A:解决两种不同的需求场景,前者的使用场景是你已经有原生App,在此基础上扩展宿主App的小程序能力,或者用小程序替换原生App的部分功能模块,仅支持uni-app并使用v3编译器;后者的使用场景是你没有原生App时使用5+ SDK本地离线打包发布为原生App,或者替换你已有的原生App,支持5+ App、wap2app、uni-app等。虽然5+ sdk也可以集成到已有原生App中,但后续会逐步迁移放到uni小程序sdk中。
Q:想了解uni小程序sdk都有哪些js api,和微信小程序相比如何?
A:uni小程序sdk的js api比微信小程序的多,不同于微信小程序的“小”的限制,uni小程序sdk的功能更接近于正常的app,没有包体积限制,并且提供了更多丰富的api,以支持正常app的开发。文档和演示demo见:https://uniapp.dcloud.io/
Q: uni小程序不需要集成分享、支付等第三方的功能。集成到我的Android项目中APK的体积会增加多少?
A: 如果排除视频、地图、分享、支付、登录、直播pusher等功只集成基础模块。占用APK体积大小如下:
cpu型号.so选择 | apk占用大小 |
---|---|
armeabi-v7a | 约7MB左右 |
'armeabi-v7a'、'x86'、'arm64-v8a' | 约16MB左右 |
uni小程序SDK原生开发者交流QQ群: 892918401
请移步查看 新的文档,此文档已过期
uni小程序SDK
概述
uni小程序SDK,是一个用于原生App中集成的SDK,它可以帮助原生App快速实现小程序的能力。
效果如下:
基本概念
- 宿主:想要构建小程序的原生App,集成uni小程序SDK的宿主
- 小程序:运行在宿主中的前端代码编写的小程序,使用uni-app框架开发
注意:- uni小程序SDK仅支持使用uni-app开发的小程序,不支持纯wxml微信小程序运行。但uni-app支持使用wxml格式的小程序组件。
- uni小程序SDK仅支持uni-app的v3编译器。使用老版的开发者,需首先保障应用可以运行在v3编译模式下。
集成流程
- 宿主开发者下载uni小程序sdk,根据文档集成到宿主App中
- 小程序开发者使用uni-app框架开发,通过运行到手机App进行测试,开发完毕后在发行菜单导出wgt包
- 宿主开发者将wgt集成到宿主App中,可离线集成直接打包进去,也可以在线下载wgt
- 通过uni小程序sdk的api,可以调起wgt,执行为一个小程序
- uni小程序支持wgt升级,可在线更新wgt应用,即热更新
原生工程集成 uni小程序sdk 文档
特色
uni-app在app端是双渲染引擎,可以像微信那样使用webview渲染,也可以使用改造版的weex进行原生渲染。包括webview渲染在Android上也同时支持系统webview渲染和x5渲染。
- 性能
uni小程序sdk的性能,与uni-app的app端v3编译器相同,性能超过市面其他小程序引擎。在启动速度、页面加载速度、逻辑层与视图层通信优化等方面均优有更优秀的表现。 - 功能
uni小程序sdk的功能,与uni-app的app端功能相同,所有原生能力均可调用。不同于小程序的限制和有限API,uni小程序sdk的功能更加强大。 - 插件生态
uni-app插件市场有大量丰富的插件。
应用场景
- 宿主App构建自己的应用生态,可以面向广泛开发者,也可以定向部分开发者
- 原生App利用小程序模式对模块解耦,让不同模块的开发团队各自独立发版,灵活更新
- 原生App中部分功能使用uni-app实现,降低开发成本、提升发布效率
注意
- 目前uni小程序sdk仅支持同时运行一个小程序实例,启动下一个小程序会关闭上一个小程序
- uni小程序sdk无法使用插件市场中付费的原生插件,需自己开发
FAQ
Q:uni小程序sdk与5+ sdk的差别是什么?
A:解决两种不同的需求场景,前者的使用场景是你已经有原生App,在此基础上扩展宿主App的小程序能力,或者用小程序替换原生App的部分功能模块,仅支持uni-app并使用v3编译器;后者的使用场景是你没有原生App时使用5+ SDK本地离线打包发布为原生App,或者替换你已有的原生App,支持5+ App、wap2app、uni-app等。虽然5+ sdk也可以集成到已有原生App中,但后续会逐步迁移放到uni小程序sdk中。
Q:想了解uni小程序sdk都有哪些js api,和微信小程序相比如何?
A:uni小程序sdk的js api比微信小程序的多,不同于微信小程序的“小”的限制,uni小程序sdk的功能更接近于正常的app,没有包体积限制,并且提供了更多丰富的api,以支持正常app的开发。文档和演示demo见:https://uniapp.dcloud.io/
Q: uni小程序不需要集成分享、支付等第三方的功能。集成到我的Android项目中APK的体积会增加多少?
A: 如果排除视频、地图、分享、支付、登录、直播pusher等功只集成基础模块。占用APK体积大小如下:
cpu型号.so选择 | apk占用大小 |
---|---|
armeabi-v7a | 约7MB左右 |
'armeabi-v7a'、'x86'、'arm64-v8a' | 约16MB左右 |
iOS 原生工程集成 uni小程序SDK 教程
uni小程序SDK原生开发者交流QQ群: 892918401
请移步 新的文档。此文档以停止更新
开发环境
- iOS开发环境,请使用 Xcode 11 及以上版本;
- 下载 uni小程序SDK;
- 安装开发工具 HBuilderX 版本请与SDK的版本保持一致
集成方法
首先您需要一个 iOS 项目,已有项目或 新建一个 Single View Application
的项目,解压 SDK 包,将目录中的 UniMPSDK 文件夹 copy 到工程目录中, 然后按照以下步骤配置您的项目。
第一步、添加基础依赖库及资源文件
基础依赖库及资源是必须要引入到工程中的,基础依赖库及资源存放在 UniMPSDK/Core
目录中
UniMPSDK/Core 目录结构说明
|-- UniMPSDK/Core
|-- Headers // .h 头文件
|-- Libs // 基础依赖库
|-- Resources // 资源文件
添加基础依赖库
在 Xcode 项目左侧目录选中工程名,在 TARGETS->Build Phases-> Link Binary With Libaries
中点击“+”按钮,在弹出的窗口中点击 Add Other -> Add Files...
,然后打开 UniMPSDK/Core/Libs 基础依赖库目录,选中目录中的 .a 库以及 .framework 库单击 open
按钮将依赖库添加到工程中
添加系统依赖库
接下来需要添加系统依赖库,在 Xcode 项目左侧目录选中工程名,在 TARGETS->Build Phases-> Link Binary With Libaries
中点击“+”按钮,在弹出的窗口中查找并选择所需的库(见下表),单击 “Add” 按钮,将库文件添加到工程中。
依赖的系统库 | ||
---|---|---|
JavaScriptCore.framework | CoreMedia.framework | MediaPlayer.framework |
AVFoundation.framework | AVKit.framework | GLKit.framework |
OpenGLES.framework | CoreText.framework | QuartzCore.framework |
CoreGraphics.framework | libc++.tbd | QuickLook.framework |
CoreTelephony.framework | AssetsLibrary.framework | CoreLocation.framework |
AddressBook.framework |
添加依赖资源文件
接下来需要添加依赖资源文件,建议在项目中新建一个 Group
,来管理资源文件,如示例在工程目录中创建的 UniMP 文件夹,然后按功能模块创建不同的目录存放资源文件;
添加资源文件方法:在左侧目录中选中导入资源文件的位置(示例中是 UniMP/Core),在右键菜单中选择Add Files to “工程名...”,然后打开 UniMPSDK/Core 目录,选择 Resources 文件夹,然后点击“Add”,将资源文件添加到工程中
添加 .h 头文件
在左侧目录中选中导入头文件的位置(示例中是 UniMP/Core),在右键菜单中选择Add Files to “工程名...”,然后打开 UniMPSDK/Core 目录,选择 Headers 文件夹,然后点击“Add”,将头文件资源添加到工程中
配置工程
在 Xcode 项目左侧目录选中工程名,在 TARGETS->Build Settings->Other Linker Flags
中添加 -ObjC
如下图
第二步,生成小程序应用资源
注意!!!! uni小程序仅支持v3模式编译的uni应用!!!
首先在 HBuilderX 中选择您的 uni-app 项目,如果没有请新建一个 uni-app 项目,如下图,创建 uni-app 项目
有一点需要注意,项目的编译模式必须选择 v3 编译器(新建uni-app项目默认是v3编译模式),点击页面中的“详情”可了解更多关于 v3 模式的注意事项,如下图,查看编译模式
然后选中您的项目,右键->发行->原生App-制作应用wgt包
(注:HBuilderX 2.6.2 以下版本选项是 “原生App-制作移动App资源升级包” ,2.6.2 版本近期会发布,只是修改了描述功能是一样的)
然后点击“浏览” 选择wgt包导出路径,点击 “生成wgt”
项目编译完成后会在控制台输出wgt包的路径,点击路径打开 wgt 资源包所在目录
如图,__UNI__11E9B73.wgt
就是应用资源包,(__UNI__11E9B73
为小程序的 appid)
应用wgt资源文件可以选择从云端获取,也可以直接放到工程中使用,为了方便演示,示例工程将应用wgt资源文件添加到工程中使用
接下来将刚刚生成的应用wgt资源文件导入到原生工程中
打开原生工程目录在 UniMP 路径中创建名称为Apps
的文件夹,将之前导出的wgt包拷贝到Apps
文件夹中,如下图
然后在原生工程中左侧目录中选中导资源文件的位置(示例中是 UniMP/),在右键菜单中选择Add Files to “工程名...”,然后打开工程目录,选择 Apps 文件夹,然后点击“Add”,将应用资源包添加到工程中,如下图所示;
第三步、书写代码
首先需要初始化 sdk engine,并设置启动参数,建议在 application:didFinishLaunchingWithOptions
方法中添加
在 AppDelegate.m
中引用头文件 #import "DCUniMP.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 配置参数
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:launchOptions];
// 设置 debug YES 会在控制台输出 js log,默认不输出 log,注:需要引入 liblibLog.a 库
[options setObject:[NSNumber numberWithBool:YES] forKey:@"debug"];
// 初始化引擎
[DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];
return YES;
}
在 AppDelegate.m
App 的生命周期方法中调用 SDK 相关方法
#pragma mark - App 生命周期方法
- (void)applicationDidBecomeActive:(UIApplication *)application {
[DCUniMPSDKEngine applicationDidBecomeActive:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
[DCUniMPSDKEngine applicationWillResignActive:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[DCUniMPSDKEngine applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[DCUniMPSDKEngine applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
[DCUniMPSDKEngine destory];
}
根据项目需求,可以实现以下方法
#pragma mark - 如果需要使用 URL Scheme 或 通用链接相关功能,请实现以下方法
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
// 通过 url scheme 唤起 App
[DCUniMPSDKEngine application:app openURL:url options:options];
return YES;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
// 通过通用链接唤起 App
[DCUniMPSDKEngine application:application continueUserActivity:userActivity];
return YES;
}
#pragma mark - 如需使用远程推送相关功能,请实现以下方法
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// 远程通知注册成功,收到 deviceToken 调用sdk方法,传入 deviceToken
[DCUniMPSDKEngine application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
// 远程通知注册失败
[DCUniMPEngine application:application didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// 收到远程推送消息
[DCUniMPSDKEngine application:application didReceiveRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
#pragma mark - 如需使用本地推送通知功能,请实现以下方法
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
// 收到本地推送消息
[DCUniMPSDKEngine application:application didReceiveLocalNotification:notification];
}
在您需要打开小程序的文件中添加以下逻辑(参考示例工程 ViewController.m )
首先需要引用头文件
#import "DCUniMP.h"
添加代理协议 DCUniMPEngineDelegate
@interface ViewController () <DCUniMPSDKEngineDelegate>
@end
小程序应用资源必须部署到指定的沙盒路径中才可以正常运行,请参考下面的方法
/// 检查运行目录是否存在应用资源,不存在将应用资源部署到运行目录
- (void)checkUniMPResource {
if (![DCUniMPSDKEngine isExistsApp:k_AppId]) {
// 读取导入到工程中的wgt应用资源
NSString *appResourcePath = [[NSBundle mainBundle] pathForResource:k_AppId ofType:@"wgt"];
// 将应用资源部署到运行路径中
if ([DCUniMPSDKEngine releaseAppResourceToRunPathWithAppid:k_AppId resourceFilePath:appResourcePath]) {
NSLog(@"应用资源文件部署成功");
}
}
}
打开小程序应用
/// 打开 App
- (IBAction)openUniMP:(id)sender {
// 配置胶囊按钮菜单 ActionSheet 全局项(点击胶囊按钮 ··· ActionSheet弹窗中的项)
DCUniMPMenuActionSheetItem *item1 = [[DCUniMPMenuActionSheetItem alloc] initWithTitle:@"Item 1" identifier:@"item1"];
DCUniMPMenuActionSheetItem *item2 = [[DCUniMPMenuActionSheetItem alloc] initWithTitle:@"Item 2" identifier:@"item2"];
// 添加到全局配置
[DCUniMPSDKEngine setDefaultMenuItems:@[item1,item2]];
// 设置 delegate
[DCUniMPSDKEngine setDelegate:self];
// 启动 uni小程序,(参数可以在小程序中通过 plus.runtime.arguments 获取此参数)
NSDictionary *arguments = @{ @"value":@"Hello uni microprogram" };
[DCUniMPSDKEngine openApp:k_AppId
arguments:arguments];
}
实现代理方法
#pragma mark - DCUniMPSDKEngineDelegate
/// DCUniMPMenuActionSheetItem 点击触发回调方法
- (void)defaultMenuItemClicked:(NSString *)identifier {
NSLog(@"标识为 %@ 的 item 被点击了", identifier);
}
/// 返回打开小程序时的自定义闪屏视图(此视图会以屏幕大小展示)
- (UIView *)splashViewForApp:(NSString *)appid {
UIView *splashView = [[[NSBundle mainBundle] loadNibNamed:@"SplashView" owner:self options:nil] lastObject];
return splashView;
}
至此代码部分已完成,可以运行查看效果
应用资源管理
uni小程序的应用资源集成方式
开发者自行下载或其他方式获取到 uni小程序 wgt 应用资源包后。通过 DCUniMPSDKEngine 类的releaseAppResourceToRunPathWithAppid:resourceFilePath:
方法传入wgt资源路径即可将wgt资源部署到运行路径。然后通过openApp:arguments:
运行uni小程序应用。
uni小程序应用资源升级
需要您将新的wgt资源部署到应用运行路径,通过 DCUniMPSDKEngine 类的releaseAppResourceToRunPathWithAppid:resourceFilePath:
方法传入wgt资源路径即可将wgt资源部署到运行路径,直接替换原有应用资源。暂时不支持应用版本号提供。待补充!
uni小程序应用删除
可通过 DCUniMPSDKEngine 类的 getAppRunPathWithAppid:
方法获取应用运行路径,删除应用资源即可;
集成其他原生功能模块
如果您想使用 SDK 现有的原生功能模块例如 推送、地图、音视频等,请参考文档 集成其他原生功能模块
扩展原生功能模块
如果您想扩展原生功能模块,请参考文档 扩展原生功能模块
扩展官方原生功能
如果您想在官方的原生模块基础上扩展能力,请参考文档 [扩展官方原生功能]() 文档待补充
常见问题
集成 sdk 常见问题请参考文档 [集成 uni小程序SDK 常见问题]() 文档待补充
uni小程序SDK原生开发者交流QQ群: 892918401
请移步 新的文档。此文档以停止更新
开发环境
- iOS开发环境,请使用 Xcode 11 及以上版本;
- 下载 uni小程序SDK;
- 安装开发工具 HBuilderX 版本请与SDK的版本保持一致
集成方法
首先您需要一个 iOS 项目,已有项目或 新建一个 Single View Application
的项目,解压 SDK 包,将目录中的 UniMPSDK 文件夹 copy 到工程目录中, 然后按照以下步骤配置您的项目。
第一步、添加基础依赖库及资源文件
基础依赖库及资源是必须要引入到工程中的,基础依赖库及资源存放在 UniMPSDK/Core
目录中
UniMPSDK/Core 目录结构说明
|-- UniMPSDK/Core
|-- Headers // .h 头文件
|-- Libs // 基础依赖库
|-- Resources // 资源文件
添加基础依赖库
在 Xcode 项目左侧目录选中工程名,在 TARGETS->Build Phases-> Link Binary With Libaries
中点击“+”按钮,在弹出的窗口中点击 Add Other -> Add Files...
,然后打开 UniMPSDK/Core/Libs 基础依赖库目录,选中目录中的 .a 库以及 .framework 库单击 open
按钮将依赖库添加到工程中
添加系统依赖库
接下来需要添加系统依赖库,在 Xcode 项目左侧目录选中工程名,在 TARGETS->Build Phases-> Link Binary With Libaries
中点击“+”按钮,在弹出的窗口中查找并选择所需的库(见下表),单击 “Add” 按钮,将库文件添加到工程中。
依赖的系统库 | ||
---|---|---|
JavaScriptCore.framework | CoreMedia.framework | MediaPlayer.framework |
AVFoundation.framework | AVKit.framework | GLKit.framework |
OpenGLES.framework | CoreText.framework | QuartzCore.framework |
CoreGraphics.framework | libc++.tbd | QuickLook.framework |
CoreTelephony.framework | AssetsLibrary.framework | CoreLocation.framework |
AddressBook.framework |
添加依赖资源文件
接下来需要添加依赖资源文件,建议在项目中新建一个 Group
,来管理资源文件,如示例在工程目录中创建的 UniMP 文件夹,然后按功能模块创建不同的目录存放资源文件;
添加资源文件方法:在左侧目录中选中导入资源文件的位置(示例中是 UniMP/Core),在右键菜单中选择Add Files to “工程名...”,然后打开 UniMPSDK/Core 目录,选择 Resources 文件夹,然后点击“Add”,将资源文件添加到工程中
添加 .h 头文件
在左侧目录中选中导入头文件的位置(示例中是 UniMP/Core),在右键菜单中选择Add Files to “工程名...”,然后打开 UniMPSDK/Core 目录,选择 Headers 文件夹,然后点击“Add”,将头文件资源添加到工程中
配置工程
在 Xcode 项目左侧目录选中工程名,在 TARGETS->Build Settings->Other Linker Flags
中添加 -ObjC
如下图
第二步,生成小程序应用资源
注意!!!! uni小程序仅支持v3模式编译的uni应用!!!
首先在 HBuilderX 中选择您的 uni-app 项目,如果没有请新建一个 uni-app 项目,如下图,创建 uni-app 项目
有一点需要注意,项目的编译模式必须选择 v3 编译器(新建uni-app项目默认是v3编译模式),点击页面中的“详情”可了解更多关于 v3 模式的注意事项,如下图,查看编译模式
然后选中您的项目,右键->发行->原生App-制作应用wgt包
(注:HBuilderX 2.6.2 以下版本选项是 “原生App-制作移动App资源升级包” ,2.6.2 版本近期会发布,只是修改了描述功能是一样的)
然后点击“浏览” 选择wgt包导出路径,点击 “生成wgt”
项目编译完成后会在控制台输出wgt包的路径,点击路径打开 wgt 资源包所在目录
如图,__UNI__11E9B73.wgt
就是应用资源包,(__UNI__11E9B73
为小程序的 appid)
应用wgt资源文件可以选择从云端获取,也可以直接放到工程中使用,为了方便演示,示例工程将应用wgt资源文件添加到工程中使用
接下来将刚刚生成的应用wgt资源文件导入到原生工程中
打开原生工程目录在 UniMP 路径中创建名称为Apps
的文件夹,将之前导出的wgt包拷贝到Apps
文件夹中,如下图
然后在原生工程中左侧目录中选中导资源文件的位置(示例中是 UniMP/),在右键菜单中选择Add Files to “工程名...”,然后打开工程目录,选择 Apps 文件夹,然后点击“Add”,将应用资源包添加到工程中,如下图所示;
第三步、书写代码
首先需要初始化 sdk engine,并设置启动参数,建议在 application:didFinishLaunchingWithOptions
方法中添加
在 AppDelegate.m
中引用头文件 #import "DCUniMP.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// 配置参数
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:launchOptions];
// 设置 debug YES 会在控制台输出 js log,默认不输出 log,注:需要引入 liblibLog.a 库
[options setObject:[NSNumber numberWithBool:YES] forKey:@"debug"];
// 初始化引擎
[DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];
return YES;
}
在 AppDelegate.m
App 的生命周期方法中调用 SDK 相关方法
#pragma mark - App 生命周期方法
- (void)applicationDidBecomeActive:(UIApplication *)application {
[DCUniMPSDKEngine applicationDidBecomeActive:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
[DCUniMPSDKEngine applicationWillResignActive:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[DCUniMPSDKEngine applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[DCUniMPSDKEngine applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
[DCUniMPSDKEngine destory];
}
根据项目需求,可以实现以下方法
#pragma mark - 如果需要使用 URL Scheme 或 通用链接相关功能,请实现以下方法
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
// 通过 url scheme 唤起 App
[DCUniMPSDKEngine application:app openURL:url options:options];
return YES;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
// 通过通用链接唤起 App
[DCUniMPSDKEngine application:application continueUserActivity:userActivity];
return YES;
}
#pragma mark - 如需使用远程推送相关功能,请实现以下方法
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// 远程通知注册成功,收到 deviceToken 调用sdk方法,传入 deviceToken
[DCUniMPSDKEngine application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
// 远程通知注册失败
[DCUniMPEngine application:application didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// 收到远程推送消息
[DCUniMPSDKEngine application:application didReceiveRemoteNotification:userInfo];
completionHandler(UIBackgroundFetchResultNewData);
}
#pragma mark - 如需使用本地推送通知功能,请实现以下方法
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
// 收到本地推送消息
[DCUniMPSDKEngine application:application didReceiveLocalNotification:notification];
}
在您需要打开小程序的文件中添加以下逻辑(参考示例工程 ViewController.m )
首先需要引用头文件
#import "DCUniMP.h"
添加代理协议 DCUniMPEngineDelegate
@interface ViewController () <DCUniMPSDKEngineDelegate>
@end
小程序应用资源必须部署到指定的沙盒路径中才可以正常运行,请参考下面的方法
/// 检查运行目录是否存在应用资源,不存在将应用资源部署到运行目录
- (void)checkUniMPResource {
if (![DCUniMPSDKEngine isExistsApp:k_AppId]) {
// 读取导入到工程中的wgt应用资源
NSString *appResourcePath = [[NSBundle mainBundle] pathForResource:k_AppId ofType:@"wgt"];
// 将应用资源部署到运行路径中
if ([DCUniMPSDKEngine releaseAppResourceToRunPathWithAppid:k_AppId resourceFilePath:appResourcePath]) {
NSLog(@"应用资源文件部署成功");
}
}
}
打开小程序应用
/// 打开 App
- (IBAction)openUniMP:(id)sender {
// 配置胶囊按钮菜单 ActionSheet 全局项(点击胶囊按钮 ··· ActionSheet弹窗中的项)
DCUniMPMenuActionSheetItem *item1 = [[DCUniMPMenuActionSheetItem alloc] initWithTitle:@"Item 1" identifier:@"item1"];
DCUniMPMenuActionSheetItem *item2 = [[DCUniMPMenuActionSheetItem alloc] initWithTitle:@"Item 2" identifier:@"item2"];
// 添加到全局配置
[DCUniMPSDKEngine setDefaultMenuItems:@[item1,item2]];
// 设置 delegate
[DCUniMPSDKEngine setDelegate:self];
// 启动 uni小程序,(参数可以在小程序中通过 plus.runtime.arguments 获取此参数)
NSDictionary *arguments = @{ @"value":@"Hello uni microprogram" };
[DCUniMPSDKEngine openApp:k_AppId
arguments:arguments];
}
实现代理方法
#pragma mark - DCUniMPSDKEngineDelegate
/// DCUniMPMenuActionSheetItem 点击触发回调方法
- (void)defaultMenuItemClicked:(NSString *)identifier {
NSLog(@"标识为 %@ 的 item 被点击了", identifier);
}
/// 返回打开小程序时的自定义闪屏视图(此视图会以屏幕大小展示)
- (UIView *)splashViewForApp:(NSString *)appid {
UIView *splashView = [[[NSBundle mainBundle] loadNibNamed:@"SplashView" owner:self options:nil] lastObject];
return splashView;
}
至此代码部分已完成,可以运行查看效果
应用资源管理
uni小程序的应用资源集成方式
开发者自行下载或其他方式获取到 uni小程序 wgt 应用资源包后。通过 DCUniMPSDKEngine 类的releaseAppResourceToRunPathWithAppid:resourceFilePath:
方法传入wgt资源路径即可将wgt资源部署到运行路径。然后通过openApp:arguments:
运行uni小程序应用。
uni小程序应用资源升级
需要您将新的wgt资源部署到应用运行路径,通过 DCUniMPSDKEngine 类的releaseAppResourceToRunPathWithAppid:resourceFilePath:
方法传入wgt资源路径即可将wgt资源部署到运行路径,直接替换原有应用资源。暂时不支持应用版本号提供。待补充!
uni小程序应用删除
可通过 DCUniMPSDKEngine 类的 getAppRunPathWithAppid:
方法获取应用运行路径,删除应用资源即可;
集成其他原生功能模块
如果您想使用 SDK 现有的原生功能模块例如 推送、地图、音视频等,请参考文档 集成其他原生功能模块
扩展原生功能模块
如果您想扩展原生功能模块,请参考文档 扩展原生功能模块
扩展官方原生功能
如果您想在官方的原生模块基础上扩展能力,请参考文档 [扩展官方原生功能]() 文档待补充
常见问题
集成 sdk 常见问题请参考文档 [集成 uni小程序SDK 常见问题]() 文档待补充
收起阅读 »uni小程序SDK 更新日志
iOS 平台 uni小程序SDK 更新日志
请移步 新的文档,此文档已停止更新
2020年04月21日发布
点击下载SDK UniMP_iOS_SDK@2.6.15.20200421
- 需使用HBuilderX(2.6.15)版本生成 小程序App 资源
- 其他更新 详情
iOS 小程序SDK 历史更新记录
2020年04月19日发布
点击下载SDK UniMP_iOS_SDK@2.6.14.20200419
- 需使用HBuilderX(2.6.14)版本生成 小程序App 资源
- iOS平台 补齐 wgt 编译版本与js框架版本校验,不一致会弹窗提示 详情
- iOS平台 修复 小程序内 wgt 热更新资源后启动会显示一下 LaunchScreen.storyboard 页面的Bug
- 其他更新 详情
2020年04月15日发布
点击下载SDK UniMP_iOS_SDK@2.6.13.20200414
- 需使用HBuilderX(2.6.13)版本生成 小程序App 资源。
- 更新 详情
2020年04月13日发布
点击下载SDK UniMP_iOS_SDK@2.6.12.20200412
- 需使用HBuilderX(2.6.12)版本生成 小程序App 资源。
- 更新 详情
2020年04月09日发布
点击下载SDK UniMP_iOS_SDK@2.6.11.20200409
- 需使用HBuilderX(2.6.11)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 其他更新 详情
2020年04月03日发布
点击下载SDK UniMP_iOS_SDK@2.6.10.20200403
- 需使用HBuilderX(2.6.10)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 新增 宿主与小程序通讯机制 详情
- 修复 在监听小程序被关闭的方法中紧接着在打开小程序可能会崩溃的Bug
- 修复 uni.chooseImage 引起内存泄露的Bug
- 其他更新 详情
2020年04月03日发布
点击下载SDK UniMP_iOS_SDK@2.6.9.20200403
- 需使用HBuilderX(2.6.9)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 其他更新详情
2020年03月30日发布
点击下载SDK UniMP_iOS_SDK@2.6.8.20200330
- 需使用HBuilderX(2.6.8)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 其他更新详情
2020年03月19日发布
点击下载SDK UniMP_iOS_SDK@2.6.6.20200319
- 需使用HBuilderX(2.6.6)版本生成 小程序App 资源。
- 新增 获取当前显示小程序页面直达Url方法(用于启动直达二级页面)iOS、Android
- 新增 获取已部署的小程序资源版本信息方法 iOS、Android
- 新增 胶囊按钮添加点击效果
- 开放 小程序内部调用 plus.runtime.install 热更新wgt资源 详情
- 修复 原生工程勾选 'Hide status bar' 导致小程序页面导航栏被系统状态栏挡住的Bug
- 修复 pickDate、pickTime 无法显示的Bug
- 基础库移除对 StoreKit.framework 的依赖
2020年03月10日发布
点击下载SDK UniMP_iOS_SDK@2.6.4.20200310
- 需使用HBuilderX(2.6.4 alpha版,或 2.6.5 正式版)生成 小程序App 资源。
- 更新 uni-jsframework 框架;
2020年03月05日发布
点击下载SDK UniMP_iOS_SDK@2.6.3.20200305
- 需使用HBuilderX(2.6.3)版本生成 小程序App 资源。
- 新增 启动小程序支持传入参数及直达指定页面 详情
- 新增 关闭当前小程序方法及小程序关闭回调方法 详情
- 新增 获取当前运行的小程序appid方法
2020年02月25日发布
点击下载SDK UniMP_iOS_SDK@2.6.1.20200225
- 需使用HBuilderX(2.6.1)版本生成 小程序App 资源。
- 修改集成小程序资源为 wgt 包,详情请查看集成文档关于生成小程序应用资源说明;
2020年02月12日发布
点击下载SDK UniMP_iOS_SDK@2.5.11.20200212
- 需使用HBuilderX(2.5.11.20200212)版本生成 小程序App 资源。
- 修复反复打开关闭小程序导致内存不断增加的Bug;
2020年02月05日发布
点击下载SDK UniMP_iOS_SDK@2.5.10.20200205
- 需使用HBuilderX(2.5.10.20200205)版本生成 小程序App 资源。
Android 平台 uni小程序SDK 更新日志
2020年04月21日发布
点击下载SDK UniMP_ANDROID_SDK@2.6.15.20200421.zip
- 需使用HBuilderX(2.6.15)版本生成 小程序App 资源。
- 其他更新 详情
Android 小程序SDK 历史更新记录
历史版本
链接: https://pan.baidu.com/s/1Gb19IMm2ihRA0u4MNzCT4Q 提取码: hnug
uni小程序sdk可以免费下载使用,但源码公开策略如下:
iOS 平台 uni小程序SDK 更新日志
请移步 新的文档,此文档已停止更新
2020年04月21日发布
点击下载SDK UniMP_iOS_SDK@2.6.15.20200421
- 需使用HBuilderX(2.6.15)版本生成 小程序App 资源
- 其他更新 详情
iOS 小程序SDK 历史更新记录
2020年04月19日发布
点击下载SDK UniMP_iOS_SDK@2.6.14.20200419
- 需使用HBuilderX(2.6.14)版本生成 小程序App 资源
- iOS平台 补齐 wgt 编译版本与js框架版本校验,不一致会弹窗提示 详情
- iOS平台 修复 小程序内 wgt 热更新资源后启动会显示一下 LaunchScreen.storyboard 页面的Bug
- 其他更新 详情
2020年04月15日发布
点击下载SDK UniMP_iOS_SDK@2.6.13.20200414
- 需使用HBuilderX(2.6.13)版本生成 小程序App 资源。
- 更新 详情
2020年04月13日发布
点击下载SDK UniMP_iOS_SDK@2.6.12.20200412
- 需使用HBuilderX(2.6.12)版本生成 小程序App 资源。
- 更新 详情
2020年04月09日发布
点击下载SDK UniMP_iOS_SDK@2.6.11.20200409
- 需使用HBuilderX(2.6.11)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 其他更新 详情
2020年04月03日发布
点击下载SDK UniMP_iOS_SDK@2.6.10.20200403
- 需使用HBuilderX(2.6.10)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 新增 宿主与小程序通讯机制 详情
- 修复 在监听小程序被关闭的方法中紧接着在打开小程序可能会崩溃的Bug
- 修复 uni.chooseImage 引起内存泄露的Bug
- 其他更新 详情
2020年04月03日发布
点击下载SDK UniMP_iOS_SDK@2.6.9.20200403
- 需使用HBuilderX(2.6.9)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 其他更新详情
2020年03月30日发布
点击下载SDK UniMP_iOS_SDK@2.6.8.20200330
- 需使用HBuilderX(2.6.8)版本生成 小程序App 资源。
- 更新 uni-jsframework 框架;
- 其他更新详情
2020年03月19日发布
点击下载SDK UniMP_iOS_SDK@2.6.6.20200319
- 需使用HBuilderX(2.6.6)版本生成 小程序App 资源。
- 新增 获取当前显示小程序页面直达Url方法(用于启动直达二级页面)iOS、Android
- 新增 获取已部署的小程序资源版本信息方法 iOS、Android
- 新增 胶囊按钮添加点击效果
- 开放 小程序内部调用 plus.runtime.install 热更新wgt资源 详情
- 修复 原生工程勾选 'Hide status bar' 导致小程序页面导航栏被系统状态栏挡住的Bug
- 修复 pickDate、pickTime 无法显示的Bug
- 基础库移除对 StoreKit.framework 的依赖
2020年03月10日发布
点击下载SDK UniMP_iOS_SDK@2.6.4.20200310
- 需使用HBuilderX(2.6.4 alpha版,或 2.6.5 正式版)生成 小程序App 资源。
- 更新 uni-jsframework 框架;
2020年03月05日发布
点击下载SDK UniMP_iOS_SDK@2.6.3.20200305
- 需使用HBuilderX(2.6.3)版本生成 小程序App 资源。
- 新增 启动小程序支持传入参数及直达指定页面 详情
- 新增 关闭当前小程序方法及小程序关闭回调方法 详情
- 新增 获取当前运行的小程序appid方法
2020年02月25日发布
点击下载SDK UniMP_iOS_SDK@2.6.1.20200225
- 需使用HBuilderX(2.6.1)版本生成 小程序App 资源。
- 修改集成小程序资源为 wgt 包,详情请查看集成文档关于生成小程序应用资源说明;
2020年02月12日发布
点击下载SDK UniMP_iOS_SDK@2.5.11.20200212
- 需使用HBuilderX(2.5.11.20200212)版本生成 小程序App 资源。
- 修复反复打开关闭小程序导致内存不断增加的Bug;
2020年02月05日发布
点击下载SDK UniMP_iOS_SDK@2.5.10.20200205
- 需使用HBuilderX(2.5.10.20200205)版本生成 小程序App 资源。
Android 平台 uni小程序SDK 更新日志
2020年04月21日发布
点击下载SDK UniMP_ANDROID_SDK@2.6.15.20200421.zip
- 需使用HBuilderX(2.6.15)版本生成 小程序App 资源。
- 其他更新 详情
Android 小程序SDK 历史更新记录
历史版本
链接: https://pan.baidu.com/s/1Gb19IMm2ihRA0u4MNzCT4Q 提取码: hnug
uni小程序sdk可以免费下载使用,但源码公开策略如下:
收起阅读 »Android平台隐私与政策提示框配置方法
此文档将不再维护,请参考新文档:https://uniapp.dcloud.io/tutorial/app-privacy-android
HBuilderX2.6.3+版本开始支持配置隐私政策提示框
HBuilderX3.1.10+版本优化template模式策略,解决应用市场检测到弹出隐私政策提示框之前读取mac地址和应用列表的问题
HBuilderX3.2.1+版本开始支持androidPrivacy.json文件配置隐私政策提示框,真机运行时也可生效
HBuilderX3.2.5+版本隐私政策提示框内容中的链接支持本地 html 页面地址
注意:目前设置custom模式策略,并不能完全避免在弹出隐私提示框之前,读取设备信息(如mac地址、应用列表等)的情况,主要原因是自定义模式隐私提示框并不能阻塞应用的生命周期,使用到一些三方SDK(如X5 Webview内核、UniPush等)在应用启动时会执行初始化操作,三方SDK这是可能会读取设备信息。碰到此问题的开发者请先使用template模式解决,我们后续会提供新的自定义隐私提示框样式解决方案。
uni小程序SDK暂时不支持uniapp自己配置隐私弹窗,需要宿主自行实现隐私弹窗
请使用HBuilderX3.2.15
+ 版本打包,并使用template配置隐私弹窗否则无法正常上架应用市场
概述
根据工业和信息化部关于开展APP侵害用户权益专项整治要求,App提交到应用市场必须满足以下条件:
- 应用启动运行时需弹出隐私政策协议,说明应用采集用户数据
这里将详细介绍如何配置弹出“隐私协议和政策”提示框 - 应用不能强制要求用户授予权限,即不能“不给权限不让用”
如不希望应用启动时申请“读写手机存储”和“访问设备信息”权限,请参考:https://ask.dcloud.net.cn/article/36549
为了兼顾隐私政策提示框的易用性和灵活性,解决弹出隐私政策提示框之前可能弹出系统授权框的问题。Android平台提供了以下隐私政策提示配置策略:
- template
使用原生提供的隐私政策模板提示框,应用启动时在splash界面弹出。- 优点:在系统授权提示框之前显示,用户点击确认后才会进入应用
- 缺点:只能配置提示文本及链接地址,无法自定义提示框样式
- none
不处理隐私政策
不提交到应用市场时使用
<a id='collect'></a>
DCloud数据采集说明
为了持续优化应用及提供统计报表功能,在运行过程中会采集应用启动时间、异常错误日志等数据,其中包含设备唯一识别码。
DCloud通过了国家信息安全等级保护三级,证书编号:11010813802-20001,保障相关数据的安全性
DCloud并非大数据公司,采集的数据是为开发者提供统计服务和产品持续优化,不包含个人隐私信息
请在《隐私政策》中必告知用户您的应用基于DCloud uni-app(5+ App/Wap2App)开发,增加如下参考条款
我们的产品基于DCloud uni-app(5+ App/Wap2App)开发,应用运行期间需要收集您的设备唯一识别码(IMEI/android ID/DEVICE_ID/IDFA、SIM 卡 IMSI 信息、OAID)以提供统计分析服务,并通过应用启动数据及异常错误日志分析改进性能和用户体验,为用户提供更好的服务。详情内容请访问《DCloud用户服务条款》。(DCloud用户服务条款超链至:https://ask.dcloud.net.cn/protocol.html)
配置方式
HBuilderX3.2.1及以上版本配置方式
从HBuilderX3.2.1+版本开始新增androidPrivacy.json文件配置隐私政策提示框,支持真机运行查看效果,在androidPrivacy.json中也支持配置部分样式(如背景颜色、标题颜色、按钮颜色等)。
打开项目的manifest.json文件,切换到“App启动界面配置”,在“Android启动界面样式”中勾选“使用原生隐私政策提示框”
注意!androidPrivacy.json不要添加注释,会影响隐私政策提示框的显示!!!
勾选后会在项目中自动添加androidPrivacy.json文件,可以双击打开自定义配置以下内容:
{
"version": "1",
"prompt": "template",
"title": "服务协议和隐私政策",
"message": " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意并接受",
"buttonRefuse": "暂不同意",
// HX 3.4.13之后版本新增,system 使用系统webview 打开隐私协议链接,默认使用uni-app内置web组件
"hrefLoader":“system|default”
"second": {
"title": "确认提示",
"message": " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。",
"buttonAccept": "同意并继续",
"buttonRefuse": "退出应用"
},
"styles": {
"backgroundColor": "#00FF00",
"borderRadius":"5px",
"title": {
"color": "#ff00ff"
},
"buttonAccept": {
"color": "#ffff00"
},
"buttonRefuse": {
"color": "#00ffff"
}
}
}
- version
隐私政策版本号,如果应用升级后希望重新弹出隐私政策提示框,则需要设置新版本 - prompt
是否使用原生隐私政策提示框,值为“template”表示使用,“none”表示不使用 - title
隐私政策提示框标题文本内容 - message
隐私政策提示框正文内容,支持富文本richtext类型字符串,支持a/font/br等节点,点击a链接会调用内置页面打开其href属性中链接地址。
注意:务必配置此提示内容,参考上面示例内容并修改《服务协议》和《隐私政策》链接地址 - buttonAccept
模板提示框上接受按钮的文本,默认值为“同意” - buttonRefuse
模板提示框上拒绝按钮的文本,默认不显示此按钮 - hrefLoader
HX 3.4.13之后版本新增,system 使用系统webview 打开隐私协议链接,默认 default 使用uni-app内置web组件 - second
配置二次确认提示框显示内容,message属性值不为空时弹出二次确认提示框- title 二次确认提示框上的标题
- message 二次确认提示框上的内容,支持富文本richtext类型字符串
- buttonAccept 二次确认提示框上接受按钮的文本
- buttonRefuse 二次确认提示框上拒绝按钮的文本
- styles
配置隐私政策提示框样式- backgroundColor 提示框背景颜色,#RRGGBB格式字符串
- borderRadius 提示框背景圆角半径,单位为px(逻辑像素)
- title 提示框标题样式,其下仅支持color属性配置文本颜色,值为#RRGGBB格式字符串
- buttonAccept 接受按钮样式,其下仅支持color属性配置文本颜色,值为#RRGGBB格式字符串
- buttonRefuse 拒绝按钮样式,其下仅支持color属性配置文本颜色,值为#RRGGBB格式字符串
- disagreeMode
未同意隐私政策模式HBuilder X 3.3.1版本新增支持
具体配置及说明查看https://uniapp.dcloud.io/app-disagreemode- support true表示开启disagreeMode;false表示不开启(用户不同意“隐私政策”则退出应用)。默认值为false。
- loadNativePlugins 表示在disagreeMode模式是否加载uni原生插件,true表示加载;false表示不加载(此时调用uni.requireNativePlugin加载插件扩展Module返回undefined,扩展组件Component也无法使用)。默认值为true。
uni-app项目可以使用uni原生插件能支持更多自定义隐私政策提示框样式,可参考:https://ext.dcloud.net.cn/plugin?id=5581
HBuilderX3.2.0及以下版本配置方法
打开项目的manifest.json文件,切换到“源码视图”项
- uni-app项目
在 "app-plus" -> "privacy" 节点下添加 prompt节点 - 5+ App项目
在 "plus" -> "privacy" 节点下添加 prompt节点
privacy节点数据格式如下:
"privacy": {
"prompt": "template", //可取值template、none
"template": { //prompt取值为template时有效,用于配置模板提示框上显示的内容
}
}
- prompt
字符串类型,必填,隐私政策提示框配置策略,可取值template,none,默认值为none- template
使用原生提示框模板,可自定义标题、内容已经按钮上的文本 - none
不弹出隐私政策提示框
- template
- template
json格式,可选,模板提示框上显示的内容
模板提示框
配置promt属性值为template时,表示使用原生模板隐私政策提示框,效果如下:
应用启动前,在Splash页面时显示此提示框,用户点击同意按钮后才会进入应用
可使用以下配置模板提示框内容
"privacy": {
"prompt": "template",
"template": {
"title": "服务协议和隐私政策",
"message": " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意",
"buttonRefuse": "暂不同意",
"second": {
"title": "温馨提示",
"message": " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。",
"buttonAccept": "同意并继续",
"buttonRefuse": "退出应用",
}
}
}
- title
模板提示框上的标题,默认为“服务协议和隐私政策” - message
模板提示框上的内容,richtext类型字符串,支持a/font/br等节点,点击a链接会调用内置页面打开其href属性中链接地址。- HBuilderX3.2.5以下版本a链接的href属性仅支持网络地址,以http:或https:开头,如“https://www.dcloud.io/privacy.html”
- HBuilder3.2.5及以上版本a链接的href属性支持本地地址,相对于应用根目录,如“static/privacy.html”
注意:务必配置此提示内容,参考上面示例内容并修改《服务协议》和《隐私政策》链接地址
- buttonAccept
模板提示框上接受按钮的文本,默认值为“同意”,
注意:接受按钮设置为“我知道了”在部分应用市场上架时会审核不通过。 - buttonRefuse
模板提示框上拒绝按钮的文本,默认不显示此按钮 - second
HBuilderX3.1.12+版本新增支持隐私提示框二次确认提示,用于配置二次确认提示框显示内容,message属性值不为空时弹出二次确认提示框- title 二次确认提示框上的标题
- message 二次确认提示框上的内容,支持richtext类型字符串
- buttonAccept 二次确认提示框上接受按钮的文本
- buttonRefuse 二次确认提示框上拒绝按钮的文本
配置后提交云端打包后生效
提供5+ API设置/获取状态:
- 获取是否同意隐私政策
参考规范:plus.runtime.isAgreePrivacy
应用启动时调用此API查下状态,如果用户未同意则弹出自定义隐私政策提示框。if(!plus.runtime.isAgreePrivacy()){ //弹出自定义隐私政策提示框 }
- 设置为同意隐私政策
参考规范:plus.runtime.agreePrivacy
在自定义隐私政策提示界面用,用户点击“同意”按钮时需调用此API设置状态 - 设置为不同意隐私政策
参考规范:plus.runtime.disagreePrivacy
在自定义隐私政策提示界面用,用户点击“不同意”按钮时需调用此API设置状态
注意:用户同意隐私政策前不要调用可能弹出系统授权框的API,如定位(plus.geolocation)、录音(plus.audio.getRecorder)等
无提示框
配置prompt属性值为none时,表示不显示隐私政策提示框。
如果不提交到应用市场,可以使用此模式。
离线打包配置方式
HBuilderX3.2.1+版本新增androidPrivacy.json配置隐私政策提示框,优先级高于原生环境配置,新版本没有必要再在原生工程中配置
在原生工程中应用的AndroidManifest.xml中配置隐私提供框模式,在application节点下添加meta-data节点数据,如下:
<application>
<meta-data
android:name="DCLOUD_PRIVACY_PROMPT" android:value="template"/>
</application>
android:value可取值为"template"、"none"。
配置使用"template"模板提示框时,需要按以下方法配置提示框内容
在原生工程的res/values目录下的strings.xml配置默认语言内容(为了兼容用户设置不同语言的清空,此文件必须配置)。中文还需要创建res/values-zh目录下的strings.xml文件并配置中文内容。
模板提示框
请在原生工程的strings.xml中添加以下字段配置模板提示框内容。
<resources>
<string name="dcloud_privacy_prompt_title">弹窗标题</string>
<string name="dcloud_privacy_prompt_accept_button_text">接收按钮文字配置字段(不存在该字段,即使用默认内容“同意”)</string>
<string name="dcloud_privacy_prompt_refuse_button_text">拒绝按钮文字配置字段(没有该字段或该字段内容为空,拒绝按钮不显示)</string>
<string name="dcloud_privacy_prompt_message"><Data><![CDATA[弹窗内容,如果内容中有富文本,请将内容放入cdata下,如当前配置]]></Data></string>
</resources>
二次确认提示框
HBuilderX3.1.12+版本新增支持模板隐私提示框二次确认功能,点击隐私模板提示框时用户选择“拒绝”按钮并且二次确认提示框内容dcloud_second_privacy_prompt_message配置不为空时会弹出二次确认提示框。
请在原生工程的strings.xml中添加以下字段配置二次确认提示框内容。
<string name="dcloud_second_privacy_prompt_title">二级弹窗标题</string>
<string name="dcloud_second_privacy_prompt_accept_button_text">接收按钮文字配置字段(不存在该字段,即使用默认内容“确定”)</string>
<string name="dcloud_second_privacy_prompt_message"><![CDATA[协议内容]]></string>
<string name="dcloud_second_privacy_prompt_refuse_button_text">拒绝按钮文字配置字段(没有该字段或该字段内容为空,拒绝按钮不显示)</string>
国际化
HBuilderX3.2.12+版本androidPrivacy.json支持国际化
- uni-app项目
可参考page.json文件国际化方式处理,详见:uni-app项目 pages.json 国际化 - 5+ App项目
不支持uni-app形式的国际化配置,可以对androidPrivacy.json文件中需要国际化处理的字段添加Locales,示例如下:{ "prompt": "template", "buttonAccept" : "默认接受按钮文本" }
添加buttonAcceptLocales处理buttonAccept的国际化文本,如下
{ "prompt": "template", "buttonAccept" : "默认接受按钮文本", "buttonAcceptLocales": { "en": "英文接受按钮文本", "zh-Hans":"中文简体接受按钮文本", "zh-Hant": "中文繁体接受按钮文本" } }
隐私协议内容需要注意的问题
需要在《隐私政策》中必告知用户您的应用基于DCloud uni-app(5+ App/Wap2App)开发,添加如下参考条款:
我们的产品基于DCloud uni-app(5+ App/Wap2App)开发,应用运行期间需要收集您的设备唯一识别码(IMEI/android ID/DEVICE_ID/IDFA、SIM 卡 IMSI 信息、OAID)以提供统计分析服务,并通过应用启动数据及异常错误日志分析改进性能和用户体验,为用户提供更好的服务。
另外隐私政策中需要补充使用到的三方SDK,参考:
uni-app默认集成三方SDK
请参考文档Android平台各功能模块隐私合规条款
uni原生插件
如果应用使用了uni原生插件,需要注意一下几点:
- 使用插件时请查看插件详情页面中的
隐私、权限声明
。(插件使用什么sdk?获取了什么用户信息?都应由插件作者提供并填写在隐私、权限声明
中) - 将插件中用到的三方SDK信息添加到用户隐私协议中。例如集成了
百度定位
。就需要在隐私协议中说明集成了百度定位SDK。获取了xxx用户信息!用于xxx. - 如果发现插件有获取用户信息而插件详情页并没有提供
隐私、权限声明
,请与插件开发者或与我们反馈共同督促进行补充。
其它
《隐私政策》必须非常清楚、全面地说明(不要用可能收集、了解用户信息这种模糊不清晰的词语)收集用户个人信息的目的、方式和范围。
如果应用使用“通讯录”、“短信”等相关功能,请根据应用业务场景进行描述。
其它相关问题
Android平台配置权限参考:https://ask.dcloud.net.cn/article/36982
iOS平台隐私与政策提示框实现参考:https://ask.dcloud.net.cn/article/36955
附件
可参考附件隐私权政策协议模板
注意:请修改模板中的公司名称及联系邮件地址
此文档将不再维护,请参考新文档:https://uniapp.dcloud.io/tutorial/app-privacy-android
HBuilderX2.6.3+版本开始支持配置隐私政策提示框
HBuilderX3.1.10+版本优化template模式策略,解决应用市场检测到弹出隐私政策提示框之前读取mac地址和应用列表的问题
HBuilderX3.2.1+版本开始支持androidPrivacy.json文件配置隐私政策提示框,真机运行时也可生效
HBuilderX3.2.5+版本隐私政策提示框内容中的链接支持本地 html 页面地址
注意:目前设置custom模式策略,并不能完全避免在弹出隐私提示框之前,读取设备信息(如mac地址、应用列表等)的情况,主要原因是自定义模式隐私提示框并不能阻塞应用的生命周期,使用到一些三方SDK(如X5 Webview内核、UniPush等)在应用启动时会执行初始化操作,三方SDK这是可能会读取设备信息。碰到此问题的开发者请先使用template模式解决,我们后续会提供新的自定义隐私提示框样式解决方案。
uni小程序SDK暂时不支持uniapp自己配置隐私弹窗,需要宿主自行实现隐私弹窗
请使用HBuilderX3.2.15
+ 版本打包,并使用template配置隐私弹窗否则无法正常上架应用市场
概述
根据工业和信息化部关于开展APP侵害用户权益专项整治要求,App提交到应用市场必须满足以下条件:
- 应用启动运行时需弹出隐私政策协议,说明应用采集用户数据
这里将详细介绍如何配置弹出“隐私协议和政策”提示框 - 应用不能强制要求用户授予权限,即不能“不给权限不让用”
如不希望应用启动时申请“读写手机存储”和“访问设备信息”权限,请参考:https://ask.dcloud.net.cn/article/36549
为了兼顾隐私政策提示框的易用性和灵活性,解决弹出隐私政策提示框之前可能弹出系统授权框的问题。Android平台提供了以下隐私政策提示配置策略:
- template
使用原生提供的隐私政策模板提示框,应用启动时在splash界面弹出。- 优点:在系统授权提示框之前显示,用户点击确认后才会进入应用
- 缺点:只能配置提示文本及链接地址,无法自定义提示框样式
- none
不处理隐私政策
不提交到应用市场时使用
<a id='collect'></a>
DCloud数据采集说明
为了持续优化应用及提供统计报表功能,在运行过程中会采集应用启动时间、异常错误日志等数据,其中包含设备唯一识别码。
DCloud通过了国家信息安全等级保护三级,证书编号:11010813802-20001,保障相关数据的安全性
DCloud并非大数据公司,采集的数据是为开发者提供统计服务和产品持续优化,不包含个人隐私信息
请在《隐私政策》中必告知用户您的应用基于DCloud uni-app(5+ App/Wap2App)开发,增加如下参考条款
我们的产品基于DCloud uni-app(5+ App/Wap2App)开发,应用运行期间需要收集您的设备唯一识别码(IMEI/android ID/DEVICE_ID/IDFA、SIM 卡 IMSI 信息、OAID)以提供统计分析服务,并通过应用启动数据及异常错误日志分析改进性能和用户体验,为用户提供更好的服务。详情内容请访问《DCloud用户服务条款》。(DCloud用户服务条款超链至:https://ask.dcloud.net.cn/protocol.html)
配置方式
HBuilderX3.2.1及以上版本配置方式
从HBuilderX3.2.1+版本开始新增androidPrivacy.json文件配置隐私政策提示框,支持真机运行查看效果,在androidPrivacy.json中也支持配置部分样式(如背景颜色、标题颜色、按钮颜色等)。
打开项目的manifest.json文件,切换到“App启动界面配置”,在“Android启动界面样式”中勾选“使用原生隐私政策提示框”
注意!androidPrivacy.json不要添加注释,会影响隐私政策提示框的显示!!!
勾选后会在项目中自动添加androidPrivacy.json文件,可以双击打开自定义配置以下内容:
{
"version": "1",
"prompt": "template",
"title": "服务协议和隐私政策",
"message": " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意并接受",
"buttonRefuse": "暂不同意",
// HX 3.4.13之后版本新增,system 使用系统webview 打开隐私协议链接,默认使用uni-app内置web组件
"hrefLoader":“system|default”
"second": {
"title": "确认提示",
"message": " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。",
"buttonAccept": "同意并继续",
"buttonRefuse": "退出应用"
},
"styles": {
"backgroundColor": "#00FF00",
"borderRadius":"5px",
"title": {
"color": "#ff00ff"
},
"buttonAccept": {
"color": "#ffff00"
},
"buttonRefuse": {
"color": "#00ffff"
}
}
}
- version
隐私政策版本号,如果应用升级后希望重新弹出隐私政策提示框,则需要设置新版本 - prompt
是否使用原生隐私政策提示框,值为“template”表示使用,“none”表示不使用 - title
隐私政策提示框标题文本内容 - message
隐私政策提示框正文内容,支持富文本richtext类型字符串,支持a/font/br等节点,点击a链接会调用内置页面打开其href属性中链接地址。
注意:务必配置此提示内容,参考上面示例内容并修改《服务协议》和《隐私政策》链接地址 - buttonAccept
模板提示框上接受按钮的文本,默认值为“同意” - buttonRefuse
模板提示框上拒绝按钮的文本,默认不显示此按钮 - hrefLoader
HX 3.4.13之后版本新增,system 使用系统webview 打开隐私协议链接,默认 default 使用uni-app内置web组件 - second
配置二次确认提示框显示内容,message属性值不为空时弹出二次确认提示框- title 二次确认提示框上的标题
- message 二次确认提示框上的内容,支持富文本richtext类型字符串
- buttonAccept 二次确认提示框上接受按钮的文本
- buttonRefuse 二次确认提示框上拒绝按钮的文本
- styles
配置隐私政策提示框样式- backgroundColor 提示框背景颜色,#RRGGBB格式字符串
- borderRadius 提示框背景圆角半径,单位为px(逻辑像素)
- title 提示框标题样式,其下仅支持color属性配置文本颜色,值为#RRGGBB格式字符串
- buttonAccept 接受按钮样式,其下仅支持color属性配置文本颜色,值为#RRGGBB格式字符串
- buttonRefuse 拒绝按钮样式,其下仅支持color属性配置文本颜色,值为#RRGGBB格式字符串
- disagreeMode
未同意隐私政策模式HBuilder X 3.3.1版本新增支持
具体配置及说明查看https://uniapp.dcloud.io/app-disagreemode- support true表示开启disagreeMode;false表示不开启(用户不同意“隐私政策”则退出应用)。默认值为false。
- loadNativePlugins 表示在disagreeMode模式是否加载uni原生插件,true表示加载;false表示不加载(此时调用uni.requireNativePlugin加载插件扩展Module返回undefined,扩展组件Component也无法使用)。默认值为true。
uni-app项目可以使用uni原生插件能支持更多自定义隐私政策提示框样式,可参考:https://ext.dcloud.net.cn/plugin?id=5581
HBuilderX3.2.0及以下版本配置方法
打开项目的manifest.json文件,切换到“源码视图”项
- uni-app项目
在 "app-plus" -> "privacy" 节点下添加 prompt节点 - 5+ App项目
在 "plus" -> "privacy" 节点下添加 prompt节点
privacy节点数据格式如下:
"privacy": {
"prompt": "template", //可取值template、none
"template": { //prompt取值为template时有效,用于配置模板提示框上显示的内容
}
}
- prompt
字符串类型,必填,隐私政策提示框配置策略,可取值template,none,默认值为none- template
使用原生提示框模板,可自定义标题、内容已经按钮上的文本 - none
不弹出隐私政策提示框
- template
- template
json格式,可选,模板提示框上显示的内容
模板提示框
配置promt属性值为template时,表示使用原生模板隐私政策提示框,效果如下:
应用启动前,在Splash页面时显示此提示框,用户点击同意按钮后才会进入应用
可使用以下配置模板提示框内容
"privacy": {
"prompt": "template",
"template": {
"title": "服务协议和隐私政策",
"message": " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept": "同意",
"buttonRefuse": "暂不同意",
"second": {
"title": "温馨提示",
"message": " 进入应用前,你需先同意<a href=\"\">《服务协议》</a>和<a href=\"\">《隐私政策》</a>,否则将退出应用。",
"buttonAccept": "同意并继续",
"buttonRefuse": "退出应用",
}
}
}
- title
模板提示框上的标题,默认为“服务协议和隐私政策” - message
模板提示框上的内容,richtext类型字符串,支持a/font/br等节点,点击a链接会调用内置页面打开其href属性中链接地址。- HBuilderX3.2.5以下版本a链接的href属性仅支持网络地址,以http:或https:开头,如“https://www.dcloud.io/privacy.html”
- HBuilder3.2.5及以上版本a链接的href属性支持本地地址,相对于应用根目录,如“static/privacy.html”
注意:务必配置此提示内容,参考上面示例内容并修改《服务协议》和《隐私政策》链接地址
- buttonAccept
模板提示框上接受按钮的文本,默认值为“同意”,
注意:接受按钮设置为“我知道了”在部分应用市场上架时会审核不通过。 - buttonRefuse
模板提示框上拒绝按钮的文本,默认不显示此按钮 - second
HBuilderX3.1.12+版本新增支持隐私提示框二次确认提示,用于配置二次确认提示框显示内容,message属性值不为空时弹出二次确认提示框- title 二次确认提示框上的标题
- message 二次确认提示框上的内容,支持richtext类型字符串
- buttonAccept 二次确认提示框上接受按钮的文本
- buttonRefuse 二次确认提示框上拒绝按钮的文本
配置后提交云端打包后生效
提供5+ API设置/获取状态:
- 获取是否同意隐私政策
参考规范:plus.runtime.isAgreePrivacy
应用启动时调用此API查下状态,如果用户未同意则弹出自定义隐私政策提示框。if(!plus.runtime.isAgreePrivacy()){ //弹出自定义隐私政策提示框 }
- 设置为同意隐私政策
参考规范:plus.runtime.agreePrivacy
在自定义隐私政策提示界面用,用户点击“同意”按钮时需调用此API设置状态 - 设置为不同意隐私政策
参考规范:plus.runtime.disagreePrivacy
在自定义隐私政策提示界面用,用户点击“不同意”按钮时需调用此API设置状态
注意:用户同意隐私政策前不要调用可能弹出系统授权框的API,如定位(plus.geolocation)、录音(plus.audio.getRecorder)等
无提示框
配置prompt属性值为none时,表示不显示隐私政策提示框。
如果不提交到应用市场,可以使用此模式。
离线打包配置方式
HBuilderX3.2.1+版本新增androidPrivacy.json配置隐私政策提示框,优先级高于原生环境配置,新版本没有必要再在原生工程中配置
在原生工程中应用的AndroidManifest.xml中配置隐私提供框模式,在application节点下添加meta-data节点数据,如下:
<application>
<meta-data
android:name="DCLOUD_PRIVACY_PROMPT" android:value="template"/>
</application>
android:value可取值为"template"、"none"。
配置使用"template"模板提示框时,需要按以下方法配置提示框内容
在原生工程的res/values目录下的strings.xml配置默认语言内容(为了兼容用户设置不同语言的清空,此文件必须配置)。中文还需要创建res/values-zh目录下的strings.xml文件并配置中文内容。
模板提示框
请在原生工程的strings.xml中添加以下字段配置模板提示框内容。
<resources>
<string name="dcloud_privacy_prompt_title">弹窗标题</string>
<string name="dcloud_privacy_prompt_accept_button_text">接收按钮文字配置字段(不存在该字段,即使用默认内容“同意”)</string>
<string name="dcloud_privacy_prompt_refuse_button_text">拒绝按钮文字配置字段(没有该字段或该字段内容为空,拒绝按钮不显示)</string>
<string name="dcloud_privacy_prompt_message"><Data><![CDATA[弹窗内容,如果内容中有富文本,请将内容放入cdata下,如当前配置]]></Data></string>
</resources>
二次确认提示框
HBuilderX3.1.12+版本新增支持模板隐私提示框二次确认功能,点击隐私模板提示框时用户选择“拒绝”按钮并且二次确认提示框内容dcloud_second_privacy_prompt_message配置不为空时会弹出二次确认提示框。
请在原生工程的strings.xml中添加以下字段配置二次确认提示框内容。
<string name="dcloud_second_privacy_prompt_title">二级弹窗标题</string>
<string name="dcloud_second_privacy_prompt_accept_button_text">接收按钮文字配置字段(不存在该字段,即使用默认内容“确定”)</string>
<string name="dcloud_second_privacy_prompt_message"><![CDATA[协议内容]]></string>
<string name="dcloud_second_privacy_prompt_refuse_button_text">拒绝按钮文字配置字段(没有该字段或该字段内容为空,拒绝按钮不显示)</string>
国际化
HBuilderX3.2.12+版本androidPrivacy.json支持国际化
- uni-app项目
可参考page.json文件国际化方式处理,详见:uni-app项目 pages.json 国际化 - 5+ App项目
不支持uni-app形式的国际化配置,可以对androidPrivacy.json文件中需要国际化处理的字段添加Locales,示例如下:{ "prompt": "template", "buttonAccept" : "默认接受按钮文本" }
添加buttonAcceptLocales处理buttonAccept的国际化文本,如下
{ "prompt": "template", "buttonAccept" : "默认接受按钮文本", "buttonAcceptLocales": { "en": "英文接受按钮文本", "zh-Hans":"中文简体接受按钮文本", "zh-Hant": "中文繁体接受按钮文本" } }
隐私协议内容需要注意的问题
需要在《隐私政策》中必告知用户您的应用基于DCloud uni-app(5+ App/Wap2App)开发,添加如下参考条款:
我们的产品基于DCloud uni-app(5+ App/Wap2App)开发,应用运行期间需要收集您的设备唯一识别码(IMEI/android ID/DEVICE_ID/IDFA、SIM 卡 IMSI 信息、OAID)以提供统计分析服务,并通过应用启动数据及异常错误日志分析改进性能和用户体验,为用户提供更好的服务。
另外隐私政策中需要补充使用到的三方SDK,参考:
uni-app默认集成三方SDK
请参考文档Android平台各功能模块隐私合规条款
uni原生插件
如果应用使用了uni原生插件,需要注意一下几点:
- 使用插件时请查看插件详情页面中的
隐私、权限声明
。(插件使用什么sdk?获取了什么用户信息?都应由插件作者提供并填写在隐私、权限声明
中) - 将插件中用到的三方SDK信息添加到用户隐私协议中。例如集成了
百度定位
。就需要在隐私协议中说明集成了百度定位SDK。获取了xxx用户信息!用于xxx. - 如果发现插件有获取用户信息而插件详情页并没有提供
隐私、权限声明
,请与插件开发者或与我们反馈共同督促进行补充。
其它
《隐私政策》必须非常清楚、全面地说明(不要用可能收集、了解用户信息这种模糊不清晰的词语)收集用户个人信息的目的、方式和范围。
如果应用使用“通讯录”、“短信”等相关功能,请根据应用业务场景进行描述。
其它相关问题
Android平台配置权限参考:https://ask.dcloud.net.cn/article/36982
iOS平台隐私与政策提示框实现参考:https://ask.dcloud.net.cn/article/36955
附件
可参考附件隐私权政策协议模板
注意:请修改模板中的公司名称及联系邮件地址
分享一个跨平台小程序脚手架,涵盖UI库和主题、请求、代码规范等插拔式应用框架
源码地址:https://github.com/PhotoArtLife/uniapp-scaffold
备注:基于此脚手架曾上线过多款厂里商业性产品,欢迎issue!
简介:
uniapp-scaffold 是一款跨平台小程序脚手架,配备一定基础UI库和规范的插拔式应用框架。
特点:
全: 一套code,实时打包输出各端小程序 + h5 + app
简: Vue写法,内置基础组件库和Vuex、Services、Utils等丰富能力
调研:
基于跨平台小程序框架+ kpui(一个不断完善的组件库)的mini app(小程序 + h5 + app)
当前混合版仅上线百度、头条、支付宝、微信等相关平台,更多动态请参观uni-app官网或联系脚手架作者
Guides | 参考资料:
uni-app文档
微信官方小程序文档
微信小程序API
头条小程序文档
百度小程序文档
支付宝小程序文档
airbnb javascript standard
Ant Design introduce
JD Front-End Coding Guidelines
React系多端统一开发解决方案Taro
Dev | 开发姿势:
- 工具,先下载各平台开发工具(IDE)https://uniapp.dcloud.io/quickstart
- 在Hbuilder中导入当前项目(uni-app类型),修改或更新当前配置,manifest.json和pages.json
- 配置插件,菜单栏点击插件管理可以安装当前项目所需要的插件和loader,(less、babel、es6等),npm insatll (步骤3不分先后)
- 菜单栏点击运行到要开发的小程序平台,不习惯当前IDE的可以切换到VS Code,同步开发,实时编译
- 进入各家开发者后台,提审,发布
- 更多补充中
注意事项/坑:
使用Vue.js注意事项 https://uniapp.dcloud.io/use
根据不同平台小程序做条件编译(非常重要)https://uniapp.dcloud.io/platform
高级开发技巧 https://uniapp.dcloud.io/snippet
性能优化建议 https://uniapp.dcloud.io/performance
源码地址:https://github.com/PhotoArtLife/uniapp-scaffold
备注:基于此脚手架曾上线过多款厂里商业性产品,欢迎issue!
简介:
uniapp-scaffold 是一款跨平台小程序脚手架,配备一定基础UI库和规范的插拔式应用框架。
特点:
全: 一套code,实时打包输出各端小程序 + h5 + app
简: Vue写法,内置基础组件库和Vuex、Services、Utils等丰富能力
调研:
基于跨平台小程序框架+ kpui(一个不断完善的组件库)的mini app(小程序 + h5 + app)
当前混合版仅上线百度、头条、支付宝、微信等相关平台,更多动态请参观uni-app官网或联系脚手架作者
Guides | 参考资料:
uni-app文档
微信官方小程序文档
微信小程序API
头条小程序文档
百度小程序文档
支付宝小程序文档
airbnb javascript standard
Ant Design introduce
JD Front-End Coding Guidelines
React系多端统一开发解决方案Taro
Dev | 开发姿势:
- 工具,先下载各平台开发工具(IDE)https://uniapp.dcloud.io/quickstart
- 在Hbuilder中导入当前项目(uni-app类型),修改或更新当前配置,manifest.json和pages.json
- 配置插件,菜单栏点击插件管理可以安装当前项目所需要的插件和loader,(less、babel、es6等),npm insatll (步骤3不分先后)
- 菜单栏点击运行到要开发的小程序平台,不习惯当前IDE的可以切换到VS Code,同步开发,实时编译
- 进入各家开发者后台,提审,发布
- 更多补充中
注意事项/坑:
使用Vue.js注意事项 https://uniapp.dcloud.io/use
根据不同平台小程序做条件编译(非常重要)https://uniapp.dcloud.io/platform
高级开发技巧 https://uniapp.dcloud.io/snippet
性能优化建议 https://uniapp.dcloud.io/performance
开发者中心Unipush推送页面优化介绍
1. 更新简述
(1)页面改版,模块区分出公共模块、安卓配置、ios配置
(2)通知消息-支持ios推送
(3)通知消息-增加通知渠道模板
(4)通知消息-后续动作改造
(5)通知消息-通知栏图标改造
(6)透传消息-新增测试预览
(7)特定用户支持手动输入
(8)apn模块,payload支持自定义key、value
(9)数据统计中,推送记录和推送数据页面合并
(10)用户分组从头部移到应用内
2. 页面改版
(1)页面改版,模块区分出公共模块、安卓配置、ios配置
(2)新旧版本按钮位置移动
(3)数据统计,推送记录和推送统计合并,点击红框“推送数据”弹窗新页面
(4)用户分组位置从头部转移到应用内;且支持系统版本标签选择
3. 新增点
(1)通知消息-支持ios推送,并增加手机预览样式
(2)通知消息-增加拆分记录:同一条消息推送,会在推送记录中,分别以Android和ios两个目标平台区分为两条记录
(3)通知消息-后续动作改造:原高级设置中的透传消息移动到后续动作的附加消息
(4)通知消息-增加通知渠道模板:整合铃声、震动、浮动、唤醒屏幕这些提醒方式,通过渠道的方式来使用;
作用如下:
1、有些厂商要求一定要带Channel ID;
2、设置好提醒方式,下次可直接使用,方便管理
配置管理-增加渠道配置页面,用于新增、查看通知渠道模板
1、模板不支持修改和删除
2、每个应用默认有两个模板:“Default”和“Push”;
(1)Default的渠道优先级为“默认(PRIORITY_DEFAULT)”;
(2)Push的渠道优先级为“PRIORITY_HIGH”;
(5)通知消息-增加通知栏图标:
1、默认图标 :通知栏展示个推SDK已设置好的push.png;厂商不支持显示push.png,显示的是桌面图标;图标无预览样式
2、APP本地图标:个推SDK支持设置多个push.png,填写指定的push.png;厂商不支持显示push.png,显示的是桌面图标;图标无预览样式
3、上传图标:通知栏展示上传的本地图标;该选项不支持走厂商通道
4、指定图标路径:通知栏展示指定路径的图标,图片可以为http或https开头的网络资源;该选项不支持走厂商通道;图标无预览样式
(6)透传消息-增加测试预览
(7)APNs模块-payload改造:用于补充下发需携带的其他参数
Payload和消息内容一致:key=payload,value=消息内容
Payload内容自定义:key=payload,value自定义
自定义:key和value均自定义
(8)特定用户,支持手动输入
4. 删除点
展示条件、联网方式近半年使用率极低;厂商不支持清除通知,功能无效;故删除该3点功能
1. 更新简述
(1)页面改版,模块区分出公共模块、安卓配置、ios配置
(2)通知消息-支持ios推送
(3)通知消息-增加通知渠道模板
(4)通知消息-后续动作改造
(5)通知消息-通知栏图标改造
(6)透传消息-新增测试预览
(7)特定用户支持手动输入
(8)apn模块,payload支持自定义key、value
(9)数据统计中,推送记录和推送数据页面合并
(10)用户分组从头部移到应用内
2. 页面改版
(1)页面改版,模块区分出公共模块、安卓配置、ios配置
(2)新旧版本按钮位置移动
(3)数据统计,推送记录和推送统计合并,点击红框“推送数据”弹窗新页面
(4)用户分组位置从头部转移到应用内;且支持系统版本标签选择
3. 新增点
(1)通知消息-支持ios推送,并增加手机预览样式
(2)通知消息-增加拆分记录:同一条消息推送,会在推送记录中,分别以Android和ios两个目标平台区分为两条记录
(3)通知消息-后续动作改造:原高级设置中的透传消息移动到后续动作的附加消息
(4)通知消息-增加通知渠道模板:整合铃声、震动、浮动、唤醒屏幕这些提醒方式,通过渠道的方式来使用;
作用如下:
1、有些厂商要求一定要带Channel ID;
2、设置好提醒方式,下次可直接使用,方便管理
配置管理-增加渠道配置页面,用于新增、查看通知渠道模板
1、模板不支持修改和删除
2、每个应用默认有两个模板:“Default”和“Push”;
(1)Default的渠道优先级为“默认(PRIORITY_DEFAULT)”;
(2)Push的渠道优先级为“PRIORITY_HIGH”;
(5)通知消息-增加通知栏图标:
1、默认图标 :通知栏展示个推SDK已设置好的push.png;厂商不支持显示push.png,显示的是桌面图标;图标无预览样式
2、APP本地图标:个推SDK支持设置多个push.png,填写指定的push.png;厂商不支持显示push.png,显示的是桌面图标;图标无预览样式
3、上传图标:通知栏展示上传的本地图标;该选项不支持走厂商通道
4、指定图标路径:通知栏展示指定路径的图标,图片可以为http或https开头的网络资源;该选项不支持走厂商通道;图标无预览样式
(6)透传消息-增加测试预览
(7)APNs模块-payload改造:用于补充下发需携带的其他参数
Payload和消息内容一致:key=payload,value=消息内容
Payload内容自定义:key=payload,value自定义
自定义:key和value均自定义
(8)特定用户,支持手动输入
4. 删除点
展示条件、联网方式近半年使用率极低;厂商不支持清除通知,功能无效;故删除该3点功能
承接各行业商城及衍生项目, 成熟商城产品, 支持B2C, B2B2C, 社交电商
官方网站
www.ccmao.net
产品体验
联系微信(qinghuiyang-lcq)
1 产品介绍
创创猫电商系统是基于JavaEE技术的企业级电子商务平台系统,以其安全稳定、强大易用、高效专业等优势赢得了用户的广泛好评。创创猫为大、中、小企业提供一个安全、高效、强大的电子商务解决方案,协助企业快速构建、部署和管理其电子商务平台,拓展企业销售渠道,突显电子商务商业价值。
2 产品优势
支持多种电商模式
B2C: 商家自营
B2B2C : 平台商家入驻
社交电商: 会员快速裂变, 分销分润
绚丽风格,极致视觉体验
采用电子商务流行设计元素,给用户以极致的视觉体验,基于uniapp技术开发,提供丰富的组件,让页面设计更加简单。
100%源代码支持
严格遵循Java标准开发规范,全面源代码及注释支持。
优异性能,轻松实现高负载应用
采用多级缓存、全文检索、云存储等技术,使得系统的响应速度和负载能力大大提升。
多重安全防护机制
XSS防御、CSRF防御、SQL安全防御、RSA传输加密、暴力破解防护...针对各类攻击采用多重安全防护措施,有效保障用户的系统及数据安全。
强大功能,丰富的第三方应用扩展
高扩展性插件设计,多种第三方应用集成。微信、支付宝、快捷登录、快递100、站长统计、阿里云存储、FTP存储...
3 技术架构
开发语言: Java
数 据 库 : MySQL
后端框架: Spring、SpringMVC、JPA
前端框架: Uni-app、vue、iview
全文检索: Lucene
依赖管理: Maven
官方网站
www.ccmao.net
产品体验
联系微信(qinghuiyang-lcq)
1 产品介绍
创创猫电商系统是基于JavaEE技术的企业级电子商务平台系统,以其安全稳定、强大易用、高效专业等优势赢得了用户的广泛好评。创创猫为大、中、小企业提供一个安全、高效、强大的电子商务解决方案,协助企业快速构建、部署和管理其电子商务平台,拓展企业销售渠道,突显电子商务商业价值。
2 产品优势
支持多种电商模式
B2C: 商家自营
B2B2C : 平台商家入驻
社交电商: 会员快速裂变, 分销分润
绚丽风格,极致视觉体验
采用电子商务流行设计元素,给用户以极致的视觉体验,基于uniapp技术开发,提供丰富的组件,让页面设计更加简单。
100%源代码支持
严格遵循Java标准开发规范,全面源代码及注释支持。
优异性能,轻松实现高负载应用
采用多级缓存、全文检索、云存储等技术,使得系统的响应速度和负载能力大大提升。
多重安全防护机制
XSS防御、CSRF防御、SQL安全防御、RSA传输加密、暴力破解防护...针对各类攻击采用多重安全防护措施,有效保障用户的系统及数据安全。
强大功能,丰富的第三方应用扩展
高扩展性插件设计,多种第三方应用集成。微信、支付宝、快捷登录、快递100、站长统计、阿里云存储、FTP存储...
3 技术架构
开发语言: Java
数 据 库 : MySQL
后端框架: Spring、SpringMVC、JPA
前端框架: Uni-app、vue、iview
全文检索: Lucene
依赖管理: Maven
收起阅读 »分享mqtt.js插件要用到的package.json
大佬的连接在这里:
https://ext.dcloud.net.cn/plugin?id=854#
大佬的连接在这里:
https://ext.dcloud.net.cn/plugin?id=854#
动态修改TitleNView RedDot,支持App 和 H5
app部分来自于 https://github.com/dcloudio/hello-uniapp
export function setTitleNViewStyle(index, show,text) {
let pages = getCurrentPages();
let page = pages[pages.length - 1];
// #ifdef APP-PLUS
let currentWebview = page.$getAppWebview();
if(show){
if(index === 0){
currentWebview.showTitleNViewButtonRedDot({index:index,text:text})
}else{
currentWebview.setTitleNViewButtonBadge({index:index,text:text})
}
}else{
if(index === 0){
currentWebview.hideTitleNViewButtonRedDot({index:index})
}else{
currentWebview.removeTitleNViewButtonBadge({index:index})
}
}
// #endif
// #ifdef H5
if(show){
if(index === 0){
document.querySelectorAll('.uni-page-head-hd .uni-page-head-btn')[1].classList.add('uni-page-head-btn-red-dot');
} else {
document.querySelector('.uni-page-head-ft .uni-page-head-btn').classList.add('uni-page-head-btn-red-dot');
}
} else {
if(index === 0){
document.querySelector('.uni-page-head-btn-red-dot').classList.remove('uni-page-head-btn-red-dot');
} else {
document.querySelector('.uni-page-head-ft .uni-page-head-btn-red-dot').classList.remove('uni-page-head-btn-red-dot');
}
}
// #endif
}
app部分来自于 https://github.com/dcloudio/hello-uniapp
export function setTitleNViewStyle(index, show,text) {
let pages = getCurrentPages();
let page = pages[pages.length - 1];
// #ifdef APP-PLUS
let currentWebview = page.$getAppWebview();
if(show){
if(index === 0){
currentWebview.showTitleNViewButtonRedDot({index:index,text:text})
}else{
currentWebview.setTitleNViewButtonBadge({index:index,text:text})
}
}else{
if(index === 0){
currentWebview.hideTitleNViewButtonRedDot({index:index})
}else{
currentWebview.removeTitleNViewButtonBadge({index:index})
}
}
// #endif
// #ifdef H5
if(show){
if(index === 0){
document.querySelectorAll('.uni-page-head-hd .uni-page-head-btn')[1].classList.add('uni-page-head-btn-red-dot');
} else {
document.querySelector('.uni-page-head-ft .uni-page-head-btn').classList.add('uni-page-head-btn-red-dot');
}
} else {
if(index === 0){
document.querySelector('.uni-page-head-btn-red-dot').classList.remove('uni-page-head-btn-red-dot');
} else {
document.querySelector('.uni-page-head-ft .uni-page-head-btn-red-dot').classList.remove('uni-page-head-btn-red-dot');
}
}
// #endif
}
收起阅读 »