标签归档: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 中编写控件,通常我们采用 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 来通知主线程进行操作。