入行5年多的as3程序员,不知道未来怎么样!该转型吗?对未来有些迷茫!

这是我对知乎上一个问题的回答,原文见: http://www.zhihu.com/question/28584388/answer/41376487

原文问题:

入行5年多的as3程序员,不知道未来怎么样!该转型吗? ?对未来有些迷茫!?

转型哪个新语言呢?unity,cocos2dx,swift,oc,h5,java还是c++呢!

我的回答: 继续阅读入行5年多的as3程序员,不知道未来怎么样!该转型吗?对未来有些迷茫!

JDK在MAC OS X下的路径设置

不同的JDK,在OSX下的路径是不同的,需要有针对性的进行设置。下面是我能找到的所有JDK在OSX下的路径,在此做个记录。

OS X 自带JDK

这个JDK由APPLE维护,也是OSX默认的JDK,它的路径是:

/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home

这其实是一个符号链接,它指向:

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home

OS X自带的JRE

/System/Library/Frameworks/JavaVM.framework/Versions/Current

ORACLE的JDK7

/Library/Java/JavaVirtualMachines/jdk1.7.0_xx.jdk/Contents/Home

ORACLE的JRE7

/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home

OPENJDK7

/Library/Java/JavaVirtualMachines/jdk1.7.0.jdk/Contents/Home

Ant编译的jar文件,ANE不识别

Ant编译的jar文件,ANE不识别

问题描述

Android的ANE打包需要jar文件。Eclipse可以提供jar文件的导出。

然而,当我使用Ant来自动化完成ANE打包流程的时候,jar文件出了问题。

如果使用Ant生成的jar文件来打包ANE。那么ANE在使用的时候,会发生 ExtensionContext 无法初始化的情况。

也就是说,在调用 ExtensionContext.createExtensionContext(EXTENSION_ID) 的时候,得到的永远是null。

问题分析

以下是构建jar的target: 继续阅读Ant编译的jar文件,ANE不识别

在Ant中替换尖括号

在Ant中替换尖括号

我使用 ReplaceRegexp 任务写了一段脚本替换XML的值:

<replaceregexp file="app.xml"
            match="<filename>"
            replace="name"
            encoding="UTF-8"/>

由于XML规范不允许在属性值中出现尖括号,Ant会报错:

d:\works\build\build.xml:70: 与元素类型 “null” 相关联的 “match” 属性值不能包含 ‘<‘ 字符。

把左右尖括号用他们的十六进制代码代替就可以解决这个问题:

<replaceregexp file="app.xml"
            match="\x3Cfilename\x3E"
            replace="name"
            encoding="UTF-8"/>

如果希望在替换的内容中也使用尖括号,需要一点点小技巧:

<replaceregexp file="app.xml"
            match="(\x3C)filename(\x3E)"
            replace="\1name\2"
            encoding="UTF-8"/>

当然,还有更简单的办法,就是使用 Replacereplacetoken

<replace file="app.xml" encoding="UTF-8">
    <replacetoken><![CDATA[<filename>]]></replacetoken>
    <replacevalue><![CDATA[<name>]]></replacevalue>
</replace>

在Eclipse中给JAVA项目传递参数

在Debug Configurations 或者 Run Configuration 界面中,进入Arguments选项卡。

如果是给main方法传递的参数,直接写在Program arguments中,使用空格分隔。

如果是给系统JVM传递的参数,写在VM arguments中,使用-D打头。

以设置LAF为例:

-Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel

如果设置了一个变量,名称为swing.defaultlaf,值为com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel。则可以这样调用它:

-Dswing.defaultlaf=${swing.defaultlaf}

要注意对变量的引用,只会使用变量的值,而不会自动调用变量的名称。即使把swing.defaultlaf定义为变量,像下面这样调用,也是没有效果的:

-D${laf_name}=${swing.defaultlaf}

在64位操作系统上使用FlashDevelop的Debug功能

最近用上了FlashDevelop。与Flash Builder比起来,它确实优点很多:小巧,快速,灵活的定制功能,免费且开源。

使用FlashDevelop开发AS/Flex/AIR程序的时候,可以使用Flex SDK来编译和调试。Flex SDK使用JAVA写成,需要系统中安装JAVA虚拟机(JVM)。而我的系统中已经安装了64位的JAVA虚拟机。

在调试的时候,FlashDevelop报告了下面的错误。

Debugger startup error: System.BadImageFormatException: 试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)
在 net.sf.jni4net.jni.JNI.Dll.JNI_GetDefaultJavaVMInitArgs(JavaVMInitArgs* args)
在 net.sf.jni4net.jni.JNI.Init()
在 net.sf.jni4net.jni.JNI.CreateJavaVM(JavaVM& jvm, JNIEnv& env, Boolean attachIfExists, String[] options)
在 net.sf.jni4net.Bridge.CreateJVM()
在 net.sf.jni4net.Bridge.CreateJVM(BridgeSetup setup)
在 FlashDebugger.DebuggerManager.Start(Boolean alwaysStart)

FlashDevelop是直接调用Flex SDK中的fdb进行调试的,出现这个错误的原因,是因为fdb仅支持32位的JVM。

继续阅读在64位操作系统上使用FlashDevelop的Debug功能

Android中SharedPreferences的模式

Android中SharedPreferences的模式

在Android开发中,使用SharedPreferences来共享一些小的配置数据是非常方便的。可是我发现在不同版本上,SharedPreferences的表现并不一样。

我的测试机是Android 2.3.6,程序的写入和读取都正常。但把相同的程序在Android 4.1上运行,就发现虽然写入正常,但刚刚写入的数据不能被读取到。

经过仔细调试,发现在Android 4.1中,读取到的写入的SharedPreference并不在同一个线程中,其实是2个不同的SharedPreference。

找到SDK文档,才发现可以通过设置 Context.getSharedPreferences 的第二个参数解决这个问题。

因为这个方法比较简单,一直没怎么看文档,直接写0代表私有访问模式。没想到这个方法的第二个参数从Android 3.0开始有了变化。

下面是第二个参数mode的说明:

Operating mode. Use 0 or MODE_PRIVATE for the default operation, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE to control permissions. The bit MODE_MULTI_PROCESS can also be used if multiple processes are mutating the same SharedPreferences file. MODE_MULTI_PROCESS is always on in apps targetting Gingerbread (Android 2.3) and below, and off by default in later versions.

下面是 MODE_MULTI_PROCESS 的说明:

SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.
This was the legacy (but undocumented) behavior in and before Gingerbread (Android 2.3) and this flag is implied when targetting such releases. For applications targetting SDK versions greater than Android 2.3, this flag must be explicitly set if desired.

也就是说, MODE_MULTI_PROCESS 这个值是一个标志,在Android 2.3及以前,这个标志位都是默认开启的,允许多个进程访问同一个 SharedPrecferences 对象。而以后的Android版本,必须通过明确的将 MODE_MULTI_PROCESS 这个值传递给mode参数,才能开启多进程访问。

所以,我们在获得 SharedPreferences 的时候,需要判断一下SDK的版本号:

int __sdkLevel = Build.VERSION.SDK_INT;
SharedPreferences __sp = $context.getSharedPreferences(SETTING_NAME, (__sdkLevel > Build.VERSION_CODES.FROYO) ? 4 : 0);

改变AIR for Android消息通知栏默认图标

改变AIR for Android消息通知栏默认图标

如果从愤怒的角度来说,这个勉强可以算作AIR的BUG,但我知道不是。估计这事儿也只有我能碰上。且听我细细道来……

show notification in Android

在Android中显示消息通知,是个很简单的事情,见下面的代码:

Intent __activityIntent = _context.getPackageManager().getLaunchIntentForPackage(_setting.getPackageName());
if(__activityIntent == null) throw new NullPointerException("无法获取到名称为【"+_setting.getPackageName()+"】的Intent!");
Notification __msg = new Notification(R.drawable.ic_launcher, $ticket, System.currentTimeMillis());
ApplicationInfo __info = _context.getApplicationInfo();
__activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent __intent = PendingIntent.getActivity(_context, getRequestCode(), __activityIntent, PendingIntent.FLAG_UPDATE_CURRENT);
__msg.ledARGB = $color;
__msg.ledOnMS = 300;
__msg.ledOffMS = 1000;
__msg.flags |= Notification.FLAG_SHOW_LIGHTS;
__msg.flags |= Notification.FLAG_AUTO_CANCEL;
__msg.defaults |= Notification.DEFAULT_SOUND;
__msg.setLatestEventInfo(_context, $title, $msg, __intent);
NotificationManager __nm = (NotificationManager) _context.getSystemService(Context.NOTIFICATION_SERVICE);
__nm.notify(0, __msg);

上面的代码基于Android 2.2,Android 3.0以后有更好的方法,google也不推荐使用这样的方法。但我们为了兼容旧设备,只能这么用。

将这段代码编译后打包成ANE,在AS中调用,在Android设备中调试运行,就可以弹出一个通知栏,显示的图标是AIR的程序配置文件中配置的图标。

想要知道如何打包ANE,可以参考Adobe的官方教程(中文)

但是本文讲的不是这么简单的东西,本文讲的是一个相当纠结的问题。

问题出现

这个方法在我的设备中一直运行得很好,直到有一天,当我要发布它的时候,出问题了。

显示在Notification bar区域的图标,变成了AIR的红色图标,而不是我的应用的图标了。就像下面这样:

AIR的默认图标

而我的应用的图标,原本是这样的:

正确的图标

这个问题让我百思不得其解,为什么在调试的时候正常,在正式的发布版之后就不正常了么?郁闷的寻找了一段时间之后,一个偶然的机会让我发现了该问题的原因。

问题原因

我们知道,AIR在打包成Android apk文件的时候,可以选择AIR运行时的处理方式。我们可以选择“共享AIR运行时(apk)”和“运行时绑定(apk-captive-runtime)”两种方式。

在调试的时候,Flash Builder会直接将apk打包成共享AIR运行时版本。而在发布的时候,我们一般都会选择运行时绑定。至于原因,你懂的。

而这两种运行时打包方式对于图标的处理方式是不一样的。我解压了同一个项目的“共享运行时”和“运行时绑定”apk文件,发现他们的res/drawable目录中的图像文件不同。在“共享运行时”的apk文件中,该目录只有一个alert形式的半透明图标,而“运行时绑定”的apk文件中,则多出了一个AIR的默认图标。

比较解压文件

看完这张图,出现AIR默认图标的原因已经找到了,下面是分析。

问题的分析

由于应用需要支持多种分辨率,Notification bar的图标并不是使用一个图标文件来指定的,而是使用一个编号。也就是上面代码中的 R.drawable.ic_launcher 。这是一个int类型的值。

在ANE的代码中指定的这个常量,其实和AIR项目并没有什么关系,ANE项目是没有界面的,所使用的资源与AIR项目的资源也完全不同。将ANE打包到AIR项目中之后,就会改用AIR项目的资源。

但为什么在ANE中指定的图标编号值,在AIR项目中依然有作用呢(仅限“共享AIR运行时”)?

为了弄清这个问题,我创建了一个原生的Android项目。我发现默认情况下,它使用的图标也指向 R.drawable.ic_launcher ,而且这个常量的值与ANE项目中的值完全相同,都是 0x7f020000

我可以这样认为,这是Android项目的默认程序图标常量值。既然是这样,那么AIR也会遵循这个常量值。因此,在ANE中指定的图标常量值正好和AIR中的图标常量值相同,这是个“正确的巧合”。

在“共享AIR运行时”的时候,因为apk的 res/drawable 目录中没有其他的系统图标,Notification 会自动去 res/drawable-hdpi;res/drawable-ldpi;res/drawable-mdpi 3个图标文件夹下寻找匹配的图标。这3个文件夹中保存的就是我们在AIR程序配置文件中指定的程序图标。

在“运行时绑定”的APK文件中,由于AIR添加了一个默认图标,Notification 显示的时候就直接中又直接调用了 res/drawable 中的这个图标,因此显示的就是默认图标了。

问题解决

有了上面的分析,我只要在指定Notification图标的时候,指定一个图标资源的对应常量值,就能够得到正确的图标了。但可惜的是,除了我自己要求AIR包含的文件外,我并不知道AIR在打包的时候将哪些图标文件放在了APK包中,也不知道它们的常量是什么。

在使用 Android SDK 开发的应用中,这些常量都在SDK自动生成的R类中,我很容易得到他们。但AIR并没有告诉我怎么得到这些资源。

看来我只能自己想办法。

我发现,Android SDK自动生成的 R.java 文件中的常量值是有规律的,比如:

  • drawable 资源都以 0x7f02 开头;
  • string 资源都以 0x7f04 开头;
  • id 资源都已0x7f07`开头;
  • 0000开始顺号排列。

如下所示:

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int ic_action_search=0x7f020000;
        public static final int ic_launcher=0x7f020001;
    }
    public static final class id {
        public static final int menu_settings=0x7f070002;
        public static final int textView1=0x7f070000;
        public static final int toggleButton1=0x7f070001;
    }
    public static final class layout {
        public static final int activity_main=0x7f030000;
    }
    public static final class menu {
        public static final int activity_main=0x7f060000;
    }
    public static final class string {
        public static final int app_name=0x7f040000;
        public static final int hello_world=0x7f040001;
        public static final int menu_settings=0x7f040002;
        public static final int title_activity_main=0x7f040003;
    }
    public static final class style {
        public static final int AppTheme=0x7f050000;
    }
}

我可以这样认为,0x7f020000 就是第一个图标文件的常量值,而第二个图标文件应该是 0x7f020001 ,第三个是 0x7f020002 ,第四个……唔,没有第四个,如果使用 0x7f020003 ,AIR会直接崩溃退出。

测试证明,我的猜想是正确的。至此问题解决。

感受

和Adobe打交道这么多年,已经被无数的BUG折磨得“百度不亲”。Flex 的 BUG 因为有源码,可以自己动手解决。而 Flash Player和AIR的BUG就只能想办法绕过 。现在做 AIR for mobile 开发也有一段时间了,碰到了不少棘手的调试问题 ,忍受了 ipa 那乌龟一般的编译速度和 iTunes 那烂到无敌的用户体验,最后在这个不是 BUG 的问题上纠结了2天时间,彻底无语了……

从Adobe的角度看,在自己的产品中保留一个自己的默认图标,好像也无可厚非。从我的角度看,既然选择用 AIR 技术,碰到这样的问题,只能怪我手贱。

AIR for mobile给我的感觉,就像是一个保险箱,在我往里面放东西的时候,非常顺手。但我要修理它的时候,却发现我没有工具、没有手册、也没有指导。

当然,个人能力有限,也许我对Android更加了解之后,这个问题根本就不是问题了。

有哪位Android专家能给点建议么?

如何知道某个Activity是否在前台?

如何知道某个Activity是否在前台?

有一个Android应用包含包含一个后台程序,该程序会定期连接服务器来实现自定义信息的推送。但是,当这个应用处于前台的时候,后台程序就没有必要连接服务器了。这样可以节省网络资源,也更省电。

用什么方法知道该应用是否处于前台呢?

网上搜到的方法大多数都是使用下面的代码:

ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
//获得task列表
List<ActivityManager.RunningTaskInfo > taskInfo = am.getRunningTasks(1); 
Log.d("topActivity", "CURRENT Activity ::"+ taskInfo.get(0).topActivity.getClassName());
ComponentName componentInfo = taskInfo.get(0).topActivity;
componentInfo.getPackageName();

但是查阅 Android文档 后发现,google并不推荐使用这个方法:

This should never be used for core logic in an application, such as deciding between different behaviors based on the information found here. Such uses are not supported, and will likely break in the future. For example, if multiple applications can be actively running at the same time, assumptions made about the meaning of the data here for purposes of control flow will be incorrect.

而且,这个方法还要求设置android.permission.GET_TASKS权限。

因此,我必须寻找更加合适的方法来做这件事。最终,我找到这个方法 getRunningAppProcesses() ,它并不需要增加特殊的权限。

下面是范例代码:

/**
 * 返回当前的应用是否处于前台显示状态
 * @param $packageName
 * @return
 */
private boolean isTopActivity(String $packageName) 
{
    //_context是一个保存的上下文
    ActivityManager __am = (ActivityManager) _context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> __list = __am.getRunningAppProcesses();
    if(__list.size() == 0) return false;
    for(ActivityManager.RunningAppProcessInfo __process:__list)
    {
        Log.d(getTAG(),Integer.toString(__process.importance));
        Log.d(getTAG(),__process.processName);
        if(__process.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
                __process.processName.equals($packageName))
        {
            return true;
        }
    }
    return false;
}

在ANE中连接Socket服务器的注意事项

在ANE中连接Socket服务器的注意事项

前戏

也许你会奇怪,既然AS提供了Socket实现,为什么还要用ANE来实现Socket连接?

在ANE插件中启动AIR开发的Android应用 一文的最后,我提到了一个应用案例,我现在将这个案例明确的说明一下。

对于游戏开发者来说,我们希望能推送给用户一些消息。如果使用常规的手段,只能在用户打开游戏的时候,才能和服务器通信,收到这些消息。

如果用户几天不上线,那么可能会错过这些消息,导致游戏中的公告、奖励不能及时到达。

要解决这个问题,我们可以在Android系统中注册一个Service。这个Service长期保持与服务器的连接,或者隔段时间连接一次服务器,收到消息后马上推送给用户。

这种Service,使用AIR是无法实现的,必须用ANE来解决。因此,我们不可能使用AS的Socket来连接服务器,必须用Android SDK提供的Socket连接方法。

阻力

在JAVA中实现Socket客户端的方法很简单,这里提供一些简单(且不完整)的代码: 继续阅读在ANE中连接Socket服务器的注意事项