HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

iOS 扩展 uni小程序SDK 原生能力

扩展小程序原生能力 unimpsdk uni小程序sdk

本文档已过期,请查看 新文档

概述

本文档主要介绍如何扩展 uni小程序SDK 原生能力。

什么是扩展原生能力?

扩展原生能力指的是将您原生开发的功能通过一定规范暴露给 uni小程序环境,然后即可在 uni小程序应用中调用您的原生功能。

扩展方式

uni 原生端是基于 WeexSDK 来实现扩展原生能力,扩展原生能力有两种方式:一种是不需要参与页面布局,只需要通过 API 调用原生功能,比如:获取当前定位信息、数据请求等功能,这种情况可通过扩展module的方式来实现;另一种是需要参与页面布局,比如:map、image,这种情况需要通过扩展component即组件的方法来实现;

开发前准备

  • iOS开发环境,请使用 Xcode 11.0 及以上版本;
  • 已有集成 iOS uni小程序SDK 原生工程;集成文档
  • 安装 uni小程序开发工具 HBuilderX (注:版本需与您使用的 uni小程序SDK 版本保持一致)

扩展 module

下面以TestModule为例,源码请查看 uni小程序SDK 包中的示例 demo 工程;

1. 新建TestModule类,继承 NSObject,让该类遵循 WXModuleProtocol 的协议。

#import <Foundation/Foundation.h>  
// 引入 WeexSDK.h 头文件  
#import "WeexSDK.h"  

@interface TestModule : NSObject <WXModuleProtocol>  

@end  

2. 添加实现方法

异步方法实现

/// 异步方法(注:异步方法会在主线程(UI线程)执行)  
/// @param options js 端调用方法时传递的参数  
/// @param callback 回调方法,回传参数给 js 端  
- (void)testAsyncFunc:(NSDictionary *)options callback:(WXModuleKeepAliveCallback)callback {  
    // options 为 js 端调用此方法时传递的参数  
    NSLog(@"%@",options);  

    // 可以在该方法中实现原生能力,然后通过 callback 回调到 js  

    // 回调方法,传递参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型  
    if (callback) {  
        // 第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传 YES;  
        callback(@"success",NO);  
    }  
}

通过宏 WX_EXPORT_METHOD 将异步方法暴露给 js 端,只有通过WX_EXPORT_METHOD暴露的原生方法才能被 js 端识别到

// 通过宏 WX_EXPORT_METHOD 将异步方法暴露给 js 端  
WX_EXPORT_METHOD(@selector(testAsyncFunc:callback:))

同步方法实现

/// 同步方法(注:同步方法会在 js 线程执行)  
/// @param options js 端调用方法时传递的参数  
- (NSString *)testSyncFunc:(NSDictionary *)options {  
    // options 为 js 端调用此方法时传递的参数  
    NSLog(@"%@",options);  

    /*  
     可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js  
     */  

    // 同步返回参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型  
    return @"success";  
}

通过宏 WX_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端

// 通过宏 WX_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端  
WX_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))

3. 注册 module

在初始化 DCUniMPSDKEngine 方法后,进行 module 的注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    ...  
    // 初始化引擎  
    [DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];  

    // 注册 module 注:module的 Name 需要保证唯一, class:为 module 的类名  
    [WXSDKEngine registerModule:@"TestModule" withClass:NSClassFromString(@"TestModule")];  

    return YES;  
}

到此,我们已经完成了一个简单的 module 扩展

4. 在 uni小程序 中调用 module 方法

module 支持在 vue 和 nvue 中使用

<template>  
    <div>  
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>  
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>  
    </div>  
</template>  

<script>  
    // 获取 module   
    var testModule = uni.requireNativePlugin("TestModule")  
    export default {  
        methods: {  
            testAsyncFunc() {  
                // 调用异步方法  
                testModule.testAsyncFunc({  
                        'name': 'unimp',  
                        'age': 1  
                    },  
                    (ret) => {  
                        console.log(ret)  
                    })  
            },  
            testSyncFunc() {  
                // 调用同步方法  
                var ret = testModule.testSyncFunc({  
                    'name': 'unimp',  
                    'age': 1  
                })  
            }  
        }  
    }  
</script>  

然后可以导出 uni小程序资源,导入到 App 中查看效果;

扩展组件 component

下面以TestComponent为例,源码请查看 uni小程序SDK 包中的示例 demo 工程;

1. 新建TestComponent类,继承WXComponent类。如果这个类里什么代码也不写,它和默认的的 view 组件能力是一致的

#import "WXComponent.h"  

@interface TestComponent : WXComponent  

@end  

2. 覆写 WXComponent 中的生命周期方法

- loadView 方法

一个组件默认对应于一个原生 view,如果未覆盖loadView提供自定义 view,基类会默认返回一个继承于 UIView 的实例。比如我们要实现一个组件支持地图功能,我们可以返回系统的 MKMapView

- (UIView *)loadView {  
    return [MKMapView new];  
}

- viewDidLoad

对组件 view 需要做一些配置,比如设置 delegate,可以在 viewDidLoad 生命周期做。如果当前 view 没有添加 subview 的话,不要设置 view 的 frame,WeexSDK 会根据 style 进行排版后设置。

- (void)viewDidLoad {  
      ((MKMapView*)self.view).delegate = self;  
}

3. 注册组件

在初始化 DCUniMPSDKEngine 方法后,进行 component 的注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    ...  
    // 初始化引擎  
    [DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];  

    // 注册 component 注:component 的 Name 需要保证唯一, class:为 component 的类名  
    [WXSDKEngine registerComponent:@"testmap" withClass:NSClassFromString(@"TestMapComponent")];  

    return YES;  
}

4. 在uni小程序代码中使用组件

注意:扩展的 component 只能在 nvue 文件中使用

<template>  
    <div>  
        <testmap style="width:200px;height:200px"></testmap>  
    </div>  
</template>

自定义事件

1. 对于每个组件默认提供了一些事件能力,如点击等。假如想给我们的地图组件提供 mapLoaded 事件。

在uni小程序代码中,通过 @事件名="方法名" 添加事件,如下添加mapLoaded 事件

<template>  
    <div>  
        <testmap style="width:200px;height:200px" @mapLoaded="onMapLoaded"></testmap>  
    </div>  
</template>  

<script>  
export default {  
    methods: {  
        onMapLoaded:function(e) {  
            console.log("map loaded"+JSON.stringify(e))  
        }  
    }  
}  
</script>
2. 原生端实现:覆盖组件生命周期方法,记录事件是否需要处理

我们需要额外添加一个 BOOL 成员 mapLoadedEvent 用来记录该事件是否生效。

/// 前端注册的事件会调用此方法  
/// @param eventName 事件名称  
- (void)addEvent:(NSString *)eventName {  
    if ([eventName isEqualToString:@"mapLoaded"]) {  
        _mapLoadedEvent = YES;  
    }  
}  

/// 对应的移除事件回调方法  
/// @param eventName 事件名称  
- (void)removeEvent:(NSString *)eventName {  
    if ([eventName isEqualToString:@"mapLoaded"]) {  
        _mapLoadedEvent = NO;  
    }  
}
3. 给前端发送事件
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {  
    if (_mapLoadedEvent) {  
        // 想前端发送事件 params:传给前端的数据  
        [self fireEvent:@"mapLoaded" params:@{@"customKey":@"customValue"} domChanges:nil];  
    }  
}

自定义属性

给我们的地图组件添加一个新的属性showTraffic。在前端代码里可以控制组件是否显示路况信息

<template>  
    <div>  
        <testmap style="width:200px;height:200px" showTraffic="true"></testmap>  
    </div>  
</template>
原生端实现

1. 覆盖组件初始化方法 initWithRef...
给组件添加一个成员变量记录 showTraffic 属性的值,并在 init 方法中初始化。

- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance {  
    if(self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {  
        if (attributes[@"showsTraffic"]) {  
            _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];  
        }  
    }  
    return self;  
}

2. 在生命期事件中记得将属性值同步给地图控件

- (void)viewDidLoad {  
  ((MKMapView*)self.view).showsTraffic = _showsTraffic;  
}

3. 当属性更新时,同步给地图控件

/// 前端更新属性回调方法  
/// @param attributes 更新的属性  
- (void)updateAttributes:(NSDictionary *)attributes {  
    // 解析属性  
    if (attributes[@"showsTraffic"]) {  
        _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];  
        ((MKMapView*)self.view).showsTraffic = _showsTraffic;  
    }  
}

更多的组件生命期方法

组件是由框架管理的,比如创建、布局、渲染、销毁。组件的生命周期方法都是可以重写的,你可以在这些生命周期中去做自己的事情。

方法 描述
initWithRef:type:… 用给定的属性初始化一个component.
layoutDidFinish 在component完成布局时候会调用.
loadView 创建component管理的view.
viewWillLoad 在component的view加载之前会调用.
viewDidLoad 在component的view加载完之后调用.
viewWillUnload 在component的view被释放之前调用.
viewDidUnload 在component的view被释放之后调用.
updateStyles: 在component的style更新时候调用.
updateAttributes: 在component的attribute更新时候调用.
addEvent: 给component添加event的时候调用.
removeEvent: 在event移除的时候调用.

给组件添加方法

1.原生端实现

在组件代码中使用宏 WX_EXPORT_METHOD 暴露原生方法供前端调用

@implementation TestMapComponent  

// 通过 WX_EXPORT_METHOD 将方法暴露给前端  
WX_EXPORT_METHOD(@selector(focus:))  

- (void)focus:(NSDictionary *)options {  
    // options 为前端传递的参数  
    NSLog(@"%@",options);  
}  
@end

2.在uni小程序代码 中调用 focus: 方法。

<template>  
  <testmap ref='mycomponent'></testmap>  
</template>  
<script>  
  module.exports = {  
    created: function() {  
      this.$refs.mycomponent.focus("Hello");  
    }  
  }  
</script>

至此,您已完成组件扩展;

以上示例中的完整uni小程序代码

<template>  
    <div>  
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>  
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>  
        <testmap ref='mycomponent' style="width:200px;height:200px" showTraffic="true" @mapLoaded="onMapLoaded"></testmap>  
    </div>  
</template>  

<script>  
    // 获取 module   
    var testModule = uni.requireNativePlugin("TestModule")  
    export default {  
        onLoad() {  
            // 调用组件方法  
            this.$nextTick(()=>{  
                this.$refs.mycomponent.focus("Hello")  
            })  
        },  
        methods: {  
            testAsyncFunc() {  
                // 调用异步方法  
                testModule.testAsyncFunc({  
                        'name': 'unimp',  
                        'age': 1  
                    },  
                    (ret) => {  
                        console.log(ret)  
                    })  
            },  
            testSyncFunc() {  
                // 调用同步方法  
                var ret = testModule.testSyncFunc({  
                    'name': 'unimp',  
                    'age': 1  
                })  
            },  
            onMapLoaded(e) {  
                console.log("map loaded" + JSON.stringify(e))  
            }  
        }  
    }  
</script>
继续阅读 »

本文档已过期,请查看 新文档

概述

本文档主要介绍如何扩展 uni小程序SDK 原生能力。

什么是扩展原生能力?

扩展原生能力指的是将您原生开发的功能通过一定规范暴露给 uni小程序环境,然后即可在 uni小程序应用中调用您的原生功能。

扩展方式

uni 原生端是基于 WeexSDK 来实现扩展原生能力,扩展原生能力有两种方式:一种是不需要参与页面布局,只需要通过 API 调用原生功能,比如:获取当前定位信息、数据请求等功能,这种情况可通过扩展module的方式来实现;另一种是需要参与页面布局,比如:map、image,这种情况需要通过扩展component即组件的方法来实现;

开发前准备

  • iOS开发环境,请使用 Xcode 11.0 及以上版本;
  • 已有集成 iOS uni小程序SDK 原生工程;集成文档
  • 安装 uni小程序开发工具 HBuilderX (注:版本需与您使用的 uni小程序SDK 版本保持一致)

扩展 module

下面以TestModule为例,源码请查看 uni小程序SDK 包中的示例 demo 工程;

1. 新建TestModule类,继承 NSObject,让该类遵循 WXModuleProtocol 的协议。

#import <Foundation/Foundation.h>  
// 引入 WeexSDK.h 头文件  
#import "WeexSDK.h"  

@interface TestModule : NSObject <WXModuleProtocol>  

@end  

2. 添加实现方法

异步方法实现

/// 异步方法(注:异步方法会在主线程(UI线程)执行)  
/// @param options js 端调用方法时传递的参数  
/// @param callback 回调方法,回传参数给 js 端  
- (void)testAsyncFunc:(NSDictionary *)options callback:(WXModuleKeepAliveCallback)callback {  
    // options 为 js 端调用此方法时传递的参数  
    NSLog(@"%@",options);  

    // 可以在该方法中实现原生能力,然后通过 callback 回调到 js  

    // 回调方法,传递参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型  
    if (callback) {  
        // 第一个参数为回传给js端的数据,第二个参数为标识,表示该回调方法是否支持多次调用,如果原生端需要多次回调js端则第二个参数传 YES;  
        callback(@"success",NO);  
    }  
}

通过宏 WX_EXPORT_METHOD 将异步方法暴露给 js 端,只有通过WX_EXPORT_METHOD暴露的原生方法才能被 js 端识别到

// 通过宏 WX_EXPORT_METHOD 将异步方法暴露给 js 端  
WX_EXPORT_METHOD(@selector(testAsyncFunc:callback:))

同步方法实现

/// 同步方法(注:同步方法会在 js 线程执行)  
/// @param options js 端调用方法时传递的参数  
- (NSString *)testSyncFunc:(NSDictionary *)options {  
    // options 为 js 端调用此方法时传递的参数  
    NSLog(@"%@",options);  

    /*  
     可以在该方法中实现原生功能,然后直接通过 return 返回参数给 js  
     */  

    // 同步返回参数给 js 端 注:只支持返回 String 或 NSDictionary (map) 类型  
    return @"success";  
}

通过宏 WX_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端

// 通过宏 WX_EXPORT_METHOD_SYNC 将同步方法暴露给 js 端  
WX_EXPORT_METHOD_SYNC(@selector(testSyncFunc:))

3. 注册 module

在初始化 DCUniMPSDKEngine 方法后,进行 module 的注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    ...  
    // 初始化引擎  
    [DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];  

    // 注册 module 注:module的 Name 需要保证唯一, class:为 module 的类名  
    [WXSDKEngine registerModule:@"TestModule" withClass:NSClassFromString(@"TestModule")];  

    return YES;  
}

到此,我们已经完成了一个简单的 module 扩展

4. 在 uni小程序 中调用 module 方法

module 支持在 vue 和 nvue 中使用

<template>  
    <div>  
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>  
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>  
    </div>  
</template>  

<script>  
    // 获取 module   
    var testModule = uni.requireNativePlugin("TestModule")  
    export default {  
        methods: {  
            testAsyncFunc() {  
                // 调用异步方法  
                testModule.testAsyncFunc({  
                        'name': 'unimp',  
                        'age': 1  
                    },  
                    (ret) => {  
                        console.log(ret)  
                    })  
            },  
            testSyncFunc() {  
                // 调用同步方法  
                var ret = testModule.testSyncFunc({  
                    'name': 'unimp',  
                    'age': 1  
                })  
            }  
        }  
    }  
</script>  

然后可以导出 uni小程序资源,导入到 App 中查看效果;

扩展组件 component

下面以TestComponent为例,源码请查看 uni小程序SDK 包中的示例 demo 工程;

1. 新建TestComponent类,继承WXComponent类。如果这个类里什么代码也不写,它和默认的的 view 组件能力是一致的

#import "WXComponent.h"  

@interface TestComponent : WXComponent  

@end  

2. 覆写 WXComponent 中的生命周期方法

- loadView 方法

一个组件默认对应于一个原生 view,如果未覆盖loadView提供自定义 view,基类会默认返回一个继承于 UIView 的实例。比如我们要实现一个组件支持地图功能,我们可以返回系统的 MKMapView

- (UIView *)loadView {  
    return [MKMapView new];  
}

- viewDidLoad

对组件 view 需要做一些配置,比如设置 delegate,可以在 viewDidLoad 生命周期做。如果当前 view 没有添加 subview 的话,不要设置 view 的 frame,WeexSDK 会根据 style 进行排版后设置。

- (void)viewDidLoad {  
      ((MKMapView*)self.view).delegate = self;  
}

3. 注册组件

在初始化 DCUniMPSDKEngine 方法后,进行 component 的注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
    ...  
    // 初始化引擎  
    [DCUniMPSDKEngine initSDKEnvironmentWihtLaunchOptions:options];  

    // 注册 component 注:component 的 Name 需要保证唯一, class:为 component 的类名  
    [WXSDKEngine registerComponent:@"testmap" withClass:NSClassFromString(@"TestMapComponent")];  

    return YES;  
}

4. 在uni小程序代码中使用组件

注意:扩展的 component 只能在 nvue 文件中使用

<template>  
    <div>  
        <testmap style="width:200px;height:200px"></testmap>  
    </div>  
</template>

自定义事件

1. 对于每个组件默认提供了一些事件能力,如点击等。假如想给我们的地图组件提供 mapLoaded 事件。

在uni小程序代码中,通过 @事件名="方法名" 添加事件,如下添加mapLoaded 事件

<template>  
    <div>  
        <testmap style="width:200px;height:200px" @mapLoaded="onMapLoaded"></testmap>  
    </div>  
</template>  

<script>  
export default {  
    methods: {  
        onMapLoaded:function(e) {  
            console.log("map loaded"+JSON.stringify(e))  
        }  
    }  
}  
</script>
2. 原生端实现:覆盖组件生命周期方法,记录事件是否需要处理

我们需要额外添加一个 BOOL 成员 mapLoadedEvent 用来记录该事件是否生效。

/// 前端注册的事件会调用此方法  
/// @param eventName 事件名称  
- (void)addEvent:(NSString *)eventName {  
    if ([eventName isEqualToString:@"mapLoaded"]) {  
        _mapLoadedEvent = YES;  
    }  
}  

/// 对应的移除事件回调方法  
/// @param eventName 事件名称  
- (void)removeEvent:(NSString *)eventName {  
    if ([eventName isEqualToString:@"mapLoaded"]) {  
        _mapLoadedEvent = NO;  
    }  
}
3. 给前端发送事件
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {  
    if (_mapLoadedEvent) {  
        // 想前端发送事件 params:传给前端的数据  
        [self fireEvent:@"mapLoaded" params:@{@"customKey":@"customValue"} domChanges:nil];  
    }  
}

自定义属性

给我们的地图组件添加一个新的属性showTraffic。在前端代码里可以控制组件是否显示路况信息

<template>  
    <div>  
        <testmap style="width:200px;height:200px" showTraffic="true"></testmap>  
    </div>  
</template>
原生端实现

1. 覆盖组件初始化方法 initWithRef...
给组件添加一个成员变量记录 showTraffic 属性的值,并在 init 方法中初始化。

- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance {  
    if(self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {  
        if (attributes[@"showsTraffic"]) {  
            _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];  
        }  
    }  
    return self;  
}

2. 在生命期事件中记得将属性值同步给地图控件

- (void)viewDidLoad {  
  ((MKMapView*)self.view).showsTraffic = _showsTraffic;  
}

3. 当属性更新时,同步给地图控件

/// 前端更新属性回调方法  
/// @param attributes 更新的属性  
- (void)updateAttributes:(NSDictionary *)attributes {  
    // 解析属性  
    if (attributes[@"showsTraffic"]) {  
        _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];  
        ((MKMapView*)self.view).showsTraffic = _showsTraffic;  
    }  
}

更多的组件生命期方法

组件是由框架管理的,比如创建、布局、渲染、销毁。组件的生命周期方法都是可以重写的,你可以在这些生命周期中去做自己的事情。

方法 描述
initWithRef:type:… 用给定的属性初始化一个component.
layoutDidFinish 在component完成布局时候会调用.
loadView 创建component管理的view.
viewWillLoad 在component的view加载之前会调用.
viewDidLoad 在component的view加载完之后调用.
viewWillUnload 在component的view被释放之前调用.
viewDidUnload 在component的view被释放之后调用.
updateStyles: 在component的style更新时候调用.
updateAttributes: 在component的attribute更新时候调用.
addEvent: 给component添加event的时候调用.
removeEvent: 在event移除的时候调用.

给组件添加方法

1.原生端实现

在组件代码中使用宏 WX_EXPORT_METHOD 暴露原生方法供前端调用

@implementation TestMapComponent  

// 通过 WX_EXPORT_METHOD 将方法暴露给前端  
WX_EXPORT_METHOD(@selector(focus:))  

- (void)focus:(NSDictionary *)options {  
    // options 为前端传递的参数  
    NSLog(@"%@",options);  
}  
@end

2.在uni小程序代码 中调用 focus: 方法。

<template>  
  <testmap ref='mycomponent'></testmap>  
</template>  
<script>  
  module.exports = {  
    created: function() {  
      this.$refs.mycomponent.focus("Hello");  
    }  
  }  
</script>

至此,您已完成组件扩展;

以上示例中的完整uni小程序代码

<template>  
    <div>  
        <button type="primary" @click="testAsyncFunc">testAsyncFunc</button>  
        <button type="primary" @click="testSyncFunc">testSyncFunc</button>  
        <testmap ref='mycomponent' style="width:200px;height:200px" showTraffic="true" @mapLoaded="onMapLoaded"></testmap>  
    </div>  
</template>  

<script>  
    // 获取 module   
    var testModule = uni.requireNativePlugin("TestModule")  
    export default {  
        onLoad() {  
            // 调用组件方法  
            this.$nextTick(()=>{  
                this.$refs.mycomponent.focus("Hello")  
            })  
        },  
        methods: {  
            testAsyncFunc() {  
                // 调用异步方法  
                testModule.testAsyncFunc({  
                        'name': 'unimp',  
                        'age': 1  
                    },  
                    (ret) => {  
                        console.log(ret)  
                    })  
            },  
            testSyncFunc() {  
                // 调用同步方法  
                var ret = testModule.testSyncFunc({  
                    'name': 'unimp',  
                    'age': 1  
                })  
            },  
            onMapLoaded(e) {  
                console.log("map loaded" + JSON.stringify(e))  
            }  
        }  
    }  
</script>
收起阅读 »

iOS平台隐私与政策提示框实现注意问题

通知 iOS 隐私 隐私政策

根据工业和信息化部关于开展APP侵害用户权益专项整治要求,App提交到应用市场必须满足以下条件:

  • 应用启动运行时需弹出隐私政策协议,说明应用采集用户数据
    这里将详细介绍如何配置弹出“隐私协议和政策”提示框
  • 应用不能强制要求用户授予权限,即不能“不给权限不让用”

iOS系统因为系统授权管理比较完善,调用访问涉及到个人隐私信息的接口都会弹出系统授权提示框,并且在提示框上描述应用使用此权限的用途。
因此没有提供Android平台这样的提示框配置方法,需要开发者在应用中自己实现隐私政策提示框。

<a id="collect"/>

数据采集说明

为了持续优化应用及提供统计报表功能,应用在运行过程中会采集以下数据:

  • 应用启动时会采集应用启动时间信息用于优化启动速度,不包含个人隐私信息
  • 应用启动后会采集统计数据用于提供uni统计服务,iOS平台如果用户开启了IDFA则采集数据会包含IDFA信息。uni统计配置方法参考uni-app的manifest.json配置项列表
  • 应用异常时会采集错误日志信息,用于优化改进产品

DCloud通过了国家信息安全等级保护三级,证书编号:11010813802-20001,保障相关数据的安全性
DCloud并非大数据公司,采集的数据是为开发者提供统计服务和产品持续优化,不包含个人隐私相关信息

隐私政策提示框实现

iOS平台没有提供隐私政策模板提示框,所以需要开发者在应用中自己实现隐私政策提示框。

  • uni-app项目
    建议使用nvue页面来实现,渲染速度更快,用于体验更好。
  • 5+ APP(WAP2APP)项目
    简单提示框可以使用系统确认对话框plus.nativeUI.confirm,缺点是无法自定义样式,无法使用链接;
    如果希望自定义隐私政策提示框,可以在Webview中使用html渲染,如在首页使用div渲染、或打开独立的Webview窗口。
    用户点击同意隐私政策时推荐调用plus.runtime.agreePrivacy保存状态,应用启动时调用plus.runtime.isAgreePrivacy获取用户是否已经同意。

系统授权提示框处理

如果应用第一次启动时会弹出系统授权提示框,可以参考以下说明处理。

使用无线数据

提交应用启动时间信息等需要用到网络,所以应用第一次启动时可能会弹出“使用无线数据”系统授权框

目前AppStore上大部分应用在显示隐私政策提示框同时会弹出此授权提示款,不会影响上线审核

<a id="push"></a>

发送通知

如果应用使用了uniPush(发送消息通知)功能,在应用启动时会弹出“发送通知”系统授权框

这是因为应用启动时会自动向系统注册要使用消息推送功能引起,如果希望在应用启动时不弹出此授权框,HBuilderX2.6.3+版本可以按以下方法配置配置应用启动时不注册。
在用户接受隐私政策后调用异步获取客户端推送标识信息方法 plus.push.getClientInfoAsync进行注册。

配置方法

打开项目的manifest.json文件,切换到“源码视图”项

  • uni-app项目
    在 "app-plus" -> "distribute" -> "ios" 节点下添加 pushRegisterMode节点
  • 5+ App项目
    在 "plus" -> "distribute" -> "apple" 节点下添加 pushRegisterMode节点

pushRegisterMode字段值设置如下:

        "pushRegisterMode": "manual"

注意plus.push.getClientInfo是同步方法,不会触发向系统注册操作,必须使用异步方法 plus.push.getClientInfoAsync

配置后提交云端打包后生效

本地离线打包参考:https://ask.dcloud.net.cn/article/41#pushRegister

其它授权框

应用启动不会主动调用涉及到系统授权框的功能,但是调用类似定位等功能时也会触发“位置信息”授权框
这时需要修改应用内部业务逻辑,在用户同意隐私政策前不要调用这些API来避免。

继续阅读 »

根据工业和信息化部关于开展APP侵害用户权益专项整治要求,App提交到应用市场必须满足以下条件:

  • 应用启动运行时需弹出隐私政策协议,说明应用采集用户数据
    这里将详细介绍如何配置弹出“隐私协议和政策”提示框
  • 应用不能强制要求用户授予权限,即不能“不给权限不让用”

iOS系统因为系统授权管理比较完善,调用访问涉及到个人隐私信息的接口都会弹出系统授权提示框,并且在提示框上描述应用使用此权限的用途。
因此没有提供Android平台这样的提示框配置方法,需要开发者在应用中自己实现隐私政策提示框。

<a id="collect"/>

数据采集说明

为了持续优化应用及提供统计报表功能,应用在运行过程中会采集以下数据:

  • 应用启动时会采集应用启动时间信息用于优化启动速度,不包含个人隐私信息
  • 应用启动后会采集统计数据用于提供uni统计服务,iOS平台如果用户开启了IDFA则采集数据会包含IDFA信息。uni统计配置方法参考uni-app的manifest.json配置项列表
  • 应用异常时会采集错误日志信息,用于优化改进产品

DCloud通过了国家信息安全等级保护三级,证书编号:11010813802-20001,保障相关数据的安全性
DCloud并非大数据公司,采集的数据是为开发者提供统计服务和产品持续优化,不包含个人隐私相关信息

隐私政策提示框实现

iOS平台没有提供隐私政策模板提示框,所以需要开发者在应用中自己实现隐私政策提示框。

  • uni-app项目
    建议使用nvue页面来实现,渲染速度更快,用于体验更好。
  • 5+ APP(WAP2APP)项目
    简单提示框可以使用系统确认对话框plus.nativeUI.confirm,缺点是无法自定义样式,无法使用链接;
    如果希望自定义隐私政策提示框,可以在Webview中使用html渲染,如在首页使用div渲染、或打开独立的Webview窗口。
    用户点击同意隐私政策时推荐调用plus.runtime.agreePrivacy保存状态,应用启动时调用plus.runtime.isAgreePrivacy获取用户是否已经同意。

系统授权提示框处理

如果应用第一次启动时会弹出系统授权提示框,可以参考以下说明处理。

使用无线数据

提交应用启动时间信息等需要用到网络,所以应用第一次启动时可能会弹出“使用无线数据”系统授权框

目前AppStore上大部分应用在显示隐私政策提示框同时会弹出此授权提示款,不会影响上线审核

<a id="push"></a>

发送通知

如果应用使用了uniPush(发送消息通知)功能,在应用启动时会弹出“发送通知”系统授权框

这是因为应用启动时会自动向系统注册要使用消息推送功能引起,如果希望在应用启动时不弹出此授权框,HBuilderX2.6.3+版本可以按以下方法配置配置应用启动时不注册。
在用户接受隐私政策后调用异步获取客户端推送标识信息方法 plus.push.getClientInfoAsync进行注册。

配置方法

打开项目的manifest.json文件,切换到“源码视图”项

  • uni-app项目
    在 "app-plus" -> "distribute" -> "ios" 节点下添加 pushRegisterMode节点
  • 5+ App项目
    在 "plus" -> "distribute" -> "apple" 节点下添加 pushRegisterMode节点

pushRegisterMode字段值设置如下:

        "pushRegisterMode": "manual"

注意plus.push.getClientInfo是同步方法,不会触发向系统注册操作,必须使用异步方法 plus.push.getClientInfoAsync

配置后提交云端打包后生效

本地离线打包参考:https://ask.dcloud.net.cn/article/41#pushRegister

其它授权框

应用启动不会主动调用涉及到系统授权框的功能,但是调用类似定位等功能时也会触发“位置信息”授权框
这时需要修改应用内部业务逻辑,在用户同意隐私政策前不要调用这些API来避免。

收起阅读 »

三天上线社区防疫人员管理小程序,阿里小程序云联手开发者科技抗疫

uniCloud uniapp

2020年春节伊始,一场新型冠状病毒的疫情突如其来。病毒猖獗,传染性极强。截止北京时间 2月11日,全国确诊新型冠状病毒肺炎的人数已达到了42714例,死亡1017例,疑似患病人数高达21675例!

战疫情,阿里巴巴在行动

面对不断攀升的人数,面对严峻的疫情,阿里巴巴集团义不容辞,承担起了社会责任,第一时间设立了10亿元医疗物资供给专项基金,马云公益基金会捐赠1亿元用于支持新型冠状病毒的疫苗研发。“武汉加油”公益筹款仅8小时就筹集了7140万善款,并通过绿色通道确保物资第一时间送达武汉。

01

战疫情,阿里云小程序云在行动

疫情之下,国家延长了假期,但却有无数的医生、护士、工人、快递员、外卖小哥、网约车司机等成为了最美逆行者,冲到第一线浴血奋战。阿里人作为中国科技互联网人,责无旁贷。虽身不在一线,却始终心系武汉和全国。目前仍是疫情防控的关键时期,即将到来的返程高峰大大增加了疫情防控难度。 现各省市的街道、小区、园区和写字楼等都加强了对流动人员的测温和登记。民政部陈司长发言:“一个有益的公益软件,比捐10个亿还管用!”

鉴于此,阿里云小程序云和DCloud共同发起了安全登记应用的开源项目。开发者们在线出动,仅3天就基于小程序Serverless开发出了一款社区防疫人员管理小程序——人员出入管理工具。满足疫期人员出入管理的需求,帮助企事业单位、医院、卫生委、交通、教育等部门应对即将到来的返程高峰。在后方用科技的力量支援这场没有硝烟的战争。

小程序云

小程序云(Mini Program Cloud)是阿里云面向小程序场景提供的一站式云服务,帮助开发者实现一云多端的业务战略,提供了有服务器和无服务器两种模式。云应用是有服务器模式,提供了包括资源编排、应用托管等服务。小程序Serverless是无服务模式,提供了开发、运营、业务增值等服务。跨端开发工具链为开发者提供了一次开发全网小程序运行的能力,并在一朵云内实现统一的资源管理、统一的数据运营和统一的业务设计。

DCloud

DCloud为开发者提供包括HBuilder、uni-app等开发工具,帮助开发者快速、低成本制作移动互联网多端应用。使用uni-app,可以同时发布为Android App、iOS App、H5网页、以及微信/支付宝/百度/头条/QQ小程序。DCloud旗下产品月活设备规模4.5亿。

战疫情,社区防疫人员出入管理工具问世

研发初衷

由于新型肺炎潜伏期长则14天, 且可通过接触传播,在外来人员登记时如使用纸笔择优交叉感染的可能。因此阿里云开发者们使用小程序云快速开发出通用的出入人员登记应用,无纸化管理,即管理员生成二维码并张贴后,来访人员即可扫码快速填写表单记录体温。降低交叉感染的同时,更便于数据管理。

系统介绍

小程序云人员出入管理工具(链接https://sfsi-wlry.m3w.cn/ )既可减少交叉感染,又具有感染风险预警的功能,同时支持数据管理、跨端支持和弹性扩容,即使人流返程高峰也可安心使用。

1.用户登记界面

扫码即填信息,高效便捷

2.登记人员列表

小程序云人员出入管理工具,联合火遍全网的肺炎确诊患者同行查询工具的开发团队,使用了该工具了数据查询的API。使用人员登记应用时如填写行程信息,系统还会检测是否和已确诊的患者行程有交集,如有重合则会标记预警。例如标红的用户与确诊感染者行程有交集,进一步提升了管理效率。

3.搜索页面

可快速按姓名搜索信息

4.管理员页面

管理员可操作页面,含二维码。

这个项目自2月4日正式发布,天津滨海区的三个工业园区率先使用,利用门卫老大爷的话说,我们的应用减少了他们和来访者的接触,降低了他们感染的风险。

2月5日,看到阿里云官网的疫情专题后,又先后有内蒙古通辽市巴鲁县公安局、甘肃省电力研究所的工作人员联系到我们,表示很想应用我们的登记小程序。宁波市大榭开发区海韵社区的工作人员更是表示他们想把这个工具应用到所辖的所有网格。目前已对接了来自内蒙、甘肃、海南、云南等19个省份,40个社区、企事业单位的出入登记应用需求。

小程序云Serverless为开发者提供了一键构建后端应用运行环境、后端服务部署、运维监控等能力的一站式小程序部署服务。针对此次突发疫情,基于小程序云Serverless,目前已经在短短几天时间内快速上线了5款疫情管理工具,除人员出入管理系统外,还包括学生健康报备管理系统、员工疫情筛查工具、物资管理系统以及消毒检查登记系统。这些项目面向开发者完全开源,开发者可以基于这些项目构建更多的防疫工具。开源项目链接:小程序Serverless防疫小程序最佳实践。

此外,阿里云联合支付宝一起发起了抗疫情的扶持计划和公益开发者联盟。开放生态面向开发者提供七大激励政策,包括为优质开发者提供50万元现金激励,免费试用云资源和小程序模板3个月、免费使用数据可视化产品3个月等,也欢迎广大开发者加入公益开发者联盟。

能力越大,责任越大。经历过非典,正能量的阿里人为战胜疫情不断用代码铸成新的长城,日夜奋战,保卫家国,祈祷疫情早日过去。

武汉加油!中国加油!

继续阅读 »

2020年春节伊始,一场新型冠状病毒的疫情突如其来。病毒猖獗,传染性极强。截止北京时间 2月11日,全国确诊新型冠状病毒肺炎的人数已达到了42714例,死亡1017例,疑似患病人数高达21675例!

战疫情,阿里巴巴在行动

面对不断攀升的人数,面对严峻的疫情,阿里巴巴集团义不容辞,承担起了社会责任,第一时间设立了10亿元医疗物资供给专项基金,马云公益基金会捐赠1亿元用于支持新型冠状病毒的疫苗研发。“武汉加油”公益筹款仅8小时就筹集了7140万善款,并通过绿色通道确保物资第一时间送达武汉。

01

战疫情,阿里云小程序云在行动

疫情之下,国家延长了假期,但却有无数的医生、护士、工人、快递员、外卖小哥、网约车司机等成为了最美逆行者,冲到第一线浴血奋战。阿里人作为中国科技互联网人,责无旁贷。虽身不在一线,却始终心系武汉和全国。目前仍是疫情防控的关键时期,即将到来的返程高峰大大增加了疫情防控难度。 现各省市的街道、小区、园区和写字楼等都加强了对流动人员的测温和登记。民政部陈司长发言:“一个有益的公益软件,比捐10个亿还管用!”

鉴于此,阿里云小程序云和DCloud共同发起了安全登记应用的开源项目。开发者们在线出动,仅3天就基于小程序Serverless开发出了一款社区防疫人员管理小程序——人员出入管理工具。满足疫期人员出入管理的需求,帮助企事业单位、医院、卫生委、交通、教育等部门应对即将到来的返程高峰。在后方用科技的力量支援这场没有硝烟的战争。

小程序云

小程序云(Mini Program Cloud)是阿里云面向小程序场景提供的一站式云服务,帮助开发者实现一云多端的业务战略,提供了有服务器和无服务器两种模式。云应用是有服务器模式,提供了包括资源编排、应用托管等服务。小程序Serverless是无服务模式,提供了开发、运营、业务增值等服务。跨端开发工具链为开发者提供了一次开发全网小程序运行的能力,并在一朵云内实现统一的资源管理、统一的数据运营和统一的业务设计。

DCloud

DCloud为开发者提供包括HBuilder、uni-app等开发工具,帮助开发者快速、低成本制作移动互联网多端应用。使用uni-app,可以同时发布为Android App、iOS App、H5网页、以及微信/支付宝/百度/头条/QQ小程序。DCloud旗下产品月活设备规模4.5亿。

战疫情,社区防疫人员出入管理工具问世

研发初衷

由于新型肺炎潜伏期长则14天, 且可通过接触传播,在外来人员登记时如使用纸笔择优交叉感染的可能。因此阿里云开发者们使用小程序云快速开发出通用的出入人员登记应用,无纸化管理,即管理员生成二维码并张贴后,来访人员即可扫码快速填写表单记录体温。降低交叉感染的同时,更便于数据管理。

系统介绍

小程序云人员出入管理工具(链接https://sfsi-wlry.m3w.cn/ )既可减少交叉感染,又具有感染风险预警的功能,同时支持数据管理、跨端支持和弹性扩容,即使人流返程高峰也可安心使用。

1.用户登记界面

扫码即填信息,高效便捷

2.登记人员列表

小程序云人员出入管理工具,联合火遍全网的肺炎确诊患者同行查询工具的开发团队,使用了该工具了数据查询的API。使用人员登记应用时如填写行程信息,系统还会检测是否和已确诊的患者行程有交集,如有重合则会标记预警。例如标红的用户与确诊感染者行程有交集,进一步提升了管理效率。

3.搜索页面

可快速按姓名搜索信息

4.管理员页面

管理员可操作页面,含二维码。

这个项目自2月4日正式发布,天津滨海区的三个工业园区率先使用,利用门卫老大爷的话说,我们的应用减少了他们和来访者的接触,降低了他们感染的风险。

2月5日,看到阿里云官网的疫情专题后,又先后有内蒙古通辽市巴鲁县公安局、甘肃省电力研究所的工作人员联系到我们,表示很想应用我们的登记小程序。宁波市大榭开发区海韵社区的工作人员更是表示他们想把这个工具应用到所辖的所有网格。目前已对接了来自内蒙、甘肃、海南、云南等19个省份,40个社区、企事业单位的出入登记应用需求。

小程序云Serverless为开发者提供了一键构建后端应用运行环境、后端服务部署、运维监控等能力的一站式小程序部署服务。针对此次突发疫情,基于小程序云Serverless,目前已经在短短几天时间内快速上线了5款疫情管理工具,除人员出入管理系统外,还包括学生健康报备管理系统、员工疫情筛查工具、物资管理系统以及消毒检查登记系统。这些项目面向开发者完全开源,开发者可以基于这些项目构建更多的防疫工具。开源项目链接:小程序Serverless防疫小程序最佳实践。

此外,阿里云联合支付宝一起发起了抗疫情的扶持计划和公益开发者联盟。开放生态面向开发者提供七大激励政策,包括为优质开发者提供50万元现金激励,免费试用云资源和小程序模板3个月、免费使用数据可视化产品3个月等,也欢迎广大开发者加入公益开发者联盟。

能力越大,责任越大。经历过非典,正能量的阿里人为战胜疫情不断用代码铸成新的长城,日夜奋战,保卫家国,祈祷疫情早日过去。

武汉加油!中国加油!

收起阅读 »

uniapp项目在支付宝开发工具打开报错,未识别项目类型,无法打开问题的解决

uniapp 支付宝小程序


原因是未配置支付宝小程序开发工具在hbuildx中的运行路径,方法如下:

然后在支付宝开发工具内运行项目,一定要选中到mp-alipay目录位置,然后运行即可,我的路径如下,供参考
alipay_applet-->unpackage-->dist-->dev--mp-alipay

继续阅读 »


原因是未配置支付宝小程序开发工具在hbuildx中的运行路径,方法如下:

然后在支付宝开发工具内运行项目,一定要选中到mp-alipay目录位置,然后运行即可,我的路径如下,供参考
alipay_applet-->unpackage-->dist-->dev--mp-alipay

收起阅读 »

uni.scanCode 识别率低

附件的二维码 barcode 识别不了 uni.scanCode 也识别不了 但是微信扫码有结果 希望尽快升级基座

附件的二维码 barcode 识别不了 uni.scanCode 也识别不了 但是微信扫码有结果 希望尽快升级基座

BookChatApp v1.3 发布,uni-app 开发的开源书籍阅读APP

uniapp

BookChatApp 介绍

BookChatApp,通用的书籍阅读 APP,微信小程序BookChatuni-app 实现版本;不但拥有简洁美观的UI视觉呈现,还拥有堪比原生应用的性能体验,支持多端分发编译生成 Android 和 iOS 手机 APP 以及各平台小程序。

使用 BookChatApp,您可以轻松地将您的电子书、文档手册和在线书籍分发到各个小程序平台,以及编译生成手机 APP 发布到各大手机软件市场供用户下载和使用。

升级日志

  • [x] 增加签到功能
  • [x] 增加榜单功能
  • [x] 图书封面样式调整
  • [x] iOSUIWebView更换为WKWebView
  • [x] 自定义header,增加home键和搜索按钮
  • [x] 书籍介绍页面显示书籍标签,以便用户快捷搜索
  • [x] 收藏到书架的书籍,点击的时候直接跳转到书籍内容阅读页
  • [x] 修复用户退出再用另外的账号登录的时候,书架不更新的问题
  • [x] 用户个人中心,显示个人相关阅读时长等相关个人学习成就数据
  • [x] 修复内容阅读设置,恢复默认值时,屏幕亮度没有跟着恢复的问题
  • [x] 书籍列表、分类列表增加懒加载功能(之前懒加载使用姿势不正确)
  • [x] 解决类似mgt-30upx等class样式不生效的问题,将此类class名更换为mgt-30
  • [x] 使用localstorage的方式存储app.globalData,以避免h5下的一系列小问题

注意事项

  1. 不同版本的HBuilderX开发,可能展现页面效果不一样,甚至可能会错乱。本版本APP,使用HBuilderX v2.5.1 进行开发。
  2. 编译成手机APP的时候,不要使用uni-app的 v3 模式进行编译,因为v3刚出来,技术感觉还不是很成熟,使用v3模式编译的时候,APP阅读页面的内容渲染会出现错乱。
  3. 当前1.3版本需配套后端程序BookStack v2.5 版本一起使用: https://gitee.com/truthhun/BookStack/releases (明天发布)

相关链接

BookChatApp 开源地址

  • Gitee: https://gitee.com/truthhun/BookChatApp
  • GitHub: https://github.com/truthhun/BookChatApp

BookChatApp 官网:https://www.bookstack.cn

BookChatApp 下载体验

BookChatApp

目前BookChatApp iOS 版本已经上架APP Store,可以在APP Store 搜索BookChatBookChatApp进行下载体验。

Android 版本并未上架到各大Android市场,临时下载体验地址: https://www.bookstack.cn/app

继续阅读 »

BookChatApp 介绍

BookChatApp,通用的书籍阅读 APP,微信小程序BookChatuni-app 实现版本;不但拥有简洁美观的UI视觉呈现,还拥有堪比原生应用的性能体验,支持多端分发编译生成 Android 和 iOS 手机 APP 以及各平台小程序。

使用 BookChatApp,您可以轻松地将您的电子书、文档手册和在线书籍分发到各个小程序平台,以及编译生成手机 APP 发布到各大手机软件市场供用户下载和使用。

升级日志

  • [x] 增加签到功能
  • [x] 增加榜单功能
  • [x] 图书封面样式调整
  • [x] iOSUIWebView更换为WKWebView
  • [x] 自定义header,增加home键和搜索按钮
  • [x] 书籍介绍页面显示书籍标签,以便用户快捷搜索
  • [x] 收藏到书架的书籍,点击的时候直接跳转到书籍内容阅读页
  • [x] 修复用户退出再用另外的账号登录的时候,书架不更新的问题
  • [x] 用户个人中心,显示个人相关阅读时长等相关个人学习成就数据
  • [x] 修复内容阅读设置,恢复默认值时,屏幕亮度没有跟着恢复的问题
  • [x] 书籍列表、分类列表增加懒加载功能(之前懒加载使用姿势不正确)
  • [x] 解决类似mgt-30upx等class样式不生效的问题,将此类class名更换为mgt-30
  • [x] 使用localstorage的方式存储app.globalData,以避免h5下的一系列小问题

注意事项

  1. 不同版本的HBuilderX开发,可能展现页面效果不一样,甚至可能会错乱。本版本APP,使用HBuilderX v2.5.1 进行开发。
  2. 编译成手机APP的时候,不要使用uni-app的 v3 模式进行编译,因为v3刚出来,技术感觉还不是很成熟,使用v3模式编译的时候,APP阅读页面的内容渲染会出现错乱。
  3. 当前1.3版本需配套后端程序BookStack v2.5 版本一起使用: https://gitee.com/truthhun/BookStack/releases (明天发布)

相关链接

BookChatApp 开源地址

  • Gitee: https://gitee.com/truthhun/BookChatApp
  • GitHub: https://github.com/truthhun/BookChatApp

BookChatApp 官网:https://www.bookstack.cn

BookChatApp 下载体验

BookChatApp

目前BookChatApp iOS 版本已经上架APP Store,可以在APP Store 搜索BookChatBookChatApp进行下载体验。

Android 版本并未上架到各大Android市场,临时下载体验地址: https://www.bookstack.cn/app

收起阅读 »

微信公众号利用JSSDK选择图片并上传的分享

微信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 原生功能模块

小程序SDK unimpsdk uni小程序sdk

此文档以过期,请访问 新的文档

uni小程序SDK 提供了丰富的原生能力,如果您在小程序中调用了相关模块的 API 但是原生工程没有添加相关依赖的时候会出现如下提示


按照下面的教程将依赖库添加到原生工程中即可
您可根据需求自行添加功能模块,各功能模块所需的依赖库及资源文件,在UniMPSDK/Features目录中

目录结构

|-- UniMPSDK/Features  
    |-- Feature-iOS.xls // 功能模块配置表  
    |-- inc             // 框架使用的第三库 .h 头文件  
    |-- Libs                // 各功能模块的依赖库存放位置  
    |-- Resources           // 资源文件

请参考 Feature-iOS.xls 配置表,添加模块所需依赖库及资源文件即可;

集成方式

下面以 Gallery 模块为例

首先查看配置文件,需要添加 liblibCamera.aAssetsLibrary.framework 依赖库,及 TZImagePickerController.bundle 资源文件

添加依赖库

将 UniMPSDK/Features/Lib 中的 liblibCamera.a库及AssetsLibrary.framework 系统库,添加到工程的 TARGETS->Build Phases-> Link Binary With Libaries 中;

添加依赖资源文件

然后将 UniMPSDK/Features/Resources 中的TZImagePickerController.bundle 资源文件添加到工程中

然后即可调用 Gallery 相关功能;

功能模块与 API 对应关系

功能模块 5+ API uni-app API
Accelerometer(加速度传感器) plus.accelerometer https://uniapp.dcloud.io/api/system/compass
Audio(音频) plus.audio https://uniapp.dcloud.io/api/media/record-manager https://uniapp.dcloud.io/api/media/audio-context
Barcode(二维码) plus.barcode https://uniapp.dcloud.io/api/system/barcode
Bluetooth(低功耗蓝牙) plus.bluetooth https://uniapp.dcloud.io/api/system/bluetooth
Camera(摄像头) plus.camera https://uniapp.dcloud.io/api/media/image
Contacts(通讯录) plus.contacts https://uniapp.dcloud.io/api/system/contact
Fingerprint(指纹识别) plus.fingerprint https://uniapp.dcloud.io/api/other/authentication
Geolocation(系统定位) plus.geolocation https://uniapp.dcloud.io/api/location/location
Geolocation(百度定位) plus.geolocation https://uniapp.dcloud.io/api/location/location
iBeacon plus.ibeacon https://uniapp.dcloud.io/api/system/ibeacon
IO(文件系统) plus.io https://uniapp.dcloud.io/api/file/file
Maps(地图基础库) plus.map https://uniapp.dcloud.io/api/location/map
Maps(高德德图) plus.map https://uniapp.dcloud.io/api/location/map
Maps(百度地图) plus.map https://uniapp.dcloud.io/api/location/map
Messaging(短彩邮件消息) plus.messaging
Oauth(登录基础库) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(小米登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(QQ登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(新浪微博登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(微信登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Orientation(设备方向) plus.orientation
Payment(支付基础库) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Payment(支付宝支付) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Payment(苹果应用内支付) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Payment(微信支付) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Push(推送基础库) plus.push https://uniapp.dcloud.io/api/plugins/push
Push(个推推送) plus.push https://uniapp.dcloud.io/api/plugins/push
Push(UniPush推送) plus.push https://uniapp.dcloud.io/api/plugins/push
Proximity(距离传感器) plus.proximity
Share(分享基础库) plus.share https://uniapp.dcloud.io/api/plugins/share
Share(QQ分享) plus.share https://uniapp.dcloud.io/api/plugins/share
Share(新浪微博分享) plus.share https://uniapp.dcloud.io/api/plugins/share
Share(微信分享) plus.share https://uniapp.dcloud.io/api/plugins/share
Speech(语音识别基础库) plus.speech https://uniapp.dcloud.io/api/plugins/voice
Speech(百度语音识别) plus.speech https://uniapp.dcloud.io/api/plugins/voice
Speech(讯飞语音识别) plus.speech https://uniapp.dcloud.io/api/plugins/voice
Statistic(友盟统计) plus.statistic
VideoPlayer(视频播放) plus.video.VideoPlayer https://uniapp.dcloud.io/api/media/video
XHR(网络请求) plus.net https://uniapp.dcloud.io/api/request/request?id=request
Zip(文件压缩和解压) plus.zip
nvue原生组件: barcode(二维码) 不支持 https://uniapp.dcloud.io/component/barcode
nvue原生组件: map(地图基础库) 不支持 https://uniapp.dcloud.io/component/map
nvue原生组件: map(高德地图) 不支持 https://uniapp.dcloud.io/component/map
nvue原生组件: video(视频) 不支持 https://uniapp.dcloud.io/component/video
nvue原生组件: canvas 不支持 https://github.com/dcloudio/NvueCanvasDemo
nvue原生模块: FaceID 不支持 https://uniapp.dcloud.io/api/other/authentication
继续阅读 »

此文档以过期,请访问 新的文档

uni小程序SDK 提供了丰富的原生能力,如果您在小程序中调用了相关模块的 API 但是原生工程没有添加相关依赖的时候会出现如下提示


按照下面的教程将依赖库添加到原生工程中即可
您可根据需求自行添加功能模块,各功能模块所需的依赖库及资源文件,在UniMPSDK/Features目录中

目录结构

|-- UniMPSDK/Features  
    |-- Feature-iOS.xls // 功能模块配置表  
    |-- inc             // 框架使用的第三库 .h 头文件  
    |-- Libs                // 各功能模块的依赖库存放位置  
    |-- Resources           // 资源文件

请参考 Feature-iOS.xls 配置表,添加模块所需依赖库及资源文件即可;

集成方式

下面以 Gallery 模块为例

首先查看配置文件,需要添加 liblibCamera.aAssetsLibrary.framework 依赖库,及 TZImagePickerController.bundle 资源文件

添加依赖库

将 UniMPSDK/Features/Lib 中的 liblibCamera.a库及AssetsLibrary.framework 系统库,添加到工程的 TARGETS->Build Phases-> Link Binary With Libaries 中;

添加依赖资源文件

然后将 UniMPSDK/Features/Resources 中的TZImagePickerController.bundle 资源文件添加到工程中

然后即可调用 Gallery 相关功能;

功能模块与 API 对应关系

功能模块 5+ API uni-app API
Accelerometer(加速度传感器) plus.accelerometer https://uniapp.dcloud.io/api/system/compass
Audio(音频) plus.audio https://uniapp.dcloud.io/api/media/record-manager https://uniapp.dcloud.io/api/media/audio-context
Barcode(二维码) plus.barcode https://uniapp.dcloud.io/api/system/barcode
Bluetooth(低功耗蓝牙) plus.bluetooth https://uniapp.dcloud.io/api/system/bluetooth
Camera(摄像头) plus.camera https://uniapp.dcloud.io/api/media/image
Contacts(通讯录) plus.contacts https://uniapp.dcloud.io/api/system/contact
Fingerprint(指纹识别) plus.fingerprint https://uniapp.dcloud.io/api/other/authentication
Geolocation(系统定位) plus.geolocation https://uniapp.dcloud.io/api/location/location
Geolocation(百度定位) plus.geolocation https://uniapp.dcloud.io/api/location/location
iBeacon plus.ibeacon https://uniapp.dcloud.io/api/system/ibeacon
IO(文件系统) plus.io https://uniapp.dcloud.io/api/file/file
Maps(地图基础库) plus.map https://uniapp.dcloud.io/api/location/map
Maps(高德德图) plus.map https://uniapp.dcloud.io/api/location/map
Maps(百度地图) plus.map https://uniapp.dcloud.io/api/location/map
Messaging(短彩邮件消息) plus.messaging
Oauth(登录基础库) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(小米登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(QQ登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(新浪微博登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Oauth(微信登录) plus.oauth https://uniapp.dcloud.io/api/plugins/login
Orientation(设备方向) plus.orientation
Payment(支付基础库) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Payment(支付宝支付) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Payment(苹果应用内支付) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Payment(微信支付) plus.payment https://uniapp.dcloud.io/api/plugins/payment
Push(推送基础库) plus.push https://uniapp.dcloud.io/api/plugins/push
Push(个推推送) plus.push https://uniapp.dcloud.io/api/plugins/push
Push(UniPush推送) plus.push https://uniapp.dcloud.io/api/plugins/push
Proximity(距离传感器) plus.proximity
Share(分享基础库) plus.share https://uniapp.dcloud.io/api/plugins/share
Share(QQ分享) plus.share https://uniapp.dcloud.io/api/plugins/share
Share(新浪微博分享) plus.share https://uniapp.dcloud.io/api/plugins/share
Share(微信分享) plus.share https://uniapp.dcloud.io/api/plugins/share
Speech(语音识别基础库) plus.speech https://uniapp.dcloud.io/api/plugins/voice
Speech(百度语音识别) plus.speech https://uniapp.dcloud.io/api/plugins/voice
Speech(讯飞语音识别) plus.speech https://uniapp.dcloud.io/api/plugins/voice
Statistic(友盟统计) plus.statistic
VideoPlayer(视频播放) plus.video.VideoPlayer https://uniapp.dcloud.io/api/media/video
XHR(网络请求) plus.net https://uniapp.dcloud.io/api/request/request?id=request
Zip(文件压缩和解压) plus.zip
nvue原生组件: barcode(二维码) 不支持 https://uniapp.dcloud.io/component/barcode
nvue原生组件: map(地图基础库) 不支持 https://uniapp.dcloud.io/component/map
nvue原生组件: map(高德地图) 不支持 https://uniapp.dcloud.io/component/map
nvue原生组件: video(视频) 不支持 https://uniapp.dcloud.io/component/video
nvue原生组件: canvas 不支持 https://github.com/dcloudio/NvueCanvasDemo
nvue原生模块: FaceID 不支持 https://uniapp.dcloud.io/api/other/authentication
收起阅读 »

uni小程序SDK 概述

小程序SDK unimpsdk uni小程序sdk

uni小程序SDK原生开发者交流QQ群: 892918401

请移步查看 新的文档,此文档已过期

uni小程序SDK

概述

uni小程序SDK,是一个用于原生App中集成的SDK,它可以帮助原生App快速实现小程序的能力。

效果如下:

基本概念

  • 宿主:想要构建小程序的原生App,集成uni小程序SDK的宿主
  • 小程序:运行在宿主中的前端代码编写的小程序,使用uni-app框架开发
    注意:
    1. uni小程序SDK仅支持使用uni-app开发的小程序,不支持纯wxml微信小程序运行。但uni-app支持使用wxml格式的小程序组件。
    2. uni小程序SDK仅支持uni-app的v3编译器。使用老版的开发者,需首先保障应用可以运行在v3编译模式下。

集成流程

  1. 宿主开发者下载uni小程序sdk,根据文档集成到宿主App中
  2. 小程序开发者使用uni-app框架开发,通过运行到手机App进行测试,开发完毕后在发行菜单导出wgt包
  3. 宿主开发者将wgt集成到宿主App中,可离线集成直接打包进去,也可以在线下载wgt
  4. 通过uni小程序sdk的api,可以调起wgt,执行为一个小程序
  5. 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插件市场有大量丰富的插件。

应用场景

  1. 宿主App构建自己的应用生态,可以面向广泛开发者,也可以定向部分开发者
  2. 原生App利用小程序模式对模块解耦,让不同模块的开发团队各自独立发版,灵活更新
  3. 原生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框架开发
    注意:
    1. uni小程序SDK仅支持使用uni-app开发的小程序,不支持纯wxml微信小程序运行。但uni-app支持使用wxml格式的小程序组件。
    2. uni小程序SDK仅支持uni-app的v3编译器。使用老版的开发者,需首先保障应用可以运行在v3编译模式下。

集成流程

  1. 宿主开发者下载uni小程序sdk,根据文档集成到宿主App中
  2. 小程序开发者使用uni-app框架开发,通过运行到手机App进行测试,开发完毕后在发行菜单导出wgt包
  3. 宿主开发者将wgt集成到宿主App中,可离线集成直接打包进去,也可以在线下载wgt
  4. 通过uni小程序sdk的api,可以调起wgt,执行为一个小程序
  5. 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插件市场有大量丰富的插件。

应用场景

  1. 宿主App构建自己的应用生态,可以面向广泛开发者,也可以定向部分开发者
  2. 原生App利用小程序模式对模块解耦,让不同模块的开发团队各自独立发版,灵活更新
  3. 原生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 教程

小程序SDK iOS小程序SDK uni小程序 unimpsdk

uni小程序SDK原生开发者交流QQ群: 892918401

请移步 新的文档。此文档以停止更新

开发环境

集成方法

首先您需要一个 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 项目,已有项目或 新建一个 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 更新日志

小程序SDK MiniSDK

请移步 新的文档,此文档已停止更新

2020年04月21日发布

点击下载SDK UniMP_iOS_SDK@2.6.15.20200421

  • 需使用HBuilderX(2.6.15)版本生成 小程序App 资源
  • 其他更新 详情

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方法(用于启动直达二级页面)iOSAndroid
  • 新增 获取已部署的小程序资源版本信息方法 iOSAndroid
  • 新增 胶囊按钮添加点击效果
  • 开放 小程序内部调用 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 资源。

2020年04月21日发布

点击下载SDK UniMP_ANDROID_SDK@2.6.15.20200421.zip

  • 需使用HBuilderX(2.6.15)版本生成 小程序App 资源。
  • 其他更新 详情

历史版本

链接: https://pan.baidu.com/s/1Gb19IMm2ihRA0u4MNzCT4Q 提取码: hnug

  1. 部分功能开源地址见:iOSAndroid
  2. 还有部分源码未提交GitHub,如需获取全部源代码,请发邮件到bd@dcloud.io联系。获取源码无需付费,但目前仅针对大型开发商提供源码。
继续阅读 »

请移步 新的文档,此文档已停止更新

2020年04月21日发布

点击下载SDK UniMP_iOS_SDK@2.6.15.20200421

  • 需使用HBuilderX(2.6.15)版本生成 小程序App 资源
  • 其他更新 详情

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方法(用于启动直达二级页面)iOSAndroid
  • 新增 获取已部署的小程序资源版本信息方法 iOSAndroid
  • 新增 胶囊按钮添加点击效果
  • 开放 小程序内部调用 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 资源。

2020年04月21日发布

点击下载SDK UniMP_ANDROID_SDK@2.6.15.20200421.zip

  • 需使用HBuilderX(2.6.15)版本生成 小程序App 资源。
  • 其他更新 详情

历史版本

链接: https://pan.baidu.com/s/1Gb19IMm2ihRA0u4MNzCT4Q 提取码: hnug

  1. 部分功能开源地址见:iOSAndroid
  2. 还有部分源码未提交GitHub,如需获取全部源代码,请发邮件到bd@dcloud.io联系。获取源码无需付费,但目前仅针对大型开发商提供源码。
收起阅读 »