1. 程式人生 > >Android開發:getViewById返回null的原因定位

Android開發:getViewById返回null的原因定位

      最近在研究開發一些基於AndroidApp,遇到了一些問題,其中一個比較關鍵的是在Activity中的onCreate()方法中獲取Button物件,程式碼大概如下:

private Button mTrueButton;

@Override

public void onCreateBundle savedInstanceState{

   super.onCreate(savedInstanceState);

   setContentView(R.layout.activity_quiz);

   mTrueButton = (Button) getViewById(R.id.true_button);

   mTrueButton.setOnClickListener(…);

}

該程式碼是根據一本android的程式設計指南進行修改的,但意想不到的是,一執行,app提示系統錯誤,終止執行,通過debug跟蹤發現:mTrueButtonnull,系統執行到mTrueButton.setOnclickListener丟擲java.lang.NullPointerException很明顯,此時呼叫getViewById無法獲取到Button這個View物件,於是開始網上搜索資料,認為可能的原因是下面幾個:

一,呼叫順序不當導致的異常

持這種觀點的主要原因是getViewById的呼叫放到了setContentView

之前,如下:

super.onCreate(savedInstanceState);

mTrueButton = ButtongetViewById(R.id.true_button);

setContentView(R.layout.activity_quiz);

理由是:當activity 呼叫 setContentView() 時,android 才會去繪製 layout 上的各個元素,併為其分配記憶體。只有 分配了記憶體以後,才能繼續執行 ,findViewById(); 才能得到引用,不然得到空引用,空引用意味著,後面使用相應變數時就會發生訪問的物件不存在的問題。

而且當Activity重新setContentView()以後,那些之前繪製的控制元件,記憶體都被滅掉了。

所以,若是通過setContentView 來達到畫面切換目的的,要注意重新繪製以後重新取得引用

二,getViewById的上下文物件不匹配

這種方式讓筆者想到Javascript中的document.getElementById,兩者具有非常高的相似性,getElementById的呼叫需要指定對應的document物件,表示從該document物件獲取元素,同理,Android中的getViewById的完整呼叫是View.getViewById,因此需要關注該方法預設的context物件,一般是this,即當前的Activity,但有時候可能不是這樣,如:

userDialog=new Dialog(addevent.this);

userDialog.setContentView(R.layout.user_list);

userDialog.setTitle("請選擇");

ListView lv=(ListView)userDialog.findViewById(R.id.userList);

lv.setAdapter(new MyAdapter());            

userDialog.show();

如上,例項化lv時必須指定userDialog.findViewById()而不能直接findViewById(),否則就會從Activity而不是Dialog的佈局檔案中找R.id.userList,此時當然會返回null,執行lv.setAdapter(new MyAdapter());時就會出現NullPointException異常

三,開發工具Eclipse導致的問題

假定在自定的AdaptergetView方法中有類似如下的程式碼:

View rowview = (View)inflater.inflate(R.layout.rowview, parent, false);

TextView tv_contact_id =(TextView)rowview.findViewById(R.id.tv_contact_id);

TextView tv_contactname =(TextView)rowview.findViewById(R.id.tv_contactname);

有時候居然也會發現rowview非空,但tv_contact_idtv_contactname都是null!仔細看程式碼,怎麼也看不出錯誤來。到底是什麼原因造成的呢?答案是Eclipse造成的,要解決這個問題,需要這個專案clean一次(Project選單 -> Clean子選單),這樣就OK了。

四,新版本SDK不能在onCreate方法中呼叫了-筆者的問題原因在此。

重要的環境交代:剛學Android,在官網下載的新版的ADT以及新版的SDK在新版的IDE(ADT)建立專案時如果你的最小版本(minimumrequiredSDK)要支援4.0以下版,並且目標版本為(4.0+),那麼此時IDE會為你建立一個相容包(appcompat_v7)建立專案後,這個時候在生成的專案主Activity不是以前的那種繼承的Activity,而是繼承的ActionBarActivity

此時,如果你仍然用舊的辦法在onCreate呼叫getViewById,那麼會返回null,原因是:在新的layout檔案不是存放在預設的(res/layout/activity_quiz.xml)檔案中,而是存放在(res/layout/fragment_quiz.xml)檔案中。所以要在fragment_quiz.xml去找對應的ID才會找到,而新的IDE生成的程式碼中載入(fragment_quiz.xml)檔案是在一個內部類載入的,所以一種方法是:我們可以在內部類載入處來得到Button

    /**

     * A placeholder fragment containing a simple view.

     */

    public static class PlaceholderFragment extends Fragment {

        View rootView = null;

        public PlaceholderFragment() {

        }

        @Override

        public View onCreateView(LayoutInflater inflater, ViewGroup container,

                Bundle savedInstanceState) {

            rootView = inflater.inflate(R.layout.fragment_quiz, container, false);

            mTrueButton = (Button) rootView.findViewById(R.id.true_button);

          System.out.println(button);

            return rootView;

        }

    }

另外一種方法是:如果熟悉Activity的生命週期的人可以知道:onCreate呼叫的時候其實還沒構造對應的佈局物件,因此不能在onCreate函式中獲取控制元件,但可以在onStart函式中獲取:(筆者的方案就是在onStart方法中獲取的)

@Override
protected void onStart() {
   
super.onStart();
   
mTrueButton = (Button)findViewById(R.id.true_button);
mTrueButton.setOnClickListener(new android.view.View.OnClickListener(){
       
public void onClick(android.view.View v) {
       
//TODO...
       
}
   
});
}