Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能

公司有个Flash的外包项目,现在用户量在稳步增加,公司想集成下UM统计的功能,统计看一下App的活跃情况之类的...外包公司做不来...好吧,自己做调研。因为很多框架都都不在维护ANE的拓展库了,而且网上普遍资料较老,在调研的过程中遇到很多坑,记下一篇给后来之人,防止他们走更多的弯路...

一、ANE是什么,他的作用是什么?

1.ANE(Air Native Extension)本机扩展,就是对Air运行的本机的功能的扩展:(自我白话理解非官方解释)因为Flash Air可以开发各种跨平台的项目:由于Flash可能没有提供实现一些特定需求功能的API或者平台不提供对外的API接口,导致Flash项目不能实现想要实现一些功能,或者调用平台的底层API。这时候通过ANE桥接原平台的方法的方式来实现一些flash平台不能实现的功能<打开硬件设备(摄像头,打开相册,获取定位信息),获取硬件信息...,调用该平台下的第三方框架(友盟数据统计...)

二、如何制作ANE

2.1 制作工具
  1.xcode 8.2
  2.Flash Builder 4.7

2.2 生成一个ANE需要的文件
  1. ActionScript扩展库(.swc)
  2.本机的静态库(
.a)<.framework如何生成.a静态库请参考:xcode制作iOS静态库SDK<包含第三方.a或者.framework静态库>>
  3. 本机扩展的描述性文件(extension.xml)
  4. 本机扩展的配置文件(platformoptions.xml)
  5. 从swc文件(第1个文件)中解压得出swf文件 (library.swf)
  6.Flash builder的 adt工具

三、ANE的制作过程

以制作友盟统计的ANE为例<我尽量写的详细一点>

1.打开Flash Builder 创建Flex库项目


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第1张图片
1.png

Flex项目基本设置


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第2张图片
2.png

2.创建AS类,写好接口代买。完成之后构建生成UMForANE.swc文件


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第3张图片
创建AS类.png

Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第4张图片
命名.png

3.代码实现

package 
{
    //    EventDispatcher类是用来管理侦听器函数的
    import flash.events.EventDispatcher;
    // 扩展上下文  主要用于通信作用
    import flash.external.ExtensionContext;
    import flash.events.IEventDispatcher;
    // 监听的事件
    import flash.events.StatusEvent;
    
    public class UMForANE extends EventDispatcher
    {
        private var ec:ExtensionContext = null;
        //唯一标识 
        public static const ID:String = "com.cpapptest.test";
        
        // 初始化方法
        public function UMForANE(target:IEventDispatcher=null)
        {
            super(target);
            //创建一个ExtensionContext实例,扩展ID需要注意一下,打包的时候会用到
            ec = ExtensionContext.createExtensionContext(ID,null);
            if(ec){
                //添加事件用来监听里面抛出的事件,或者消息用的
                ec.addEventListener(StatusEvent.STATUS,onStatus);   
            }
        }   
        
    
        /**
         * 耳机检测事件回调,返回耳机状态
         */
        private function onStatus(event:StatusEvent):void
        {
            trace("事件回调 \n"+JSON.stringify(event));
        }
        
        /**
         * 初始化友盟统计模块 并开始统计
         @param appkey app统计的key.
         @param channelID  统计途径 为空时默认为 App Store .
          @param isDebug   是否是Debug模式 
         @return void.
         */
        public function startAnaly(appkey:String="", channelID:String="", isDebug:Boolean=false):void
        {
            if (ec)
                // call 一下就是主动调用该设备上的扩展方法
                ec.call('startAnaly', appkey, channelID, isDebug);
        }
        
        
        /**
         * 手动页面时长统计, 记录某个页面展示的时长.
            @param pageName 统计的页面名称.
            @param seconds 单位为秒,转成string类型.
            @return void.
         */
        public function logPageView(pageName:String, seconds:String):void
        {
            if (ec)
                ec.call("logPageView", pageName, seconds);
        }
        
        /** 自动页面时长统计, 开始记录某个页面展示时长.
         使用方法:必须配对调用beginLogPageView:和endLogPageView:两个函数来完成自动统计,若只调用某一个函数不会生成有效数据。
         在该页面展示时调用beginLogPageView:,当退出该页面时调用endLogPageView:
         @param pageName 统计的页面名称.
         @return void.
         */
        public function beginLogPageView(pageName:String):void
        {
            if (ec)
                ec.call("beginLogPageView", pageName);
        }
        
        /** 自动页面时长统计, 结束记录某个页面展示时长.
         使用方法:必须配对调用beginLogPageView:和endLogPageView:两个函数来完成自动统计,若只调用某一个函数不会生成有效数据。
         在该页面展示时调用beginLogPageView:,当退出该页面时调用endLogPageView:
         @param pageName 统计的页面名称.
         @return void.
         */
        public function endLogPageView(pageName:String):void
        {
            if (ec)
                ec.call("endLogPageView", pageName);
        }
        
        /** 自定义事件,数量统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID
         
         @param  eventId 网站上注册的事件Id.
         @param  label 分类标签。不同的标签会分别进行统计,方便同一事件的不同标签的对比,为nil或空字符串时后台会生成和eventId同名的标签.
         @param  accumulation 累加值。为减少网络交互,可以自行对某一事件ID的某一分类标签进行累加,再传入次数作为参数。
         @return void.
         */
        public function event(eventID:String):void
        {
            if (ec)
                ec.call('event', eventID);
        }
        
        /** 自定义事件,数量统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID
         */
        public function eventWithLabel(eventID:String, label:String=null):void
        {
            if (ec)
                ec.call("eventWithLabel", eventID, label);
        }
        
        /** 自定义事件,时长统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID.
         beginEvent,endEvent要配对使用,也可以自己计时后通过durations参数传递进来
         
         @param  eventId 网站上注册的事件Id.
         @param  label 分类标签。不同的标签会分别进行统计,方便同一事件的不同标签的对比,为nil或空字符串时后台会生成和eventId同名的标签.
         @param  primarykey 这个参数用于和event_id一起标示一个唯一事件,并不会被统计;对于同一个事件在beginEvent和endEvent 中要传递相同的eventId 和 primarykey
         @param millisecond 自己计时需要的话需要传毫秒进来
         @return void.
         
         @warning 每个event的attributes不能超过10个
         eventId、attributes中key和value都不能使用空格和特殊字符,必须是NSString,且长度不能超过255个字符(否则将截取前255个字符)
         id, ts, du是保留字段,不能作为eventId及key的名称
         */
        public function beginEvent(eventID:String):void
        {
            if (ec)
                ec.call("beginEvent", eventID);
        }
        
        /** 自定义事件,时长统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID.
         */
        public function endEvent(eventID:String):void
        {
            if (ec)
                ec.call("endEvent", eventID);
        }
        
        public function beginEventWithLabel(eventID:String,label:String=null):void
        {
            if (ec)
                ec.call("beginEventWithLabel", eventID,label);
        }
        
        /** 自定义事件,时长统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID.
         */
        public function endEventWithLabel(eventID:String,label:String=null):void
        {
            if (ec)
                ec.call("endEventWithLabel", eventID,label);
        }
        
        /** 自定义事件,时长统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID.
         */
        public function eventWithDuration(eventID:String, seconds:String):void
        {
            if (ec)
                ec.call("eventWithDuration", eventID);
        }
        
        /** 自定义事件,时长统计.
         使用前,请先到友盟App管理后台的设置->编辑自定义事件 中添加相应的事件ID,然后在工程中传入相应的事件ID.
         */
        public function eventLabelWithDuration(eventID:String ,label:String,seconds:String):void
        {
            if (ec)
                ec.call("eventLabelWithDuration", eventID,label,seconds);
        }
    }
}

4.编译生成swc文件


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第5张图片
编译生成swc文件.png

5.安装xcode ANE打包模板xcode-template-ane,安装完成后创建一个ANE静态库模板项目<不使用模板也是可以的,只不过需要自己通过命令手动生成最后的ANE文件,使用模板可以一键式生成,节省很多步骤>

Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第6张图片
xcode-template-ane.png

Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第7张图片
项目设置.png

6.将Flash Build SDK/include中的 FlashRuntimeExtensions.h和友盟的SDK文件拖入到项目中去


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第8张图片
FlashRuntimeExtensions.h路径.png

7.配置xcode的build setting选项<兼容arm64 armv7 armv7>


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第9张图片
11.png

Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第10张图片
12.png

8.编写ane接口方法,使用xcode-ANE模板,自动生成ane的初始化方法
在自动生成的方法基础上重写ContextInitializer方法


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第11张图片
编写接口方法.png

UMeng.h

/*
 
 Copyright (c) 2012, DIVIJ KUMAR
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met: 
 
 1. Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer. 
 2. Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution. 
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 The views and conclusions contained in the software and documentation are those
 of the authors and should not be interpreted as representing official policies, 
 either expressed or implied, of the FreeBSD Project.
 
 
 */

/*  
 * UMeng
 *
 * Created by collegepre on 17/3/6.
 * Copyright (c) 2017年 __MyCompanyName__. All rights reserved.
*/

#import 
#import "FlashRuntimeExtensions.h"

#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 startAnaly(FREContext context, void* funcData, uint32_t argc, FREObject argv[]);
/* UMengExtInitializer()
 * The extension initializer is called the first time the ActionScript side of the extension
 * calls ExtensionContext.createExtensionContext() for any context.
 *
 * Please note: this should be same as the  specified in the extension.xml 
*/
void UMengExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet);

/* UMengExtFinalizer()
 * The extension finalizer is called when the runtime unloads the extension. However, it may not always called.
 *
 * Please note: this should be same as the  specified in the extension.xml 
*/
void UMengExtFinalizer(void* extData);

/* ContextInitializer()
 * The context initializer is called when the runtime creates the extension context instance.
*/
void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet);

/* ContextFinalizer()
 * The context finalizer is called when the extension's ActionScript code
 * calls the ExtensionContext instance's dispose() method.
 * If the AIR runtime garbage collector disposes of the ExtensionContext instance, the runtime also calls ContextFinalizer().
*/
void ContextFinalizer(FREContext ctx);

/* This is a sample function that is being included as part of this template. 
 *
 * Users of this template are expected to change this and add similar functions 
 * to be able to call the native functions in the ANE from their ActionScript code
*/
ANE_FUNCTION(isSupported);

UMeng.m

/*
 
 Copyright (c) 2012, DIVIJ KUMAR
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met: 
 
 1. Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer. 
 2. Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution. 
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 The views and conclusions contained in the software and documentation are those
 of the authors and should not be interpreted as representing official policies, 
 either expressed or implied, of the FreeBSD Project.
 */

/*
 * UMeng.m
 * UMeng
 *
 * Created by collegepre on 17/3/6.
 * Copyright (c) 2017年 __MyCompanyName__. All rights reserved.
 */

#import "UMeng.h"
#import "UMMobClick.framework/Headers/MobClick.h"

/* UMengExtInitializer()
 * The extension initializer is called the first time the ActionScript side of the extension
 * calls ExtensionContext.createExtensionContext() for any context.
 *
 * Please note: this should be same as the  specified in the extension.xml 
 */
void UMengExtInitializer(void** extDataToSet, FREContextInitializer* ctxInitializerToSet, FREContextFinalizer* ctxFinalizerToSet) 
{
    NSLog(@"Entering UMengExtInitializer()");

    *extDataToSet = NULL;
    *ctxInitializerToSet = &ContextInitializer;
    *ctxFinalizerToSet = &ContextFinalizer;

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

/* UMengExtFinalizer()
 * The extension finalizer is called when the runtime unloads the extension. However, it may not always called.
 *
 * Please note: this should be same as the  specified in the extension.xml 
 */
void UMengExtFinalizer(void* extData) 
{
    NSLog(@"Entering UMengExtFinalizer()");

    // Nothing to clean up.
    NSLog(@"Exiting UMengExtFinalizer()");
    return;
}

FREObject startAnaly(FREContext context, void* funcData, uint32_t argc, FREObject argv[]){
    
    const uint8_t* appKey;
    const uint8_t* channelID;
    uint32_t* isDebug = NULL;
    uint32_t stringLength;
    NSString *appKeyString = nil;
    NSString *channelIDString = nil;
    
    if(argv[0] && (FREGetObjectAsUTF8(argv[0], &stringLength, &appKey) == FRE_OK)){
        appKeyString = [NSString stringWithUTF8String:(char*)appKey];
    }
    if(argv[1] && (FREGetObjectAsUTF8(argv[1], &stringLength, &channelID) == FRE_OK)){
        channelIDString = [NSString stringWithUTF8String:(char*)channelID];
    }
    
    if(argv[2] && (FREGetObjectAsBool(argv[2], isDebug) == FRE_OK)){
        if(isDebug)
        {
            [MobClick setLogEnabled:YES];
        }
    }
    
    [UMConfigInstance setAppKey:appKeyString];
    [UMConfigInstance setChannelId:@"App Store"];
    
    [MobClick startWithConfigure:UMConfigInstance];
    
    NSLog(@"Called Init Function Finished In UMeng, AppKey: %@ ChannelID: %@", appKeyString, channelIDString);
    
    return nil;
}

/* ContextInitializer()
 * The context initializer is called when the runtime creates the extension context instance.
 */
void ContextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctionsToTest, const FRENamedFunction** functionsToSet)
{
    uint numOfFun = 1;
    
    FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * numOfFun);
    *numFunctionsToTest = numOfFun;
    
    func[0].name = (const uint8_t*) "startAnaly";
    func[0].functionData = NULL;
    func[0].function = &startAnaly;

    *functionsToSet = func;
}

/* ContextFinalizer()
 * The context finalizer is called when the extension's ActionScript code
 * calls the ExtensionContext instance's dispose() method.
 * If the AIR runtime garbage collector disposes of the ExtensionContext instance, the runtime also calls ContextFinalizer().
 */
void ContextFinalizer(FREContext ctx) 
{
    NSLog(@"Entering ContextFinalizer()");

    // Nothing to clean up.
    NSLog(@"Exiting ContextFinalizer()");
    return;
}


/* This is a TEST function that is being included as part of this template. 
 *
 * Users of this template are expected to change this and add similar functions 
 * to be able to call the native functions in the ANE from their ActionScript code
 */
ANE_FUNCTION(isSupported)
{
    NSLog(@"Entering IsSupported()");
    
    FREObject fo;
    
    FREResult aResult = FRENewObjectFromBool(YES, &fo);
    if (aResult == FRE_OK)
    {
        NSLog(@"Result = %d", aResult);
    }
    else
    {
        NSLog(@"Result = %d", aResult);
    }
    
    NSLog(@"Exiting IsSupported()");    
    return fo;
}

9.配置ANE的描述文件,配置文件和生成脚本
1. extension.xml描述文件 用来指定id,入口方法和销毁方法等。


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第12张图片
13.png

2. platformoptions.xml配置文件用来配置ane的依赖库和一些系统需求


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第13张图片
14.png

3.修改生成脚本generateANE.sh ,如果引用的是第三方放静态库<*.a>则不用添加如果引用的是framework静态库需要将framework资源名放在打包命令中


15.png

10.编译生成ANE文件


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第14张图片
16.png

四、如何使用ANE扩展

  1. 1.新建一个flex手机项目,向项目中添加ane扩展库


    Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第15张图片
    17.png

2.编写测试方法


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第16张图片
18.png

五、制作和使用ANE的注意事项

1.集成ANE后调用时报The ABC data is corrupt,attempt to read out of bounds.


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第17张图片
21.png

解决方法:点击项目右键属性->构建打包->Apple iOS->本机扩展 选中 相应的ANE


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第18张图片
22.png

2.打包ANE的时候报*.a静态库 i386 x86_64

23.png

解决方法:-->选择target为真机运行-->设置Build Setting
Architectures armv7 arm64
24.png

Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第19张图片
25.png

3.运行时报错


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第20张图片
26.png

解决方法:>ane打包配置文件未包含相应需要的库文件


Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能_第21张图片
27.png

4.需要打包的静态库是.framework不是.a的静态库,参考:xcode制作iOS静态库SDK<包含第三方.a或者.framework静态库>

六.参考文档

1.http://www.jianshu.com/p/62a682792db8
2.http://rolfzhang.com/articles/1108.html
3.http://blog.csdn.net/u010784415/article/details/18038443
4.http://blog.csdn.net/u010784415/article/details/18038443
5.http://bbs.9ria.com/thread-434444-1-1.html
6.https://www.adobe.com/devnet/air/native-extensions-for-air.html

七.Demo地址:链接: https://pan.baidu.com/s/1WF-LSxcSr_PcNY8gbZTxWA 提取码: zvd2,转载请注明出处!

你可能感兴趣的:(Flash Air ANE for iOS 集成最新版本的友盟统计SDK.framework功能)