2016-07-07 18:37:16 qq_27626333 阅读数 6490
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57773 人正在学习 去看看 李宁

1、常用API

1.1、API on Android

  为了能更好的理解NJS调用Java Native API,我们在Android平台用Java实现以下测试类,将在后面API说明中的示例来调用。
文件NjsHello.java代码如下:

<span style="font-family:FangSong_GB2312;">package io.dcloud;
// 定义类NjsHello
public class NjsHello {
    // 静态常量
    public static final int CTYPE = 1;
    // 静态变量
    public static int count;
    // 成员常量
    public final String BIRTHDAY = "2013-01-13";
    // 成员变量
    String name; //定义属性name
    NjsHelloEvent observer;
    public void updateName( String newname ) { //定义方法updateName
        name = newname;
    }
    public void setEventObserver( NjsHelloEvent newobserver ) {
        observer = newobserver;
    }
    public void test() { //定义方法test
        System.out.printf( "My name is: %s", name );
        observer.onEventInvoked( name );
    }
    public static void testCount() {
        System.out.printf( "Static count is:%d", count );
    }
    static{  // 初始化类的静态变量
        NjsHello.count = 0;
    }
}</span>

文件NjsHelloEvent.java代码如下:

<span style="font-family:FangSong_GB2312;">package io.dcloud;
// 定义接口NjsHelloEvent
public interface NjsHelloEvent {
    public void onEventInvoked( String name );
}</span>

注:此NjsHello示例仅为了说明原生代码与NJS代码之间的映射关系,以下示例代码无法直接在HBuilder里真机运行,必须在以后HBuilder开放自定义打包后方可把NjsHello类打入app并被NJS调用。实际使用中,这种需要并非必要,大多数情况可以通过直接写NJS代码调用操作系统API,而无需由原生语言二次封装类供JS调用。

1.1.1、plus.android.importClass

 导入Java类对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">      ClassObject plus.android.importClass( String classname );</span>

导入类对象后,就可以通过“.”操作符直接调用对象(类对象/实例对象)的常量和方法。classname:要导入的Java类名,必须是完整的命名空间(使用"."分割),如果指定的类名不存在,则导入类失败,返回null。

注意:导入类对象可以方便的使用“.”操作符来调用对象的属性和方法,但也会消耗较多的系统资源。因此导入过多的类对象会影响性能,此时可以使用“高级API”中提供的方法在不导入类对象的情况下调用Native API。

示例:
(1)、导入类对象
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 创建对象的实例
    NjsHello hello = new NjsHello();
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 创建NjsHello的实例对象
var hello = new NjsHello();
// ...</span>

ClassObject

调用plus.android.importClass()方法导入类并返回ClassObject类对象,通过该类对象,可以创建类的实例对象。在Java中类的静态方法会转换成NJS类对象的方法,可通过类对象的“.”操作符调用;类的静态常量会转换为NJS类对象的属性,可通过类对象的“.”操作符访问;类的静态属性则需通过NJS类对象的plusGetAttribute、plusSetAttribute方法操作。
示例:

(2)、导入类后获取类的静态常量属性
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 获取类的静态常量属性
    int type = NjsHello.CTYPE;
    System.out.printf( "NjsHello Final's value: %d", type );  // 输出“NjsHello Final's value: 1”
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 获取类的静态常量属性
var type = NjsHello.CTYPE;
console.log( "NjsHello Final's value: "+type ); // 输出“NjsHello Final's value: 1”
// ...</span>

(3)、导入类后调用类的静态方法
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 调用类的静态方法
    NjsHello.testCount();
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 调用类的静态方法
NjsHello.testCount();
// ...</span>

1.1.2、ClassObject.plusGetAttribute

获取类对象的静态属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">Object classobject.plusGetAttribute( String name );</span>

导入类对象后,就可以调用其plusGetAttribute方法获取类的静态属性值。
- name:要获取的静态属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。

注意:如果导入的类对象中存在“plusGetAttribute”同名的静态方法,则必须通过plus.android.invoke()方法调用。

示例:
(1)、 导入类后获取类的静态属性值
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 获取类的静态属性
    int count = NjsHello.count;
    System.out.printf( "NjsHello Static's value: %d", count );  // 输出“NjsHello Static's value: 0”
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 获取类的静态属性
var count = NjsHello.plusGetAttribute( "count" );
console.log( "NjsHello Static's value: "+count ); // 输出“NjsHello Static's value: 0”
// ...</span>

1.1.3、ClassObject.plusSetAttribute

设置类对象的静态属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">void classobject.plusSetAttribute( String name, Object value );</span>

导入类对象后,就可以调用其plusSetAttribute方法设置类的静态属性值。
- name:要设置的静态属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。
- value:要设置的属性值,其类型必须与Native层类对象的静态属性区配,否则设置操作不生效,将保留以前的值。

注意:如果导入的类对象中存在“plusSetAttribute”同名的静态方法,则必须通过plus.android.invoke()方法调用。

示例:
(1)、导入类后设置类的静态属性值
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 设置类的静态属性值
    NjsHello.count = 2;
    System.out.printf( "NjsHello Static's value: %d", NjsHello.count );  // 输出“NjsHello Static's value: 2”
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 设置类的静态属性值
NjsHello.plusSetAttribute( "count", 2 );
console.log( "NjsHello Static's value: "+NjsHello.plusGetAttribute( "count" ) ); // 输出“NjsHello Static's value: 2”
// ...</span>

1.1.4、InstanceObject

NJS中实例对象与Java中的对象对应,调用plus.android.importClass()方法导入类后,通过new操作符可创建该类的实例对象,或直接调用plus.android.newObject方法创建类的实例对象,也可通过调用Native API返回实例对象。在Java中对象的方法会转换成NJS实例对象的方法,可通过实例对象的“.”操作符调用;对象的常量属性会转换NJS实例对象的属性,可通过实例对象的“.”操作符访问。对象的非常量属性则必须通过NJS实例对象的plusGetAttribute、plusSetAttribute方法操作。
示例:
(1)、 导入类创建实例对象,调用对象的方法
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 创建NjsHello的实例对象
    NjsHello hello = new NjsHello();
    // 调用对象的方法
    hello.updateName( "Tester" );
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 创建NjsHello的实例对象
var hello = new NjsHello();
// 调用对象的方法
hello.updateName( "Tester" );
// ...</span>

(2)、导入类创建实例对象,获取对象的常量属性
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 创建NjsHello的实例对象
    NjsHello hello = new NjsHello();
    // 访问对象的常量属性
    String birthday = hello.BIRTHDAY;
    System.out.printf( "NjsHello Object Final's value: %s", birthday );  // 输出“NjsHello Object Final's value: 2013-01-13”
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 创建NjsHello的实例对象
var hello = new NjsHello();
// 访问对象的常量属性
var birthday = hello.BIRTHDAY;
console.log( "NjsHello Object Final's value: "+birthday ); // 输出“NjsHello Object Final's value: 2013-01-13”
// ...</span>

1.1.5、InstanceObject.plusGetAttribute

获取实例对象的属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">            Object instancebject.plusGetAttribute( String name );</span>

获取实例对象后,就可以调用其plusGetAttribute方法获取对象的属性值。
name:要获取对象的属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。

注意:如果实例对象中存在“plusGetAttribute”同名的方法,则必须通过plus.android.invoke()方法调用。

示例:
(1)、 导入类创建实例对象,获取对象的属性值
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 创建对象的实例
    NjsHello hello = new NjsHello();
    hello.updateName( "Tester" );
    // 获取其name属性值
    String name = hello.name;
    System.out.printf( "NjsHello Object's name: %s", name );  // 输出“NjsHello Object's name: Tester”
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 创建对象的实例
var hello = new NjsHello();
hello.updateName( "Tester" );
// 获取其name属性值
var name = hello.plusGetAttribute( "name" );
console.log( "NjsHello Object's name: "+name );  // 输出“NjsHello Object's name: Tester”
// ...</span>

1.1.6、InstanceObject.plusSetAttribute

设置类对象的静态属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">void instanceobject.plusSetAttribute( String name, Object value );</span>

导入类对象后,就可以调用其plusSetAttribute方法设置类的静态属性值。
- name:要设置的静态属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。
- value:要设置的属性值,其类型必须与Native层类对象的静态属性区配,否则设置操作不生效,将保留以前的值。

注意:如果导入的类对象中存在“plusSetAttribute”同名的静态方法,则必须通过plus.android.invoke()方法调用。

示例:
(1)、 导入类创建实例对象,设置对象的属性值
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 创建对象的实例
    NjsHello hello = new NjsHello();
    // 设置其name属性值
    hello.name = "Tester";
    System.out.printf( "NjsHello Object's name: %s", hello.name );  // 输出“NjsHello Object's name: Tester”
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var Hello = plus.android.importClass("io.dcloud.NjsHello");
// 创建对象的实例
var hello = new NjsHello();
// 设置其name属性值
hello.plusSetAttribute( "name", "Tester" );
console.log( "NjsHello Object's name: "+hello.plusGetAttribute("name") ); // 输出“NjsHello Object's name: Tester”
// ...</span>

1.1.7、plus.android.implements

   在Java中可以通过定义新类并实现Interface的接口,并创建出新类对象作为其它接口的参数,在NJS中则可快速创建对应的Interface对象,方法原型如下:

Object plus.android.implements( String name, Object obj );

此方法创建Native层中Java的接口实现对象,作为调用其它Native API的参数。
- name:接口的名称,必须是完整的命名空间(使用"."分割),如果不存在此接口,则创建接口实现对象失败,返回null。
- obj:JSON对象类型,接口实现方法的定义,JSON对象中key值为接口方法的名称;value值为Function,方法参数必须与接口中方法定义的参数区配。

示例:
(1)、 Test类中实现接口NjsHelloEvent的方法,并调用NjsHello对象的test方法触发接口中函数的运行。
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
import io.dcloud.NjsHelloEvent;
//...
// Test类实现NjsHelloEvent接口
public class Test implements NjsHelloEvent {
public static void main( String args[] ) {
    // 创建对象的实例
    NjsHello hello = new NjsHello();
    // 调用updateName方法
    hello.updateName( "Tester" );
    // 设置监听对象
    hello.setEventObserver( this );
    // 调用test方法,触发接口事件
    hello.test(); // 触发onEventInvoked函数运行
    //...
}
// 实现接口NjsHelloEvent的onEventInvoked方法
@Override
public void onEventInvoked( String name ) {
    System.out.printf( "Invoked Object's name is: %s", name );
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.android.importClass("io.dcloud.NjsHello");
// 实现接口“NjsHelloEvent”对象
var hevent = plus.android.implements( "io.dcloud.NjsHelloEvent", {
    "onEventInvoked":function( name ){
        console.log( "Invoked Object’s name: "+name ); // 输出“Invoked Object’s name: Tester”
    }
} );
// 创建对象的实例
var hello = new NjsHello();
// 调用updateName方法
hello.updateName( "Tester" );
// 设置监听对象
hello.setEventObserver( hevent );
// 调用test方法,触发代理事件
hello.test(); // 触发上面定义的匿名函数运行
// ...</span>

1.1.8、plus.android.runtimeMainActivity

获取运行期环境主Activity实例对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">InstanceObject plus.android.runtimeMainActivity();</span>

此方法将获取程序的主Activity的实例对象,它是Html5+运行期环境主组件,用于处理与用户交互的各种事件,也是应用程序全局环境android.app.Activity的实现对象。android.app.Activity是一个特殊的类,需要在原生开发环境中注册后才能使用,所以使用new操作符创建对象无实际意义。

示例:
(1)、调用Activity的startActivity方法来拨打电话
Java代码:

<span style="font-family:FangSong_GB2312;">import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
//...
// 获取主Activity对象的实例
Activity main = context;
// 创建Intent
Uri uri = Uri.parse("tel:10086");
Intent call = new Intent("android.intent.action.CALL",uri);
// 调用startActivity方法拨打电话
main.startActivity(call);
//...</span>


NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入Activity、Intent类
var Intent = plus.android.importClass("android.content.Intent");
var Uri = plus.android.importClass("android.net.Uri");
// 获取主Activity对象的实例
var main = plus.android.runtimeMainActivity();
// 创建Intent
var uri = Uri.parse("tel:10086");
var call = new Intent("android.intent.action.CALL",uri);
// 调用startActivity方法拨打电话
main.startActivity( call );
// ...</span>

1.1.9、plus.android.currentWebview

获取当前Webview窗口对象的native层实例对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">    InstanceObject plus.android.currentWebview();</span>

Android平台完整Java类名为android.webkit.Webview,完整API请参考Android开发文档android.webkit.Webview

示例:
(1)、 调用Webview的loadUrl方法跳转页面
Java代码:

<span style="font-family:FangSong_GB2312;">import android.webkit.Webview;
//...
// 获取Webview对象
Webview wv = this;
// 跳转页面
wv.loadUrl("http://www.dcloud.io/");
//...</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入Webview类
var Webview = plus.android.importClass("android.webkit.Webview");
// 当前Webview对象的实例
var wv = plus.android.currentWebview();
// 跳转页面
wv.loadUrl("http://www.dcloud.io/");
// ...</span>

完整API文档参考:HTML5+ API - Native.js for Android

1.2、API on iOS

   为了能更好的理解NJS调用Objective-C Native API,我们在iOS平台用Objective-C实现以下测试类,将会在后面API说明中的示例来调用。
头文件njshello.h代码如下:

<span style="font-family:FangSong_GB2312;">// 定义协议
@protocol NjsHelloEvent <NSObject>
@required
-(void) onEventInvoked:(NSString*)name;
@end
// -------------------------------------------------------------
// 定义类NjsHello
@interface NjsHello : NSObject {
    NSString *_name;
    id<NjsHelloEvent > _delegate;
}
@property (nonatomic,retain) NSString *name;
@property (nonatomic,retain) id delegate;
-(void)updateName:(NSString*)newname;
-(void)setEventObserver:(id<NjsHelloEvent >)delegate;
-(void)test;
+(void)testCount;
@end</span>

实现文件njshello.m源代码如下:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
// 实现类NjsHello
@implementation NjsHello
@synthesize name=_name;
-(void)updateName:(NSString*)newname{
    _name = [newname copy];
}
-(void)setEventObserver:(id<NjsHelloEvent >)delegate{
    _delegate = delegate;
}
-(void)test{
    NSLog("My name is: %@",_name);
    [[self delegate]onEventInvoked:name];
}
-(void)dealloc{
    [_name release];
    [supper dealloc];
}
+(void)testCount{
    NSLog( "Static test count" );
}
@end</span>

plus.ios.importClass

导入Objective-C类对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">ClassObject plus.ios.importClass( String classname );</span>

导入类对象后,就可以通过“.”操作符直接调用对象(类对象/实例对象)的常量和方法。通过“.”操作符号调用方法时,不需要使用“:”来分割方法名。
- classname:要导入的Objective-C类名,如果指定的类名不存在,则导入类失败,返回null。

注意:导入类对象可以方便的使用“.”操作符来调用对象的属性和方法,但也会消耗较多的系统资源。因此导入过多的类对象会影响性能,此时可以使用“高级API”中提供的方法在不导入类对象的情况下调用Native API。
示例:
1. 导入类并创建实例对象
Objective-C代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // ...
}
// ...</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 创建对象的实例
var hello = new NjsHello();
// ...</span>

ClassObject

调用plus.ios.importClass()方法导入类并返回ClassObject类对象,通过该类对象,可以创建类的实例对象。在Objective-C中类的静态方法会转换成NJS类对象的方法,可通过类对象的“.”操作符调用;

注意:由于Objective-C中类没有静态变量,而是通过定义全局变量来实现,目前NJS中无法访问全局变量的值。对于全局常量,在NJS中也无法访问,对于原类型常量可在文档中找到其具体的值,在JS代码中直接赋值;对于非原类型常量目前还无法访问。

示例:
1. 导入类后调用类的静态方法
Objective-C代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
// ...
int main( int argc, char *argv[] )
{
    // 调用类的静态方法
    [NjsHello testCount];
    // ...
}
// ...</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 调用类的静态方法
NjsHello.testCount();
// ...</span>

InstanceObject

NJS中实例对象与Objective-C中的对象对应,调用plus.ios.importClass()方法导入类后,通过new操作符可创建该类的实例对象,或直接调用plus.ios.newObject方法创建类的实例对象,也可通过调用Native API返回实例对象。在Objective-C中对象的方法会转换成NJS实例对象的方法,可通过实例对象的“.”操作符调用;对象的属性则必须通过NJS实例对象的plusGetAttribute、plusSetAttribute方法操作。

示例:
1. 导入类创建实例对象,调用对象的方法
Objective-C代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // ...
}
// ...</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 创建对象的实例
var hello = new NjsHello();
// ...</span>

InstanceObject.plusGetAttribute

获取实例对象的属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">Object instancebject.plusGetAttribute( String name );</span>

获取实例对象后,就可以调用其plusGetAttribute方法获取对象的属性值。
- name:要获取对象的属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。

注意:如果实例对象中存在“plusGetAttribute”同名的方法,则只能通过plus.ios.invoke()方法调用。

示例:
1. 导入类创建实例对象,获取对象的属性值
Objective-C代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    [hello updateName:@"Tester"];
    // 获取其name属性值
    NSString* name = hello.name;
    NSLog("NjsHello Object's name: %@",name);  // 输出“NjsHello Object's name: Tester”
    // ...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 创建对象的实例
var hello = new NjsHello();
hello.updateName( "Tester" );
// 获取其name属性值
var name = hello.plusGetAttribute( "name" );
console.log( "NjsHello Object’s name: "+name );  // 输出“NjsHello Object’s name: Tester”
// ...</span>

InstanceObject.plusSetAttribute

设置类对象的静态属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">void instanceobject.plusSetAttribute( String name, Object value );</span>

导入类对象后,就可以调用其plusSetAttribute方法设置类的静态属性值。
- name:要设置的静态属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。
- value:要设置的属性值,其类型必须与Native层类对象的静态属性区配,否则设置操作不生效,将保留以前的值。

注意:如果导入的类对象中存在“plusSetAttribute”同名的静态方法,则只能通过plus.android.invoke()方法调用。

示例:
1. 导入类创建实例对象,设置对象的属性值
Java代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // 设置其name属性值
    hello.name = @"Tester";
    NSLog("NjsHello Object's name: %@",hello.name);  // 输出“NjsHello Object's name: Tester”
    // ...
}
//...</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 创建对象的实例
var hello = new NjsHello();
// 设置其name属性值
hello.plusSetAttribute( "name", "Tester" );
console.log( "NjsHello Object’s name: "+hello.plusGetAttribute("name") ); // 输出“NjsHello Object’s name: Tester”
// ...</span>

plus.ios.implements

在Objective-C中可以通过定义新类并实现Protocol的协议,并创建出新类对象作为代理对象,在NJS中则可实现协议快速创建代理对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">Object plus.ios.implements( String name, Object obj );</span>

此方法返回一个NJS实例对象,映射到Native层中的代理对象,其父类为“NSObject”,并且实现obj中指定的协议方法。通常作为调用其它Native API的参数。
- name:协议的名称,也可以是自定的字符串名称用于定义一个代理。
- obj:JSON对象类型,代理实现方法的定义,JSON对象中key值为协议中定义的方法名称,必须保留方法名称中的“:”字符;value值为Function,方法参数必须与协议中定义方法的参数区配。

示例:
1. 实现一个代理,并调用test方法触发调用代理的方法
Objective-C代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
// 定义代理类NjsDelegate
@interface NjsDelegate: NSObject<NjsHelloEvent> {
    -(void) onEventInvoked:(NSString*)name;
}
@end
// -------------------------------------------------------------
// 实现代理类NjsDelegate
@implementation NjsDelegate
-(void) onEventInvoked:(NSString*)name{
    NSLog("Invoked Object's name:%@",name);  // 输出“Invoked Object’s name: Tester”
}
@end
// -------------------------------------------------------------
// 主函数
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // 调用updateName方法
    [hello updateName:@"Tester"];
    // 创建代理对象
    NjsDelegate* delegate = [[NjsDelegate alloc] init];
    // 设置监听对象
    [hello setEventObserver:delegate];
    // 调用test方法,触发代理事件
    [hello test];  // 触发上面代理对象定义的onEventInvoked运行
    // ...
}</span>

在NJS中不需要创建新的类对象,调用plus.ios.implements实现协议接口即可创建出代理对象,代码如下:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 实现协议“NjsHelloEvent”的代理
var hevent = plus.ios.implements( "NjsHelloEvent", {
    "onEventInvoked":function( name ){
        console.log( "Invoked Object’s name: "+name ); // 输出“Invoked Object’s name: Tester”
    }
} );
// 调用updateName方法
hello.updateName( "Tester" );
// 设置监听对象
hello.setEventObserver( hevent );
// 调用test方法,触发代理事件
hello.test(); // 触发上面代理对象定义的匿名函数运行
// ...</span>

plus.ios.deleteObject

释放NJS中实例对象中映射的Native对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">void plus.ios.deleteObject( Object obj );</span>

NJS中所有的实例对象(InstanceObject)都可以通过此方法释放,会将Native层的对象使用的资源进行释放。
- obj:要释放的实例对象,如果obj对象不是有效的实例对象,则不执行对象的是否资源操作。

注意:此方法是可选的,如果不调用此方法释放实例对象,则在页面关闭时会自动释放所有对象;若对象占用较多的系统资源,则在业务逻辑处理完成时应该主动调用此方法释放资源,以提到程序的运行效率。

示例:
1. 创建实例对象使用完成后,显式操作销毁对象
Objective-C代码:

<span style="font-family:FangSong_GB2312;">#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // 调用updateName方法
    [hello updateName:@"Tester"];
    // ...
    // 使用完后销毁对象的实例
    [hello release];
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入测试类NjsHello
var NjsHello = plus.ios.importClass("NjsHello");
// 创建对象的实例
var hello = new NjsHello();
// 调用updateName方法
hello.updateName( "Tester" );
// ...
// 使用完后销毁对象的实例
plus.ios.deleteObject( hello );</span>

plus.ios.currentWebview

获取当前Webview窗口对象的native层UIWebview实例对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">InstanceObject plus.ios.currentWebview();</span>

UIWebview对象的API请参考Apple开发文档UIWebview

示例:
1. 创建实例对象使用完成后,显式操作销毁对象
Objective-C代码:

<span style="font-family:FangSong_GB2312;">// 获取当前Webview对象的实例
UIWebview* wv=self;
// 创建请求对象
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.dcloud.io/"]];
// 跳转页面
[web loadRequest:req];
// 释放对象
// 系统自动回收
// ...</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 导入UIWebview、NSURLRequest、NSURL类
var Webview = plus.ios.importClass("UIWebview");
var NSURLRequest = plus.ios.import('NSURLRequest');
var NSURL = plus.ios.import('NSURL');
// 获取当前Webview对象的实例
var wv = plus.ios.currentWebview();
// 创建请求对象
var req = NSURLRequest.requestWithURL(NSURL.URLWithString('http://www.dcloud.io/'));
// 跳转页面
plus.ios.invoke(wv,"loadRequest:",req);
// 释放对象(可选)
plus.ios.deleteObject(req);
plus.ios.deleteObject(wv);
// ...</span>

完整API文档参考:HTML5+ API - Native.js for iOS

2、完整业务演示

Android

在Android手机桌面上创建快捷方式图标,这是原本只有原生程序才能实现的功能。即使使用Hybrid方案,也需要原生工程师来配合写插件。
下面我们演示如何直接使用js在Android手机桌面创建快捷方式,在HelloH5+应用中Native.JS页面中“Shortcut (Android)”可以查看运行效果。
这段代码是使用原生Java实现的创建快捷方式的代码,用于参考比对:

<span style="font-family:FangSong_GB2312;">import android.app.Activity;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
// 创建桌面快捷方式
void createShortcut(){
    // 获取主Activity
    Activity main = this;
    // 创建快捷方式意图
    Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
    // 设置快捷方式的名称
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "HelloH5+");
    // 设置不可重复创建
    shortcut.putExtra("duplicate",false);
    // 设置快捷方式图标
    Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/icon.png");
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
    // 设置快捷方式启动执行动作
    Intent action = new Intent(Intent.ACTION_MAIN);
    action.setComponent( main.getComponentName() );
    shortcut.putExtra( Intent.EXTRA_SHORTCUT_INTENT, action );
    // 广播创建快捷方式
    main.sendBroadcast(shortcut);
}</span>

使用NJS实现时首先导入需要使用到的android.content.Intent、android.graphics.BitmapFactory类,按照Java代码中的方法对应转换成JavaScript代码。其中快捷方式图标是通过解析本地png文件进行设置,在JavaScript中需要使用plus.io.* API转换成本地路径传递给Native API,完整代码如下:

<span style="font-family:FangSong_GB2312;">var Intent=null,BitmapFactory=null;
var main=null;
document.addEventListener( "plusready", function() {//"plusready"事件触发时执行plus对象的方法
    // ...
    if ( plus.os.name == "Android" ) {
        // 导入要用到的类对象
        Intent = plus.android.importClass("android.content.Intent");
        BitmapFactory = plus.android.importClass("android.graphics.BitmapFactory");
        // 获取主Activity
        main = plus.android.runtimeMainActivity();
    }
}, false);
/**
 * 创建桌面快捷方式
 */
function createShortcut(){
    // 创建快捷方式意图
    var shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
    // 设置快捷方式的名称
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "测试快捷方式");
    // 设置不可重复创建
    shortcut.putExtra("duplicate",false);
    // 设置快捷方式图标
    var iconPath = plus.io.convertLocalFileSystemURL("/icon.png"); // 将相对路径资源转换成系统绝对路径
    var bitmap = BitmapFactory.decodeFile(iconPath);
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON,bitmap);
    // 设置快捷方式启动执行动作
    var action = new Intent(Intent.ACTION_MAIN);
    action.setClassName(main.getPackageName(), 'io.dcloud.PandoraEntry');
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT,action);
    // 广播创建快捷方式
    main.sendBroadcast(shortcut);
    console.log( "桌面快捷方式已创建完成!" );
}</span>

注意:提交到云平台打包时需要添加Android权限才能在桌面创建快捷方式,在HBuilder工程中双击应用的“manifest.json”文件,切换到“代码视图”中在plus->distribute->google->permissions节点下添加权限数据:

<span style="font-family:FangSong_GB2312;">"google": {
    // ...
    "permissions": [
"<uses-permission android:name=\"com.android.launcher.permission.INSTALL_SHORTCUT\"/>"
    ]
}</span>

如下图所示:
manifest.json中Android权限 permissions

iOS

在iOS手机上登录game center,一个游戏中心服务,这是原本只有原生程序才能实现的功能。即使使用Hybrid方案,也需要原生工程师来配合写插件。
下面我们演示如何直接使用js在iOS手机上登录game center,在HelloH5+应用中Native.JS页面中的“Game Center (iOS)”可以查看运行效果。
注意手机未开通game center则无法登陆,请先点击iOS自带的game center进行配置。
这段代码是使用原生Objective-C实现的登录game center的代码,用于参考比对。原生Objective-C代码的头文件Test.h中代码如下:

<span style="font-family:FangSong_GB2312;">@interface Test: NSObject
// 游戏玩家登录状态监听函数
- (void)authenticationChanged:(NSNotification*)notification;
// 获取游戏玩家状态信息
- (void)playerInformation:(GKPlayer *)player;
// 登录到游戏中心
- (void)loginGamecenter;
// 停止监听登录游戏状态变化
- (void)logoutGamecenter;
@end

实现文件Test.m中代码如下:
@implementation Test
// 游戏玩家登录状态监听函数
- (void)authenticationChanged:(NSNotification*)notification
{
    // 获取游戏玩家共享实例对象
    GKLocalPlayer *player = notification.object;
    if ( player.isAuthenticated ) {
        // 玩家已登录认证,获取玩家信息
        [self playerInformation:player];
    } else {
        // 玩家未登录认证,提示用户登录
        NSLog(@"请登录!");
    }
    // 释放使用的对象
    [player release];
}
// 获取游戏玩家状态信息
- (void)playerInformation:(GKPlayer *)player
{
    // 获取游戏玩家的名称
    NSLog(@"Name: %@",player.displayName);
}

// 登录到游戏中心
- (void)loginGamecenter
{
    // 监听用户登录状态变更事件
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self
           selector:@selector(authenticationChanged)
               name:@"GKPlayerAuthenticationDidChangeNotificationName"
             object:nil];
    // 获取游戏玩家共享实例对象
    GKLocalPlayer *localplayer = [GKLocalPlayer localPlayer];
    // 判断游戏玩家是否已经登录认证
    if ( localplayer.isAuthenticated ) {
        // 玩家已登录认证,获取玩家信息
        [self playerInformation:localplayer];
    } else {
        // 玩家未登录认证,发起认证请求
        [localplayer authenticateWithCompletionHandler:nil];
        NSLog(@"登录中...");
    }
    // 释放使用的对象
    [localplayer release];
    [nc release];
}

// 停止监听登录游戏状态变化
- (void)logoutGamecenter
{
    // 取消监听用户登录状态变化
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc removeObserver:self
                  name:@"GKPlayerAuthenticationDidChangeNotificationName"
                object:nil];
    // 释放使用的对象
    [nc release];
}
@end</span>

使用NJS实现时可以按照Objective-C代码中的方法对应转换成JavaScript代码,最关键的代码是loginGamecenter方法中对用户登录状态的监听,需调用NSNotificationCenter对象的“addObserver:selector:name:object”方法,
1. addObserver:后要求传入一个实例对象用于查找selector参数中指定的方法,在Objective-C中通常将对象自身(self)传入,但在NJS中没有此概念,因此需使用plus.ios.implements方法来创建一个新的对象:
var delegate = plus.ios.implements("NSObject",{"authenticationChanged:":authenticationChanged});
第一个参数“NSObject”表示对象的类型,第二个参数中的JSON对象表明对象拥有的方法,“authenticationChanged”方法是delegate对象的方法。
2. selector:后要传入一个类函数指针,在Objective-C中通过“@selector”指令可选择函数指针,在NJS中则需使用plus.ios.newObject方法来创建一个函数对象:
plus.ios.newObject("@selector","authenticationChanged:")
第一个参数需固定值为“@selector”,表示创建的是类函数指针对象,第二个参数。
在"plusready"事件中导入GKLocalPlayer和NSNotificationCenter类,并调用登录方法longinGamecenter()。

完整JavaScript代码如下:

<span style="font-family:FangSong_GB2312;">// 处理"plusready"事件
var bLogin=false;
document.addEventListener( "plusready", function() {
    // ...
    if ( plus.os.name == "iOS" ) {
        GKLocalPlayer  = plus.ios.importClass("GKLocalPlayer");
        NSNotificationCenter = plus.ios.importClass("NSNotificationCenter");
        longinGamecenter();
    } else {
        alert("欢迎您");
        bLogin = true;
        setTimeout( function(){
            plus.ui.toast( "此平台不支持Game Center功能!" );
        }, 500 );
    }
}, false);

var GKLocalPlayer=null,NSNotificationCenter=null;
var delegate=null;

// 游戏玩家登录状态监听函数
function authenticationChanged( notification ){
    // 获取游戏玩家共享实例对象
    var player = notification.plusGetAttribute("object");
    if ( player.plusGetAttribute("isAuthenticated") ) {
        // 玩家已登录认证,获取玩家信息
        playerInformation(player);
        bLogin = true;
    } else {
        // 玩家未登录认证,提示用户登录
        alert("请登录");
        bLogin = false;
    }
    // 释放使用的对象
    plus.ios.deleteObject(player);
}

// 获取游戏玩家状态信息
function playerInformation( player ){
    var name = player.plusGetAttribute("displayName");
    alert( name+" 已登录!" );
}

// 登录到游戏中心
function longinGamecenter(){
    if ( bLogin ){
        return;
    }
    // 监听用户登录状态变更事件
    var nc = NSNotificationCenter.defaultCenter();
    delegate = plus.ios.implements("NSObject",{"authenticationChanged:":authenticationChanged});
    nc.addObserverselectornameobject(delegate,
        plus.ios.newObject("@selector","authenticationChanged:"),
        "GKPlayerAuthenticationDidChangeNotificationName",
        null);
    // 获取游戏玩家共享实例对象
    var localplayer = GKLocalPlayer.localPlayer();
    // 判断游戏玩家是否已经登录认证
    if ( localplayer.isAuthenticated() ) {  // localplayer.plusGetAttribute("isAuthenticated")
        // 玩家已登录认证,获取玩家信息
        playerInformation( localplayer );
        bLogin = true;
    } else {
        // 玩家未登录认证,发起认证请求
        localplayer.authenticateWithCompletionHandler(null);
        alert( "登录中..." );
    }
    // 释放使用的对象
    plus.ios.deleteObject(localplayer);
    plus.ios.deleteObject(nc);
}

// 停止监听登录游戏状态变化
function stopGamecenterObserver()
{
    // 取消监听用户登录状态变化
    var nc = NSNotificationCenter.defaultCenter();
    nc.removeObservernameobject(delegate,"GKPlayerAuthenticationDidChangeNotificationName",null);
    plus.ios.deleteObject(nc);
    plus.ios.deleteObject(delegate);
    delegate = null;
}</span>

注意
1. 提交到云平台打包时需要添加Game Center API的系统库(framework)才能正确调用,在HBuilder工程中双击应用的“manifest.json”文件,切换到“代码视图”中在plus->distribute->apple->frameworks节点下添加要引用的系统Framework:

<span style="font-family:FangSong_GB2312;">"apple": {
    "devices": "universal",
    "frameworks": [
        "GameKit.framework"
    ]
}</span>

,如下图所示:
HBuilder frameworks
2. 正式发布提交到AppStore时,在配置苹果开发者网站上配置App ID需要选中“Game Center”服务:
Game Center

3、开发注意和建议用途

Native.js的运行性能仍然不比纯原生应用;JS与Native之间的数据交换效率并不如在js内部的数据交换效率;基于如上原因,有几点开发建议:
- 以标准web 代码为主,当遇到web能力不足的时候,调用Native.js。
- 以标准web 代码为主,当遇到web性能不足的时候,需要分析,
if ((原生进行运算的效率-js与原生通讯的损耗)>纯web的效率){
使用Native.js
}else{
还应该使用纯js
}
- 应避免把程序设计为在短时间内并发触发Native.js代码

4、调试

使用safari和chrome的控制台调试HBuilder的5+App时,一样可以调试NJS对象,即可以在浏览器控制台中查看各种原生对象的属性和方法,如下图所示,57行设了断点,watch了Intent对象,并在右边展开了该对象的所有属性方法:
Chrome Debug
关于如何在浏览器控制台调试HBuilder的5+App,请参考HBuilder的5+App开发入门教程。

5、开发资源

iOS 官方在线文档:https://developer.apple.com/library/ios/navigation/
Android 官方在线文档:https://developer.android.com/reference/packages.html
演讲视频:http://v.youku.com/v_show/id_XNzYzNTcwNDI4.html

6、高级API

   有前述的常用API,已经可以完成各项业务开发。此处补充的高级API,是在熟悉NJS后,为了提升性能而使用的API。高级API无法直接用“.”操作符使用原生对象的方法,在debug时也无法watch原生对象,但高级API性能高于常规API。
虽然导入类对象(plus.android.importClass和plus.ios.importClass)后,可以方便的通过“.”操作符来访问对象的常量、调用对象的方法,但导入类对象也需要消耗较多的系统资源,所以在实际开发时应该尽可能的减少导入类对象,以提高程序效率。可以参考以下依据进行判断:
a. 如导入的类特别复杂,继承自很多基类,方法和属性特别多则考虑不导入类;
b. 对导入类是否需要频繁操作,若导入类仅是为了实例化,并作为调用其它API的参数,则不应该导入类;
c. 在同一页面中是否导入了很多类?如果导入太多则需要考虑减少导入类的数目。

  如果我们不导入类对象则无法通过new操作符实例化类对象,这时可通过plus.ios.newObject()、plus.android.newObject()方法来创建实例对象,如下:

<span style="font-family:FangSong_GB2312;">// iOS平台创建NSDictionary的实例对象
var ns = plus.ios.newObject( "NSDictionary" );

// Android平台创建Intent的实例对象
var intent = plus.android.newObject( "android.content.Intent" );</span>

6.1、API on Android

plus.android.newObject

不导入类对象直接创建类的实例对象,方法原型如下:

<span style="font-family:FangSong_GB2312;">InstanceObject plus.android.newObject( String classname, Object...args );</span>

此方法对Native层中对类进行实例化操作,创建一个类的实体并返回NJS层的实例对象。相比导入类对象后使用new操作符创建对象效率要高。
- classname:要创建实例对象的类名,类名必须是完整的命名空间,使用“.”分隔符(如“android.app.AlertDialog”),如果需要创建内部类对象需要使用“$”分割符(如“android.app.AlertDialog$Builder”)。如果指定的类名不存在,则创建对象失败,返回null。
- args:调用类构造函数的参数,其类型和数目必须与Native层Java类构造函数区配,否则无法创建类对象,将返回null。

注意:由于没有导入类对象,所以通过此方法创建的实例对象无法通过“.”操作符直接调用对象的方法,而必须使用plus.android.invoke方法来调用。

示例:
1. 不导入类创建实例对象
Java代码:

<span style="font-family:FangSong_GB2312;">import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 创建对象的实例
    NjsHello hello = new NjsHello();
    //...
}
//...
}</span>

NJS代码:

<span style="font-family:FangSong_GB2312;">// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 创建对象的实例
var hello = plus.android.newObject( "io.dcloud.NjsHello" );
// ...</span>

plus.android.getAttribute

不导入类对象,则无法通过类对象并访问类的静态属性,需调用以下方法获取类的静态属性值,方法原型如下:

<span style="font-family:FangSong_GB2312;">Object plus.android.getAttribute( String|Object obj, String name );</span>

此方法也可以获取类对象或实例对象的属性值,如果是类对象获取的则是类的静态属性,如果是实例对象则获取的是对象的非静态属性。
- obj:若是String类型,表示要获取静态属性值的类名,类名必须是完整的命名空间(使用"."分割);若是ClassObject类型,表示要获取静态属性的类对象;若是InstanceObject类型,表示要获取属性值的实例对象。
- name:要获取的属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。

注意:同样导入类对象后也可以调用此方法,obj参数类型为ClassObject时,其作用与ClassObject.plusSetAttribute方法一致。obj参数类型为InstanceObject时,其作用与InstanceObject.plusSetAttribute方法一致。

示例:
1. 不导入类对象获取类的静态常量属性
Java代码:

import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 获取类的静态常量属性
    int type = NjsHello.CTYPE;
    System.out.printf( "NjsHello Final's value: %d", type );  // 输出“NjsHello Final's value: 1”
    // 获取类的静态属性
    int count = NjsHello.count;
    System.out.printf( "NjsHello Static's value: %d", count );  // 输出“NjsHello Static's value: 0”
    //...
}
//...
}

NJS代码:

// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 访问类的静态常量属性
var type = plus.android.getAttribute( "io.dcloud.NjsHello", "CTYPE" );
console.log( "NjsHello Final's value: "+type ); // 输出“NjsHello Final's value: 1”
// ...

  1. 不导入类对象,创建实例对象,并获取其name属性值
    Java代码:
    import io.dcloud.NjsHello;
    //...
    public class Test {
    public static void main( String args[] ) {
        // 创建对象的实例
        NjsHello hello = new NjsHello();
        // 获取其name属性值
        String name = hello.name;
        System.out.printf( "NjsHello Object's name: %s", name );  // 输出“NjsHello Object's name: Tester”
        //...
    }
    //...
    }

NJS代码:

// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 创建对象的实例
var hello = plus.android.newObject( "io.dcloud.NjsHello" );
// 获取其name属性值
var name = plus.android.getAttribute( hello, "name" );
console.log( "NjsHello Object's name: "+name );  // 输出“NjsHello Object's name: Tester”
// ...

plus.android.setAttribute

若没有导入类对象,则无法通过类对象设置类的静态属性值,需调用以下方法设置类的静态属性值,方法原型如下:

              void plus.android.setAttribute( String|Object obj, String name, Object value );

此方法也可以设置类对象或实例对象的属性值,如果是类对象设置的则是类的静态属性,如果是实例对象则设置的是对象的非静态属性。
- obj:若是String类型,表示要设置静态属性值的类名,类名必须是完整的命名空间(使用"."分割);若是ClassObject类型,表示要设置静态属性的类对象;若是InstanceObject类型,表示要设置属性值的实例对象。
- name:要设置的属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。
- value:要设置的属性值,其类型必须与Native层obj对象的属性区配,否则设置操作不生效,将保留以前的值。

注意:同样导入类对象后也可以调用此方法,obj参数类型为ClassObject时,其作用与ClassObject.plusSetAttribute方法一致。obj参数类型为InstanceObject时,其作用与InstanceObject.plusSetAttribute方法一致。

示例:
1. 不导入类对象设置类的静态属性值
Java代码:

import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 设置类的静态属性值
    NjsHello.count = 2;
    System.out.printf( "NjsHello Static's value: %d", NjsHello.count );  // 输出“NjsHello Static's value: 2”
    //...
}
//...
}

NJS代码:

// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 设置类的静态属性值
plus.android.setAttribute( "io.dcloud.NjsHello", "count", 2 );
console.log( "NjsHello Static's value: "+plus.android.getAttribute("io.dcloud.NjsHello","count") ); // 输出“NjsHello Static's value: 2”
// ...

  1. 导入类对象,创建实例对象,并设置其name属性值
    Java代码:
    import io.dcloud.NjsHello;
    //...
    public class Test {
    public static void main( String args[] ) {
        // 创建对象的实例
        NjsHello hello = new NjsHello();
        // 设置其name属性值
        hello.name = "Tester";
        System.out.printf( "NjsHello Object's name: %s", hello.name );  // 输出“NjsHello Object's name: Tester”
        //...
    }
    //...
    }

NJS代码:

// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 创建对象的实例
var hello = plus.android.newObject( "io.dcloud.NjsHello" );
// 设置其name属性值
plus.android.setAttribute( hello, "name", "Tester" );
console.log( "NjsHello Object's name: "+hello.plusGetAttribute("name") ); // 输出“NjsHello Object's name: Tester”
// ...

plus.android.invoke

若没有导入类对象,则无法通过实例对象的“.”操作符调用其成员方法,需通过以下方法调用实例对象的成员方法,方法原型如下:

Object plus.android.invoke( String|Object obj, String name, Object... args );

此方法也可以调用类对象或实例对象的方法,如果是类对象则调用的是类的静态方法,如果是实例对象则调用的是对象的普通成员方法。函数返回值是调用Native层方法运行后的返回值,Native对象的方法无返回值则返回undefined。
- obj:若是String类型,表示要调用静态方法的类名,类名必须包含完整的包名;若是ClassObject类型,表示要调用静态方法的类对象;若是InstanceObject类型,表示要调用成员方法的实例对象。
- name:要调用的方法名称,如果指定的方法不存在,则调用方法失败,返回值为null。
- args:调用方法的参数,其类型和数目必须与Native层对象方法的函数区配,否则无法调用对象的方法,将返回null。

注意:同样导入类对象后也可以调用此方法,其作用与通过类对象或实例对象的“.”操作符调用方法作用一致。

示例:
1.不导入类对象,调用类的静态方法
Java代码:

import io.dcloud.NjsHello;
//...
public class Test {
public static void main( String args[] ) {
    // 调用类的静态方法
    NjsHello.testCount();
    //...
}
//...
}

NJS代码:

// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 调用类的静态方法
plus.android.invoke( "io.dcloud.NjsHello", "testCount" );
// ...

  1. 不导入类对象,创建实例对象,并调用其updateNmae方法
    Java代码:
    import io.dcloud.NjsHello;
    //...
    public class Test {
    public static void main( String args[] ) {
        // 创建对象的实例
        NjsHello hello = new NjsHello();
        // 调用updateName方法
        hello.updateName( "Tester" );
        System.out.printf( "NjsHello Object's name: %s", name );  // 输出“NjsHello Object's name: Tester”
        //...
    }
    //...
    }

NJS代码:

// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello
// 创建对象的实例
var hello = plus.android.newObject( "io.dcloud.NjsHello" );
// 调用updateName方法
plus.android.invoke( hello, "updateName", "Tester" );
console.log( "NjsHello Object's name: "+hello.getAttribute("name") ); // 输出“NjsHello Object's name: Tester”
// ...

完整API文档参考:HTML5+ API - Native.js for Android

6.2、API on iOS

plus.ios.newObject

不导入类对象直接创建类的实例对象,方法原型如下:

InstanceObject plus.ios.newObject( String classname, Object..args );

此方法会在Native层中对类进行实例化操作,创建一个类的实体并返回NJS层的类实例对象。相比导入类对象后使用new操作符创建对象效率要高。
- classname:要创建实例对象的类名,如果指定的类名不存在,则创建对象失败,返回null。
- args:调用类构造函数的参数,其类型和数目必须与Native层对象构造函数区配,否则无法创建类对象,将返回null。

注意:由于没有导入类对象,所以通过此方法创建的实例对象无法通过“.”操作符直接调用对象的方法,而必须使用plus.ios.invoke方法来调用。classname参数值为“@selector”表示需要创建一个函数指针对象,与Objective-C中的@selector指令功能相似,args参数为函数的名称,此时函数的名称需要包含“:”字符。

示例:
1. 不导入类创建实例对象
Objective-C代码:

#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // ...
}

NJS代码:

// 未导入“NjsHello”类
// 创建对象的实例
var hello = plus.ios.newObject( "NjsHello" );
// ...

plus.ios.invoke

若没有导入类对象,则无法通过实例对象的“.”操作符调用其成员方法,需通过以下方法调用实例对象的成员方法,方法原型如下:

Object plus.ios.invoke( String|Object obj, String name, Object... args );

此方法也可以调用类对象或实例对象的方法,如果是类对象则调用的是类的静态方法,如果是实例对象则调用的是对象的普通成员方法。函数返回值是调用Native层方法运行后的返回值,Native对象的方法无返回值则返回undefined。
- obj:若是String类型,表示要调用静态方法的类名,类名必须包含完整的包名;若是ClassObject类型,表示要调用静态方法的类对象;若是InstanceObject类型,表示要调用成员方法的实例对象。
- name:要调用的方法名称,必须保留方法名称中的“:”字符,如果指定的方法不存在,则调用方法失败,返回值为null。
- args:调用方法的参数,其类型和数目必须与Native层对象方法的函数区配,否则无法调用对象的方法,将返回null。

注意:同样导入类对象后也可以调用此方法,其作用与通过类对象或实例对象的“.”操作符调用方法作用一致。

示例:
1. 不导入类创建实例对象,并调用updateName方法
Objective-C代码:

#import "njshello.h"
int main( int argc, char *argv[] )
{
    // 创建对象的实例
    NjsHello* hello = [[NjsHello alloc] init];
    // 调用updateName方法
    [hello updateName:@"Tester"];
    NSLog("NjsHello Object's name: %@",hello.name);  // 输出“NjsHello Object's name: Tester”
    // ...
}

NJS代码:

// 未导入“NjsHello”类
// 创建对象的实例
var hello = plus.ios.newObject( "NjsHello" );
// 调用updateName方法
plus.ios.invoke( hello, "updateName", "Tester" );
console.log( "NjsHello Object's name: "+hello.getAttribute("name") ); // 输出“NjsHello Object's name: Tester”
// ...

完整API文档参考:HTML5+ API - Native.js for iOS

7、性能优化

7.1、调整代码结构优化

前面章节中我们介绍如何通过NJS调用Native API来显示系统提示框,在真机运行时会发现第一次调用时会有0.5s左右的延时,再次调用则不会延时。这是因为NJS中导入类对象操作会花费较长的时间,再次调用时由于类对象已经导入过,会能很快执行完毕。因此可以调整代码结构进行优化,在页面打开后触发的“plusready”事件中进行类对象的导入操作,从而避免第一次调用的延时。

Android平台调整NJS代码结构如下:

// 保存Android导入对象和全局环境对象
var AlertDialog=null,mainActivity=null;
// H5+事件处理
document.addEventListener("plusready",function(){
    switch ( plus.os.name ) {
        case "Android":
        // 程序全局环境对象,内部自动导入Activity类
        mainActivity = plus.android.runtimeMainActivity();
        // 导入AlertDialog类
        AlertDialog = plus.android.importClass("android.app.AlertDialog");
        break;
        default:
        break;
    }
},false);
//...
/**
 * 在Android平台通过NJS显示系统提示框
 */
function njsAlertForAndroid(){
    // 创建提示框构造对象,构造函数需要提供程序全局环境对象,通过plus.android.runtimeMainActivity()方法获取
    var dlg = new AlertDialog.Builder(mainActivity);
    // 设置提示框标题
    dlg.setTitle("自定义标题");
    // 设置提示框内容
    dlg.setMessage("使用NJS的原生弹出框,可自定义弹出框的标题、按钮");
    // 设置提示框按钮
    dlg.setPositiveButton("确定(或者其他字符)",null);
    // 显示提示框
    dlg.show();
}
//...

iOS平台调整NJS代码结构如下:

// 保存iOS平台导入的类对象
var UIAlertView=null;
// H5+事件处理
document.addEventListener("plusready",function(){
    switch ( plus.os.name ) {
        case "iOS":
        // 导入UIAlertView类
        UIAlertView = plus.ios.importClass("UIAlertView");
        break;
        default:
        break;
    }
},false);
//...
/**
 * 在iOS平台通过NJS显示系统提示框
 */
function njsAlertForiOS(){
    // 创建UIAlertView类的实例对象
    var view = new UIAlertView();
    // 设置提示对话上的内容
    view.initWithTitlemessagedelegatecancelButtonTitleotherButtonTitles("自定义标题" // 提示框标题
        , "使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容
        , null // 操作提示框后的通知代理对象,暂不设置
        , "确定(或者其他字符)" // 提示框上取消按钮的文字
        , null ); // 提示框上其它按钮的文字,设置为null表示不显示
    // 调用show方法显示提示对话框
    view.show();
}
//...

7.2、使用高级API优化

前面章节中我们提到导入类对象会消耗较多的系统资源,导入过多的类对象会影响性能。在高级API中提供一组接口可以在不导入类对象的情况下调用Native API,从而提升代码运行性能。

Android平台使用高级API优化代码如下:

// 保存Android导入对象和全局环境对象
var mainActivity=null;
// H5+事件处理
document.addEventListener("plusready",function(){
    switch ( plus.os.name ) {
        case "Android":
        // 程序全局环境对象,内部自动导入Activity类
        mainActivity = plus.android.runtimeMainActivity();
        break;
        default:
        break;
    }
},false);
//...
/**
 * 在Android平台通过NJS显示系统提示框
 */
function njsAlertForAndroid(){
    // 由于Builder类是android.app.AlertDialog类的内部类,这里需要使用$符号分割
    var dlg = plus.android.newObject("android.app.AlertDialog$Builder",mainActivity);
    // 设置提示框标题
    plus.android.invoke(dlg,"setTitle","自定义标题");
    // 设置提示框内容
    plus.android.invoke(dlg,"setMessage","使用NJS的原生弹出框,可自定义弹出框的标题、按钮");
    // 设置提示框按钮
    plus.android.invoke(dlg,"setPositiveButton","确定(或者其他字符)",null);
    // 显示提示框
    plus.android.invoke(dlg,"show");
}
//...

iOS平台使用高级API优化代码如下:

/**
 * 在iOS平台通过NJS显示系统提示框
 */
function njsAlertForiOS(){
    // 创建UIAlertView类的实例对象
    var view = plus.ios.newObject("UIAlertView");
    // 设置提示对话上的内容,这里的方法名称中必须包含':'字符
    plus.ios.invoke(view,"initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:"
        ,"自定义标题" // 提示框标题
        , "使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容
        , null // 操作提示框后的通知代理对象,暂不设置
        , "确定(或者其他字符)" // 提示框上取消按钮的文字
        , null ); // 提示框上其它按钮的文字,设置为null表示不显示
    // 调用show方法显示提示对话框,在JS中使用()语法调用对象的方法
    plus.ios.invoke(view,"show");
}
//...

2016-10-18 19:01:30 liu__520 阅读数 13903
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57773 人正在学习 去看看 李宁

我们知道RN帮我们封装了一个Linking的模块,这样我们就能调用系统的电话、短信、邮件、浏览器、地理位置等应用了,极大地方便了我们的功能实现。

本文介绍我的一个项目总用到的这些功能,当然示例demo依旧在github上(src-pages-me-more里面:https://github.com/LiuC520/react-native-jifenmao),这里呢只介绍如何使用,具体的更多的功能需要查看官方文档。

 一、配置环境:

1.1、android端基本上不需要配置的:

但是如果你想做下面的事:

1.1.1、如果你的应用被其注册过的外部url调起, 则可以在任何组件内这样获取和处理它.

componentDidMount(){

var url = Linking.getInitialURL().then((url)=>{

if(url){

console.log('Initial url is: '+ url);

}

}).catch(err=> console.error('An error occurred', err));}

注意: 如果想了解更多 Android 端的信息, 请查看 Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links.

1.1.2、如果要在现有的 MainActivity 中监听传入的 intent, 那么需要在AndroidManifest. xml 中将MainActivity 的 launchMode 设置为 singleTask. 了解更多信息, 请查看 <activity> 文档.

<activity android:name=".MainActivity" android:launchMode="singleTask">

1.1.3、但是你只是简单的调用系统的上述功能,上面两部不需要做的;

1.2、ios端配置

1.2.1、需要在*AppDelegate.m文件中添加:

#import"RCTLinkingManager.h"

-(BOOL)application:(UIApplication*)application openURL:(NSURL*)url

sourceApplication:(NSString*)sourceApplication annotation:(id)annotation{

return[RCTLinkingManager application:application openURL:url

sourceApplication:sourceApplication annotation:annotation];}

// Only if your app is using [Universal Links] (https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html).

-(BOOL)application:(UIApplication*)application continueUserActivity:(NSUserActivity*)userActivity

restorationHandler:(void(^)(NSArray* _Nullable))restorationHandler{

return[RCTLinkingManager application:application

continueUserActivity:userActivity

restorationHandler:restorationHandler];

}

1.2.2、但是因为我们没有RCTLinkingManager添加库头文件的路径所以编译会报错,需要按照下图的步骤进行操作:


二、写代码喽:我遇到下面的一个问题,本来想按照下面的步骤操作,结果只要打开这个页面,就会自动把这几个linking打开,不用点击就能打开:

请注意:下面的代码是坑,不能用:

 <TouchableOpacity onPress={this.linking('tel:18585025253')}>
    <Text style={{color:'red'}}>打电话给刘成:18585025253</Text>
  </TouchableOpacity>
linking=(url)=>{
    Linking.canOpenURL(url).then(supported => {
    if (!supported) {
      console.log('Can\'t handle url: ' + url);
    } else {
      return Linking.openURL(url);
    }
  }).catch(err => console.error('An error occurred', err));
}

正确的做法还是按照官网来操作:

首先封装一个点击的组件,把url和组件名字以属性的方式传进去:

如下所示:

class Touchable extends Component{
  constructor(props){
    super(props);
  }
  protoTypes:{
    url:React.ProtoTypes.string
  }
  render (){
    return(
      <TouchableOpacity onPress={()=>{
        Linking.canOpenURL(this.props.url).then(supported => {
          if (!supported) {
            console.log('Can\'t handle url: ' + this.props.url);
          } else {
            return Linking.openURL(this.props.url);
          }
        }).catch(err => console.error('An error occurred', err));
        }}>
        <MeItem lefttitle={this.props.title}/>
      </TouchableOpacity>
    )
  }
}

注意:上面的MeItem是我自己封装的一个组件,组件左边是标题,可以根据属性值更改,右边是一个箭头,你也可以用text后者其他组件代替

然后在组件中调用:

<Touchable url={'tel:18585025253'} title={'电话热线:18585025253'} />
           <Touchable url={'mailto:674668211@qq.com'} title={'发送邮件:674668211@qq.com'} />
           <Touchable url={'http://www.baidu.com'} title={'打开http网页'} />
           <Touchable url={'https://www.baidu.com'} title={'打开https网页'} />
           <Touchable url={'smsto:18585025253'} title={'发送短信'} />

最终的效果如下所示哦:





2017-10-24 22:01:09 supernovaExp 阅读数 2303
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57773 人正在学习 去看看 李宁

这一篇主要内容是Native调用java方法和Native调用Android API,以及External Tools快速生成.h文件,依然是使用NDK方式编译,如果是复制粘贴党,建议跟本文用一样的工程名,本文后面会提供demo链接


一、创建工程


1.创建名为Jnitest01的工程



2.点击Next 选择API等级,我选择的是26,Activity选择Empty Activity,一路next后点击Finish创建工程


二、创建Myjni类并编写load与Native接口


1.创建Myjni类



填写类名并点击OK




2.为Myjni类添加代码


代码:

public class Myjni {

    private Context lContext;
    static
    {
        System.loadLibrary("Myjni");
    }
    public Myjni(Context context){
        this.lContext = context;
    }

    public void MyToast(String s)
    {
        Toast.makeText(lContext, s, Toast.LENGTH_SHORT).show();
    }

    //native
    public native void MyjniToast(String s);
    //native
    public static native void NativeToast(Context context, String s);
    //native
    public static native String NativeString();
}

三、配置External tools快速生成.h头文件

1.打开External对话框

在菜单栏找到 File->Settings..->


然后找到 Tools->External tools点击添加Tool按钮



2.设置tool信息


不愿意添加宏照抄即可,需要注意的是Android SDK 的JAR包路径要根据自己的路径修改以下

Name:Javah

Description:Javah

Program:$JDKPath$\bin\javah

Parameters:-d jni -classpath D:\Android\android-sdk\platforms\android-19\\android.jar;$OutputPath$ $FileClass$

这里需要特别注意一下  D:\Android\android-sdk\platforms\android-19\\android.jar    这个SDK包的位置请根据自己的机器环境填写 否则会发生错误)

Working directory:$ProjectFileDir$

设置完毕点击OK即可


3. Make Project生成class文件



点击Make Project后会在工程路径  \app\build\intermediates\classes\debug\com\example\file\jnitest01 中生成出class文件,

下面生成头文件需要class文件


4.使用tool生成头文件




点击 javah 后会开始自动生成,生成成功会提示处理完成 错误为0个




由于宏设置的工作目录在工程的根目录,所以头文件会生成在工程根目录下面的jni文件夹中,

比如的我工程目录在 D:\AndroidStudioProjects\Jnitest01 ,那么在头文件会生成在 D:\AndroidStudioProjects\Jnitest01\jni 中


四、编写native代码


1.在Java目录下新建Jni文件夹



2.把上面生成的com_example_file_test01_Myjni.h 移动到新建的jni目录下


3.在jni目录下新建一个cpp文件名为Myjni.cpp




点击OK创建即可



4.为新建的Myjni.cpp编写native代码

由于代码有点长就不截图了,直接上代码


// Created by file on 2017/10/24.
#include 
#include 

#define  LOG_TAG    "native-dev"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


JNIEXPORT void JNICALL Java_com_example_file_jnitest01_Myjni_MyjniToast
(JNIEnv* env, jobject job, jstring jStr)
{
    jclass myjniclass = NULL;
    jmethodID mid = NULL;
    const char* pNativeStr = NULL;
    jstring szOut = NULL;

    LOGI("MyjniToast---FindClass");
    myjniclass = env->FindClass("com/example/file/jnitest01/Myjni");
    if(myjniclass == NULL) {
        return;
    }
    mid = env->GetMethodID(myjniclass,"MyToast","(Ljava/lang/String;)V");
    if(mid == NULL) {
        return;
    }
    //jstring 转 char* 需要开内存所以下面必须使用ReleaseStringUTFChars释放  防止泄露
    pNativeStr = env->GetStringUTFChars(jStr, 0);
    if(pNativeStr == NULL) {
        return;
    }
    //重新生成一个jstring  也需要释放一下szOut
    szOut = env->NewStringUTF(pNativeStr);
    if(szOut == NULL) {
        return;
    }
    //调用Myjni.MyToast 方法
    env->CallVoidMethod(job, mid, szOut);
    //释放pNativeStr char*指针
    env->ReleaseStringUTFChars(jStr, pNativeStr);
    //释放szOut
    env->DeleteLocalRef(szOut);
}

JNIEXPORT void JNICALL Java_com_example_file_jnitest01_Myjni_NativeToast
(JNIEnv* env, jclass jc, jobject context, jstring jStr)
{
    jclass tclass = NULL;
    jmethodID mID = NULL;
    jobject job = NULL;
    jmethodID showId = NULL;

    tclass = env->FindClass("android/widget/Toast");
    if(tclass == NULL) {
        return;
    }
    //获取makeText方法ID
    mID = env->GetStaticMethodID(tclass,"makeText","(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
    if(mID == NULL) {
        return;
    }
    //调用makeText  第5个参数是makeText的duration
    job = env->CallStaticObjectMethod(tclass, mID, context, jStr , 0);
    if(job == NULL) {
        return;
    }
    showId = env->GetMethodID(tclass,"show","()V");
    if(showId == NULL) {
        return;
    }
    env->CallVoidMethod(job, showId, context, jStr);
}

JNIEXPORT jstring JNICALL Java_com_example_file_jnitest01_Myjni_NativeString
(JNIEnv* env, jclass)
{
    return env->NewStringUTF("NativeString");
}


5.创建Android.mk文件







代码如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := cpp
LOCAL_MODULE    := Myjni
LOCAL_SRC_FILES := Myjni.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)


6.创建Application.mk





代码如下:

APP_ABI := all
APP_STL := stlport_static
APP_OPIM := debug

7.修改Build.gradle  进行编译


Build.gradle:



ndk{
    moduleName "myjni"                              //so文件名
}


externalNativeBuild {
    ndkBuild {
        path "src/main/jni/android.mk"
    }
}

6.修改完成点击 Build->Build APK ,提示编译成功的话就可以添加调用Native方法的代码了


五、为Activity添加两个测试按钮


1.添加两个测试按钮




2.为按钮添加回调方法



3.在 MainActivity中实现回调方法并调用Native方法




代码:

public void button_MyjniToast(View view) {

    jni.MyjniToast("Myjni.MyjniToast->Myjni->MyToast");
}


public void button_NativeToast(View view) {
    jni.NativeToast(this, "Myjni.NativeToast->android.widget.Toast");
}

六、Build APK 测试吧






2016-07-20 15:21:59 jj120522 阅读数 4724
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57773 人正在学习 去看看 李宁

      在使用React Native(RN) 开发时,由于RN技术比较新,框架与控件相对较少,这时很自然就想到的RN如何调用Native以及如何进行通讯,个人感觉这块是开发必须要掌握的,今后的开发模式肯定是Hybird模式,由于初学,所以先按照文档流程跑通,至于原理后期再研究。

     为了避免发生一些异常,这里友情提示下:将gradle升级成最新版本,不然在你整合的时候有可能会报:Module 0 is not a registered callable module.

     这里简单做个安卓自定义Dialog的的示例,来了解下流程:

1.创建一个dialog的module 继承自ReactContextBaseJavaModule

package com.listviewdem;

import android.view.View;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

/**
 * Created by plus on 16/7/19.
 * 自定义删除对话框-->RN 调用
 */
public class ReactRemoveChannelModule extends ReactContextBaseJavaModule{

    public static final String REACT_CLASS = "RCTRemoveChannel";


    public ReactRemoveChannelModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    //注写 该方法为RN调用方法.
    @ReactMethod
    public void show(String title, final Callback callback){
       final  RemoveChannelDialog removeChannelDialog=new RemoveChannelDialog(getCurrentActivity());
        removeChannelDialog.show(title);
        removeChannelDialog.setPositiveButtonListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                removeChannelDialog.dismiss();
                //回调给RN
                callback.invoke(true);
            }
        });
    }
}

简单说明下:
  • getName方法返回的是JS中组件的名称.(模块名前的RCT前缀会被自动移除。)
  • 自定义RN调用的方法。注:需要加上注解@ReactMethod

RemoveChannelDialog是自定义Dialog,对有过安卓开发经验的应该很简单。


2.接下来我们要将创建好的module添加到package中,然后注入该模块。

创建一个类实现ReactPackage接口,在createNativeModules方法中封装好自定义的module。

package com.listviewdem;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by plus on 16/7/19.
 *  实现ReactPackage接口封装NativeModule实体。
 */
public class RNJavaReactPackage implements ReactPackage {

    //将创建的NativeNModule封装返回
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules=new ArrayList<>();
        modules.add(new ReactRemoveChannelModule(reactContext));
        return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return new ArrayList<>();
    }
}

这里有个诡异的问题,纠结我好久,多亏了群里一个朋友提醒了我,主要是因为相关资料太少,遇到问题无从下手.

  • 在0.29版本以前,我们是通过MainActivity也就是基于ReactActivity加入的,但是在0.29版本以后应该通过MainApplication也就是基于ReactApplication加入的.

package com.listviewdem;

import android.app.Application;
import android.util.Log;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),new RNJavaReactPackage()
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }
}
到这里我们java端就完成了,后面的就是在RN端如何进行调用.

在RN中,首先引入NativeModules,通过NativeModules获取到我们自定义原生的控件.

var {NativeModules}=require('react-native');
var dialog=NativeModules.RemoveChannel;//原生组件.
  /**
  长按事件
  **/
  onLongPress(data){
   dialog.show(data.name,(msg)=>{
     alert('移除成功');
   });
  }

这样我们就完成了react native调用Native 控件.是不是很实用.

效果图:

     

以上需求对产品而言其实没有多大作用,这里只是简单介绍了下react native 该如何调用安卓原生控件,在实际的项目开发中,应该根据自身项目情况来决定,后续我会将这个完整的项目放到github供大家参考。

RN开发群:527459711.欢迎大伙加入.


2019-08-28 18:44:08 Rokeli 阅读数 122
  • 完全征服React Native

    React Native是Facebook于2015年推出的跨平台开发工具,可用于开发Android和iOS App,并且同时具有混合开发的优点(热更新,跨平台)以及本地App的性能。 本课程采用新的ES6开发,主要内容包括ReactNative的基础知识,ReactNative的布局,组件,API,封装本地API和组件,发布ReactNative App,本地与ReactNative深度结合

    57773 人正在学习 去看看 李宁

这篇主要写一下JNI的Native开发, 没看JNI基础知识的可以去上一篇文章看一下。
JNI初识,函数规范,基本数据类型,与之对应签名

这里我们要在Android Studio中进行JNI的开发,首先创建一个NDK工程,然后打开cpp目录下的  native-lib.cpp  进行jni的开发。其中有一些c与c++的语法,如果又看不懂的可以看我的几篇别的文章了解一下c与c++语法。

首先说一下jni基本数据类型的使用

#include "jni.h"
#include <iostream>

//c++中需要以c的方式编译
extern "C"
//JNIEnv: 由Jvm传入与线程相关的变量。定义了JNI系统操作、java交互等方法。
//jobject: 表示当前调用对象,即 this , 如果是静态的native方法,则获得jclass
JNIEXPORT jstring JNICALL Java_com_dongnao_jniTest_ExampleUnitTest_test
(JNIEnv *env, jobject, jint i, jstring j, jfloat k) {
	// 获得c字符串  
	// 参数1 需要获取的字符串
	// 参数2 是否拷贝
	//提供一个boolean(int)指针,用于接收jvm传给我们的字符串是否是拷贝的。
	//通常,我们不关心这个,一般传个NULL就可以
	const char* str = env->GetStringUTFChars(j, JNI_FALSE);

	char returnStr[100];
	//格式化字符串
	sprintf_s(returnStr, "C++ string:%d,%s,%f\n", i, str, k);
	
	//释放掉内存 x
	env->ReleaseStringUTFChars(j,str);
	// 返回java字符串
	return  env->NewStringUTF(returnStr);
}

在java 中的定义与调用该Native方法:

//调用Nitive 方法
String s = test(1,"java", 1.1f);

//写一个方法,但是不实现,直接就按这种方式定义,以供与Nativie方法链接
native String test(int i,String  j,float k);

输出:

这个Native方法中我们可以看出,除了jstring类型的数据外,其他我们可以直接使用。而jstring类型的数据我们需要使用 env 变量,然后通过 GetStringUTFChars 获取char 指针,而且使用完之后需要 ReleaseStringUTFChars 进行手动释放。‘运行之后输出了相应的结果。

 

接下来我们看一下c/c++中获取java数组的写法:

java 中的定义和使用 :


int i[] = {11,22,33,44};
String j[] = {"测","试"};
testarr(i,j);

native int testarr(int[] i, String[] s);

Native方法:

extern "C"
JNIEXPORT jint JNICALL Java_com_example_costomviewtext_ExampleUnitTest_testarr
(JNIEnv *env, jobject instance,jintArray i_,jobjectArray s_)
{	
	//指向数组首元素地址
	//第二个参数:指针;指向内存地址
	//在这个地址存数据
	//true:是拷贝的一个新数据(新申请内存)
	//false:就是使用的java的数组(地址)
	jint *i  = env->GetIntArrayElements(i_, NULL);

	int32_t  length = env->GetArrayLength(i_);

	for (int  k = 0; k < length; ++k)
	{
		printf("java---jint%d\n",*(i+k));
	}
	env->ReleaseIntArrayElements(i_,i,0);

	jint strlength = env->GetArrayLength(s_);
	for (int k = 0; k < strlength; ++k)
	{	
		jstring str = static_cast<jstring>(env->GetObjectArrayElement(s_, k));//根据下标获取数组中的
		//转成可操作的字符串
		const char* s = env->GetStringUTFChars(str, 0);
		printf("java----jstring %s\n",s);
		env->ReleaseStringUTFChars(str, s);
	}
	return 100;
}

输出两个数组中的值:

这个方法接收了两个java数组,我们这里针对有差异性的两种数组进行针对性处理。
首先试 jint 类型的数组,在这里我们使用 GetIntArrayElements 获取数组指针,接着使用 GetArrayLength 获取数组长度,之后使用循环数组指针,最后使用数组指针记得释放。

然后我们处理 jobject 类型的数组,这里我们也是使用 GetArrayLength 获取数组长度,之后循环获取,然后使用c++中的类型转换转换到 jstring 类型,最后在使用上面同样的获取jstring类型的方法,就可以使用了,同样不能忘记释放内存。

 

最后看看Native中c/c++反射java,反射调用方法,反射修改属性 :

java代码:

 //java中的类 :
public class Bean {

    private static final String TAG = "Bean";

    private int i = 100;

    public int getI() {
        return i;
    }

    public void setI(int i) {
        System.out.println("setI success :"+i);
        this.i = i;
    }

    public static void printInfo(String msg){
        System.out.println(msg);
    }

}

//java中的定义与调用:
native void paseObject(Bean bean, String str);

Bean bean = new Bean();
paseObject(bean);

Native方法:

extern "C"
JNIEXPORT void JNICALL Java_com_example_costomviewtext_ExampleUnitTest_paseObject
(JNIEnv *env,jobject instance,jobject bean)
{
	//反射调用java的方法
	//1.获取java对应的class对象
	jclass beanCls = env->GetObjectClass(bean);
	//2.找到要调用的方法
	jmethodID getI  =  env->GetMethodID(beanCls, "getI", "()I"); // 签名
	//3.调用
	jint i = env->CallIntMethod(bean, getI);
	cout << i << endl;

	//set方法
	jmethodID setI = env->GetMethodID(beanCls, "setI","(I)V");
	env->CallVoidMethod(bean, setI, 200);

	//static 方法
	jmethodID printInfo = env->GetStaticMethodID(beanCls, "printInfo", "(Ljava/lang/String;)V");
	//创建java字符串
	jstring str2 = env->NewStringUTF("printInfo is called ");
	env->CallVoidMethod(beanCls,printInfo, str2);
	//释放局部引用
	env->DeleteLocalRef(str2);

	//修改属性值
	jfieldID fileId = env->GetFieldID(beanCls,"i","I");//获取属性
	env->SetIntField(bean,fileId,300);
	jint amendi = env->CallIntMethod(bean, getI);
	cout << "修改后的i:" << amendi << endl;

	env->DeleteLocalRef(beanCls);

}

输出:

在这里我们通过 GetObjectClass 反射获取 jclass 对象, 然后使用 GetMethodID 找到要调用的方法,这里就用到了上篇
文章中说的签名了。
GetMethodID 这个方法第一个参数需要传 jclass 对象,第二个参数需要传我们要找的方法名,第三个参数需要传方法的
签名,可以看到上面我们的Bean类中的 getI 方法不需要传参数,返回值是int ,所以这里对应的就是 ()I 括号内的是穿的参数,
后面带着返回值,int对应的是I,所以这里就传 “()I”。
然后接着  CallVoidMethod 调用就可以了。
后面的 setI 的调用流程和 getI 一样了,不同的是,setI 的需要传一个int值,返回值是void,所以这里它的签名就是"(I)V"。
在  CallVoidMethod 调用 setI 的时候还得将参数传过去。

还有一个static方法的示例,与普通不同的是 这里要使用的方法是 GetStaticMethodID ,参数都一样,上面我举例的这个
static方法的参数是一个string值,所以这里他的签名就是 "(Ljava/lang/String;)V" 这样。

修改属性的就使用 GetFieldID 获取,SetIntField 修改。

说了这么多,其实具体说来不怎么难,都是一些api的调用,不涉及一些逻辑算法这些,大家看过之后练习几遍自然就掌握了,这些东西在进行Ndk开发,比如我们自己编译了ffmpeg的库,然后在调用其中的一些方法,与之链接的话就需要使用JNI进行开发,JNI在其中扮演的角色就是中间链接,java 调用 Native 方法 使用native 定义之后使用,而Native调用java的时候就需要用到反射了,这部分比较重要,稍微麻烦点。

 

没有更多推荐了,返回首页