打通Android、IOS、ANE制作流程

时间:2022-10-06 09:48:09

1 原理

1.1 什么是ANE

ANE,全名Adobe Air Native Extension, 中文就是Adobe Air本地扩展。

1.1.1 什么是Adobe Air

学习新知识的一个有效途径当然是从它的出处入手。下面链接就是Adobe官方对于Adobe Air的介绍,如果你已经是Adobe Air开发人员了,那我觉得你可以跳过。

Adobe air官方文档介绍
Adobe air开发人员中心

总结一下Adobe Air:
① 是一个跨操作系统的运行时(类似于java运行时)
② 你可以通过运行时将你开发的Air应用(游戏)部署到桌面或移动设备上
③ 运行基于swf构建的应用(适用于桌面、移动设备)。即通过ActionScript3.0(Adobe官方发布的语言)开发的应用。
④ 运行基于html构建的应用(只适用于桌面)。

本文只涉及移动设备,桌面设备可举一反三

1.1.2 什么是本地扩展(Native Extension)

你通过Adobe Air开发工具开发了一款Air应用,该应用必然运行在桌面或移动设备上,那么这些设备在Air应用看来就是一个本地设备,也就是基础。

那什么是扩展?比如移动设备有音量控制的功能,然而Air应用本身却无法控制移动设备的本地功能,为了使用移动设备的音量控制功能,air应用就需要音量控制功能的本地扩展来帮助完成。类似这样的需求会有很多,而且不同的应用想要的效果也不尽相同。于是Adobe的工程师们基于这样的需求提供了一套解决办法。你只需要按照他们规定的步骤去做,你就能够让你的Air应用像原生的应用一样控制和使用移动设备的功能了。这套解决办法就是ANE(本地扩展),它能够将你的air应用的能力扩展到本地设备上。

当然互联网中也存在着很多大牛实现的一些ANE。奉行不重复造*和节省成本的原则,如果他人提供的ANE能够满足需求,那就首选已有的ANE。不过,你想制作定制你自己的功能的ANE,可以按照下文操作。

PS:ANE虽然在Adobe Air应用(游戏)开发中被称为高级部分,它绝没有你想象的那么困难。或许你不懂ActionScript,也不了解安卓,也不怎么知道IOS。没关系,只要你懂得一门面向对象编程语言,那么以下内容对你来说没有障碍。语法这个东西,懂一门你可触类旁通(只要你愿意)。只要你能理解代码逻辑,之后你可以再深入学习。至于安卓和IOS的信息,我会在文中与ANE相关的内容处做简要说明,可以根据我提供的链接深入学习。

1.2 ANE调用原理(ANE怎么起作用的)

ANE调用原理图
打通Android、IOS、ANE制作流程
说明几点ANE调用原理图:
① 建立Air应用与本地设备的联系的关键就是ane文件
② ANE内部核心1是ActionScript库(swc文件)。它建立Air应用与本地代码的联系
③ ANE内部核心2是本地代码。也就是Android-java类库/Object-C类库。它建立ActionScript库与本地设备的联系
④ 他们的联系遵循着一种规范

本文围绕着ANE展开的,如果你想深入学习其他相关知识,可以看看一下链接。

官方相关知识内容
Adobe ActionScript 3.0 * 使用本手册
ActionScript 3.0 API
面向 ActionScript 开发人员的 Adobe AIR 教程
官方安卓开发培训内容(需要*)
安卓开发培训内容(不需要*)
官方IOS开发教学内容(纯英文good luck)

OK,原理应该很好理解,那具体是怎么做的呢?遵循的规范又是什么?看第二部分实现。

2 实现

2.1 准备制作ANE的材料

通过第一部分ANE原理的描述,我们应该可以看出下一步要做的工作:
①制作一个ActionScript库
②制作一个Android(或IOS)本地代码库。
当然还有一些细节需要我们在下面的制作过程中完善。

2.1.1 实现ActionScript类库

① 创建一个ActionScript库项目,选择图一还是图二根据你的情况来定,他们只是提供的部分库不一样,对于我们的例子来说不影响。
打通Android、IOS、ANE制作流程
打通Android、IOS、ANE制作流程
打通Android、IOS、ANE制作流程
创建名为MySWCLib的项目
再创建一个名为EntryPoint的ActionScript类,我们以这个作为扩展的入口,包名com.xuemin.myswclib
OK,工程创建结束。

代码有以下三点:

① 继承EventDispatcher

public class EntryPoint extends EventDispatcher

EventDispatcher是可调度事件的所有类的基类,事件模型的重要组成部分)暂时不懂没关系

② 创造一个ExtensionContext

ExtensionContext.createExtensionContext(EXTENSION_ID, "");

通过ExtensionContext来访问本地代码
可以通过它的call()方法来调用本地方法
EXTENSION_ID:是我们创建的扩展的唯一标识

③ 给ExtensionContext添加事件监听

extensionContext.addEventListener(StatusEvent.STATUS, onStatus);

异步的本地代码结果会传递到该监听中

PS:如果在你的项目中没有找到ExtensionContext类的话,看看项目的配置是否勾选了包括Adobe AIR库,看下图
打通Android、IOS、ANE制作流程

OK,经过以上步骤,我们的代码大致应该是这样的

package com.xuemin.myswclib
{
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;


    public class EntryPoint extends EventDispatcher {
        private static var instance:EntryPoint; 
        private static var extensionContext:ExtensionContext;
        public static const EXTENSION_ID:String = "com.xuemin.myane"; // 我们编写的ane的唯一标识

        public function EntryPoint()
        {
            extensionContext = ExtensionContext.createExtensionContext(EXTENSION_ID, ""); // 创建一个与本地代码调用的ExtensionContext
            extensionContext.addEventListener(StatusEvent.STATUS, onStatus); // 添加回调事件返回监听
        }

        public static function getInstance():EntryPoint { //类单例方式实现
            if(instance == null)
                instance = new EntryPoint();
            return instance;
        }

        protected function onStatus(event:StatusEvent):void { // 本地代码的回调会返回到该方法

        }

    }
}

好,这就是一个ActionScript库的基本结构,下面具体描述与本地代码交互。上面叙述了通过ExtensionContext来访问本地代码,通过它的call()方法来调用本地方法,具体代码如下:

public static function getNativeData():String {
            var params1:int = 3;
            var params2:String = "paramsString";
            if(extensionContext != null)
                return extensionContext.call("getNativeData",params1,params2) as String;
            else
                return null;
        }

① call()方法中的第一个参数是本地代码中函数名称,它可以不是本地方法实际的名称,但是必须是约定好的,也就是ActionScript与本地代码中的名称要相同

② call()方法除了第一个之后是一个参数列表,参数可以是ActionScript中的原始数据类型或其ActionScript类对象

通过上面的代码,你可以简单的调用一个本地方法,并向本地方法传递参数,然后再获得本地方法处理后的结果。但是实际的应用情境明显要复杂很多,当你调用一个本地方法后,你不希望air应用停在那里,而是可以继续进行其他工作,当本地方法处理好之后再把结果回调给你,很明显上面的方法的返回结果是无法满足这个需求的。

这个时候就用到了上面addEventListener方法的监听了。它可以获取本地代码发送回来的数据。返回的结果中我们最常用到两个参数code和level

code:作为回调的标识
level:传递的内容

设置监听和监听收到的内容代码大致如下:

// 添加回调事件返回监听
extensionContext.addEventListener(StatusEvent.STATUS, onStatus); 
protected function onStatus(event:StatusEvent):void { // 本地代码的回调会返回到该方法
            this.dispatchEvent(event)
            if(event.code == "nativeCallbackSuccess") { //本地回调成功的信息
                trace(event.level); // 将回调信息打印
            } else if(event.code == "nativeCallbackFailed") { //本地回调失败的信息
                trace(event.level); // 将回调信息打印
            }
        }

当然,例子代码中仅仅是将返回的level数据打印了出来,你完全可以再创建一个ActionScript接口,然后将event.level的数据处理完后通过接口回调给air应用。

OK,ActionScript库的编写就这么多了。总结一下吧
① 创建ExtensionContext,这是ActionScript与本地代码的桥梁
② 调用call()方法来调用本地方法,其中参数是方法名并且要保持一致
③ 异步的回调方式,ActionScript会在addEventListener方法添加的监听中获得返回的数据

代码如下:

package com.xuemin.myswclib {
    import flash.events.EventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;


    public class EntryPoint extends EventDispatcher {
        private static var instance:EntryPoint; 
        private static var extensionContext:ExtensionContext;
        public static const EXTENSION_ID:String = "com.xuemin.myane"; // 我们编写的ane的唯一标识

        public function EntryPoint() {
            extensionContext = ExtensionContext.createExtensionContext(EXTENSION_ID, ""); // 创建一个与本地代码调用的ExtensionContext
            extensionContext.addEventListener(StatusEvent.STATUS, onStatus); // 添加回调事件返回监听
        }

        public static function getInstance():EntryPoint { //类单例方式实现
            if(instance == null)
                instance = new EntryPoint();
            return instance;
        }

        protected function onStatus(event:StatusEvent):void { // 本地代码的回调会返回到该方法
            this.dispatchEvent(event)
            if(event.code == "nativeCallbackSuccess") { //本地回调成功的信息
                trace(event.level); // 将回调信息打印
            } else if(event.code == "nativeCallbackFailed") { //本地回调失败的信息
                trace(event.level); // 将回调信息打印
            }
        }

        public static function getNativeData():String {
            var params1:int = 3;
            var params2:String = "paramsString";
            if(extensionContext != null)
                return extensionContext.call("getNativeData",params1,params2) as String;
            else
                return null;
        }
    }
}

2.1.2 实现本地代码

2.1.2.1 实现Android本地代码

首先创建一个Android工程,默认你了解这个过程,可参考以下链接
官方安卓开发培训内容(需要*)
安卓开发培训内容(不需要*)

创建的工程大致如下图:
打通Android、IOS、ANE制作流程
由于这套规范必然是Adobe的工程师们设计的,所以我们得通过他们提供的入口和规范来实现我们想要的功能,因此我们需要Adobe提供的java库了,路径在/AirSDK根目录/lib/android/FlashRuntimeExtensions.jar

将FlashRuntimeExtensions.jar添加到你的安卓工程中

如果你还不知道最新的air sdk在哪里下载,点击air_sdk下载链接

对比ActionScript部分,步骤如下:

① 实现FREExtension接口

FREExtension是扩展的入口,需要返回一个FREContext的对象,通过接口定义的createContext可以获得本地代码的连接器(上下文)

public class NativeExtension implements FREExtension{

    @Override
    public FREContext createContext(String arg0) { //需要返回一个FREContext对象
        return null;
    }

    @Override
    public void dispose() { // Extension结束后的操作

    }

    @Override
    public void initialize() { //Extension初始化的操作

    }

}

② 实现FREContext对象。

该对象就是本地代码的连接器(上下文),通过该对象的getFunctions()返回给ActionScript能够调用到的方法集合,该方法集合都是FREFunction的子类

public class NativeContext extends FREContext{

    @Override
    public Map<String, FREFunction> getFunctions() { // 需要返回一个包含FREFunction的Map集合
        return null;
    }

    @Override
    public void dispose() { // Context结束后的操作

    }

}

把FREContext传递给Extension

@Override
public FREContext createContext(String arg0) { //需要返回一个FREContext对象
    NativeContext context = new NativeContext();
    return context;
}

③ 实现FREFunction接口

该接口定义了一个方法,叫做call,是不是很熟悉,对,这个call方法与ActionScript中的ExtensionContext的call方法是对应的,这也是它们沟通的最后一步

public class GetNativeDataFunction implements FREFunction{

    @Override
    public FREObject call(FREContext arg0, FREObject[] arg1) {
        return null;
    }

}

注意几点:
① 参数一arg0,也就是FREContext对象。它有一些实用的方法,如获取Activity,Activity在Android中是一个很重要的组件,它负责与用户交互,可以加载UI并且根据不同情况有对应的生命周期方法。

Activity activity = arg0.getActivity();

官方关于Activity的介绍

② 参数二arg1,这是个FREObject数组。它对应的就是ActionScript中通过ExtensionContext的call方法传入的参数,代码如下,因为会抛异常,所以需要try-catch:

try { int params1 = arg1[0].getAsInt(); // 获取第一个参数 String params2 = arg1[1].getAsString(); // 获取第二个参数 } catch (IllegalStateException e) { e.printStackTrace(); } catch (FRETypeMismatchException e) { e.printStackTrace(); } catch (FREInvalidObjectException e) { e.printStackTrace(); } catch (FREWrongThreadException e) { e.printStackTrace(); }

③ 返回值FREObject,本地代码返回的数据必须是FREObject对象

可以通过FREObject.newObject()创建对象,创建过程可能抛异常,加入try-catch

FREObject freObject = null;
try {
    String nativeData = params1 + params2;
    freObject = FREObject.newObject(nativeData);
} catch (FREWrongThreadException e) {
    e.printStackTrace();
}

为了便于理解,代码写的有些冗余:

public class GetNativeDataFunction implements FREFunction{
    @Override
    public FREObject call(FREContext arg0, FREObject[] arg1) {
        Activity activity = arg0.getActivity(); 
        Log.i("ANE", activity.getPackageName()); // 使用安卓的日志方法打印出应用的包名
        int params1 = 0;
        String params2 = null;
        try {
            params1 = arg1[0].getAsInt();    // 获取第一个参数
            params2 = arg1[1].getAsString(); // 获取第二个参数
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (FRETypeMismatchException e) {
            e.printStackTrace();
        } catch (FREInvalidObjectException e) {
            e.printStackTrace();
        } catch (FREWrongThreadException e) {
            e.printStackTrace();
        }

        FREObject freObject = null;
        try {
            String nativeData = params1 + params2; //将参数1与参数2拼接
            freObject = FREObject.newObject(nativeData);
        } catch (FREWrongThreadException e) {
            e.printStackTrace();
        }
        return freObject; //返回结果
    }
}

④ 将实现的FREFunction添加到FREContext的getFunctions集合中

@Override
public Map<String, FREFunction> getFunctions() { // 需要返回一个包含FREFunction的Map集合
        Map<String, FREFunction> map = new HashMap<String, FREFunction>();
        map.put("getNativeData", new GetNativeDataFunction());
        return map;
}

map的第一个参数就是与ActionScript约定好的方法名

至此简单方法的连接完成了,下面我们再完善一些细节。

① 异步方法结果的传递

ActionScript部分已经说过可以通过addEventListener方法添加监听来获取本地代码发送回来的结果,那本地代码如何发送呢?第一反应还是连接的桥梁FREContext,果然它有一个方法是dispatchStatusEventAsync(code,level) 通过该方法我们可以向ActionScript传递数据了。
由于FREContext的对象是在FREExtension的createContext()方法创建的,所以我们再改装一下,代码如下:

public class NativeExtension implements FREExtension{

    private static NativeContext context;
    @Override
    public FREContext createContext(String arg0) { //需要返回一个FREContext对象
        context = new NativeContext();
        return context;
    }

    @Override
    public void dispose() { // Extension结束后的操作

    }

    @Override
    public void initialize() { //Extension初始化的操作

    }

    public static void dispatchStatusEventAsync(String code, String level) {
        if(context != null) {
            context.dispatchStatusEventAsync(code, level);
        }
    }
}

OK,通过调用FREExtension的静态方法dispatchStatusEventAsync就可以发送数据了,当然你也可以按照你的思路处理,比如提供获取context的方法再调用等。

② 关于安卓的生命周期的处理
了解点安卓的同学应该知道,常常会用到Activity的生命周期的方法,那在本地代码中如何处理?Adobe工程师设计了一个Activity的包装类AndroidActivityWrapper,通过给包装类注册监听来监听安卓Activity的变化。

你会发现在扩展的jar包并没有这个类,那是因为扩展还是上面一层,而安卓的Activity涉及到了设备代码,所以它自然而然处在了Air的Runtime中,所以你还需要一个runtime的jar包。在路径/Air_SDK根目录/lib/android/lib/runtimeClasses.jar

导入runtimeClasses.jar,OK,可以找到AndroidActivityWrapper类了
同样我们监听还需要在runtime中的StateChangeCallback接口和ActivityResultCallback接口

注意一点:反编译runtimeClasses.jar看AndroidActivityWrapper类中StateChangeCallback接口和ActivityResultCallback接口时,发现这两个接口时protected的,也就是说,如果要实现这两个接口就需要与AndroidActivityWrapper在同一个包下,即在com.adobe.air包下。

static abstract interface ActivityResultCallback{
    public abstract void onActivityResult(int paramInt1, int paramInt2, Intent paramIntent);
}
static abstract interface StateChangeCallback {
    public abstract void onActivityStateChanged(AndroidActivityWrapper.ActivityState paramActivityState);
    public abstract void onConfigurationChanged(Configuration paramConfiguration);
}

所以,按照以下实现思路操作:
在com.adobe.air包路径下创建新接口继承ActivityResultCallback和StateChangeCallback,但是把权限放大到public

于是在com.adobe.air下先创建抽象接口,继承ActivityResultCallback和StateChangeCallback,代码如下:

package com.adobe.air;

import com.adobe.air.AndroidActivityWrapper.ActivityResultCallback;
import com.adobe.air.AndroidActivityWrapper.StateChangeCallback;


public abstract interface LifeCycle extends ActivityResultCallback, StateChangeCallback{}

通过上面的方法,在我们自己的代码中就可以处理生命周期了
于是再改造一下NativeContext类,实现我们自己的LifeCycle接口
在构造函数中添加监听,在dispose方法中移除监听

public class NativeContext extends FREContext implements LifeCycle{

    private AndroidActivityWrapper activityWrapper;

    public NativeContext() {
        activityWrapper = AndroidActivityWrapper.GetAndroidActivityWrapper(); //ActivityWrapper
        activityWrapper.addActivityResultListener(this); // 监听onActivityResult方法
        activityWrapper.addActivityStateChangeListner(this); // 监听生命周期方法
    }

    @Override
    public void dispose() { // Context结束后的操作
        if(activityWrapper != null)
            activityWrapper.removeActivityResultListener(this);
            activityWrapper.removeActivityStateChangeListner(this);
    }

    @Override
    public Map<String, FREFunction> getFunctions() { // 需要返回一个包含FREFunction的Map集合
        Map<String, FREFunction> map = new HashMap<String, FREFunction>();
        map.put("getNativeData", new GetNativeDataFunction());
        return map;
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) { // onActivityResult方法

    }

    @Override
    public void onActivityStateChanged(ActivityState state) { // 生命周期变化

    }

    @Override
    public void onConfigurationChanged(Configuration config) {

    }

}

其中生命周期的对应关系如下:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { // onActivityResult方法
    Log.i("ANE", "onActivityResult");
}

@Override
public void onActivityStateChanged(ActivityState state) { // 生命周期变化
    switch (state) {
    case RESUMED: // 生命周期onResume
        Log.i("ANE", "onResume");
    break;
    case STARTED: // 生命周期onStart
        Log.i("ANE", "onStart");
    break;
    case RESTARTED: // 生命周期onRestart
        Log.i("ANE", "onRestart");
    break;
    case PAUSED: // 生命周期onPause
        Log.i("ANE", "onPause");
    break;
    case STOPPED: // 生命周期onStop
        Log.i("ANE", "onStop");
    break;
    case DESTROYED: // 生命周期onDestory
        Log.i("ANE", "onDestory");
    break;
    }
}

以上就是安卓本地代码的处理了,总结一下
① 核心类FREExtension,FREContext,FREFunction
② 通过FREContext连通,call方法调用
③ 异步回调可以通过FREContext的dispatchStatusEventAsync处理
④ 生命周期的实现需要点技巧

2.1.2.2 实现IOS本地代码

IOS本地代码的开发借鉴了Github的一个工程,做了一些优化和增强,你完全可以按照该模板进行你自己的改编,GitHub项目如下:
xcode-template-ane

好,开始我们自己的IOS本地代码的开发

首先创建一个Xcode工程,选择Static Library静态库

打通Android、IOS、ANE制作流程

和安卓一样起名MyNativeLib

打通Android、IOS、ANE制作流程

开发IOS本地代码毫无疑问我们仍然需要遵循Adobe制定的规范,当然也需要Adobe提供的库,在Object-C语法中,我们需要一个头文件
在你的/Air_SDK根目录/include/FlashRuntimeExtension.h

引入FlashRuntimeExtension.h文件

对比安卓来分析IOS的FlashRuntimeExtension.h文件
本地扩展的初始化

typedef void (*FREInitializer)(
        void**                 extDataToSet       ,
        FREContextInitializer* ctxInitializerToSet,
        FREContextFinalizer*   ctxFinalizerToSet
);

本地扩展的结束处理

typedef void (*FREFinalizer)(
        void* extData
);

本地扩展的连接器(上下文)初始化

typedef void (*FREContextInitializer)(
        void*                    extData          ,
        const uint8_t*           ctxType          ,
        FREContext               ctx              ,
        uint32_t*                numFunctionsToSet,
        const FRENamedFunction** functionsToSet
);

本地扩展的连接器(上下文)结束的操作

typedef void (*FREContextFinalizer)(
        FREContext ctx
);

根据这四个定义,在头文件中定义了对应的方法

void ExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet);

void ExtensionFinalizer(void* extData);

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet);

void ContextFinalizer(FREContext ctx);

具体实现:
1 Extension的初始化,需要传入Context的相关方法
2 在Context中需要numFunctionsToTest和functionsToSet

functionsToSet:方法的数组(可对比安卓理解)
numFunctionsToTest :方法数组的数量

ActionScript通过该数组访问本地方法,而本地方法都是FRENamedFunction对象。

感谢github大牛的帮助:借用了他的模板的两个宏定义简化代码,方便阅读


#define ANE_FUNCTION(f) FREObject (f)(FREContext ctx, void *data, uint32_t argc, FREObject argv[])


#define MAP_FUNCTION(f, data) { (const uint8_t*)(#f), (data), &(f) }

为了方便实现我加入了几个FREObject与Object-C类型的转化方法

头定义

// FREObject <--> NSString
NSString * getStringFromFREObject(FREObject obj);
FREObject createFREString(NSString * string);
// FREObject <--> double
double getDoubleFromFREObject(FREObject obj);
FREObject createFREDouble(double value);
// FREObject <--> int
int getIntFromFREObject(FREObject obj);
FREObject createFREInt(int value);
// FREObject <--> BOOL
BOOL getBoolFromFREObject(FREObject obj);
FREObject createFREBool(BOOL value);

OK,整体代码如下:

MyNativeLib.h

#import <Foundation/Foundation.h>
#import "FlashRuntimeExtensions.h"
void ExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet);

void ExtensionFinalizer(void* extData);

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet);

void ContextFinalizer(FREContext ctx);

#define ANE_FUNCTION(f) FREObject (f)(FREContext ctx, void *data, uint32_t argc, FREObject argv[])
#define MAP_FUNCTION(f, data) { (const uint8_t*)(#f), (data), &(f) }


ANE_FUNCTION(getNativeData);

/**************************************************/
NSString * getStringFromFREObject(FREObject obj);
FREObject createFREString(NSString * string);

double getDoubleFromFREObject(FREObject obj);
FREObject createFREDouble(double value);

int getIntFromFREObject(FREObject obj);
FREObject createFREInt(int value);

BOOL getBoolFromFREObject(FREObject obj);
FREObject createFREBool(BOOL value);

MyNativeLib.m

#import "MyNativeLib.h"

void ExtensionInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet) {
    NSLog(@"Entering ExtensionInitializer()");

    *extDataToSet = NULL;
    *ctxInitializerToSet = &ContextInitializer; //传入Context初始化方法
    *ctxFinalizerToSet = &ContextFinalizer; //传入Context结束方法

    NSLog(@"Exiting ExtensionInitializer()");}

void ExtensionFinalizer(void* extData) {
    NSLog(@"Entering ExtensionFinalizer()");
    // 可以做清理工作.
    NSLog(@"Exiting ExtensionFinalizer()");
}

void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet) {

    static FRENamedFunction func[] =
    {
        MAP_FUNCTION(getNativeData, NULL)
    };

    *numFunctionsToTest = sizeof(func) / sizeof(FRENamedFunction);
    *functionsToSet = func;
}

void ContextFinalizer(FREContext ctx) {
    NSLog(@"Entering ContextFinalizer()");
    // 可以做清理工作
    NSLog(@"Exiting ContextFinalizer()");
}


ANE_FUNCTION(getNativeData)
{
    int params1 = getIntFromFREObject(argv[0]);
    NSString *params2 = getStringFromFREObject(argv[1]);
    NSString * nativeData = [[NSString new] initWithFormat:@"%d%@",params1,params2];
    return createFREString(nativeData);
}

/* * utils function */

/*--------------------------------string------------------------------------*/
NSString * getStringFromFREObject(FREObject obj)
{
    uint32_t length;
    const uint8_t *value;
    FREGetObjectAsUTF8(obj, &length, &value);
    return [NSString stringWithUTF8String:(const char *)value];
}

FREObject createFREString(NSString * string)
{
    const char *str = [string UTF8String];
    FREObject obj;

    FRENewObjectFromUTF8(strlen(str)+1, (const uint8_t*)str, &obj);
    return obj;
}
/*-------------------------------double-----------------------------------*/
double getDoubleFromFREObject(FREObject obj)
{
    double number;
    FREGetObjectAsDouble(obj, &number);
    return number;
}
FREObject createFREDouble(double value)
{
    FREObject obj = nil;
    FRENewObjectFromDouble(value, &obj);
    return obj;
}
/*---------------------------------int---------------------------------*/
int getIntFromFREObject(FREObject obj)
{
    int32_t number;
    FREGetObjectAsInt32(obj, &number);
    return number;
}
FREObject createFREInt(int value)
{
    FREObject obj = nil;
    FRENewObjectFromInt32(value, &obj);
    return obj;
}
/*------------------------------bool----------------------------------------*/
BOOL getBoolFromFREObject(FREObject obj)
{
    uint32_t boolean;
    FREGetObjectAsBool(obj, &boolean);
    return boolean;
}

FREObject createFREBool(BOOL value)
{
    FREObject obj = nil;
    FRENewObjectFromBool(value, &obj);
    return obj;
}

其中ANE_FUNCTION(getNativeData)就是最后的处理

以上就是IOS本地代码的简单调用,同安卓一样,我门也需要异步的获取本地代码处理的结果,那IOS的扩展定义中有没有类似context.dispatchStatusEventAsync的方法?
在查找FlashRuntimeExtension.h中找到了FREDispatchStatusEventAsync定义
定义如下:

FREResult FREDispatchStatusEventAsync(
        FREContext     ctx  ,
        const uint8_t* code ,
        const uint8_t* level
);

可以看出,几乎与安卓一样,我们需要一个FREContext对象,以及要发送给ActionScript的code和level值,所以我们封装一下方法方便使用

头定义:

void dispatchStatusEventAsync(NSString * code, NSString * level);

实现

void dispatchStatusEventAsync(NSString * code, NSString * level)
{
    if(mContext!= nil)
    {
        FREDispatchStatusEventAsync(mContext, (const uint8_t *) [code UTF8String], (const uint8_t *) [level UTF8String]);
    }
    else
    {
        NSLog(@"FREContext is null");
    }
}

只需要在适当的地方调用dispatchStatusEventAsync 方法就可以发送信息给ActionScript了。

调用代码片段

ANE_FUNCTION(getNativeData)
{
    int params1 = getIntFromFREObject(argv[0]);
    NSString *params2 = getStringFromFREObject(argv[1]);
    NSString * nativeData = [[NSString new]  
    initWithFormat:@"%d%@",params1,params2];     
dispatchStatusEventAsync(@"nativeCallbackSuccess",@"iosCallback"); // 回调

    return createFREString(nativeData);
}

以上就是IOS本地代码基本实现了。还有最后一个问题,我们需要解决。IOS有一个Delegate类涉及到了IOS的生命周期,那IOS生命周期是否也可以按照android的方式处理?然而不行,在air sdk中我们只找到了头文件,并没有其他的内容。最后通过一篇别人的博客,利用IOS的Hook机制达到了对生命周期的控制的目的。

Objective-C的hook方案(一): Method Swizzling
感谢作者,她的一些其他ios的文章也值得一读

根据博客中描述的原理,最后实现了一个模版性的Hook类,你可以直接按照自己的需求修改使用

大概说明一下这个原理,Object-C的方法是基于消息发送的机制,而我们的Hook类似于在一个链表中插入了一个节点,但还是保证了他们的连通

//
// HookUtils.m
// ResearchMethodSwizzl
//
// Created by 薛旻 on 15/4/27.
// Copyright (c) 2015年 薛旻. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>


@interface HookUtils : NSObject

+ (void)hookMehod:(SEL)oldSEL andDef:(SEL)defaultSEL andNew:(SEL)newSEL;

@end

@implementation HookUtils

+ (void)hookMehod:(SEL)oldSEL andDef:(SEL)defaultSEL andNew:(SEL)newSEL {
    NSLog(@"hookMehod");

    Class oldClass = objc_getClass([@"CTAppDelegate" UTF8String]);

    Class newClass = [HookUtils class];

    //把方法加给原Class
    class_addMethod(oldClass, newSEL, class_getMethodImplementation(newClass, newSEL), nil);
    class_addMethod(oldClass, oldSEL, class_getMethodImplementation(newClass, defaultSEL),nil);

    Method oldMethod = class_getInstanceMethod(oldClass, oldSEL);
    assert(oldMethod);
    Method newMethod = class_getInstanceMethod(oldClass, newSEL);
    assert(newMethod);
    method_exchangeImplementations(oldMethod, newMethod);

}

+ (void)load {
    NSLog(@"load");
    [self hookMehod:@selector(application:didFinishLaunchingWithOptions:) andDef:@selector(defaultApplication:didFinishLaunchingWithOptions:) andNew:@selector(hookedApplication:didFinishLaunchingWithOptions:)];

    [self hookMehod:@selector(applicationWillEnterForeground:) andDef:@selector(defaultApplicationWillEnterForeground:) andNew:@selector(hookedApplicationWillEnterForeground:)];
}


/*具体要走的代码*/
-(BOOL)hookedApplication:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)dic
{
    NSLog(@"applicationDidFinishLaunching");
    [self hookedApplication:application didFinishLaunchingWithOptions:dic];
    return YES;
}

- (void)hookedApplicationWillResignActive:(UIApplication *)application {
    [self hookedApplicationWillResignActive:application];
}

- (void)hookedApplicationDidEnterBackground:(UIApplication *)application {
    [self hookedApplicationDidEnterBackground:application];
}

- (void)hookedApplicationWillEnterForeground:(UIApplication *)application {
    [self hookedApplicationWillEnterForeground:application];
}

- (void)hookedApplicationDidBecomeActive:(UIApplication *)application {
    [self hookedApplicationDidBecomeActive:application];
}

- (void)hookedApplicationWillTerminate:(UIApplication *)application {
    [self hookedApplicationWillTerminate:application];
}

/*支付宝对应的方法*/
- (BOOL)hookedApplication:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    [self hookedApplication:application openURL:url sourceApplication:sourceApplication annotation:annotation];
    return YES;
}



-(BOOL)hookedApplication:(UIApplication*)application handleOpenURL:(NSURL*)url {
    [self hookedApplication:application handleOpenURL:url];
    return YES;
}


#pragma 默认
/*default 默认不需要改动*/
- (BOOL)defaultApplication:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)dic { return YES;
}

- (void)defaultApplicationWillResignActive:(UIApplication *)application {}

- (void)defaultApplicationDidEnterBackground:(UIApplication *)application {}

- (void)defaultApplicationWillEnterForeground:(UIApplication *)application {}

- (void)defaultApplicationDidBecomeActive:(UIApplication *)application {}

- (void)defaultApplicationWillTerminate:(UIApplication *)application {}

- (BOOL)defaultApplication:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return YES;
}

- (BOOL)defaultApplication:(UIApplication *)application handleOpenURL:(NSURL *)url {
    return YES;
}
@end

关于这个HookUtils做几点说明:
① HookUtils是IOS的Hook机制的实现,所以适用于所有IOS的开发,这里只是针对Air应用做了一些处理。
① Hook机制中需要获取应用的实现了生命周期的类名,这样才能Hook处理,如果你是Xcode工程开发的代码那你会很方便的找到这个类名,这适用于IOS原生,Unity以及Cocos引擎,因为它们都会使用Xcode开发工具。但是,Air开发是在Flash Builder上,并不直接涉及到Xcode工程,通过代码打印Air应用的生命周期类,找到了Air的类名CTAppDelegate,于是代码中设置为,其他引擎自行修改,当然,你也可以通过配置文件(Info.plist)获取,这样代码就不需要修改了

 Class oldClass = objc_getClass([@"CTAppDelegate" UTF8String]);

③ 你需要在hooked**方法中实现你的具体代码,最后在+ (void)load()方法中调用头文件定义的方法(void)hookMehod:(SEL)oldSEL andDef:(SEL)defaultSEL andNew:(SEL)newSEL;来达到Hook的目的

OK,以上就是IOS处理的全部过程,由于本人精通的是安卓,所以在IOS的处理上带入了Android的思路,IOS的处理上描述的也不详尽,如果在IOS本地代码上理解有问题,建议你先看看上面Android本地代码的处理,因为规范和逻辑是通的。如果IOS部分有问题还望指正。

总结一下IOS本地代码的实现
① FREInitializer,FREFinalizer,FREContextInitializer,FREContextFinalizer的内部实现

对比Android的FREExtension和FREContext的实现

② FRENamedFunction** functionsToSet:方法数组
uint32_t* numFunctionsToTest:方法大小
FREContext ctx:上下文引用
通过两个宏定义方便实现方法和数组

对比Android的getFunctions()和FREFunction的实现

③ 通过FREContext这个参数,调用方法FREDispatchStatusEventAsync实现异步消息传递给ActionScript

对比Android的dispatchStatusEventAsync方法

④ HookUtils利用Hook机制处理IOS生命周期

对比Android的生命周期的处理
AndroidActivityWrapper.GetAndroidActivityWrapper()
ActivityResultCallback,
StateChangeCallback
当然Android和IOS的生命周期的处理也是最需要技巧的一部分

至此,我们的本地代码部分也全部完成

2.2 制作ANE

进过上面操作代码逻辑层面已经处理完了,废话不说,通过上面的东西,制作可用的ANE

2.2.1 准备资源

以下是一个ANE打包的资源清单,只需要Android或者IOS的同学可*组合,后面分别介绍它们

  • Android本地代码库——jar包(Android需要)
  • IOS本地代码库——IOS静态库文件(IOS需要)
  • ActionScript库——SWC文件(必须)
  • library.swf(必须)
  • extension.xml(必须,Android与IOS配置不同)
  • platformoptions.xml (IOS可能需要)
  • 第三方库文件 (IOS可能需要)

① Android本地代码库——jar包

先将android本地代码导出jar包,右键Eclipse工程,Export

打通Android、IOS、ANE制作流程

只勾选src文件,也就是说我们最后只需要class文件,导出为MyNativeLib.jar

打通Android、IOS、ANE制作流程

拿到我们自己的库文件后,需要思考一个问题,我们在写本地代码时,为了实现一些功能(比如接入其他一些SDK)时,会引入第三方的jar包,那这个些jar包怎么处理?

很简单,把它们统统打包成一个jar包即可,即解压它们,把他们放在同一个文件夹下,使用jdk的打包命令再合并成一个包,默认你已经配置好了jdk
打包命令如下:

jar cvfm MyNativeLib.jar meta-inf/manifest.mf .

在合并的根路径下运行该命令行可打包出合并的MyNativeLib.jar,这个jar就是最终我们需要的jar

② IOS本地代码库——IOS静态库文件

运行我们的Xcode工程生成libMyNativeLib.a库文件,由于IOS现在要求必须支持全部架构,也就是说你的库必须同时支持32位(armv7、armv7s)和64位(arm64)。在Xcode中进行配置
我在MyNativeLib工程中建立了一个自动构建库文件脚本,方便你后期研究,属于衍生,此文暂不介绍。
打通Android、IOS、ANE制作流程

③ ActionScript库——SWC文件(必须)

打通Android、IOS、ANE制作流程

④ library.swf(必须)

加压上一步的swc文件就可以找到我们需要的library.swf文件

打通Android、IOS、ANE制作流程

⑤ extension.xml(必须,Android与IOS配置不同)

打通Android、IOS、ANE制作流程

<extension xmlns="http://ns.adobe.com/air/extension/3.5">
    <id>com.xuemin.myane</id>
    <versionNumber>1</versionNumber>
    <platforms>
        <platform name="default">
            <applicationDeployment>
            </applicationDeployment>
        </platform>
        <platform name="Android-ARM">
            <applicationDeployment>
                <nativeLibrary>MyNativeLib.jar</nativeLibrary>
                <initializer>com.xuemin.mynativelib.NativeExtension</initializer>
            </applicationDeployment>
        </platform>
        <platform name="iPhone-ARM">
            <applicationDeployment>
                <nativeLibrary>libMyNativeLib.a</nativeLibrary>
                <initializer>ExtensionInitializer</initializer>
                <finalizer>ExtensionFinalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>

⑥ platformoptions.xml (IOS可能需要)

刚刚处理android的jar包时,我们把需要的第三方jar包合并在一起即可使用,但是在ios的库文件处理时我们会发现,除了第三方的静态库文件(.a文件)可以和我们自己的ios库文件合并外,其他第三方的Framework库以及依赖的系统的Framework库是都无法通过合并处理。但是我们又有这样的需求,所以Adobe提供了一种方法,通过platformoptions.xml文件来配置ios依赖的系统库和第三方库

打通Android、IOS、ANE制作流程

<platform xmlns="http://ns.adobe.com/air/extension/3.5"> 
    <description>MyNativeLib</description> 
    <copyright>xuemin</copyright> 
    <linkerOptions> 
        <option>-framework Foundation</option>
        <option>-framework UIKit</option>
        <option>-framework SystemConfiguration</option>
        <option>-lsqlite</option>
    </linkerOptions> 
    <packagedDependencies> 
        <packagedDependency>AlipaySDK.framework</packagedDependency> 
    </packagedDependencies>
</platform>

以上只是为了说明配置,在例子中并没有用到该文件

注意:
第三方依赖库与系统库不同air runtime找不到这些库,所以在生成ANE时要指定这些库的位置,这也就是我们最后一步的处理

⑦ 第三方库文件 (IOS可能需要)

见⑥的注意事项

好,制作ANE的材料已经准备完成,下一步加工成ANE

2.2.2 打包ANE

制作ANE呢,就是通过air sdk提供的打包命令,根据提供的材料,生成出ANE文件来使用,ANE类似于一个压缩包,你可以解压研究其中的内容,相信对你ANE原理还是很有帮助的(题外话)

既然打包命令,那必然就需要了解一下提供的工具和参数了,我们使用的工具就是sdk中的adt
这是官方的使用说明:ADT package 命令
其中还有一些使用例子,在这里我只介绍几本的打包使用,如果想深入学习,可通过上面链接学习。

当然,网上还是有人提供了类似的工具
ANETool

下面按照我们的命令行学习,很简单只需要一句

① 只支持Android打包

打通Android、IOS、ANE制作流程

adt -package -target ane myane.ane extension.xml -swc MySWCLib.swc -platform Android-ARM  MyNativeLib.jar library.swf -platform default library.swf

② Android和IOS同时支持打包

打通Android、IOS、ANE制作流程

adt -package -target ane myane.ane extension.xml -swc MySWCLib.swc  -platform Android-ARM  MyNativeLib.jar library.swf -platform default library.swf-platform iPhone-ARM -platformoptions platformoptions.xml libMyNativeLib.a 第三方.framework 第三方.a library.swf 

③ 只支持IOS打包

可通过①和②推理出来

最后生成出myane.ane,我们可以在air应用中使用该ane文件了。

工程下载

最后提供本文写的代码工程和配置的GitHUb路径
build_ane_project

你也可以直接在GitHub上搜索build_ane_project
或通过以下链接git clone
https://github.com/justxuemin/build_ane_project.git