【Android】代码隐患规避

【注册】Activity未在AndroidManifest.xml中注册隐患

摘要

问题: 使用未在 AndroidManifest.xml 中注册的 Activity 会导致 app 出现 Crash。

解决方案: 所有用到的 Activity 都在 AndroidManifest.xml 中进行注册。

示例

在下面代码中,程序使用了 SecondActivity,但其未在 AndroidManifest.xml 中注册,程序执行到此逻辑时会报 android.content.ActivityNotFoundException,导致 app crash。

public void func(){
	Intent intent = new Intent();
	intent.putExtra("intentkey", "the message from main activity");
	intent.setClass(MainActivity.this, SecondActivity.class);
	MainActivity.this.startActivity(intent);
} 

推荐方案

在 AndroidManifest.xml 中注册 SecondActivity,如下:

      <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="@string/app_name" >
        </activity>
    </application>

【异常】ArrayList使用get方法获取元素未判断下标有效性的隐患

摘要

问题: ArrayList 通过 get 方法使用下标获取元素,如果使用的下标不在 ArrayList 大小范围内,将产生 java.lang.IndexOutOfBoundsException 的异常,导致 app 出现 Crash。

解决方案: 在使用 ArrayList 的 get 方法获取元素时,需要判断使用的下标的有效性。

示例

在下面代码中,程序调用 mRepliesData.get(selectedPosition) 方法获取元素,因 selectedPosition 超出了 mRepliesData.size() 的范围,抛出了 java.lang.IndexOutOfBoundsException 异常导致 app crash。

	private ArrayList<Reply> mRepliesData;
	public boolean onContextItemSelected(int selectedPosition) {
		mRepliesAdapter = new NoteReplyAdapter(this, mRepliesData);
		Reply reply = mRepliesData.get(selectedPosition);
		//……
		return super.onContextItemSelected(item);
	}

推荐方案

在调用 mRepliesData.get(selectedPosition) 方法前,先判断 selectedPosition 是否小于 mRepliesData.size() 再进行方法调用,修改如下:

	private ArrayList<Reply> mRepliesData;
	public boolean onContextItemSelected(int selectedPosition) {
		mRepliesAdapter = new NoteReplyAdapter(this, mRepliesData);
		if (selectedPosition < mRepliesData.size()) {
			Reply reply = mRepliesData.get(selectedPosition);
		}
		//……
		return super.onContextItemSelected(item);
	}

【NumberFormatException】格式化数字异常未捕获的隐患

摘要

问题: 在格式化数字时,无法将其转化为合适的数字,导致抛出 NumberFormatException 异常。

解决方案: 在任何用到格式化数字时,捕捉异常,对异常情况进行处理。

示例

在下面代码中,使用 Float.parseFloat 对 String 进行格式化时,出现了 NumberFormatException。

	public static String formatMileage(String mAverageSpeed) {
		float mileage;
		DecimalFormat fnum = null;
		mileage = Float.parseFloat(mAverageSpeed);
		fnum = new DecimalFormat("###,###,###.##");
	}

推荐方案

在任何用到格式化数字时,捕捉异常,对异常情况进行处理,修改如下:

	float mileage;
	DecimalFormat fnum = null;
	try {
		mileage = Float.parseFloat(mAverageSpeed);
		fnum = new DecimalFormat("###,###,###.##");
	} catch (Exception e) {
		e.printStackTrace();
		return MILEAGE_EMPTY;
	}

【OOM】大图片解析导致OOM的隐患

摘要

问题: 当图片过大或图片数量较多时使用 BitmapFactory 解码图片会出 java.lang.OutOfMemoryError: bitmap size exceeds VM budget,导致 app 出现 Crash。

解决方案: 对过大的图片进行压缩或者通过先设置缩放选项,再读取缩放的图片数据到内存,规避了内存引起的 OOM。

示例

在下面代码中,在每一个 item 中加了一个 imageview 控件,图片的 src 在 xml 中就定好了,在添加到 list 中的时候报错了,异常导致 app crash。

	LayoutInflater inflater=getLayoutInflater();
	list= new ArrayList<View>();
	list.add(inflater.inflate(R.layout.item1, null));//就在这!

推荐方案

对过大的图片进行压缩或修改采样值 BitmapFactory.Options.inSampleSize,设置 inJustDecodeBounds 为 true 后,decodeFile 并不分配空间,但可计算出原始图片的长度和宽度,即 opts.width 和 opts.height。有了这两个参数,再通过一定的算法,即可得到一个恰当的 inSampleSize。修改如下:

	BitmapFactory.Options opts= new BitmapFactory.Options();
	opts. inJustDecodeBounds = true;
	Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
	//……

【越界】数组下标越界隐患

摘要

问题: 采用下标的方式获取数组元素时,如果下标越界,将产生 java.lang.ArrayIndexOutOfBoundsException 的异常,导致 app 出现 Crash。

解决方案: 在使用下标的方式获取数组元素时,需判断下标的有效性。

示例

在下面代码中,程序调用getIndex()方法获取索引值,存在idx超出strArr的有效范围导致app crash的隐患。

	public void func(){
		String[] strArr = {"e1","e2"};
		int idx = getIndex();
		System.out.println(strArr[idx]);
	}

推荐方案

在调用 System.out.println(strArr[idx]) 方法前,先判断 idx 是否在有效范围内,再进行方法调用,修改如下:

	public void func(){
		String[] strArr = {"e1","e2"};
		int idx = getIndex();
		if(idx >=0 && idx < strArr.length){
			System.out.println(strArr[idx]);
		}
	}

【越界】使用String.substring前未判断长度隐患

摘要

问题: 在使用 String.substring 时,未对字符串的长度进行检查,截取字符串时可能会发生越界而导致 Crash。

解决方案: 在使用 substring 前,对字符串的长度进行判断。

示例

在下面代码中,虽然 sb 不为空但是长度为 0,而直接进行字符串截取导致越界 crash。

	String sb = new String();
	String value = sb.substring(0,sb.length()-1);

推荐方案

在使用 sb 之前,先对 sb 的长度作检查,修改如下:

	String sb = new String();
	if( sb.length() > 0){
		String value = sb.substring(0,sb.length()-1);
	}

【判空】使用Bundle与使用从Bundle获取到的数据未判空隐患

摘要

问题: 在使用 Bundle 对象时,因其可能为 null,若直接使用诸如 “get(String key)、getString(String key)、getByte(String key)…” 函数获取 Bundle 对象所含数据时,有可能导致 app 出现 Crash;

解决方案: 使用前,应做判空检查。

示例

在下面代码中,程序直接调用 getString(“myKey”) 方法获取对象,若 myBundle 对象为 null,则存在导致 app crash 的隐患;使用获取的 value 调用 split 函数,若 value 为 null,则导致 app 出现 crash。

	Bundle myBundle = intent.getBundleExtra("myBundle");
	value = myBundle.getString("myKey");
	String str[]  = value.split(","); 

推荐方案

在使用该对象前,先做判空处理和检查获取到的数据是否为空,修改如下:

	Bundle myBundle = intent.getBundleExtra("myBundle"); 
	if(myBundle != null ){
		value = myBundle.getString("myKey");
	}else{
		  //... 
	}
	if(value != null){
		String str[]  = value.split(",");
	} 

【异常】主动抛出异常未捕获处理隐患

摘要

问题: 在 Java 程序中,使用 throw new Exception 主动抛出异常而不对异常进行捕获,会导致 app 出现 Crash。

解决方案: 对于抛出异常的代码,增加 try{}catch{},捕获和处理能处理的异常,对于不能处理的异常重新封装并抛给上层。(若仅仅捕获所有异常而不处理,则 crash 率会下降,但会掩盖程序运行错误问题)

示例

在下面代码中,throw new IllegalArgumentException 没有加上 try catch 导致 app crash。

public static int compare(String version1, String version2) {
	if (version1 == 0 || version2 == 0)
		throw new IllegalArgumentException("Invalid parameter!");
}

推荐方案

抛出异常时,需要增加 try{}catch{} 用于补充和处理能处理的异常,对于不能处理的异常重新封装抛给上层处理,修改如下:

public static int compare(String version1, String version2) {
	if (version1 == 0||version2 == 0){
		try{
			throw new IllegalArgumentException("Invalid parameter!");
		}catch(Exception e){
			// 处理能处理的异常,不能完整处理,重新封装异常抛给上层
		}
	}
}

【异常】数据库操作异常未捕获处理隐患

摘要

问题: 在 Java 程序中,调用数据库操作时,可能存在数据库连接已关闭情况(由于多线程实现不好,导致状态被改变,尽管开始可能已经进行了状态判断),导致抛出 java.lang.IllegalStateException 异常,此时,必须要在某个位置进行异常处理,否则该异常会层层上传,直到有代码捕获和处理该异常,可能会导致 app 出现 crash。

解决方案: 对于可能抛出异常的代码,增加 try{}catch{},捕获和处理能处理的异常,对于不能处理的异常重新封装并抛给上层。(若仅仅捕获所有异常而不处理,则 crash 率会下降,但会掩盖程序运行错误问题)。

示例

在下面代码中,程序调用 query 方法时抛出了 java.lang.IllegalStateException 异常而导致 app crash。

public static boolean isTableExists(String tableName) {
	Cursor cursor = database.query("sqlite_master", new String[]{"[sql]"},"[type] = 'table' and name = ?", new String[]{tableName}, null, null, null);
	return cursor.getCount() == 1;
}

推荐方案

在可能抛出异常的方法,增加 try{}catch{} 捕获和处理能处理的异常,不能完整处理的重新封装异常抛给上层,修改如下:

public static boolean isTableExists(String tableName) {
	try{
		Cursor cursor = database.query("sqlite_master", new String[]{"[sql]"},"[type] = 'table' and name = ?", new String[]{tableName}, null, null, null);
	}catch(Exception e){
		// 处理能处理的异常,不能完整处理,重新封装异常抛给上层
	}
	return cursor.getCount() == 1;
}

【判空】使用在intent中获取的数据前未判空隐患

摘要

问题: 在 Intent 中通过诸如 “getBundleExtra、getStringExtra……” 获取数据时,得到的对象有可能为 null,若直接使用则有可能导致 app 出现 Crash。

解决方案: 在使用此类对象前,应做判空处理。

示例

在下面代码中,程序调用 getStringExtra(“info”) 方法获取对象,若获取到的对象为 null,则存在导致 app crash 的隐患。

public void func(){
	String str = intent.getStringExtra("info");
	String value = str.substring(str.indexOf(','));
	textview.setText(value);
} 

推荐方案

在使用该对象前,先做判空处理,修改如下:

public void func(){
	String str = intent.getStringExtra("info");
	String value = "null";
	if(str != null && str.contains(",")){
		value = str.substring(str.indexOf(','));
	}
	textview.setText(value);
} 

【OOM】使用IO流后没有关闭导致OOM隐患

摘要

问题: 在使用 IO 流进行相关操作后没关闭导致一直占用 IO 资源,大量流操作后没有关闭可能导致系统内存不足从而使系统抛出 OOM 异常。

解决方案: 在使用相关 IO 流操作后将其关闭,如果在 try-catch 作用域内应在 finally 中关闭。

示例

在下面代码中,使用 IO 流相关操作后没有关闭导致资源占用,可能导致系统内存不足而引发 OOM 异常。

	InputStream inputStream = httpResponse.getEntity().getContent();
	inputStream.read();

推荐方案

对 IO 流进行操作完毕后关闭相关流,修改如下:

	InputStream inputStream = null;
		try {
			inputStream = httpResponse.getEntity().getContent();
		} finally {
			try {
				if (inputStream==null) {
					inputStream.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
		}
	}

【OOM】查询数据库没有关闭游标导致OOM的隐患

摘要

问题: 在使用 Cursor 对数据库进行操作后没有关闭 Cursor,在常时间大量操作情况下出现 OOM 的问题,异常导致 app crash。

解决方案: 在使用 Cursor 对数据库进行操作完毕后关闭 Cursor。

示例

在下面代码中,使用 Cursor 对数据库进行操作后没有关闭 Cursor,在常时间大量操作情况下出现 OOM 的问题异常导致 app crash。

	Cursor cursor = getContentResolver().query(uri);
	if (cursor.moveToNext()) {
		//...
	}

推荐方案

对数据库进行操作完毕后关闭 Cursor,修改如下:

Cursor cursor = null;
	try {
		cursor = getContentResolver().query(uri);
		//……
	} finally {
		if (cursor != null) {
			try { 
				cursor.close();
			} catch (Exception e) {}
		}
	}
}

【越界】使用String.split结果未判断长度隐患

摘要

问题: 在使用 String.split 得到的结果数组前,未对数组进行长度检查,取字段的时候可能发生越界而导致 Crash。

解决方案: 在使用 split 结果数组前,添加对数组长度的判断。

示例

在下面代码中,由于 source 切分得到的结果长度为 0,而直接取下标为 0 的字段导致越界 crash。

	String source = "<br/>";
	String[] infos = source.split("<br/>");
	String poiName = infos[0];

推荐方案

在使用 infos 之前,先对 infos 的长度作检查,修改如下:

	String source = "<br/>";
	String[] infos = source.split("<br/>");
	if(0 < infos.length){
		String poiName = infos[0];
	}

【判空】通过HashMap获取对象使用未判空隐患

摘要

问题: 在 Java 代码实现时,通过 Hashmap 的 get 方法获取对象,并且不是使用 Hashmap 的 Iterator 获取,如果 key 不存在时获取的对象为 null,在使用此对象可能出现 java.lang.NullPointerException 的异常,app 出现 Crash。

解决方案: 在使用 Hashmap 获取的对象前,判断该对象是否为 null,并进行异常处理。

示例

在下面代码中,程序调用 mCacheStatus.get(bmmf.mId_1) 方法返回的为 null,因此在使用 status.path 时抛出了 java.lang.NullPointerException 异常而导致 app crash。

private HashMap<Long, OfflineCacheStatus> mCacheStatus;
synchronized (mLatestDatas) {
	for (BaiduMp3MusicFile bmmf : mLatestDatas) {
		if(bmmf == null) continue;//add this line,question?
		status = mCacheStatus.get(bmmf.mId_1);
		// status为null,使用status.path报空指针异常
		if (!StringUtils.isEmpty(status.path)) {
			bmmf.mPath = status.path;
		}
	}
}

推荐方案

在对象使用前,先进行判空操作,并进行异常处理,修改如下:

private HashMap<Long, OfflineCacheStatus> mCacheStatus;
synchronized (mLatestDatas) {
	for (BaiduMp3MusicFile bmmf : mLatestDatas) {
		if(bmmf == null) continue;//add this line,question?
		status = mCacheStatus.get(bmmf.mId_1);
		// status为null,使用status.path报空指针异常
		if (status != null) {
			if (!StringUtils.isEmpty(status.path)) {
				bmmf.mPath = status.path;
			}
		}
	}
}

【异常】使用动态载入界面的元素未判断是否属于此界面的隐患

摘要

问题: 通过 inflater.inflate(R.layout.frag_name_search, null) 方法动态载入界面,然后通过 findViewById 获取界面元素,如果这个元素不存在此界面,再使用此元素对象的方法时将产生 java.lang.NullPointerException 的异常,导致 app 出现 Crash。

解决方案: 在使用元素对象时先判断元素是否为 null。

示例

在下面代码中,程序调用 inflater.inflate(R.layout.frag_name_search, null) 方法动态加载界面,然后通过 view.findViewById(R.id.navi_logo) 获取界面元素,因 R.id.navi_logo 不存在于该 view,抛出了 java.lang. NullPointerException 异常导致 app crash。

	View view = inflater.inflate(R.layout.frag_name_search, null);
	mHistoryLV = (ListView) view.findViewById(R.id.navi_logo); 
	mHistoryLV.addHeaderView(headView);

推荐方案

在调用 mHistoryLV.addHeaderView(headView);方法前,先判断 mHistoryLV 是否为 null,再进行方法调用,修改如下:

	View view = inflater.inflate(R.layout.frag_name_search, null);
	mHistoryLV = (ListView) view.findViewById(R.id.navi_logo);
	if(mHistoryLV != null){
		mHistoryLV.addHeaderView(headView);
	}

【兼容】系统API兼容性隐患

摘要

问题: 在 Android 平台的一个系统版本中调用了该版本中不存在或不合适的系统 API,必然会出现调用失败或运行结果不符合预期,进而导致软件运行发生 Crash 或产生错误。

解决方案: 在这些 API 使用时,加上 Android 系统版本判断,避免不兼容 API 的调用。

示例

对于如下代码:

	private void setUpActionBar() {
		ActionBar actionBar = getActionBar();
		actionBar.setDisplayHomeAsUpEnabled(true);
	}

该方法中,调用了 ActionBar.setDisplayHomeAsUpEnabled() 方法,而在 Android 官方文档中有如下描述:

public abstract void setDisplayShowHomeEnabled (boolean showHome)
Added in API level 11
Set whether to include the application home affordance in the action bar. Home is presented as either an activity icon or logo.
To set several display options at once, see the setDisplayOptions methods.
Parameters
showHome
true to show home, false otherwise.

从描述可知该方法在 API Level 11(Android 3.0)中引入,若在 API Level 10(Android 2.3.3)以下系统中运行该方法必然会出现因不存在该 API 而调用失败,进而导致软件 Crash。

推荐方案

由于在 Android 系统中向下兼容性比较差,但是一个应用 APP 经过处理还是可以在各个版本间运行的。为了应用 APP 有更好的兼容性,可以利用高版本的 SDK 开发应用,并在程序运行时(Runtime)对应用所运行的平台判断,旧平台使用旧的 API,而新平台可使用新的 API,这样可以较好的提高软件兼容性。

在我们自己开发应用过程中,常常使用如下的代码形式判断运行新 API 还是旧的 API:

	private void setUpActionBar() {
		// Make sure we're running on Honeycomb or higher to use ActionBar APIs
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
			// 包含新API的代码块
			ActionBar actionBar = getActionBar();
			actionBar.setDisplayHomeAsUpEnabled(true);
		}else {
			// 包含旧的API的代码块
		}
	}

【状态】销毁Dialog前是否isShowing未判断隐患

摘要

问题: 调用 Android.app.Dialog.cancel() 方法前,如果这个 dialog 不处于 showing 状态时,会抛出 java.lang.IllegalArgumentException 的异常,导致 app 出现 Crash。

解决方案: 调用 Android.app.dialog.cancel() 方法前,先判断 Android.app.dialog.isShowing(),为 true 时才进行 cancel() 操作。

示例

在下面代码中,程序调用 mProgressDialog.cancel() 前没有判断 mProgressDialog.isShowing() 抛出了 java.lang.IllegalArgumentException 异常而导致 app crash。

public void hideProgressDialog(){
	if(mProgressDialog != null){
		mProgressDialog.cancel();
		mProgressDialog = null;
	}
}

推荐方案

在调用 cancel() 方法前,先判断 dialog 是否处于 showing 状态,再进行方法调用,修改如下:

public void hideProgressDialog(){
	if(mProgressDialog != null && mProgressDialog.isShowing()){
		mProgressDialog.cancel();
		mProgressDialog = null;
	}
}

【异常】在 FragmentActivity 中 startActivityForResult 的 requestCode 过大隐患

摘要

问题: 在 Activity 中调用 startActivityForResult(intent, int) 时,如果该 Activity 继承了 android.support.v4.app.FragmentActivity,同时传的第二参数的 int 值超过 65535 时,会导致 app crash:Caused by: java.lang.IllegalArgumentException: Can only use lower 16 bits for requestCode。

解决方案: 继承 android.app.Activity 或者第二参数 requestCode 的值不大于 65535 即可。

示例

在下面代码中,如果启动了RequestCodeTooBiggerActivity,则一定会报错,导致app crash。

public class RequestCodeTooBiggerActivity extends android.support.v4.app.FragmentActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_demo);
		startActivityForResult(new Intent(this, DemoActivity.class), 65536);
	}
}

推荐方案

requestCode 的值不能超过 2 个字节,即不能超过 65535 或者不继承 FragmentActivity,直接继承 Activity:

public class RequestCodeTooBiggerActivity extends android.support.v4.app.FragmentActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_demo);
		startActivityForResult(new Intent(this, DemoActivity.class), 1);
	}
}public class RequestCodeTooBiggerActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_demo);
		startActivityForResult(new Intent(this, DemoActivity.class), 65536);
	}
}

【判空】ArrayList 对象使用未判空隐患

摘要

问题: 在 Java 代码实现时,定义一个 ArrayList 对象,并初始化为 null,通过调用方法返回的 ArrayList 对象赋值给开始定义的对象,直接使用时可能出现 java.lang.NullPointerException 异常,app 出现 Crash。

解决方案: 在使用 ArrayList 对象前,判断该对象是否为 null,并进行异常处理。

示例

在下面代码中,程序调用 getDataWithSeq(i).get() 方法时抛出了 java.lang.NullPointerException 异常而导致 app crash。

	if(curCount >= (position+1)){
		// getDataWithSeq返回null,空指针异常
		return getDataWithSeq(i).get(position-(curCount-counts[i]));
	}
	public ArrayList<MylocResultListPoisPoiType> getDataWithSeq(int seq){
		ArrayList<MylocResultListPoisPoiType> result = null;
		if (seq > 0) {
			result = mCacheStatus;
		}
		return result;
	}

推荐方案

在对象使用前,先进行判空操作,并进行异常处理,修改如下:

if(curCount>=(position+1)){
	// getDataWithSeq返回null,空指针异常
	if (getDataWithSeq(i) != null) {
		return getDataWithSeq(i).get(position-(curCount-counts[i]));
	}
}

public ArrayList<MylocResultListPoisPoiType> getDataWithSeq(int seq){
	ArrayList<MylocResultListPoisPoiType> result = null;
	if (seq > 0) {
		result = mCacheStatus;
	}
	return result;
}

【兼容】ListView 在调用 setAdapter() 方法后再调用 addHeader() 方法隐患

摘要

问题: ListView 在调用 setAdapter() 方法后再调用 addHeaderView() 方法会出现 Caused by: java.lang.IllegalStateException: Cannot add header view to list setAdapter has already been called.异常,意思是调用 addHeaderView() 方法在调用 setAdapter() 方法之后。android 4.4 系统以前会有这个问题,在 android 4.4 及以后的版本修复了这个问题,不会报错。

解决方案: ListView 在 setAdapter() 方法之前调用 addHeaderView() 方法。

示例

如下代码如果在 android 4.4 系统以前运行时,会报 java.lang.IllegalStateException 错误,而在 4.4 及以后的系统版本中则不会报错。原因是调用 ListView 的 addHeaderView() 方法在 setAdapter() 方法之后。

public class ListViewAddHeaderBeforeSetAdapterActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ListView lv = (ListView) findViewById(android.R.id.list);
		ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
		adapter.add("Activity");
		adapter.add("Service");
		lv.setAdapter(adapter);
		addHeader(lv); // 这里会导致app crash
	}

	/**
	* 往ListView中添加头部布局
	* @param lv
	*/
	private void addHeader(ListView lv) {
		TextView tv = new TextView(this);
		tv.setText("这是Header");
		tv.setTextSize(24f);
		tv.setTextColor(Color.WHITE);
		lv.addHeaderView(tv);
	}
} 

推荐方案

ListView 在 setAdapter() 方法之前调用 addHeaderView() 方法:

public class ListViewAddHeaderBeforeSetAdapterActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ListView lv = (ListView) findViewById(android.R.id.list);
		addHeader(lv); // 先添加header

		ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
		adapter.add("Activity");
		adapter.add("Service");
		lv.setAdapter(adapter);
	}

	/**
	* 往ListView中添加头部布局
	* @param lv
	*/
	private void addHeader(ListView lv) {
		TextView tv = new TextView(this);
		tv.setText("这是Header");
		tv.setTextSize(24f);
		tv.setTextColor(Color.WHITE);
		lv.addHeaderView(tv);
	}
} 

【判断】添加 Fragment 前未判断是否 IsAdded 隐患

摘要

问题: 当使用 FragmentTransaction 调用 add 函数添加某个 fragment 时,如果该 fragment 已经被添加到 activity,再进行添加操作会抛出 Fragment already added 异常。

解决方案: 添加 fragment 前,先调用 fragment.isAdded 函数判断该 fragment 是否已经添加。

示例

在下面代码中,用户如果快速点击或者函数被重复执行时,如果 myFragment 已经被添加再进行添加操作会抛出 Fragment already added 异常。

if (!ret) {
	try {
		myFragment.setArguments(pageArgs);
		FragmentTransaction ftr = getSupportFragmentManager().beginTransaction();
		ftr.add(R.id.fragment_container, myFragment, ((Object) myFragment).getClass().getName() + myFragment.getPageTag());
	}
}

推荐方案

在进行执行 add 函数前,先调用 isAdded 判断 myFragment 是否已经被添加,修改如下:

if (!ret && !myFragment.isAdded()) {
	try {
		myFragment.setArguments(pageArgs);
		FragmentTransaction ftr = getSupportFragmentManager().beginTransaction();
		ftr.add(R.id.fragment_container, myFragment, ((Object) myFragment).getClass().getName() + myFragment.getPageTag());
	}
}

【异常】使用除法或求余没有判断分母长度隐患

摘要

问题: 使用除法或者求余运算时,如果分母是通过调用函数返回的 int,未对返回值进行判断,当返回值为 0 时,会出现 java.lang.ArithmeticException: / by zero异常。

解决方案: 调用函数前,对函数的返回值的长度进行判断。

示例

在下面代码中,程序使用了求余运算,但调用 mBoardTextList.size() 前没有判断 mBoardTextList 的长度,如果 mBoardTextList 为 0 时就抛出了java.lang.ArithmeticException: / by zero异常而导致app crash。

	text = mBoardTextList.get(mBoardIndex % mBoardTextList.size());
	mBoardIndex++;

推荐方案

在进行求余运算前,先判断 mBoardTextList 是否为 0,再进行求余运算,修改如下:

	int boardListSize = mBoardTextList.size();
	if (boardListSize > 0) {
		text = mBoardTextList.get(mBoardIndex % boardListSize);
		mBoardIndex++;
	} 

【NullPointerException】dismiss() 方法调用前 isShowing 未判断的隐患

摘要

问题: 在调用系统的 dismiss() 方法前,没有对状态进行判断,导致抛出 NullPointerException 异常。

解决方案: 在调用系统的 dismiss() 方法时,需要对状态先进行判断。

示例

在下面代码中,popupWindow.dismiss() 调用时出现了 NullPointerException。

	if (mContext != null &&!((Activity)mContext).isFinishing()) {
		popupWindow.dismiss();
		setFocusable(false);
	}

推荐方案

在调用系统的 dismiss() 方法前先进行 isShowing 的判断,修改如下:

	if (mContext != null &&!((Activity)mContext).isFinishing() && isShowing()) {
		popupWindow.dismiss();
		setFocusable(false);
	}

【判空】方法中存在 return null 返回对象直接进行方法调用隐患

摘要

问题: 方法中存在 return null,在特定条件下触发返回 return null,而对该方法的返回对象直接进方法调用将产生 java.lang.NullPointerException 的异常,导致 app 出现 Crash。

解决方案: 对方法中含有 return null 的返回对象进行直接方法调用时需要先进行判断。

示例

在下面代码中,程序调用 getRouteNodeData().get(0) 前没有判断 getRouteNodeData() 的返回存在 null 的情况,抛出了 java.lang. NullPointerException 的异常而导致 app crash。

	public ArrayList<RouteListItem> getRouteNodeData() {
		if (mRouteList.size() == 0) {
			return null;
		} else {
			return mRouteList;
		}
	}

	public void clearItem(){
		RouteResultManager mgr = RouteResultManager.instance(mContext);
		mFocusItemIndex = mgr.getRouteNodeData().get(0);
	}

推荐方案

在调用 getRouteNodeData() 方法前,先判断 getRouteNodeData() 是否为 null,再进行方法调用,修改如下:

	public ArrayList<RouteListItem> getRouteNodeData() {
		if (mRouteList.size() == 0) {
			return null;
		} else {
			return mRouteList;
		}
	}
	public void clearItem(){
		RouteResultManager mgr = RouteResultManager.instance(mContext);
		if(mgr.getRouteNodeData() != null ){
			mFocusItemIndex = mgr.getRouteNodeData().get(0);
		}
	}

【异常】复写生命周期函数没有调用父类的 super 函数隐患

摘要

问题: 继承自 Activity 或 Fragment 的类当复写相关的生命周期函数时,需要调用父类的 super 函数,该点在 Android 开发者官网有明确说明。

解决方案: 复写 Activity 或 Fragment 的生命周期函数时,需调用其父类的 super 函数。

示例

在下面代码中,复写 Fragment 的生命周期函数没有调用其父类的 super 函数,导致 SuperNotCalledException。

	@Override
	public void onDestroyView() {
		// TODO Auto-generated method stub
		myLinsenter.close();
	} 

推荐方案

对生命周期函数添加super函数,修改如下:

	@Override
	public void onDestroyView() {
		// TODO Auto-generated method stub
		super.onDestroyView();
		myLinsenter.close();
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值