近期 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 来通知主线程进行操作。