分类目录归档:Android

CentOS 64 位机器配置 Android SDK 和 NDK 环境

做个小备忘。

因为工作上的一些需要,需要在 CentOS 64 位机器上搭建一个编译 Android 项目的环境,Android SDK 的环境还比较好搭建,直接下载 android-sdk 包即可,下载页面链接在这里 http://developer.android.com/sdk/index.html, 我下载的是 sdk only 包,体积比较小不包含 ADT 包(在服务器上用不着这些东西),因为是在服务器上,所以是木有任何桌面环境的,也就木有桌面浏览器啦。用 wget 或者 curl 下载即可。下载成功后,解压,可是目前目录下面只有 tools 目录,没有 platform-tools 目录,这个目录下的东西可不少哦,神马 aapt、dx 等等非常重要的工具都可是在这个里头哦,通常我们都是直接通过 android 命令就可以启动 Android SDK Manager 的界面管理工具,通过勾选不同平台就可以选择性地更新哪个版本的 sdk 了。服务器上木有 swt 环境啊(Android SDK Manager 是基于 SWT 实现的),肿么办捏?程序员做的东西肯定是有命令行界面的嘛。come 你的 on,google 一下吧,结果在 这里 ,万能的 google,程序员的福音 stackoverflow 上已经有人解决了该问题。大体就是通过

这个命令来查看有哪些 sdk 可以更新,你可以可以通过

来查看其他的一些选项,例如通过

来查看所有的可用的(包括 Android 认为已经过时的,例如 2.3.3 之类的各个 android 版本的 SDK,而且会将名字给你写出来哦),例如:

id: 61 or “extra-android-support”
Type: Extra
Desc: Android Support Library, revision 11
By Android
Install path: extras/android/support

这个指的是就是 Android 的 suppor-library,其他的各个名称也很直观啦,这些名字可以用的地方呢就是我们在使用
android update -u
命令进行 SDK 更新的时候可以通过设置 filter 来选择我们需要安装哪些包,例如下面这个命令:
android update sdk -u –filter extra-google-google_play_services
执行之后,就会选择 Google Play Service 这个包来下载更新,同理其他的都素一样的啦。例如通过
android update sdk -u –filter platform-tool
会自动下载 platform-tools 目录,你要是加上 platform,使用
android update sdk -u –filter platform-tool,platform
就会下载所有版本的 platform 文件,其他的命令自己一个个尝试就好了。等下载完成之后,配置 PATH 变量吧
export ANDROID_SDK_HOME=/root/android-sdk-linux
export PATH=$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools:$PATH
配置完 SDK,那么开始配置 NDK 吧,毕竟很多时候我们在开发的过程中还是很有可能会使用到 NDK 的,那么开始吧,先下载 NDK 包吧,下载页面在 这里 ,选择对应平台下载链接下载吧,下载成功后解压缩之后,配置环境变量吧:
export ANDROID_NDK_HOME=/root/android-ndk-r8c
export PATH=$ANDROID_NDK_HOME:$PATH
配置好环境变量,加载一下环境变量,然后开始测试一下能否编译吧,cd 到 samples/hello-jni/jni 目录下,直接执行 ndk-build 命令,提示错误
./../../ndk-build: /root/android-ndk-r8c/prebuilt/linux-x86/bin/make: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory
提示缺少某个依赖文件,Google 之后得知,通过命令
yum install glibc.i686
装上 glibc 库即可,再次尝试 ndk-build,依然提示错误
/root/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/../lib/gcc/arm-linux-androideabi/4.6/../../../../arm-linux-androideabi/bin/as: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
提示缺少 zlib 库文件依赖文件,Google 之后得知,通过命令
yum install zlib.i686
装上 zlib 库文件之后,继续 ndk-build,这次能顺利编译出 so 文件了,也成功将文件拷贝到对应的 libs/armeabi 目录下了,可以有这么一个警告信息:
/root/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-strip: /lib/libz.so.1: no version information available (required by /root/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-strip)
大体的意思就时因为/lib 目录下的 libz.so.1 版本信息不对,导致 arm-linux-androideabi-strip 命令无法正常执行,这个会带来神马问题捏?我们来看看吧。其实就是/root/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/下的 arm-linux-androideabi-strip 命令没有办法执行呗,那么这个命令是用来干嘛的,直接运行一下看看 usage:
Usage: /root/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-strip <option(s)> in-file(s)
Removes symbols and sections from files
大体的意思就是通过这个命令可以将我们编译出来的 so 文件中的一些不必要的符号和片段给移除掉,可以让这个二进制文件更紧凑一些,运行效率会高一些?这个我真不知道,因为自己对于编译和链接这一块儿懂得真是太少了所以也就没有什么发言权啦。闲话少说,既然看到有问题,当然这个 so 肯定是可以用的(因为 ndk-build 并没有出错,而且还将 so 文件 install 到 libs 下对应的 armeabi 目录下了,我想 Android 开发团队肯定不会坑爹地将一个可能出错的编译输出文件 install 到该目录下的,所以说这个问题应该不大,肯定可以正常运行),只是看到这个警告信息让我非常的不爽,或者移除了这些符号和片段会让你的二进制文件运行起来更爽,或者可以让别人无法通过反编你的 so 来查看你调用了什么函数之类的,具体原因不明,反正我就是不爽,我要让这个警告信息给我消失。怎么办呢?

肯定先 google 啦,google 了很多,看了一些文档,大体得出结论是这个 libz.so.1 的版本比较低,而 android 这个命令可能比较新,依赖的库的版本比当前我的机器上/lib 目录下的 libz.so.1 的版本要高,好吧,那就去下载源码自己编译吧。可以到这个 zlib 的官方页面 上的这个 下载地址(WTF,竟然需要翻墙)。下载后解压之后,cd 到 zlib 目录下,三步走
./configure
make
make install
整个过程很顺利,编译后的文件都被放到/usr/local/lib 下了,这个可以通过编译安装输出看出来
[root@helihua zlib-1.2.7]# make install
cp libz.a /usr/local/lib
chmod 644 /usr/local/lib/libz.a
cp libz.so.1.2.7 /usr/local/lib
chmod 755 /usr/local/lib/libz.so.1.2.7
cp zlib.3 /usr/local/share/man/man3
chmod 644 /usr/local/share/man/man3/zlib.3
cp zlib.pc /usr/local/lib/pkgconfig
chmod 644 /usr/local/lib/pkgconfig/zlib.pc
cp zlib.h zconf.h /usr/local/include
chmod 644 /usr/local/include/zlib.h /usr/local/include/zconf.h
然后将/lib/libz.so.1 文件先备份一下,将/usr/local/lib 目录下的 libz.so.1 链接到/lib 目录下,因为 arm-linux-androideabi-strip 这个命令链接的路径中依赖的/lib 目录下的 libz.so.1,当然我们也可以通过修改 LD_CLASSPATH 的方式来让 arm-linux-androideabi-strip 这个命令加载/usr/local/lib 目录下的 libz.so.1 文件,可惜我现在不想这么麻烦,因为我现在也不知道具体做法,我懒得去 Google 了,我的方法是直接将该文件链接到/lib 目录下,替换系统原有的 libz.so.1
mv /lib/libz.so.1 /lib/libz.so.1_backup
ln -s /usr/local/lib/libz.so.1 /lib/libz.so.1
但是童鞋们啊,这还是不够滴,因为现在的系统捏是尼玛 64 位滴,而该死的 Android 都是基于 32 开发的,它依赖的是 32 位的 libz,你现在执行 ndk-build 不出意外的话,你会碰到这个错误信息:
[root@helihua jni]# ../../../ndk-build
Gdbserver : [arm-linux-androideabi-4.6] libs/armeabi/gdbserver
Gdbsetup : libs/armeabi/gdb.setup
Install : libhello-jni.so => libs/armeabi/libhello-jni.so
/root/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-strip: error while loading shared libraries: libz.so.1: wrong ELF class: ELFCLASS64
make: *** [/root/android-ndk-r8c/samples/hello-jni/libs/armeabi/libhello-jni.so] Error 127
make: *** Deleting file `/root/android-ndk-r8c/samples/hello-jni/libs/armeabi/libhello-jni.so’
唉,那么就只好开始编译 32 位的咯,怎么编呢,还是 Google 吧,程序员福音 stackoverflow 上又有对应的问题,在 这里 ,通过
export CFLAGS=-m32
指定当前编译目标平台位 32 位滴,直接在 bash 下使用该命令只会在当前 bash 会话期间有效,不会影响到其他 bash 会话和下次你重新登录 bash 时的编译环境设置,非常有用,我们只需要在这次编译 libz 时才需要如此,正中下怀啊。果断重复上面的三步走啊:
./configure
make
make install
这次执行./configure 就不会那么顺利了,会出现这样一个错误:
./configure
Checking for gcc…
.
.
.
Looking for a four-byte integer type… Not found.
如果这个时候你选择继续 make 的话,显然会出错滴,错误如下:
/usr/include/gnu/stubs.h:7:27: error: gnu/stubs-32.h: No such file or directory
通过该命令:
yum install glibc-devel.i386
安装对应的开发环境,重新执行上面的三步,
成功后再重复设置/lib 目录的操作:
mv /lib/libz.so.1 /lib/libz.so.1_backup
ln -s /usr/local/lib/libz.so.1 /lib/libz.so.1
然后再尝试执行 ndk-build 命令,这次就不再提示任何警告和信息了,输出干净多了:
Gdbserver : [arm-linux-androideabi-4.6] libs/armeabi/gdbserver
Gdbsetup : libs/armeabi/gdb.setup
Install : libhello-jni.so => libs/armeabi/libhello-jni.so
回过头来再想想,为什么会出现这些问题呢?
前两个问题是因为 Android NDK 中的某些命令依赖于系统的一些库,没有直接安装就好了,这个没有过多讨论的余地,后面那个出现版本不一致导致的警告信息,我个人的理解是这样的,因为我们直接使用的都是 Android NDK prebuild 的命令,而这些命令可能在 Google 的编译服务器上依赖的是更高版本的库,所以导致在我们的环境中会出现版本不一致的问题。因为 NDK 中带着源码,我倒是觉得可以通过在自己的机器上编译一个 NDK 出来,应该就可以解决这个问题,当然前提是 NDK 的源码中没有引用到更高版本的一些头文件,另外编译源码可能需要我们安装很多的开发环境(就是很多的库和头文件),点到为止,有机会可以一试。

Ubuntu 下让 ADB 识别所有设备的两个方法

方法一,完全参考 谷歌的文档 ,内容如下
在/etc/udev/rules.d 目录下新建一个 51-android.rules 文件,将下面所有内容拷贝到该文件中:

方法二,有点 Hack 的味道,参考“ 孔雀的小屋 ”滴,就是将下面这行内容拷贝至/etc/udev/rules.d/51-android.rules 文件中,
通杀所有 Android 设备,上面的那些都可作为浮云

Android 中解决图像解码导致的 OOM 问题

在上一篇博文 Android Bitmap 内存限制 中我们详细的了解并分析了 Android 为什么会在 Decode Bitmap 的时候出现 OOM 错误,简单的讲就是 Android 在解码图片的时候使用了本地代码来完成解码的操作,但是使用的内存是堆里面的内存,而堆内存的大小是收 VM 实例可用内存大小的限制的,所以当应用程序可用内存已经无法再满足解码的需要时,Android 将抛出 OOM 错误。

这里讲一个题外话,也就是为何 Android 要限制每个应用程序的可用内存大小呢?其实这个问题可能有多方面的解答,目前我自己考虑到的有两点:

  1. 使得内存的使用更为合理,限制每个应用的可用内存上限,可以防止某些应用程序恶意或者无意使用过多的内存,而导致其他应用无法正常运行,我们众所周知的 Android 是有多进程的,如果一个进程 (也就是一个应用) 耗费过多的内存,其他的应用还搞毛呢?当然在这里其实是有一个例外,那就是如果你的应用使用了很多本地代码,在本地代码中创建对象解码图像是不会被计算到的,这是因为你使用本地方法创建的对象或者解码的图像使用的是本地堆的内存,跟系统是平级的,而我们通过 Framework 调用 BitmapFactory.decodeFile() 方法解码时,系统虽然也是调用本地代码来进行解码的,但是 Android Framework 在实现的时候,刻意地将这部分解码使用的内存从堆里面分配了而不是从本地堆里分配的内存,所以才会出现 OOM,当然并不是说从本地堆里分配就不会出现 OOM,本地堆分配内存超过系统可用内存限制的话,通常都是直接崩溃,什么错误可能都看不到,也许会有一些崩溃的错误字节码之类的。
  2. 省电的考虑,呃…,原因我好像也不能很明白地说出来。

回到正题来,我们在应用的设计和开发中可能会经常碰到需要在一个界面上显示数十张图片乃至上百张,当然限于手机屏幕的大小我们通常在设计中会使用类似于列表或者网格的控件来展示,也就是说通常一次需要显示出来图片数还是一个相对确定的数字,通常也不会太大。如果数目比较大的画,通常显示的控件自身尺寸就会比较小,这个时候可以采用缩略图策略。下面我们来看看如果避免出现 OOM 的错误,这个解决方案参考了 Android 示范程序 XML Adapters 中的 ImageDownloader.java 中的实现,主要是使用了一个二级缓存类似的机制, 就是有一个数据结构中直接持有解码成功的 Bitmap 对象引用,同时使用一个二级缓存数据结构持有解码成功的 Bitmap 对象的 SoftReference 对象,由于 SoftReference 对象的特殊性,系统会在需要内存的时候首先将 SoftReference 对象持有的对象释放掉,也就是说当 VM 发现可用内存比较少了需要触发 GC 的时候,就会优先将二级缓存中的 Bitmap 回收,而保有一级缓存中的 Bitmap 对象用于显示。

其实这个解决方案最为关键的一点是使用了一个比较合适的数据结构,那就是 LinkedHashMap 类型来进行一级缓存 Bitmap 的容器,由于 LinkedHashMap 的特殊性,我们可以控制其内部存储对象的个数并且将不再使用的对象从容器中移除,这就给二级缓存提供了可能性,我们可以在一级缓存中一直保存最近被访问到的 Bitmap 对象,而已经被访问过的图片在 LinkedHashMap 的容量超过我们预设值时将会把容器中存在时间最长的对象移除,这个时候我们可以将被移除出 LinkedHashMap 中的对象存放至二级缓存容器中,而二级缓存中对象的管理就交给系统来做了,当系统需要 GC 时就会首先回收二级缓存容器中的 Bitmap 对象了。在获取对象的时候先从一级缓存容器中查找,如果有对应对象并可用直接返回,如果没有的话从二级缓存中查找对应的 SoftReference 对象,判断 SoftReference 对象持有的 Bitmap 是否可用,可用直接返回,否则返回空。

主要的代码段如下:

private static final int HARD_CACHE_CAPACITY = 16;

// Hard cache, with a fixed maximum capacity and a life duration
private static final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
    private static final long serialVersionUID = -57738079457331894L;

    @Override
    protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) {
        if (size() > HARD_CACHE_CAPACITY) {
            // Entries push-out of hard reference cache are transferred to soft reference cache
            sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue()));
            return true;
        } else
            return false;
    }
};

// Soft cache for bitmap kicked out of hard cache
private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2);

/**
* @param id
*            The ID of the image that will be retrieved from the cache.
* @return The cached bitmap or null if it was not found.
*/
public Bitmap getBitmap(String id) {
    // First try the hard reference cache
    synchronized (sHardBitmapCache) {
        final Bitmap bitmap = sHardBitmapCache.get(id);
        if (bitmap != null) {
            // Bitmap found in hard cache
            // Move element to first position, so that it is removed last
            sHardBitmapCache.remove(id);
            sHardBitmapCache.put(id, bitmap);
            return bitmap;
        }
    }

    // Then try the soft reference cache
    SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(id);
    if (bitmapReference != null) {
        final Bitmap bitmap = bitmapReference.get();
        if (bitmap != null) {
            // Bitmap found in soft cache
            return bitmap;
        } else {
            // Soft reference has been Garbage Collected
            sSoftBitmapCache.remove(id);
        }
    }

    return null;
}

public void putBitmap(String id, Bitmap bitmap) {
    synchronized (sHardBitmapCache) {
        if (sHardBitmapCache != null) {
            sHardBitmapCache.put(id, bitmap);
        }
    }
}

上面这段代码中使用了 id 来标识一个 Bitmap 对象,这个可能大家在实际的应用中可以选择不同的方式来索引 Bitmap 对象,图像的解码在这里就不做赘述了。这里主要讨论的就是如何管理 Bitmap 对象,使得在实际应用中不要轻易出现 OOM 错误,其实在这个解决方案中,HARD_CACHE_CAPACITY 的值就是一个经验值,而且这个跟每个应用中需要解码的图片的实际大小直接相关,如果图片偏大的话可能这个值还得调小,如果图片本身比较小的话可以适当的调大一些。本解决方案主要讨论的是一种双缓存结合使用 SoftReference 的机制,通过使用二级缓存和系统对 SoftReference 对象的回收特性,让系统自动回收不再敏感的图片 Bitmap 对象,而保有一级缓存也就是敏感的图片 Bitmap 对象。

Android PendingIntent 的一些小迷惑

近日在开发中刚好涉及到桌面 Widget 的一些开发工作,而桌面 Widget 控件的点击事件,通常只能通过 RemoteViews.setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) 方法来指定响应的行为。

通常实际应用中我们会把桌面 Widget 作为应用的快捷方式和缩略展示,那么通常我们做的事情一般是点击桌面 Widget 上某控件后,跳转到对应的 Activity 中,那么我们就需要使用到 PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags) 来获取一个 PendingIntent 实例,通常我们会在 Intent 中指定我们的目标 Activity,并通过 putExtra 方法来传递一些必要的参数。例如:

Intent intent = new Intent(context, MainActivity.class);

intent.putExtra(“GREETING”,”HelloWorld”);

PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,intent, 0);

remoteViews.setOnClickPendingIntent(R.id.widget_goto_main, pendingIntent);

上面的这个代码呢,主要目的就是能让桌面 Widget 上的控件响应点击事件,并且能直接进入 MainActivity。这里我们将 requestCode 和 flags 都设置为 0 了,目前 Android 中还没有使用到 requestCode 来做什么控制,只是预留了这么一个参数方便于未来的扩展,但是 Flag 能就非常有用了,因为系统会通过 Flag 来识别需要进行的行为。我么先来看一下官方文档上的一段话:

A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application’s process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it.

我翻译一下:一个 PendingIntent 就是一个 Android 系统管理并持有的用于描述和获取原始数据的对象的标志 (引用)。这就意味着,即便创建该 PendingIntent 对象的进程被杀死了,这个 PendingItent 对象自己在其他进程中还是可用的。如果创建该 PendingIntent 对象的进程随后又重新获取了一个同类型的 PendingIntent(对于程序来讲,就是通过同样的方法获取的,例如都是通过 getActivity、getBroadcast、getService 方法来获取的,并且传递给 getXXX 方法的 Intent 对象的 Action 是相同的,Data 也是相同的,Categories 也是相同的,Components 也是相同的,Flags 也是相同的),如果之前获取的 PendingIntent 对象还有效的话,那么该进程获取到的 PendingItent 对象将获得同一个对象的引用,而且可以通过 cancel() 方法来从系统中移除它。

如果我们只是想通过设置不同的 Extra 来生成不同的 PendingIntent 对象是行不通的,因为 PendingIntent 对象由系统持有,并且系统只通过刚才在上面提到的几个要素来判断 PendingIntent 对象是否是相同的,那么如果我们想在每次更新 Widget 的时候也更新 PendingIntent 对象的话,我们应该怎么做的,目前我能想到的就是通过设置 Flag 的方式来做。

目前在 Android 中有以下 flag:

FLAG_CANCEL_CURRENT: 如果当前系统中已经存在一个相同的 PendingIntent 对象,那么就将先将已有的 PendingIntent 取消,然后重新生成一个 PendingIntent 对象。

FLAG_NO_CREATE: 如果当前系统中不存在相同的 PendingIntent 对象,系统将不会创建该 PendingIntent 对象而是直接返回 null。

FLAG_ONE_SHOT: 该 PendingIntent 只作用一次,如果该 PendingIntent 对象已经触发过一次,那么下次再获取该 PendingIntent 并且再触发时,系统将会返回一个 SendIntentException,在使用这个标志的时候一定要注意哦。

FLAG_UPDATE_CURRENT: 如果系统中已存在该 PendingIntent 对象,那么系统将保留该 PendingIntent 对象,但是会使用新的 Intent 来更新之前 PendingIntent 中的 Intent 对象数据,例如更新 Intent 中的 Extras。这个非常有用,例如之前提到的,我们需要在每次更新之后更新 Intent 中的 Extras 数据,达到在不同时机传递给 MainActivity 不同的参数,实现不同的效果。

大概就是这么多了,:-),其实自己也还是有点晕,以后有工夫了再慢慢补充吧,先做个记录也好!整个四月份都没有发过博客了,在四月份和五月份交接的此时此刻,发布一篇,以做纪念吧,:-)。

也扯 Android 开发

首先声明一下,我做移动平台开发的时间很短,短到还不足一年,其次再强调一下,我涉及的移动开发平台只是 Android。这就意味着我看到的东西非常非常的片面,甚至都没有参考价值和意义。只是自己周末觉得应该写个博客,而暂时也没有整理好的技术博客,所以索性就扯一下。

在做 Android 之前,我做过一年时间的 JNI 开发,一门原本非常非常偏门的中间件开发技术,在进入之前的公司 (SuperMap) 之前,我从未接触过 JNI 开发,虽然在零星地做着一些 Java 方面的工作,但是均只限于 Demo 的学习与演练,绝对没有半分实践的经验。进入超图之后,开始学习 JNI 开发技术,半年之后渐入佳境,日常的开发工作之余开始接触一些业务的东西,也就是说开始接触了一些技术支持方面的工作,偶尔需要到客户的现场处理缺陷或者针对用户的使用场景提出解决方案。当然这么说好像有点大,好吧,我承认有点虚。不过当时山中无老虎,猴子尚且称霸王呢,自己当时也算是团队中的核心开发成员了,所以当时有幸出了一次差,而且有幸坐了第一次飞机,体验了一下空中交通工具的便捷性。还是回到正题吧,在超图工作的重点有两个,一个是编码一个是支持,编码的过程非常简单,流程化很高。接口设计由架构团队讨论决定之后,底层开发团队根据功能需求完成基础接口,然后由 Java 层面通过 JNI 技术将底层团队提供的 C/C++接口封装起来,当然同时还有.NET 团队在做 CLR 的封装。

JNI 封装的工作性质很简单,主要就是与 C/C++层面的通信,把一堆基础接口攒成一个可用性高的接口,当然这个工作主要有架构师团队完成设计。编码工作中主要注意的东西有一些,异常判断,数组搬移,野指针等等。SDK 作为基础开发平台主要就是模块化,稳定压倒一切。在超图的工作经历,让我对与 JNI 整个的工作机制以及编码方法有了一个大体的了解,来到 喜讯 之后,做了第一个游戏——《黄金矿工喜讯特别版》Android 版本。编码的过程中逐渐开始熟悉 Android 的开发方式以及框架结构,在开发过程中碰到了很多的问题,不过所幸最后产品还是成功的出炉了。据说市场反应还不错,因为这作为公司一个磨练团队的产品,目前还没有后续的开发和维护,只是出了一个版本,对于市场反馈也没有做太多的统计,甚至都没有放到 Google Market 上去,只是在一些国内论坛和第三方市场中做过分享。不过大体了解一些 Android 中开发的猫腻和技巧,Android 由于其开源的特性,有很多东西可以窥探到,在 Android Framework 中有很多带有// TODO 标签的代码段,很多的注释中直接标明“This is a bug”类似的字眼。不过正因为它的开源,很多 Framework 的机制可以自己一一剥开来慢慢瞧,例如对于图像解码中非常容易出现的 OOM 错误,顺着代码链,我们可以看到所有的代码,从 Java=>JNI=>C,最终可以跟踪到 VM 创建时堆大小的初始化,详情可以参见我 这篇博客

虽然最终我们看似已经找到了问题,但是究竟如何解决这个问题呢,目前我还没有找到非常好的方法,因为这个受到了 Framework 的限制,而 Framework 也没有提供非常好的机制来防止该问题的出现,不过也许可以借助异常的捕获来解决该问题,不过本人自己还未做过该方面的尝试。在做完这个游戏之后,又经历了两个软件项目,项目中自己主要负责的是 UI 层面的东西,不过主要的设计和 UE 控制并不由我主要负责,本人主要负责代码实现,主要设计与 UE 控制由设计师来完成。Android 原生的开发环境是 Java,按说开发效率是比较高的,当然这个并不是我自己说的,也是从网络上诸多的报告和大牛们的分析中看到的,至于他们为什么得出这个结论我自己并不是非常明了,我自己比较有感触的就是 Java 相对于 C/C++来说,自由度相对要小,不过基础库非常完备,很多的特性均有平台完成,程序员主要负责的就是逻辑和界面的问题,相对来说会较 C/C++效率高一些 (当然这还是因人而异的)。既然 Java 开发效率要高一些,那么 Android 中的开发效率是否会高一些呢?我看不尽然,Android 虽然提供 Java 的开发环境,但是其框架环境非常庞大和复杂,个人认为比 Java SE 复杂多了很多。Java 只是 Android 开发中的一个敲门砖而已,不过既然已经没有了语言层面上的问题,那么就剩下框架的问题。

然而框架的问题并不是那么的简单,Android 是一个在工程实验室中就被推到市场上的项目,当年 Andy Rubin 创建 Android 具体是什么时候不是很清楚,不过 Andy Rubin 还是 Danger, Inc. 的联合创始人,而 Danger 在 2008 年 2 月份才被 Microsoft 收购。

The company was originally started by former Apple Inc., WebTV and Philips employees Andy Rubin, Joe Britt, and Matt Hershenson. Danger was acquired by Microsoft on 11 February 2008, for a price rumored to be around $500 million (USD).

由此看来这家公司参与者还有 Apple 的身影。再来看看 Android

In July 2005, Google acquired Android Inc., a small startup company based in Palo Alto, California, USA.[20] Android’s co-founders who went to work at Google included Andy Rubin (co-founder of Danger),[21] Rich Miner (co-founder of Wildfire Communications, Inc.),[22] Nick Sears (once VP at T-Mobile),[23] and Chris White (headed design and interface development at WebTV).[24] At the time, little was known about the functions of Android, Inc. other than that they made software for mobile phones.[20] This began rumors that Google was planning to enter the mobile phone market.

从这两个简介中看来 Andy Rubin 的经历不能不说是很神奇啊,曾经供职过 Apple, Philips,然后是 WebTV,Danger,现在是 Google,Andy Rubin 好像一直都是在做移动设备,在该领域他的经验非常的丰富和恐怖。从 Danger 和 Android 的两个简介看来,Andy Rubin 在这两个公司之间是有交集的,不只是职务上,在时间上也是有的,Android 05 年被 Google 收购,08 年 Danger 被 Microsoft 收购。当然我们不管这个了,这只是一个八卦而已。Android 存在的问题很多,昨天看了一下在 Google Code 上的 Android Issues 列表,目前 Open Issues 的数量是 9257,总共 Issues 是 13851,也就是说 Fixed 的 Issued 最多就是 4594 个,比例是 0.331672803,百分比 33%,刚刚 1/3。当然这跟 Android Open 是有一定关系的,任何人都可以提交 Issue,还有很多的 Issue 是重复的。不过我还是认为这个是能说明一些问题的,那就是开发者对于 Android Framework 还不是很满意和放心,当然这其中包括我自己,因为 Android 中有太多@hide 标签的东西以及需要自己去 Hack 的东西,一个 AlertDialog 你想自定义其风格,非常的困难,而系统提供的默认 UI 风格真的非常的一般,从我自己的心眼里来说我非常的不感冒 Google 原生的 UI,我使用的 Legend 自带的 HTC Scense 我就认为非常出色,是一个不可多得的 UI 套件,当然现在市面上已经有了很多很多的 UI 套件,著名一些的国内 UI 套件,有点心科技、MIUI 等等。这确实说明了大家对 Google 原生的 UI 不是很感冒,而且既然你开放,别人能做得比你好干嘛不做啊。

开放带来的问题不只是 UI 的分裂和多样化,还带来了设备的极大丰富和多样,分辨率的多样和分裂更是成为诸多开发者的噩梦。不过其实也不是那么夸张啦,只是需要很大的精力来做好适配,开发者需要更多的时间来调试,设计者需要为多种设备做考虑。这样的情况下,粗看下来,其实设备更多市场越大,用户越多,开发出来的游戏和应用能更大程度地体现价值,更有可能得到不错的回报。这是一个毫无疑问的肯定句,不过能做到适配多机型的软件并不是非常多,因为这个工作量确实不小,关键是设计的工作量不小。Android 开发的难度大不大我想这个因人而异吧,小米团队号称 80 多天弄出来一个 MIUI,那可是非常强大的呢,而我折腾了半年有余,至今未见什么动静。不过我想我碰到的问题每个开发者难免多少都会碰到一些,所谓都在河边走,谁能不湿鞋啊,不湿鞋的那是光着脚走路。

  1. Android 开发中没有良好的可视化控件开发工具,虽然 ADT 中有一个简单的 XML 预览功能,但是确实非常的简陋,不过 ADT 8.0.1 已经好很多了。
  2. Android Framework 对于内存有限制,默认一个 Activity 分配的非 Native 内存由生产商在 VM 初始化确定其值为多大,在 MDPI 的模拟器上是 16M,在 HDPI 的模拟器上是 24M。很多开发者都曾碰到过让人头疼讨厌的 OOM 错误。
  3. 多设备和多分辨率带来的问题,虽然说这可以是一个正面的数据,大不了不支持那么多就行了,但是随便支持几个,你都会或多或少地碰到一些问题,这些问题就是因为分辨率的适配,你的 Layout 甚至可能需要依照分辨率来编写,你的图像资源文件几乎是完全按着分辨率来重新制作的。
  4. UI 的不统一型带来的问题,由于某些 ROM 直接修改了系统默认控件的效果,在很多时候你无法预知你的程序发布出去用户安装之后是一个什么样的运行效果,因为你无法充分测试所有的设备和 ROM,如果非得从根本上让应用在所有设备上效果一致,控件需要自己完全重新实现,这个非常的不现实。
  5. 用户免费心理对开发者的打击甚大,目前在 Android 上收费的软件成功的很少,虽然 Android 号称已经在全球市场上超过了 iPhone,但是目前还仍未出现一个公司能通过在 Android Market 上出售软件实现盈利。遍地开花的破解软件商店,粗制滥造的免费应用充斥着市场等等,无疑让这趟浑水越来越浑了。
  6. 版本的错乱导致的特别版本,针对 NDK 的程序必须在 1.6 以上的机器上运行,Android 几乎是每半年一个大版本,非常有开源社区的风格。每次更新都会带来一些新的特性,那么你能在你的应用中使用吗?我想大多数时候,回答是否定的。市场上的设备很少能及时跟上 Android 版本更新的速度,为了向下兼容,很多时候我们经常是看着已有的轮子不能用,非得自己重新造轮子。
  7. Native 开发环境的恶劣,几乎无法调试的开发,让开发者几近发狂。JNI 开发的特殊性本来就让挺多人挺头疼的了,目前还没有什么好的开发环境,更是让开发者有苦难言啊。
  8. 胜利就在眼前,而你却看不见胜利,多么地令人遗憾啊!
  9. 没有第九点。

相对而言,iPhone 成功的 App Store 和优秀的开发环境和稳定的 SDK,版本相对稳定,机型更是全球唯一确定的那么几种。Symbian 呢,我也不知道说什么,我挺喜欢 Nokia 的,能做这么好的手机的厂商也就是 Nokia 了。WP 7 会是 2011 年微软最成功的产品吗?Visual Studio 的易用性世人皆知,Windows 系统的操作习惯早日深入全世界人民心中,Microsoft 又舍得在这上面砸钱。

我想说的是 WTF,真混乱,趟浑水究竟要到什么时候啊…

I have a dream, One day there is no mobile device in this world.