写Android项目时应力求项目中没有warning警告,本文罗列几个常见的Android warning警告信息及可用的解决方法。
1. replace "-" with an "en dash" character (–, –)
解决方法:直接将“-”符号替换为“–” (不含双引号)。
2. This Handler class should be static or leaks might occur
首先解释下这句话This Handler class should be static or leaks might occur,大致意思就是说:Handler类应该定义成静态类,否则可能导致内存泄露。
具体如何解决,在国外有人提出,如下:
Issue: Ensures that Handler classes do not hold on to a reference to an outer class.
In Android, Handler classes should be static or leaks might occur. Messages enqueued on the application thread's MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class.
大体翻译如下:
Handler 类应该为static类型,否则有可能造成泄露。在程序消息队列中排队的消息保持了对目标Handler类的应用。如果Handler是个内部类,那 么它也会保持它所在的外部类的引用。为了避免泄露这个外部类,应该将Handler声明为static嵌套类,并且使用对外部类的弱应用。
使用范例:
static class MyHandler extends Handler {
WeakReference mActivity;
MyHandler(PopupActivity activity) {
mActivity = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
PopupActivity theActivity = mActivity.get();
//handler中使用mActivity前需要先判断该activity是否为空
if(theActivity==null){
return;
}
switch (msg.what) {
case 0:
theActivity.popPlay.setChecked(true);
break;
}
}
}
MyHandler ttsHandler = new MyHandler(this);
private Cursor mCursor;
private void test() {
ttsHandler.sendEmptyMessage(0);
}
注:MyHandler持有mActivity的弱引用时,mActivity生命周期不再受MyHandler影响,mActivity可能会被系统回收,因此MyHandler中使用mActivity时需要先对它的状态进行判断。
实际开发中,也可用其它方式替代Handlder代码功能。
参考文章:http://www.cnblogs.com/jevan/p/3168828.html
3. Dead Code
程序中有代码永远执行不到,就会出现这个警告。
双击problems视图中的这个Dead Code警告,会跳转到程序中永远执行不到的代码处,查看一下代码中的逻辑处理是否有问题并进行修改即可。
4. Invalid project path: Duplicate path entries found (/XXXXProject [Include path] isSystemInclude:true includePath:E:/work/android-ndk-r9/platforms/android-18/arch-arm/usr/include)......
这个警告是说项目中为c/cpp配置了多条include路径。有这个警告时,左键项目->properties->C/C++ General->Paths and Symbols->Includes标签,可发现有多条C/C++ include路径,而且删不掉。
Eclipse开发Android NDK,有时候换了新版ndk,导致路径变化,但是已有项目中 Paths and Symbols中的Includes配置中并不能生效,而且在配置中只能添加,不能编辑和删除adt插件添加的路径。包括把Android项目导出,再导入,这个路径配置就丢了。这点太烦人了。于是花了点时间找到了这个配置文件存放位置。
路径是:
${workspace_loc}/.metadata/.plugins/com.android.ide.eclipse.ndk/${ProjName}.pathInfo 如E:\workspace_caishenkezhan_commit\.metadata\.plugins\com.android.ide.eclipse.ndk\CskzAndroidClient.pathInfo
打开文件就能看到以”i,”开头的路径配置:
i,jni
i,e:/work/android-ndk-r9/toolchains/arm-linux-androideabi-4.6/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.6/include
i,e:/work/android-ndk-r9/toolchains/arm-linux-androideabi-4.6/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.6/include-fixed
i,E:/work/android-ndk-r9/platforms/android-18/arch-arm/usr/include
直接修改成当前正常的路径,重启eclipse就正常了
参考文章:http://www.rover12421.com/2013/10/11/elipse-the-android-ndk-development-configuration-paths-and-symbols-in-includes-modifications.html。
5. Implicitly using the default locale is a common source of bugs: Use toLowerCase(Locale)
警告描述:toLowerCase(),隐式的使用了Locale对象。
public String toLowerCase() {
return CaseMapper.toLowerCase(Locale.getDefault(), this, value, offset, count);
}
Locale represents a language/country/variant combination. Locales are used to alter the presentation of information such as numbers or dates to suit the conventions in the region they describe.
解决方法:将类似于
if("https".equals(url.getProtocol().toLowerCase())){
}
的代码改成下面的方式:
if("https".equalsIgnoreCase(url.getProtocol())){
}
6. Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details.AndroidManifest.xml
警告原因:Android Runtime和Dalvik会根据target SDK version决定是否工作在『兼容模式』下,所谓兼容模式,就是关闭了新版本中各种新机制和体验优化的状态。targetSdkVersion如果设置很低,就等于是关闭了所有高版本的新特性和机制,包括『屏幕自适应』、『硬件加速』。
为了保证各个版本的兼容性,及时使用到新特性,targetSdkVersion应当随Android最新版本的发布而持续提高,以保证在各个Android版本的设备上都能获得完整的体验。
这个warning是在提醒我们没有匹配使用最新的sdk版本,可能导致app在最新的系统上面无法利用最新的特性或功能。
解决方法:uses-sdk标签中的android:targetSdkVersion属性改为你电脑上面最新的sdk版本。当前sdk环境中的最新版本可在eclipse->perferences->Android中查看。
当然,我们也可以无视这个警告,毕竟市场上的android系统版本还是要比开发sdk最新版本慢一段时间的,及时我们为高版本做了适配,也要等上一段时间才可能装载相应的android高版本手机上,所以我们可以将android:targetSdkVersion定为目前市场上最新或者普遍流行的高版本即可。
参考文章:
http://blog.csdn.net/caiwenfeng_for_23/article/details/41855497、http://blog.csdn.net/zhufuing/article/details/17615985
7. Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element)
警告描述:避免给父view传递null,因为需要使用该父view的布局参数来设置将要inflated的view的根元素属性。
关于android.view.LayoutInflater类的public View inflate(int resource, ViewGroup root)方法中为什么要使用参数root及使用root后有什么用处,在http://www.cnblogs.com/kobe8/p/3859708.html中有很详尽的讲述。这篇文章是英文的,博主自认英文还可以,通读这篇文章2遍后,把一些知识要点记录如下:
/**************************************参考文章开始*************************************/
在Android开发中,LayoutInflater很常用,但很多人都意识不到他们是在用一种错误方式使用LayoutInflater,如果你写过下面这样的代码,请继续阅读下文,你会发现这种代码是错误的,以及错误的原因:
inflater.inflate(R.layout.my_layout, null);
我们先看一下LayoutInflater是怎么工作的?在实际项目开发中,LayoutInflater有2种形式的inflate()用法:
inflate(int resource, ViewGroup root);
inflate(int resource, ViewGroup root, boolean attachToRoot);
第1个参数要填充的布局文件资源ID,第2个参数是正在填充的view所要附加上去的父view,如果有第3个参数,它指示是不是需要把填充生成的view附加到父view。
查看源码可以看到这2种形式的inflate()方法的联系:
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
也就是说,如果root为null,实际上相当于执行了inflate(R.layout.my_layout,null,false)。
很多人就认为如果不想让填充生成的view附加到任何父view上,直接给inflate()方法的root参数传递一个null就可以了,从而忽略在参数root在填充view时所发挥的重要作用。
Android framework中有些场景需要开发者交互式的填充生成大量的view,最常见的是Adapter的getView()方法,和Fragement的onCreateView()方法:
getView(int position, View convertView, ViewGroup parent)
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
当Android framework希望开发者去生成一个view时,通常也会提供一个该view依附的父view参数,开发者直接使用就可以了。Notice also that in most cases (including the above two examples), it will throw an Exception later on if LayoutInflater is allowed to automatically attach the inflated view to the root(这句没明白,哪个方法会抛异常?)。
这些方法中的父View在inflate一个view的过程中,起到了很重要的作用:父view提供了正在创建的view的根节点的布局参数(evaluate the LayoutParams declared in the root element of the XML being inflated),如果传一个null,表示告诉Android framework,开发者不知道这个view的父view是什么。
The problem with this is android:layout_xxx attributes are always be evaluated in the context of the parent view. As a result, without any known parent, all LayoutParams you declared on the root element of your XML tree will just get thrown away。----这句很重要,Android中以layout开头的布局属性,即像"layout_xxxx"这样的属性,通常都是需要在父view上下文中计算的,因此,如果没有不指定父view是哪个,该view布局文件的根节点处声明的所有类似layout_xxxx的属性都会被丢弃。这样,正在填充的view的根节点属性会直接使用默认值,如果默认值与开发者期望的效果一致,那从实现结果看是没有任何问题的,这也掩盖了内在本质的问题。
下面以一个listView的例子来说明这个问题,假如listView中的子条目控件是由下面布局控制的:
从上面的代码可看出,我们是想让listView的itemView都有一个固定高度listPreferredItemHeight。
如果用下面这种方式创建listView的子条目控件:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflate(R.layout.item_row, null);
}
return convertView;
}
运行效果如下,没有达到预期的效果,listView的子条目是以wrap-content形式显示的,不是设置的高度:
如果用下面的方式创建listView的子条目view:
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflate(R.layout.item_row, null);
}
return convertView;
}
运行效果如下,这时在子控件布局中设置的layout_height值是生效的:
上面的例子很完美地阐释了inflate一个view时root参数的重要性,不过凡事都有例外,也有少数使用inflate()方法时不需要传递非空root参数值的情况,比如为AlertDialog中设置布局的情形:
AlertDialog.Builder builder = new AlertDialog.Builder(context);
View content = LayoutInflater.from(context).inflate(R.layout.item_row, null);
builder.setTitle("My Dialog");
builder.setView(content);
builder.setPositiveButton("OK", null);
builder.show();
AlertDialog.Builder的情况是,它提供了设置自定义布局的方法setView(),但是setView()不支持接收一个资源文件参数,只接收View控件,而且我们其实也不知道AlertDialog.builder对外提供的父view是什么。事实上,AlertDialog会忽略所有的布局参数而直接使用Match_Parent属性。
/*************************************************参考文章结束****************************************/
这里结合源码再分析一下 inflate(int resource, ViewGroup root);方法中root参数是怎么提供正在填充的view的根节点layout属性的。
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
if (DEBUG) System.out.println("INFLATING from resource: " + resource);
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
带3个参数的inflate方法实际是调用了另一种重载形式的inflate方法:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, attrs, false);
} else {
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp
rInflate(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
从这个inflate方法中可以看到root是否为空的区别在于下面这段代码:
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
也就是说,如果root参数不为空,那么就可调用root的generateLayoutParams(attrs)方法来获取temp(正在创建的view)根节点对应的layoutParams对象,并调用temp.setLayoutParams(params)让这些根节点布局属性生效;如果root为空,是没有进行这两步操作的,也就是temp的根节点布局属性被丢弃。
----这种处理方式为什么会处理这个警告,也是比较奇怪的现象,因为View.inflate(context, resource, root)方法实现如下:
//in View
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
可以看到它跟inflater.
inflate(int resource, ViewGroup root);方法其实是一回事,用View的静态inflate()方法不报警告只是没检测到罢了。
参考文章:
http://www.cnblogs.com/kobe8/p/3859708.html
http://blog.csdn.net/caoyicheng1/article/details/43703639
android2.3 View视图框架源码分析之一:android是如何创建一个view的?
8. Custom view overrides onTouchEvent but not performClick
警告含义:自定义View重写了onTouchEvent()方法,但是没有重写performClick()方法。
View类中的onTouchEvent()方法内部会在某个时机调用performClick()方法,这方面的原理有待总结。
警告解决:只要重写下performClick()就可以了;不重写对功能也没什么影响。
@Override
public boolean performClick() {
// Calls the super implementation, which generates an AccessibilityEvent
// and calls the onClick() listener on the view, if any
super.performClick();
// Handle the action for the custom click here
}
参考文章:
http://stackoverflow.com/questions/27462468/custom-view-overrides-ontouchevent-but-not-performclick
9. The serializable class HttpClientUtil does not declare a static final serialVersionUID field of type long
警告含义:HttpClientUtil实现了serializable接口,但是没有声明一个静态的,final的、long型的serialVersionUID。
这个warning是Eclipse开发环境给出的,可以设置Eclipse忽略这个警告,方法如下:
windows -> preferences -> compiler -> Error/Warnings -> Potential Programming problems
将Serializable class without serialVersionUID的warning改成ignore。
SerialVersionUid,简言之,其目的是序列化对象版本控制,有关各版本反序列化时是否兼容。如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化时会抛出InvalidClassException异常。如果修改较小,比如仅仅是增加了一个属性,我们希望向下兼容,老版本的数据都能保留,那就不用修改;如果我们删除了一个属性,或者更改了类的继承关系,必然不兼容旧数据,这时就应该手动更新版本号,即SerialVersionUid。
jdk文档中有解释,建议我们显式声明,因为如果不声明,JVM会为我们自动产生一个值,但这个值和编译器的实现相关,并不稳定,这样就可能在不同JVM环境下出现反序列化时报InvalidClassException异常。
警告解决:在出现该warning的代码处按下ctrl+1, 会提示3种解决该问题的方法
1. add default serial version ID
2. add generated serial version ID
3. add "Suppress Warnings" 'servial' to XXXXXXXX
前2种方式可以快速生成一个SerialVersionUid,一种就是1L,一种是生成一个很大的数,这两种有什么区别呢?
看上去,好像每个类的这个类不同,似乎这个SerialVersionUid在类之间有某种关联。其实不然,两种都可以,从JDK文档也看不出这一点。我们只要保证在同一个类中,不同版本根据兼容需要,是否更改SerialVersionUid即可。
对于第一种,需要了解哪些情况是可兼容的,哪些根本就不兼容。
参考文档:http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf。
在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。1->2->3.....
第二种方式,是根据类的结构产生的hash值。增减一个属性、方法等,都可能导致这个值产生变化。这种方式适用于这样的场景:
开发者认为每次修改类后就需要生成新的版本号,不想向下兼容,操作就是删除原有serialVesionUid声明语句,再自动生成一下。
个人认为,一般采用第一种就行了,简单。第二种能够保证每次更改类结构后改变版本号,但还是要手工去生成,并不是修改了类,会提示你要去更新这个SerialVersionUid,所以虽然看上去很cool,实际上让人很迷惑。
参考:
1. 一篇较好的关于serialVesionUid的说明:
http://www.mkyong.com/java-best-practices/understand-the-serialversionuid/
2. serialVesionUid相关讨论
http://stackoverflow.com/questions/888335/why-generate-long-serialversionuid-instead-of-a-simple-1l
3. compiler-generated ID生成算法
http://java.sun.com/javase/6/docs/platform/serialization/spec/class.html#4100
4. http://stackoverflow.com/questions/27462468/custom-view-overrides-ontouchevent-but-not-performclick