[Android问答] 旋转屏幕导致Activity重建怎么办?

file

Android开发文档上专门有一小节解释这个问题。简单来说,Activity是负责与用户交互的最主要机制,任何“设置”(Configuration)的改变都可能对Activity的界面造成影响,这时系统会销毁并重建Activity以便反映新的Configuration。

“屏幕方向”(orientation)是一个Configuration,通过查看Configuration类的javadoc可以看到其他Configuration还有哪些:如fontScalekeyboardHiddenlocale等等。

当屏幕旋转时,这个Configuration就发生了改变,因此当前显示的Activity需要被重建,Activity对象会被终止,它的onPause()onStop()onDestroy()方法依次触发,然后一个新的Activity对象被创建,onCreate()方法被触发。假设屏幕旋转前,用户正在手机上填写一个注册表单,如果处理不当,用户会发现旋转后的表单变成空白的了,严重影响使用体验。

要解决这个问题有三种方法:

方法1:禁止旋转屏幕

毫无疑问,这是最懒的办法了,相当于回避了本文提出的问题,方法如下看看就好:

<activity android:name=".MyActivity"
          android:screenOrientation="portrait"
          android:label="@string/app_name">

方法2:旋转后恢复现场

既然Activity会被销毁,那么我们就可以使用前文介绍过的“持久化/恢复现场”方法来解决。即在onPause()里将用户当前已经输入的内容保存到数据库或Preference,在onCreate()方法里读取并填充到表单中,这也是官方推荐的方法。

需要补充一点,如果Activity重建需要耗费大量资源或需要访问网络导致时间很长,可以实现onRetainNonConfigurationInstance()方法将所需数据先保存到一个对象里,像下面这样:

@Override
public Object onRetainNonConfigurationInstance() {
    final MyDataObject data = collectMyLoadedData();
    return data;
}

重建时,在onCreate()方法里通过getLastNonConfigurationInstance()方法获得之前保存的数据,如下所示:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();
    if (data == null) {//表示不是由于Configuration改变触发的onCreate()
        data = loadMyData();
    }
    ...
}

方法3:手工处理旋转

一般情况下Configuration的改变会导致Activity被销毁重建,但也有办法让指定的Configuration改变时不重建Activity,方法是在AndroidManifest.xml里通过android:configChanges属性指定需要忽略的Configuration名字,例如下面这样:

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

这样设置以后,当屏幕旋转时Activity对象不会被销毁——作为替代,Activity的onConfigurationChanged()方法被触发,在这里开发者可以获取到当前的屏幕方向以便做必要的更新。既然这种情况下的Activity不会被销毁,旋转后Activity里正显示的信息(例如文本框中的文字)也就不会丢失了。

假如你的应用里,横屏和竖屏使用同一个layout资源文件,onConfigurationChanged()里甚至可以什么都不做。但如果横屏与竖屏使用不同的layout资源文件,例如横屏用res/layout-land/main.xml,竖屏用res/layout-port/main.xml,则必须在onConfigurationChanged()里重新调用setContentView()方法以便新的layout能够生效,这时虽然Activity对象没有销毁,但界面上的各种控件都被销毁重建了,你需要写额外的代码来恢复界面信息。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "横屏模式", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "竖屏模式", Toast.LENGTH_SHORT).show();
    }
}

官方的Android开发文档不建议使用这种方式处理Configuration改变:

Note: Using this attribute should be avoided and used only as a last-resort. Please read Handling Runtime Changes for more information about how to properly handle a restart due to a configuration change.

最佳实践

考虑到旋转屏幕并不是使Activity被销毁重建的唯一因素,仍然推荐前文介绍过的方法:在onPause()里持久化Activity状态,在onCreate()里恢复现场,可以做到一举多得;虽然Google不推荐设置android:configChanges属性的方式,但如果你的Activity横向纵向共用同一个layout文件,方法3无疑是最省事的。

参考资料:

Configuration Changes
Handling Runtime Changes
Activity restart on rotation Android
How to handle screen orientation change when progress dialog and background thread active?

(搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2012/11/09/2761897.html)

[Android问答] 如何应对Activity进程被杀?

我们要了解Android手机开发与桌面开发有一个主要不同之处:通常在一部Android手机里同时运行着多个应用(app),每个app对应一个系统进程,当系统需要更多的资源(如内存)而空闲资源不足时,Android系统就会选择杀掉一些“低优先级”的进程以便释放所需资源。

Android系统是如何确定进程优先级的高低的呢?

  • 如果一个app正在与用户交互,那么它所在的进程具有最高优先级;
  • 其次,如果一个app是可见的,例如被一个对话框部分遮挡,它所在进程具有第二高的优先级;
  • 再次,如果app当前是不可见的,也就是被切换到了后台,则它所在进程具有第三高的优先级;这里要补充一点,如果这个后台app启动了一个service,则它比一般的后台app优先级高一些。
  • 最后,如果一个进程里没有包含任何app,这个进程的优先级是最低的。

当系统资源严重不足时,任何一个进程都有可能被杀掉,而当用户想回到一个已经不存在于内存中的Activity时,系统只得新建一个这样的Activity对象并调用它的onCreate()方法进行恢复。所以有时出现这种状况:一个app大部分时间运行很好,偶尔在切换Activity时出现空指针异常导致强制关闭,这多半是在onCreate()方法里使用了已经被重置为空的对象(例如intent里的变量)造成的。即使不出现异常,也会造成表单数据丢失等严重影响使用体验的问题。

要解决这类问题,切不可抱“现在手机内存大,进程一般不会被杀掉”这种侥幸心理,而应该以“应用随时都会被杀掉”的态度来谨慎处理,下面介绍Google建议的方式。

解决方法

由于Activity随时可能需要重建,所以我们要做的事情就是在适当的位置将Activity所需数据进行持久化(从ram复制到rom或sd卡),并在onCreate()方法里利用这些数据恢复现场。

Activity有两类数据需要进行持久化处理:“文档类型数据”和“内部状态类型数据”,前者例如用户正在编辑的表单,后者如用户偏好。

一、为了持久化文档类型的数据,Google建议使用”即时生效”(edit in place)的编辑策略,具体的方式如下:

  • 用户新建文档时,在SQLite数据库(根据需要也可以使用preference)里也立即新建一条记录。(与此相对的方式是:为用户提供一个“保存”按钮,只有当用户按下按钮时才将文档保存到数据库。)
  • 当用户离开当前Activity时,onPause()方法会被触发,在这个方法里将当前正在编辑的文档持久化到数据库。这样一来,如果用户是从这个Activity切换到另一个相关Activity,仍然可以看到刚刚保存的内容。

这种方式可以最大限度避免数据丢失,只要onPause()方法被触发执行,即使Activity所在进程被系统kill掉也不会造成数据丢失。唯一要注意的是,界面上最好能提供一个“取消”按钮或菜单,以便让用户可以选择不保存对文档的更改。

二、为了持久化内部状态类型的数据,可以在onPause()里使用Activity#getPreferences(int)方法,这个方法返回SharedPreferences类型的对象,利用它可以记录用户对这个Activity的偏好信息。例如一个日历应用,用户可以选择显示为周视图或月视图,这样的信息作为内部状态记录到SharedPreferences对象以后,下次再打开这个Activity时就可以按用户上次的选择来显示日历了。

以下代码来自官方文档,演示了如何持久化并恢复一个Activity的当前显示模式:

 public class CalendarActivity extends Activity {
     ...

     static final int DAY_VIEW_MODE = 0;
     static final int WEEK_VIEW_MODE = 1;

     private SharedPreferences mPrefs;
     private int mCurViewMode;

     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         //取回之前持久化的日历显示模式
         SharedPreferences mPrefs = getSharedPreferences();
         mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE);
     }

     protected void onPause() {
         super.onPause();

         //持久化日历显示模式 
         SharedPreferences.Editor ed = mPrefs.edit();
         ed.putInt("view_mode", mCurViewMode);
         ed.commit();
     }
 }

是有点麻烦,但为了能让app稳定运行也值了。

有些同学要问了,为什么是在onPause()里持久化而不是在onSaveInstanceState()里?官方文档有下面一段话简要解释了原因,即前者比后者更可靠,因为onSaveInstanceState()不属于Activity生命周期的一部分。——既然如此,我想不出onSaveInstanceState()还有什么其他用途了,大家干脆忘了它吧,还有onRestoreInstanceState()。

Note that it is important to save persistent data in onPause() instead of onSaveInstanceState(Bundle) because the latter is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.

注1:关于上面这段话,网上存有一些争议,我个人还是比较倾向在onPause()里做持久化——既可靠又好记,唯一的缺点是调用次数稍多。

注2:从Android 3.0(HoneyComb)版本开始,Activity进程在被系统杀掉之前,将保证onStop()方法先执行完成,因此如果我们开发的应用只运行在3.0以上,可以把持久化工作放在onStop()里以减少持久化的次数。

最佳实践

不要抱侥幸心理,你的Activity随时可能被销毁。

解决方法:在onPause()里持久化Activity数据,在onCreate()里恢复现场

参考资料:

ActivitySaving Activity state in Android

(搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2012/11/08/2759948.html

[Android问答] 如何理解Activity生命周期?

Android官方文档里对Activity的生命周期有比较详尽的描述,但由于资源回收机制带来不确定性,我们的程序运行结果常常与预期的不符,而调试这类问题又十分消耗时间和精力。解决的根本办法还是要理解透Activity的生命周期及相关内容,这篇帖子着重介绍Activity生命周期本身,之后会用一两篇帖子来介绍如何处理异常的状态变化。

下图是官方文档里的Activity生命周期图,其中彩色标出的四个框是Activity的四种状态,当Activity的状态改变时会触发一个或多个onXXX()方法。

file

onCreate()

当Acitivity第一次被创建时触发,一般在这里要做的事情包括创建视图(setContentView())、向视图填充必要的数据等等。

onRestart()

这个我比较少用到,按文档上的介绍,如果Activity之前被stop过,那么下一次onStart()方法之前会先触发这个方法。

onStart()

只要Activity从不可见变成可见,就会触发到这个方法,但被AlertDialog遮挡/显示的情况不算在内。

onResume()

当Activity来到最上层的时候,也就是开始与用户直接交互时,触发这个方法。例如本来Activity被一个AlertDialog遮挡,当这个AlertDialog消失时,onResume()方法就被触发。

onPause()

和onResume()的触发条件刚好相反,如果Activity本来在最上层,当它要让出最上层的位置时会触发这个方法。onPause()和onResume()是被触发最频繁的两个方法,所以在这里不应该执行过于消耗资源的方法。

onStop()

当有其他Activity覆盖了当前Activity时,不论另一个Activity是新开始的还是从下层移至最上层的,当前Activity的onStop()方法都会被触发。

onDestroy()

Activity生命周期的终点。有两种情况会导致它被触发:1)执行了Activity#finish()方法;2)Android系统由于资源不足等原因决定杀掉Activity所在进程。通过isFinishing()方法可以判断出是哪种情况。在这个方法里,我们一般要做的事情是释放Activity占有的资源,例如后台正在进行的下载线程等等。

最后,举个实际例子来说明,假设你有一个“首页Activity”和一个“编辑页Activity”。

  • 当用户点击首页里的“开始编辑”按钮时,首页的onPause()->onStart()onStop()依次触发,编辑页的onCreate()->onStart()->onResume()依次触发;(感谢James.H.Fu指出的错误)
  • 当用户在编辑页按下“返回”按钮时,编辑页的onPause()->onStop()依次触发,之后首页的onStart() -> onResume()依次触发;
  • 这时用户在首页按下“返回”按钮,首页的onPause()->onStop()->onDestroy()依次触发。

参考资料:

Activity Lifecycle
Simplest Android Activity Lifecycle
Activity lifecycle explained in details

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2012/11/07/2758276.html

[Android问答] px、dp和sp,这些单位有什么区别?

相信每个Android新手都会遇到这个问题,希望这篇帖子能让你不再纠结。

px:

即像素,1px代表屏幕上一个物理的像素点;

px单位不被建议使用,因为同样100px的图片,在不同手机上显示的实际大小可能不同,如下图所示(图片来自android developer guide,下同)。

file

偶尔用到px的情况,是需要画1像素表格线或阴影线的时候,用其他单位如dp会显得模糊。

dp:

这个是最常用但也最难理解的尺寸单位。它与“像素密度”密切相关,所以首先我们解释一下什么是像素密度。假设有一部手机,屏幕的物理尺寸为1.5英寸x2英寸,屏幕分辨率为240x320,则我们可以计算出在这部手机的屏幕上,每英寸包含的像素点的数量为240/1.5=160dpi(横向)或320/2=160dpi(纵向),160dpi就是这部手机的像素密度,像素密度的单位dpi是Dots Per Inch的缩写,即每英寸像素数量。横向和纵向的这个值都是相同的,原因是大部分手机屏幕使用正方形的像素点。

不同的手机/平板可能具有不同的像素密度,例如同为4寸手机,有480x320分辨率的也有800x480分辨率的,前者的像素密度就比较低。Android系统定义了四种像素密度:低(120dpi)、中(160dpi)、高(240dpi)和超高(320dpi),它们对应的dp到px的系数分别为0.75、1、1.5和2,这个系数乘以dp长度就是像素数。例如界面上有一个长度为“80dp”的图片,那么它在240dpi的手机上实际显示为80x1.5=120px,在320dpi的手机上实际显示为80x2=160px。如果你拿这两部手机放在一起对比,会发现这个图片的物理尺寸“差不多”,这就是使用dp作为单位的效果,见下图。

file

更新20140701: 是不是所有android手机的屏幕宽度用dp衡量都是固定值(例如320dp)呢?答案是否定的,如果写一个程序画宽度等于320dp的横线,在不同手机上运行,会发现在有些手机上横线比手机屏幕短,有些则比屏幕长,在平板上与手机上相比差别则更加明显。

dip:

与dp完全相同,只是名字不同而已。在早期的Android版本里多使用dip,后来为了与sp统一就建议使用dp这个名字了。

sp:

与缩放无关的抽象像素(Scale-independent Pixel)。sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时1sp=1dp=0.00625英寸,而当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。

还有几个比较少用到的尺寸单位:

mm:

即毫米;

in:

即英寸,1英寸=2.54厘米(约);

pt:

1pt=1/72英寸=0.035厘米;

最佳实践:

文字的尺寸一律用sp单位,非文字的尺寸一律使用dp单位。例如textSize="16sp"、layout_width="60dp";偶尔需要使用px单位,例如需要在屏幕上画一条细的分隔线时:

<View layout_width="match_parent" layout_height="1px"/>

最后,推荐一张Android UI设计参考图:Android Design Cheat Sheet

参考资料:

Difference of px, dp, dip and sp in Android?
Supporting Multiple Screens

DisplayMetrics

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2012/11/06/2757300.html

Android开发问题汇总

注:Android 5.0的问题总结在这个帖子里。

1、用(SDK starter package)的installler安装Android SDK时提示无法找到JDK,但实际上机器上已经安装了JDK。

一个对不少人有效的解决方法是看到此提示时先点一下“回退”按钮,再点下一步,就会发现JDK被找到了。参考链接

但在我机器上这个方法不起作用。所以我选择不使用installer,而是下载zip格式的文件,解压缩后运行SDK Manager.exe即可。参考链接

2、在Eclipse里新建一个Android项目,运行/调试时提示Could not find xxx.apk!

网上提到的大部分解决方案是clean整个项目,或选fix project properties菜单项。但对我的环境不起作用。

升级到Eclipse 3.7后,新创建的android项目在Build阶段报告一个异常sun/security/x509/X500Name,怀疑与所用的jdk(IBM JDK6)有关。果然,卸载IBM JDK并安装SUN JDK后问题解决。

3、让程序适应不同的屏幕分辨率

可参考这个链接:Android屏幕分辨率问题

4、在android模拟器里用10.0.2.2可访问宿主机。

5、在setWidth()方法里指定的宽度是以pixel为单位的,如何转换为使用dip(device independent pixels)为单位?

使用下面的代码,参考链接 

// Converts 14 dip into its equivalent px
Resources r = getResources();
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, r.getDisplayMetrics());

6、弹出式对话框的用法,这个链接介绍得比较详细。 

7、导出apk文件时需要签名,这个链接比较详细。 

8、android界面设计原则,参考这个链接

9、使用merge(而不是layout)可以达到在避免重复写layout的同时减少layout的数量。

10、真机USB调试比用AVD调试快得多,设置也很简单,见这个链接。不过10.0.2.2不能用了,真机可以通过wifi访问局域网内的服务器。

11、android-ui-utils,一个不错的在线Android图标生成器,地址在此

12、调试时如果出现莫名其妙的空指针错误,例如findViewByID()返回null,先试试clean一下整个project,通常都能解决。

13、让ListView里无数据时显示一行信息。

参考链接,注意ListView和TextView的id。

14、用自己的图标替换ListView里的RadioButton

在用作row的layout里添加一个图片,在java code里覆盖adapter的getView()方法,根据ListView的getCheckedItemPosition()结果设置图片的可见性。注意不要通过setOnClickListener()方法设置view里的图片可见性,因为ListView只维护可见的那些row控件,这样做会导致很奇怪的结果(点第一条记录结果第二条记录被选中,并且滚动ListView时选中状态还会随机变化,见此链接)。

另外一个办法是通过style设置checkMark为所需要的图标,可能是更简单的解决方案(还没试)。

Update: 以上结果基于对ListView实现不了解的情况,其实使用RadioButton是可以实现的,见#22。

15、ListView的selection和choice是完全不同的,所以不要指望ListView#clearChoice()能清楚你已经是checked状态的那些item。

16、屏幕方向变化时(横屏->竖屏,或反过来),ListView里发生变化的内容丢失,状态回到开始时的样子。

当屏幕方向发生变化时,android会重建整个Activity以便你构造更适合某个方向的UI。为了避免这种情况,在AndroidManifest.xml里的那个Activity声明上增加android:configChanges="orientation" 即可。详见参考链接

17、各手机平台仿真器/模拟器的下载链接

18、AlertDialog#show()方法是不会阻塞当前线程的。

19、让不同Activity之间进行通信,例如一个TabActivity里有多个Activity,它们之间需要传递一些消息。

可使用BroadcastReceiver机制。 参考链接。要注意的是,tab还未启动时是无法接收到消息的,所以要在tabhost所在的activity里也接收消息,当tab启动时传给它。

20、Android SDK里的style和theme文档

见此链接

21、Tab的样式。

Android的tab样式问题比较多,不同版本的样式也不一样。这里有一个自定义样式的参考链接

22、在ListView里使用单选/复选按钮。

问题很多,这个链接看起来解决了问题,但在我的环境里没试验成功。

Update: 以复选按钮为例,本质的问题在于ListView里的复选按钮不知道对应的model是哪个,需要事先用CheckBox#setTag(myModelObject)关联,onclick事件里用getTag()改变其选中状态。建议看这篇文档。 

23、strings.xml里定义的字符串里增加参数。

在字符串里用“%1$s”、“%2$d”表示参数的序号和类型,然后用String.format()方法赋值。参考这个链接

24、嵌入条码/二维码扫描功能

使用zxing。方法是在手机上先安装BarcodeScanner.apk,然后在程序里调用其提供的Activity,该Activity会返回扫描结果。 参考链接

25、改变ListView里每个Row的背景颜色

直接在getView()里写view.setBackgroundColor()是不行的。正确的方法是先在drawable目录里建一个xml文件,自己起名如my_row.xml,内容如下(关键是第一个和第四个<item>):

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_selected="true" android:drawable="@android:color/transparent" />
    <item android:state_selected="true" android:drawable="@android:color/transparent" />
    <item android:state_pressed="true" android:state_selected="false" android:drawable="@android:color/transparent" />
    <item android:state_selected="false" android:drawable="@color/solid_red" />
</selector>

然后在getView()里这样写就可以了:

if (item.getStopId().equals(User.stopId)) {
    view.setBackgroundResource(R.drawable.my_row);
} else {
    view.setBackgroundResource(android.R.drawable.list_selector_background);
}

参考链接1 参考链接2 参考链接3

26、在一个TableLayout里让Button宽度相同并占满表格宽度(想象一个由按钮组成的九宫格)

如果Button上的文字不长,按一般的方法就可以实现,例如这个链接。当Button上的文字很长,还是会使布局变乱,每个列的长度将不一样。解决方案是将按钮的layout_width设为0,layout_height设置为所需要的值,需要换行时将singleLine设置为false,最后将按钮的gravity设置为center。

27、对切换屏幕方向的处理

参考stackoverflow上的一个典型讨论

当MyActivity位于一个TabActivity里时,我做了一些实验表明, TabActivity是否声明android:configChanges="keyboardHidden|orientation"与MyActivity无关,只有MyActivity做了上述声明后才会在改变方向时触发onConfigurationChanged()方法。

28、strings.xml里的字符串包含html格式标签时

需要用<Data><![CDATA[...]]></Data>把html代码包起来。参考链接

29、在AlertDialog里用ListAdapter(如ArrayAdapter)时,文字不显示。

view的resourceId要用select_dialog_singlechoice而不能用simple_list_item_single_choice,否则文字颜色会与背景颜色相同而无法显示。参考链接

30、在android程序里使用第三方包的配置方法

参考这个链接成功。

31、定制tabhost的外观

这方面的需求和问题网上都很多,主要的解决方案有基于tabhost的和基于radiobutton的两大类,这里有几个可以参考的链接。链接1链接2链接3

32、取得当前屏幕方向

有好几个方法可以得到orientation值,但一些得到的值不对,例如getWindowManager().getDefaultDisplay().getOrientation()getReqestedOrientation()。我实验下来比较靠谱的是getResources().getConfiguration().orientation。

33、Activity里第一个View是EditText的时候,软键盘自动弹出。

似乎只是一些机型会这样做。要阻止软键盘弹出,可在onCreate()里加一行代码。参考链接

this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

34、Dialog的theme问题

使用Theme.Light主题时,Dialog显示不正常。相关讨论:链接1链接2

35、注意区分CheckBox和RadioButton的OnClickListener和OnCheckedChangeListner

使用前者时,当在代码里执行myCheckBox.setChecked(true),不会触发事件,而后者会触发事件。

36、监视GPS开启/关闭事件

用GpsStatus.Listener不可靠(事件不上来),这个链接的方法是监视Settings里的事件:参考链接

37、仿真器横屏

快捷键ctrl+f11。有人提到ctrl+f12和numpad 7也可以,但我这里不起作用。

38、用JAXB生成KML对应的Java Code时会遇到一些问题,解决方法如下:

1)写一个binding文件如bindings.xjb放在与ogckml22.xsd同一目录,内容可参考这个链接(根据错误提示要删除几行)

2)在命令行里加-extension

3)在命令行里加-target 2.1(否则生成的java类的annotation带有jdk6不识别的关键字"required")

完整的命令行如下:

xjc -xmlschema -verbose -extension -b bindings.xjb -d src -target 2.1 ogckml22.xsd

39、ListPreference的entryValues只能使用string-array

如果使用了integer-array,在点击这个preference项时会产生一个空指针异常,相关讨论见链接1链接2

Update: 又发现一个ListPreference的新问题,即使用string-arraydefaultValue值也不能取太大的(超过Integer.MAX_VALUE)整数,否则defaultValue不起作用(选项没有缺省被选中)。 真是问题多多。android版本2.2。

40、Android提供了方便的Share机制,但一般都是把文字share到微博或SMS,如何能"share"文字到SD卡文件呢?

这个链接描述了同样的问题,等待有人回答。 目前的想法是,在程序里自己实现一个接受ACTION_SEND的activity,做法可参考此链接、或此文章

41、一个在线查看kml文件的网站,供参考。

GPS Visualizer 

42、关于onSaveInstanceState()的使用

通常与onCreate()配合,而非onRestoreInstanceState(),参考这个链接

2015/5/18补充:一个误区是onSaveInstanceState()方法是当activity被系统销毁时才调用,其实是当activity“变得容易被系统销毁”时就会被调用,例如当按下home键时,此时activity不再在前台显示,也就变得容易被销毁了。参考链接参考链接2

43、ListView里点击一个item背景色不变为橘黄(缺省的反馈颜色),各种OnClick事件不被触发。

一种可能是在item的布局文件(如foo_list_row.xml)里使用了下面的这些属性(自动滚动显示文字时常会用到),去掉后即可恢复正常:

android:ellipsize="marquee" android:scrollHorizontally="true"
android:marqueeRepeatLimit="marquee_forever" android:focusable="true"
android:focusableInTouchMode="true"

44、实现不停向上滚动的ListView

假设你有一个长长的list需要自动展示,方式是每隔几秒向上滚动一行,有点类似TextView的marquee功能(跑马灯?)。这个需求可以通过Handler实现,具体参考这个链接;当列表滚动到最后一行时,直接滚回第一行显得很生硬,可以用这个链接里提供的方法解决。

45、在Button的文字旁加图片

<Button>里使用android:drawableLeft="@drawable/my_icon" android:gravity="left|center_vertical"即可,类似的可以加在右侧或上下方。但如果Button有其他状态时,需要用selector指定不同状态下的图片。此外,图片的大小是不会自动根据Button调整的。

46、旧版本兼容

参考sdk文档里的这个文章

47、轻松实现圆角背景

不需要做圆角图片,看看这个链接,很方便,注意把angle改为45的倍数否则运行时报错。

48、Android内存泄漏检测

在DDMS可以查看heap使用情况,大概了解是否有内存泄漏。DDMS还可以dump出.hprof文件,后者可以用Eclipse MAT打开,进一步分析错误原因。注意,startActivity()后要根据情况决定是否调用finish()方法(如果需要back则不finish(),在适当的时机用FLAG_ACTIVITY_CLEAR_TOP一并回收内存空间),未finish()的activity是会一直占用内存的。

49、Android的第三方library

这个链接总结了不少。 

50、Android UI设计模式

参考这个链接

51、Google Map扩展的使用。

参考这个链接

52、当Spinner是invisible状态时,貌似调用mySpinner.setSelection(i) 不会触发其onItemSelected()事件。

53、实现gzip压缩服务器返回的json对象时,注意要response.setContentType("application/json"),并且在server.xml里把application/json设置到compressableMimeType里才能实现。我因为前一个原因浪费了三四个小时。

54、对AlertDialog.Builder应用定制的theme

使用ContextThemeWrapper,用法参考这个链接。但后来发现这个方法不起作用,有一种说法是这个方法只对部分android版本有效,也有说其根本无效的(都是在so上)。用AlertDialog.Builder的setVIew()方法也是有问题的,因为你会发现在这个view的旁边一圈(假设你的view是浅色背景)还是黑色的,效果很不好。其实如果只是要将对话框设置为白色背景,可以使用AlertDialog.Builder的setInverseBackgroundForced(true)方法,很简单,很直接。

55、从gallery选择图片的一些可用参数

参考这个链接,和这个相关issue (文件得不到原始图片,只能得到缩小后的图片)。

56、从gallery取图片时要注意内存是有限的,而图片可能很大。

利用inSampleSize可以帮助解决,参考这个链接的代码。

57、当程序进程被系统kill掉(常常在内存紧张时发生):

HttpClient的session可以用persist方式保留;

其余数据建议保留在SharedPreferenes里;

onActivityResult()里是能够获得另一个activity传回来的intent里的参数的;

58、连接到真机时提示Unable to open sync connection!

原因未知,解决方法是在真机的设置里去掉usb调试,然后重新勾选。

59、得到GPS状态(寻星或已获得位置)

这个问题看似简单,但Android里的LocationListener#onStatusChanged()方法工作不正常,表现为在大多数版本下都不会被调用。这个链接里提供的方法试了一下好像是可以用的。

60、在Android里画统计图(柱图饼图等等)

这个链接里给出了不少解决方案,我暂时选择的是aChartEngine,用法和JFreechart比较类似,参数超多,目前的活跃度也不错。

61、进程经常被kill

可以考虑启动一个service(即使什么都不做),这样进程的“重要性”就变得很高,因此就不容易被kill了。

62、“Receiver not registered”错误

执行unregisterReceiver(myReceiver)方法时,如果之前没有注册过myReceiver,会抛出这个异常。参考链接

63、应用在后台运行,需要弹出对话框(例如触发自service产生的事件)时报错:“BadTokenException: Unable to add window”

利用isFinishing()测试activity是否在后台,参考链接

64、Emulator太慢了

试试Android x86,据说比官方的快很多(我还没试过,正在下载)。 参考链接

65、禁止自动切换到横屏模式

<activity android:name=".SomeActivity" android:label="@string/app_name"
          android:screenOrientation="portrait">
</acivity>

参考链接

66、几个众包模式的beta测试平台

iOS最著名的是testflight,也有其他一些类似的测试平台支持android,详见这个链接

67、实现锁机后黑屏但不出现锁机画面

getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

还有一个功能类似的flag是FLAG_DISMISS_KEYGUARD,区别在于前者只对当前activity有效,后者全局有效。另外前者对安全锁屏也有效,后者则只对普通锁屏有效。附一个参考链接

但是前者有一个问题,就是在两个都设置了FLAG_SHOW_WHEN_LOCKED的activity间切换时,可能锁屏界面会闪一下。见此问题报告

68、一些可用于android应用性能测试和内存泄漏检测的工具

这个链接

69、进度条的一个bug

重设setMax()以后显示的进度百分比不正确,至少在Android 2.1 (API Level 7)里有这个问题。 见这个链接这个链接

70、当ListView的item里包含控件(如按钮、复选框)时 ,这个item无法被选中。

Android不允许选中ListView里带有focusable元素的item,解决方法是将该控件的focusable属性设置为false。参考链接

71、帮助做简单web测试的工具(构造并发送各种http请求)

这个链接里总结了不少。

72、Tab放在底部(仿iphone风格)

中文的教程看了好几个都不靠谱,so上的一个链接搞定,或者这个带有源码的教程

73、让ListView没有数据时也显示HeaderView/FooterView

技巧是让empty view包含headerview/footerview,见这个链接

74、在Google map上添加popup的方法

最简单的方法见这个链接

75、一个网站,可以搜索android相关项目的代码和资源。

链接在此

76、Android里的Search Activity不支持返回结果到调用其的Activity(因为onSearchRequested()方法没有调用startActivityForResult()启动search activity)

SO上有若干个提出此问题的帖子,例如链接1链接2等等,没有特别方便的办法解决,一个我没试过但看回复应该可行的方法见这个链接

77、查看apk文件内容的工具

推荐apktool,一个命令行工具,用法如下:

> apktool.bat d my.apk

可以还原所有的资源文件,但.java文件一般无法还原。

78、Android中使用的各类图标的标准尺寸

请参考sdk自带的guideline文档。 

79、Android的Searchable接口,无法让调用者获得查询结果。

参考这篇文档(抱歉链接已失效)可以实现,思路是自己用startActivityResult()启动搜索界面,然后在onActivityResult()里取出结果。我测试可行,但该文档有两处错误需要注意:

1) 是handleIntent()而非handleActivity()

2) 在startActivityResult()前最好intent.setAction(Intent.ACTION_SEARCH)一下。

另外,注意按该文章中提到的官方文档实现相应的newIntent()和onCreate()方法,以及在AndroidManifest.xml里设置调用者的android:launchMode="singleTop"。

80、很诡异的问题,有时EditText无法输入文字(软键盘正常弹出但字符进不去文本框),必须切换到另一个输入法才可以输入。

经测试,有些机型存在这个问题,具体原因还不详。以下链接可能与此有关:链接1

81、定时重复执行一段程序

要执行类似闹钟这样的功能,用AlarmManager配合BroadCastReceiver即可,网上有很多例子不再赘述。值得一提的是,在这个BroadCastReceiver里不要执行异步操作(例如异步访问一个远程服务、获取当前位置等等),因为onReceive()方法一旦执行结束,用于容纳BroadCastReceiver的进程随时可能被系统kill掉,导致异步操作结束后出现异常。解决的办法是在onReceive()方法里启动一个Service(我用的是startService,用bindService的方式可能也行),在Service里执行任何操作就可以了。参考BroadCastReceiver Life Cycle

2015/12/10注:AlarmManager也可以直接配合Service使用

2015/12/10注2:要查看手机上所有alarm,在命令行里执行adb shell dumpsys alarm > alarms.txt命令即可。

82、Monkey测试

> adb shell monkey -v -p com.my.app 100

MonkeyRunner可以进行更高级的测试。

83、用getIntent().getExtras().clear()无法清除掉extras里的数据

原因是getExtras()返回的是一个copy实例,用getIntent().removeExtra()可一个个清除。参考链接

84、用AlertDialog实现输入对话框时,若直接builder.setView(myEditText)文本框显得太长。

用dialog.setView()可以指定padding(注意是dialog.setView()而不是builder.setView()),具体见这个链接

85、在TextView里显示图片

通过Html.fromHtml()方法可以在TextView里显示HTML格式的文本,但只支持部分tag:

myTextVIew.setText(Html.fromHtml(myHtmlStr))

要在TextView里显示远程图片,必须向fromHtml()函数里提供一个ImageGetter对象,在它的getDrawable()方法里获取远程图像并转换为Drawable类型。示例代码请参考此链接

如果TextView设置了android:lineSpacingMultiplier属性,将导致图片显示的位置不正确(顶部多出一些空白),用android:lineSpacingExtra属性则没有这个问题。

如果图片加载比较慢将导致整个TextView空白很长时间,为解决这个问题需要异步加载图片,这样文字可以先出来,待图片下载完毕后再补充道文字中间。实现方法是扩展Drawable,具体方法参考这两个链接:链接1链接2 ,其中链接1的方案存在图片尺寸不正确的问题,原因是TextView#invalidate()没能起作用(原因不详),用链接2提供的方法可以解决,这个链接提到用textView.setText(textView.getText())也可以工作但我没试。

86、在TextView里显示列表(

  • 标签)

    Android的TextView只支持一小部分的html标签(见这个链接),缺省是不支持

      • 这样的列表标签的。通过TagHandler可以实现一个简单的列表效果(见此链接),但这个方法有个严重问题:当列表文字超过一行时,第二行的文字是顶头的没有缩进效果(见这个提问),而该问题暂时还没有好的解决方法。

        87、百度地图android sdk问题

        百度地图最大的问题:文档太烂!百度地图sdk版本比较多,网上充斥着各种版本的例子代码,官网上的例子代码也不清晰,在线文档含混不清。

        遇到过一个奇怪的问题,与这个帖子描述的情况相同,即第二次打开地图只能显示上次缓存过区域的地图,最后删除了所有onPause()onResume()onDestroy()里的百度地图相关方法才解决。

        88、启用Andoird设备的otg功能

        /system/etc/permissions目录里检查是否有名为android.hardware.usb.host.xml的文件,如果没有,新建一个内容如下:

        <permissions>
            <feature name="android.hardware.usb.host" />
        </permissions>

        89、命令行对一个未签名apk进行签名

        jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore my_application.apk alias_name

        参考连接

        90、Pull-to-refresh控件不显示内容问题

        将ListView替换为PullToRefreshListView,setAdapter()后发现列表中没有数据显示,调试发现adapter里是有数据的,而listView的getView()方法没有被执行到。最后发现原因是在布局文件的<com.handmark.pulltorefresh.library.PullToRefreshListView>元素中指定了android:visibility="gone"属性(在.java文件里在setAdapter()之前先调用了listView.setVisibility(View.VISIBLE)),在布局文件里去掉此属性,改为在activity的onCreate()里执行listView.setVisibility(View.GONE)后恢复正常,没有深入调查这两种方式对PullToRefreshListView有什么区别,对标准ListView是没有区别的。

        91、切换fragment时fragment显示不全

        fragmentTransaction.replace(R.id.layoutRoot, newTransaction)时,发现当layoutRoot是LinearLayout时,(有时)newTransaction的高度无法充满layoutRoot(虽然已经指定了高度为match_parent,指定高度为固定值也无效),后来将layoutRoot改为FrameLayout问题解决。

        提供了一些线索的参考链接

        92、利用Activity的android:process属性

        在开发一个视频播放器过程中,遇到一个问题,当视频还在缓冲时按返回键退出当前activity,应用失去反应长达数秒。后来在AndroidManifest.xml里对这个activity增加了android:process=":player"后问题解决,因为这个属性使得此activity在一个独立的进程运行,当activity被关闭时,此进程也被杀掉。这样,即使播放器有未完成的工作,也不会影响到主程序的UI线程了。

        需要注意,用PreferenceManager.getDefaultSharedPreferences()得到的SharedPreferences实例,是不支持跨进程访问的,因此在独立的进程里将无法向这样获得的SharedPreferences实例存取数据。解决的办法是改为使用context.getSharedPreferences(prefName, Context.MODE_MULTI_PROCESS)获取SharedPrefences实例。

        不过还是要特别注意,在多进程环境下,如果你用一个静态变量缓存了SharedPreference里的内容,由于静态变量在多个进程间不是共享的(Application对象也不是跨进程共享的),所以取出的结果可能不是你想要的。解决办法 1)去掉这个作为缓存的静态变量,每次都直接从SharedPreferences里取 2) 有人建议使用MemoryFile作为跨进程的机制,但我没有实际测试过,而且用起来应该会比Application对象繁琐。

        参考链接 参考链接2 参考链接3

        93、Service的START_STICKY与START_NOT_STICKY的区别

        参考这个链接:当系统由于资源不足可能会杀掉一个service,之后如果系统又有了足够资源,若被杀掉的service是START_STICKY的,则系统会调用其onStartCommand()方法恢复这个service;若被杀掉的service是START_NOT_STICKY的,则系统不会尝试恢复这个service。

        94、如何用纯java的方式获取apk文件版本信息?

        apk文件就是zip文件,解析并读取AndroidManifest.xml里的信息即可。开源项目android-apk-parser已经实现了这个功能,可以直接在java项目里使用。

        95、Android手机使用https代替http会带来明显的性能问题吗?

        对CPU消耗来讲,不会。见参考链接

        对网络消耗来讲,有一些影响。见参考链接

        关于在手机端“接受所有证书”做法的安全性,见参考链接

        注意:ssl处于tcp和http之间,使用https后,url也是加密过的,因此不需要担心api地址暴露(浏览器历史除外)。参考链接

        96、代码混淆

        project.properties里的#proguard.config=xxx这句前面的#去掉即可。会按照android sdk目录里的proguard-android.txt设置文件进行混淆,如果项目有特殊要求,在项目目录下的proguard-project.txt里进行设置。有两点注意:

        1) 只有在正式export apk时才会进行混淆,直接运行产生的bin/xxx.apk不会进行混淆;

        2) library工程里的混淆设置不起作用,以宿主工程的设置为准。

        97、Fragment里getActivity()有时返回为null

        一般当activity所在进程在后台被系统回收,然后用户重新回到这个进程时,activity重建后发生。解决方法是在activity的onSaveInstanceState()方法里保存fragment的状态:

        public void onSaveInstanceState(Bundle outState) {
            activity.getSupportFragmentManager().putFragment(outState, "current_fragment", currentFragment);
        }

        然后在activity的onCreate()方法里恢复:

        if (savedInstanceState != null) {
            currentFragment = (Fragment) activityBase.getSupportFragmentManager().getFragment(savedInstanceState, "current_fragment");
        }

        这样currentFragment所代表的fragment实例的getActivity()就不为null了。

        98、分级Preference界面布局的实现方式

        引用这篇文章的总结:Android 3.0之前:采用PreferenceScreen嵌套的方法;Android 3.0及之后:采用Preference Headers的方法。

        采用Preference Headers方法时,要求继承PreferenceActivity,但这样就无法同时继承ActionbarActivity了(问题链接)。这个第三方library可以基本解决这个问题。

        99、自定义进度条progressbar的风格

        这篇文章总结得很好,特记录在此:How-to: Customize Android progress bars

        100、Color State List不能用于android:background属性

        Android似乎不支持在android:background里指定Color State List资源(例如android:background="@color/bg_button_selector"会导致崩溃,但android:background="#ff0000"是可以的),得用Drawable State List才行,例如android:background="@drawable/bg_button_selector"参考链接

        搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2011/06/28/2092514.html