标签归档: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上已经有人解决了该问题。大体就是通过

android list sdk

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

android --help list sdk

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

android  list sdk --extended --all

来查看所有的可用的(包括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下了,这个可以通过编译安装输出看出来
[[email protected] 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不出意外的话,你会碰到这个错误信息:
[[email protected] 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文件,将下面所有内容拷贝到该文件中:

SUBSYSTEM=="usb", ATTR{idVender}=="0502", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0b05", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="413c", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0489", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04c5", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="091e", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="18d1", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="109b", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0bb4", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="12d1", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="24e3", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2116", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0482", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="17ef", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="1004", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="22b8", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0409", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2080", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0955", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2257", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="10a9", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="1d4d", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0471", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04da", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="05c6", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="1f53", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04e8", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="04dd", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="054c", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0fce", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="2340", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="0930", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVender}=="19d2", MODE="0666", GROUP="plugdev"

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

SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0666", GROUP="plugdev"

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中编写控件,通常我们采用XML格式的Layout文件来进行描述控件的大小和位置。当时某些时候我们还是很有必要在程序运行时更改控件的大小。

例如某些时刻程序执行时,控件关联数据发生改变可能导致控件对数据的呈现不能达到最佳,例如有两个ListView在当前窗体中,ListViewAListViewB分别对应两组数据,一组数据对应本月代码提交列表,另一组对应本月Bug修复列表。当两组数据相当之时(即数量相差无几),保持两个控件高度一致,可能两个ListView都会出现滚动条或者均不出现滚动条。但是当本月代码提交列表条目数远远超出本月Bug修复列表时(本月主要任务是新接口的设计和功能开发,其中包含Bug的开发,-_-!)。如果依然保持两个控件高度一致的话,那么显然对于用户来说是不友好的,此时用户的视觉焦点应该更多的是在代码提交列表,所以可以适当地增大代码提交列表的高度而减小Bug列表的高度。更有可能的设计中就存在选择某个视图的功能,例如切换到Bug列表视图时,需要将Bug列表高度扩展到全屏幕高度等等。

其实主要代码就几行,例如程序中是通过某个响应某个控件的某个事件来完成列表高度改变,例如通过单击某个按钮来改变某个列表的高度:

布局文件如下:

<?xml version=“1.0” encoding=“utf-8”?>

<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”

android:orientation=“vertical” android:layout_width=“fill_parent”

android:layout_height=“fill_parent”>

<Button android:id=“@+id/change_view_size”

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:text=“ChangeSize” android:onClick=“onViewClick” />

<ImageView android:id=“@+id/logo” android:layout_width=“wrap_content”

android:layout_height=“wrap_content” android:src=“@drawable/logo”/>

<ListView android:id=“@+id/network_infos”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content” />

<ListView android:id=“@+id/network_infos2”

android:layout_width=“fill_parent”

android:layout_height=“wrap_content” />

</LinearLayout>

关键代码如下:

public void onViewClick(View view){

// 单击ChangeSize按钮,改变列表高度

if(view == mChangeViewSize){

mNetInfoList.post(new Runnable() {

@Override

public void run() {

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, mNetInfoList.getHeight()/2);

mNetInfoList.setLayoutParams(params);

mNetInfoList.requestLayout();

}

});

}

}

使用线程避免出现ANR异常

Android中经常会出现ANR错误,因为经常性的Android桌面也会出现该问题,该问题主要是因为当前程序在5秒中内未能完成UI事件的响应,Android认为该程序已经进入了无响应状态,Android主动弹出一个提示框,提示用户是继续等待该程序还是将其强制关闭。

我们来看看Google I/O大会上的一个PPT中的分享:

ANRs (“Application Not Responding”)

ANRs happen when,

main thread (“event thread” / “UI thread”) doesn’t respond to

input event in 5 seconds,

a BroadcastReceiver doesn’t finish in 10 seconds

typically, when

doing network operations on main thread,

doing slow ‘disk’ operations (un-optimized SQL) on the main thread

那么防止这些问题的出现,很多时候都是需要使用线程来进行的,特别是连网的操作通常都是耗时的,而且极有可能出现连接超时的错误(国内的网络环境,你懂的)

还有就是某些有left join right join等的SQL查询也是有可能导致耗时过长的,可以尝试着使用AsyncQueryHandler类来实现异步查询。

另外一定要注意在BroadcastReceiver中的onReceive(Context context, Intent intent)方法中的操作时间不能超过10s,否则也可能出现ANR异常,那么如果觉得有可能触碰到临界值时还是选择使用线程吧

PS:之前一直以为Service是在自身独有的线程中执行的,直到最近才明白,其实并不是这样的。我们来看看官方的说明:

A Service is an application component representing either an application’s desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use.

但是接下来的说明才是最重要的:

Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. More information on this can be found in Application Fundamentals: Processes and Threads. The IntentService class is available as a standard implementation of Service that has its own thread where it schedules its work to be done.

这也就是说Service通常都是在主线程中执行的,也就是说我们通常在ServiceonCreate()onStart(Intent intent, int startId)方法中执行的操作如果耗时耗资源,那么就应该另起线程,或者选择IntentService来实现,因为IntentService拥有自己独有的线程来执行其操作。所以还是建议使用Service时一定要注意。

不要在线程内部使用TOAST

android中,我们通常会使用Toast来通知用户一些信息,例如网络出现异常等等,但是经过实践表明在线程中使用Toastshow()方法似乎是不可行的,因为Toast最终的所有显示操作应该都是处于UI主线程中,如果我们在子线程中做这样的操作,显然是违背了“只在主线程中刷新UI”的原则,不过由于Toast的特殊性,其抛出的异常跟在子线程中改变View控件的异常并不一致,其抛出的异常是你需要在使用handler之前通过Looper.prepare()方法来将当前线程作为一个looper,这显然是不对的。所以在线程中需要作任何UI操作,还是需要通过handler来通知主线程进行操作。