空指针问题
NullPointerException绝对是开发人员遇到最多的问题,也是Android开发过程中一个大坑,总是在你意料不到的时候出现。要解决这个问题,关键地方不要吝啬if语句,需要在用到某一对象的时候多想想有没什么可能会导致对象没有初始化或者被指向为 null
,下面是一些比较有代表性的例子:
- Fragment的Handler中调用getActivity(),需要判断空和activity是否销毁
private static class MyHandler extends Handler {
...
@Override
public void handleMessage(Message msg) {
// 需要判空
if(null != mFragment.getActivity() && !mFragment.getActivity().isDestroyed()) {
// TODO
}
}
}
- 使用Cursor时没有判空,没有在finally代码块中关闭
Cursor cursor = null;
try {
cursor = contentResolver.query(uri, null, selection, selectionArgs, null);
// cursor可能为空
if(null != cursor && cursor.moveToFirst()) {
// TODO
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != cursor) {
cursor.close();
}
}
内存泄漏问题
内存泄漏是开发中特别需要注意的一个问题,程序中很多莫名其妙的OutOfMemoryError都是由此引起的。虽然现在有MAT和 leakcanary 方便分析内存泄漏的工具,但是我们在coding的时候就先避免的话无疑会好很多。
我觉得内存泄漏可以大致分为这几类:(1)某个对象的引用的生命周期大于对象本身的生命周期,导致该对象无法被回收,简称引用未释放 (2)资源对象没有关闭或者销毁
1. 引用未释放
Context泄漏
Context泄漏是Android程序中非常容易出现的问题,我列出两种隐晦的Context泄漏情况:(1)静态变量持有context的引用未释放 (2)匿名内部类隐式地持有context引用。
(1) 静态变量持有context的引用未释放
例如把一个TextView背景图片的Drawable对象设为静态变量,在设置Drawable为背景图片时drawable会调用setCallback方法而持有TextView的引用,TextView又持有Context的引用,所以就会导致Context泄漏。可以看Android官方文档的详细例子: avoiding-memory-leaks
但是在Android 3.0(API 11)之后,Drawable的setCallback方法改为弱引用的方式 mCallback = new WeakReference<Callback>(cb);
,所以在3.0以后不会因为这个问题产生内存泄漏,但是我们还是需要注意静态变量的使用。
(2) 匿名内部类隐式地持有context引用
在Android中经常使用Handler处理异步消息,一般比较懒的做法是直接使用匿名内部类,但是这样的话Handler会隐式地持有外部类即Activity的引用,而Handler的生命周期可能会比Activity更长,就会导致context泄漏。更多关于Handler内部类导致泄漏的问题,可以看 Android中由Handler和内部类引起的内存泄漏 。对于Handler,推荐使用下面的写法:
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (null != activity) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
// 避免因为delay消息导致的内存泄漏
mHandler.removeCallbacksAndMessages(null);
}
}
避免Context泄漏(即Activity内存泄漏)须谨记:
- 不要让生命周期长于Activity的对象持有Activity的强引用
- 尽量使用Application的Context而不是Activity的Context
- 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用
注册没及时取消
static关键字误用
2. 资源未关闭
WebView对象调用destroy()方法释放内存
Cursor或File没有关闭
Bitmap没有及时recycle
性能问题
忽视循环体效率
尽量不要在循环中包含内存分配操作,把try-catch放到循环体外面。(编程规范中建议不要在循环内调用同步方法和使用try-catch块)
避免创建不必要的对象
在拼接字符串的时候,不要直接用加号连接符,这会创建多余的对象效率也不高,应该优先考虑用StringBuffer或者StringBuilder来拼接。
选择static而不是virtual
如果你不需要访问一个对象的值,请保证这个方法是static类型的,这样方法调用将快15%-20%。这是一个好的习惯,因为你可以从方法声明中得知调用无法改变这个对象的状态。
使用LocalBroadcastManager代替普通的BroadcastReceiver,效率和安全性都更高
关于LocalBroadcastManager的实现原理,推荐看 LocalBroadcastManager 的实现原理,还是 Binder? 。
for循环的使用
传统的fori循环不要把获得数组长度的代码写在循环当中: for(int i = 0; i < array.size(); i ++)
,JDK 1.5后使用for-each循环遍历实现Iterable接口的集合和数组更方便,不过对于ArrayList使用传统的循环效率更高。
尽量多使用自带库函数,减少开发成本而且还有汇编级别的优化
使用 标签重用布局文件,使用 标签减少布局层级,用ViewStub按需载入布局
尽量用IntDef代替Enum
Enums的内存消耗通常是static constants的2倍,
避免Bitmaps的浪费,缩小到你需要的分辨率(小于设备分辨率)
使用Android Framework优化过的容器类,例如SpareArray, SpareBooleanArray与LongSpareArray。
应用有多个进程时,新启动一个进程时不会重新创建Application,但是会重新调用Application.onCreate(),所以要注意在onCreate()中重复你编写初始化的代码
尽量避免给window和Activity同时设置背景,这样会造成过渡绘制。
避免不必要的异常
不要使用AsyncTask处理异步任务,推荐使用Loader
AysncTask可以用简短的代码实现异步操作,但是有很多需要注意的问题,可以看 Android中糟糕的AsyncTask 进一步了解。
在Android library project中不能使用switch-case访问资源ID
因为在library project中生成的R.java资源ID不是常量,可以改写成if-else语句。
不要在Activity没有完全显示时显示Dialog或PopupWindow: Problems creating a Popup Window in Android Activity
多进程间通过SharedPreferences共享数据时不稳定,具体可以查阅《Android开发艺术探索》。
不要通过Bundle传递大块数据,否则会有TransactionTooLargeException异常:Passing large data to second Activity
数据库问题
数据库onUpgrade过程中添加重复字段的问题
假设在数据库版本号为3时新增了表A,在版本6时在表A中新增一个字段type,那么从数据库版本为1升级到6时会出现type字段已经存在的错误,这时应该判断下在版本3新增表A后就不需要新增字段了。
动画问题
android.view.animation.Animation有可能不会调用AnimationListener的onAnimtionEnd
Animation的工作流程是View.draw()—>View.drawAnimation()—>Animation.getTransformation(),但是View在onDetachedFromWindow会执行 mCurrentAnimation = null;
,这时如果动画未完成就会就此中断,接下来的动画操作和listener回调都不会执行。要解决这个问题有两个方法:(1)添加OnAttachStateChangeListener,在onViewDetachedFromWindow中执行onAnimtionEnd的逻辑 (2)使用Animator替代Animation
在android.view.animation.Animation.AnimationListener的onAnimationStart, onAnimationEnd, onAnimationRepeat 中不要addView或removeView
这样可能会引起java.lang.NullPointerException: Attempt to read from field ‘int android.view.View.mViewFlags’ on a null object reference,因为这个三个动画的回调方法都是在View.draw()中执行的,而在draw函数中不要addView或removeView。在addView的方法说明中有强调 do not invoke this method from {@link #draw(android.graphics.Canvas)},{@link #onDraw(android.graphics.Canvas)},{@link #dispatchDraw(android.graphics.Canvas)} or any related method.
####
多线程问题
不要在非UI线程更新View数据显示,特别是从网络拉取回来的数据应该注意所在线程
View问题
ListView的header,footer,item设置Visibility为Gone无效
因为ListView对child view执行measure前,没有判断Visibility为Gone的情况。解决方法是在header外面包一层parent view(如FrameLayout),再设置parent view里面的child view为Gone。
View的post,postDelayed方法在DetachedFromWindow(即不在当前的View树中)的时候无效
比较简单的修改方法是用Handler.post()
使用setEnabled(false)后没有效果
ListView的child view调用setEnabled(false)后仍然会响应onItemClick,parent view调用setEnabled(false)后Touch事件还会传递到child view。对于这两种问题可以用 view.setEnabled(false); view.setClickable(true);
解决,具体问题解析可以看 Android View setEnabled false没有效果的解决方法 。
Android-Application被回收引发空指针异常分析(消灭全局变量)
请看以下路径http://blog.youkuaiyun.com/zivensonice/article/details/51451486