1. 程式人生 > >幾個Android常見wraning警告處理方法

幾個Android常見wraning警告處理方法

寫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<PopupActivity> mActivity;
   
                MyHandler(PopupActivity activity) {
                        mActivity = new WeakReference<PopupActivity>(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中的子條目控制元件是由下面佈局控制的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingRight="15dp"
        android:text="Text1" />
    <TextView
        android:id="@+id/text2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Text2" />
</LinearLayout>
從上面的程式碼可看出,我們是想讓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("<merge /> 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的根節點佈局屬性被丟棄。
       回到Avoid passing null as the view root這個警告處理上,可以用View類的靜態方法inflate(context, resource, root)來代替LayoutInflater的inflate()方法,就不會再報警告。

----這種處理方式為什麼會處理這個警告,也是比較奇怪的現象,因為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

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