BAT、網易、蘑菇街面試題整理-8

Androidhtml

1. ActivityFragment的生命週期。java

http://blog.csdn.net/zjclugger/article/details/10442335
python

1、Activity 生命週期linux


2、Fragment 生命週期android


3、對比圖程序員



4、測試代碼web

[java]  view plain  copy
  1. package com.goso.testapp;  
  2.   
  3. import android.app.Activity;  
  4. import android.app.ListFragment;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7. import android.view.LayoutInflater;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10. import android.widget.ArrayAdapter;  
  11. import android.widget.ListView;  
  12.   
  13. /** 
  14.  * Demonstration of using ListFragment to show a list of items 
  15.  * from a canned array. 
  16.  */  
  17. public class FragmentListArray extends Activity {  
  18.   
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         Log.e("HJJ""Activity &&&& onCreate...");  
  23.         // Create the list fragment and add it as our sole content.  
  24.         if (getFragmentManager().findFragmentById(android.R.id.content) == null) {  
  25.             ArrayListFragment list = new ArrayListFragment();  
  26.             getFragmentManager().beginTransaction().add(android.R.id.content, list).commit();  
  27.         }  
  28.     }  
  29.   
  30.     @Override  
  31.     protected void onStart() {  
  32.         // TODO Auto-generated method stub  
  33.         super.onStart();  
  34.         Log.e("HJJ""Activity &&&& onStart...");  
  35.     }  
  36.       
  37.     @Override  
  38.     protected void onResume() {  
  39.         // TODO Auto-generated method stub  
  40.         super.onResume();  
  41.         Log.e("HJJ""Activity &&&& onResume...");  
  42.     }  
  43.       
  44.     @Override  
  45.     protected void onStop() {  
  46.         // TODO Auto-generated method stub  
  47.         super.onStop();  
  48.         Log.e("HJJ""Activity &&&& onStop...");  
  49.     }  
  50.       
  51.     @Override  
  52.     protected void onPause() {  
  53.         // TODO Auto-generated method stub  
  54.         super.onPause();  
  55.         Log.e("HJJ""Activity &&&& onPause...");  
  56.     }  
  57.       
  58.     @Override  
  59.     protected void onDestroy() {  
  60.         // TODO Auto-generated method stub  
  61.         super.onDestroy();  
  62.         Log.e("HJJ""Activity &&&& onDestroy...");  
  63.     }  
  64.       
  65.     public static class ArrayListFragment extends ListFragment {  
  66.   
  67.         @Override  
  68.         public void onAttach(Activity activity) {  
  69.             // TODO Auto-generated method stub  
  70.             Log.e("HJJ""ArrayListFragment **** onAttach...");  
  71.             super.onAttach(activity);  
  72.         }  
  73.           
  74.         @Override  
  75.         public void onCreate(Bundle savedInstanceState) {  
  76.             // TODO Auto-generated method stub  
  77.             Log.e("HJJ""ArrayListFragment **** onCreate...");  
  78.             super.onCreate(savedInstanceState);  
  79.         }  
  80.           
  81.         @Override  
  82.         public View onCreateView(LayoutInflater inflater, ViewGroup container,  
  83.                 Bundle savedInstanceState) {  
  84.             // TODO Auto-generated method stub  
  85.             Log.e("HJJ""ArrayListFragment **** onCreateView...");  
  86.             return super.onCreateView(inflater, container, savedInstanceState);  
  87.         }  
  88.           
  89.         @Override  
  90.         public void onActivityCreated(Bundle savedInstanceState) {  
  91.             super.onActivityCreated(savedInstanceState);  
  92.             Log.e("HJJ""ArrayListFragment **** onActivityCreated...");  
  93.             String[] array = new String[]{"C++""JAVA""PYTHON"};  
  94.             setListAdapter(new ArrayAdapter<String>(getActivity(),  
  95.                     android.R.layout.simple_list_item_1, array));  
  96.         }  
  97.   
  98.         @Override  
  99.         public void onStart() {  
  100.             // TODO Auto-generated method stub  
  101.             Log.e("HJJ""ArrayListFragment **** onStart...");  
  102.             super.onStart();  
  103.         }  
  104.           
  105.         @Override  
  106.         public void onResume() {  
  107.             Log.e("HJJ""ArrayListFragment **** onResume...");  
  108.             // TODO Auto-generated method stub  
  109.             super.onResume();  
  110.         }  
  111.           
  112.         @Override  
  113.         public void onPause() {  
  114.             Log.e("HJJ""ArrayListFragment **** onPause...");  
  115.             // TODO Auto-generated method stub  
  116.             super.onPause();  
  117.         }  
  118.           
  119.         @Override  
  120.         public void onStop() {  
  121.             Log.e("HJJ""ArrayListFragment **** onStop...");  
  122.             // TODO Auto-generated method stub  
  123.             super.onStop();  
  124.         }  
  125.           
  126.         @Override  
  127.         public void onDestroyView() {  
  128.             Log.e("HJJ""ArrayListFragment **** onDestroyView...");  
  129.             // TODO Auto-generated method stub  
  130.             super.onDestroyView();  
  131.         }  
  132.           
  133.         @Override  
  134.         public void onDestroy() {  
  135.             // TODO Auto-generated method stub  
  136.             Log.e("HJJ""ArrayListFragment **** onDestroy...");  
  137.             super.onDestroy();  
  138.         }  
  139.           
  140.         @Override  
  141.         public void onDetach() {  
  142.             Log.e("HJJ""ArrayListFragment **** onDetach...");  
  143.             // TODO Auto-generated method stub  
  144.             super.onDetach();  
  145.         }  
  146.           
  147.         @Override  
  148.         public void onListItemClick(ListView l, View v, int position, long id) {  
  149.             Log.i("FragmentList""Item clicked: " + id);  
  150.         }  
  151.     }  
  152. }  

5、測試結果

[java]  view plain  copy
  1. onCreate過程  
  2. 01-22 15:30:28.091: E/HJJ(10315): Activity &&&& onCreate...  
  3. 01-22 15:30:28.091: E/HJJ(10315): ArrayListFragment **** onAttach...  
  4. 01-22 15:30:28.091: E/HJJ(10315): ArrayListFragment **** onCreate...  
  5. 01-22 15:30:28.115: E/HJJ(10315): ArrayListFragment **** onCreateView...  
  6. 01-22 15:30:28.123: E/HJJ(10315): ArrayListFragment **** onActivityCreated...  
  7.   
  8. onStart過程  
  9. 01-22 15:30:28.123: E/HJJ(10315): Activity &&&& onStart...  
  10. 01-22 15:30:28.123: E/HJJ(10315): ArrayListFragment **** onStart...  
  11.   
  12. onResume過程  
  13. 01-22 15:30:28.123: E/HJJ(10315): Activity &&&& onResume...  
  14. 01-22 15:30:28.123: E/HJJ(10315): ArrayListFragment **** onResume...  
  15.   
  16. onPause過程  
  17. 01-22 15:31:26.748: E/HJJ(10315): ArrayListFragment **** onPause...  
  18. 01-22 15:31:26.748: E/HJJ(10315): Activity &&&& onPause...  
  19.   
  20. onStop過程  
  21. 01-22 15:31:27.638: E/HJJ(10315): ArrayListFragment **** onStop...  
  22. 01-22 15:31:27.638: E/HJJ(10315): Activity &&&& onStop...  
  23.   
  24. onStart過程  
  25. 01-22 15:31:57.537: E/HJJ(10315): Activity &&&& onStart...  
  26. 01-22 15:31:57.537: E/HJJ(10315): ArrayListFragment **** onStart...  
  27.   
  28. onResume過程  
  29. 01-22 15:31:57.537: E/HJJ(10315): Activity &&&& onResume...  
  30. 01-22 15:31:57.537: E/HJJ(10315): ArrayListFragment **** onResume...  
  31.   
  32. onPause過程  
  33. 01-22 15:32:47.412: E/HJJ(10315): ArrayListFragment **** onPause...  
  34. 01-22 15:32:47.412: E/HJJ(10315): Activity &&&& onPause...  
  35.   
  36. onStop過程  
  37. 01-22 15:32:47.865: E/HJJ(10315): ArrayListFragment **** onStop...  
  38. 01-22 15:32:47.865: E/HJJ(10315): Activity &&&& onStop...  
  39.   
  40. onDestroy過程  
  41. 01-22 15:32:47.865: E/HJJ(10315): ArrayListFragment **** onDestroyView...  
  42. 01-22 15:32:47.865: E/HJJ(10315): ArrayListFragment **** onDestroy...  
  43. 01-22 15:32:47.865: E/HJJ(10315): ArrayListFragment **** onDetach...  
  44. 01-22 15:32:47.865: E/HJJ(10315): Activity &&&& onDestroy...  

2. Acitivty的四中啓動模式與特色。正則表達式

一:Standard的啓動模式

Standard是默認的模式每開始一個activity,就會在棧中加一個activity,相同的也會加,算法

因此加多少個,就要按多少次返回鍵才能回到最初的界面sql

二:singleTop的啓動模式

1.清單配置:

<activity

android:name="com.itcode.taskstack.SecondActivity"

android:label="@string/_second"

android:launchMode="singleTop">

</activity>

Singletop:若是任務棧的棧頂已經存在這個activity的實例,

不會建立新的activity,而是利用舊的activity實例

調用 舊的activity的onNewIntent()方法

2.做用:

避免一個糟糕的用戶體驗,若是這個界面已經被打開且在任務棧的棧頂,就不會重複開啓了

 

三:Singletask的啓動模式:

1.Androidfest配置:

<activity

android:name="com.itcode.taskstack.SecondActivity"

android:label="@string/_second"

android:launchMode="singleTask">

</activity>

2.做用:

singletask的啓動模式:在任務棧裏面只容許一個實例存在,假如02是singletask,

棧裏是:01 02 01 03 若此時開啓02,則會複用這個已經存在的activity,而且把當前activity上面其餘的activity從任務棧裏清空!

至關於條用 onNewIntent+ClearTop

1. 設置了"singleTask"啓動模式的Activity,它在啓動的時候,會先在系統中查找屬性值affinity等於它的屬性值taskAffinity的任務存在;若是存在這樣的任務,它就會在這個任務中啓動,不然就會在新任務中啓動。所以,若是咱們想要設置了"singleTask"啓動模式的Activity在新的任務中啓動,就要爲它設置一個獨立的taskAffinity屬性值。

        2. 若是設置了"singleTask"啓動模式的Activity不是在新的任務中啓動時,它會在已有的任務中查看是否已經存在相應的Activity實例,若是存在,就會把位於這個Activity實例上面的Activity所有結束掉,即最終這個Activity實例會位於任務的堆棧頂端中。


3.應用場景:

瀏覽器:底層使用的是webkit c 內核,初始化一次須要申請不少的內存資源,佔用cpu時間

因此使用singletask,保證在任務棧裏只會有一個實例存在

四:singleInstance的啓動模式(至關於實例):

 1.Androidfest的配置:

<activity

android:name="com.itcode.taskstack.SecondActivity"

android:label="@string/_second"

android:launchMode="singleInstance">

</activity>

2.特色:

singleInstance的啓動模式更加極端,

開啓新的activity,會給本身建立一個單獨的任務棧

無論是從應用內部打開仍是經過其餘應用調用

TaskId是單獨的,已存在的則只需調用onNewIntent

 3.應用場景:

在整個手機操做系統裏面只會有一個該activity的實例存在,

有道詞典,金山詞典

因此多個應用程序共享這個activity的實例,有線程安全問題!

例如鬧鈴提醒,將鬧鈴提醒與鬧鈴設置分離





Affinity(吸引力)和新任務

默認狀況下,一個應用程序中的activity相互之間會有一種Affinity──也就是說,它們首選都歸屬於一個任務。然而,能夠在<activity>元素中把每一個activitytaskAffinity屬性設置爲一個獨立的affinity。因而在不一樣的應用程序中定義的activity能夠享有同一個affinity,或者在同一個應用程序中定義的activity有着不一樣的affinityaffinity在兩種狀況下生效:當加載activityIntent對象包含了FLAG_ACTIVITY_NEW_TASK 標記,或者當activityallowTaskReparenting屬性設置爲「true」

FLAG_ACTIVITY_NEW_TASK標記

如前所述,在默認狀況下,一個新activity被另一個調用了startActivity()方法的activity載入了任務之中。並壓入了調用者所在的堆棧。然而,若是傳遞給startActivity()Intent對象包含了FLAG_ACTIVITY_NEW_TASK標記,系統會爲新activity安排另一個任務。通常狀況下,如同標記所暗示的那樣,這會是一個新任務。然而,這並非必然的。若是已經存在了一個與新activity有着一樣affinity的任務,則activity會載入那個任務之中。若是沒有,則啓用新任務。

allowTaskReparenting 屬性

若是一個activityallowTaskReparenting屬性設置爲「true」。它就能夠從初始的任務中轉移到與其擁有同一個affinity並轉向前臺的任務之中。好比說,一個旅行應用程序中包含的預報所選城市的天氣狀況的activity。它與這個應用程序中其它的activity擁有一樣的affinity(默認的affinity)並且容許重定父級。你的另外一個activity啓動了天氣預報,因而它就會與這個activity共處與同一任務之中。然而,當那個旅行應用程序再次回到前臺的時候,這個天氣預報activity就會被再次安排到原先的任務之中並顯示出來。

若是在用戶的角度看來,一個.apk文件中包含了多於一個的應用程序,你可能會想要爲它們所轄的activity安排不同的affinity

加載模式

<activity>元素的launchMode屬性能夠設置四種不一樣的加載模式:

"standard(默認值)
"singleTop
"singleTask
"singleInstance"

這些模式之間的差別主要體如今四個方面:

· 哪一個任務會把持對intent作出響應的activitystandardsingleTop模式而言,是產生intent(並調用 startActivity())的任務──除非Intent對象包含FLAG_ACTIVITY_NEW_TASK標記。而在這種狀況下,如同上面Affinitie和新任務一節所述,會是另一個任務。 

相反,對singleTasksingleInstance模式而言,activity老是位於任務的根部。正是它們定義了一個任務,因此它們毫不會被載入到其它任務之中。

· activity是否能夠存在多個實例。一個standardsingleTopactivity能夠被屢次初始化。它們能夠歸屬於多個任務,而一個任務也能夠擁有同一activity的多個實例。

相反,對singleTasksingleInstanceactivity被限定於只能有一個實例。由於這些activity都是任務的起源,這種限制意味着在一個設備中同一時間只容許存在一個任務的實例。

· 在實例所在的任務中是否會有別的activity一個singleInstance模式的activity將會是它所在的任務中惟一的activity。若是它啓動了別的activity,那個activity將會依據它本身的加載模式加載到其它的任務中去──如同在intent中設置了FLAG_ACTIVITY_NEW_TASK 標記同樣的效果。在其它方面,singleInstance模式的效果與singleTask是同樣的。

剩下的三種模式容許一個任務中出現多個activitysingleTask模式的activity將是任務的根activity,但它能夠啓動別的activity並將它們置入所在的任務中。standardsingleTop」activity則能夠在堆棧的任意位置出現。

· 是否要載入新的類實例以處理新的intent對默認的"standard"模式來講,對於每一個新intent都會建立一個新的實例以進行響應,每一個實例僅處理一個intentsingleTop模式下,若是activity位於目的任務堆棧的最上面,則重用目前現存的activity來處理新的intent。若是它不是在堆棧頂部,則不會發生重用。而是建立一個新實例來處理新的intent並將其推入堆棧。 

舉例來講,假設一個任務的堆棧由根activityAactivity BC和位於堆棧頂部的D組成,即堆棧A-B-C-D。一個針對D類型的activityintent抵達的時候,若是D是默認的standard加載模式,則建立並加載一個新的類實例,因而堆棧變爲A-B-C-D-D。 然而,若是D的載入模式爲singleTop,則現有的實例會對新intent進行處理(由於它位於堆棧頂部)而堆棧保持A-B-C-D的形態。

換言之,若是新抵達的intent是針對B類型的activity,則不管B的模式是standard仍是singleTop」 ,都會加載一個新的B的實例(由於B不位於堆棧的頂部),而堆棧的順序變爲A-B-C-D-B

如前所述,singleTasksingleInstance模式的activity永遠不會存在多於一個實例。因此實例將處理全部新的intent。一個singleInstance模式的activity永遠保持在堆棧的頂部(由於它是那個堆棧中惟一的一個activity),因此它一直堅守在處理intent的崗位上。然而,對一個singleTask模式的activity來講,它上面可能有,也可能沒有別的activity和它處於同一堆棧。在有的狀況下,它就不在可以處理intent的位置上,則那個intent將被捨棄。(即使在intent被捨棄的狀況下,它的抵達仍將使這個任務切換至前臺,並一直保留)


3. Activity緩存方法。

Activity的onSaveInstanceState()和 onRestoreInstanceState()方法  

2013-01-28 22:08:13|  分類: Android |  標籤:activity組件  |舉報|字號 訂閱

     Activity的 onSaveInstanceState() 和 onRestoreInstanceState()並非生命週期方法,它們不一樣於 onCreate()onPause()等生命週期方法,它們並不必定會被觸發。

     當應用遇到意外狀況(如:內存不足)由系統銷燬一個Activity時,onSaveInstanceState() 會被調用。可是當用戶主動去銷燬一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被調用。由於在這種狀況下,用戶的行爲決定了不須要保存Activity的狀態。一般onSaveInstanceState()只適合用於保存一些臨時性的狀態onPause()適合用於數據的持久化保存

    另外,當屏幕的方向發生了改變 Activity會被摧毀而且被從新建立,若是你想在Activity摧毀前緩存一些數據,而且在Activity被從新建立後恢復緩存的數據。能夠重寫Activity的 onSaveInstanceState() onRestoreInstanceState()方法.

    注意:在Activity的onCreate(Bundle savedInstanceState)方法裏面,該方法的參數與onRestoreInstanceState()方法中的參數一致,所以在onCreate()方法中也能恢復緩存的數據。


    以下:

public class PreferencesActivity extends Activity {
private String name;
protected void onRestoreInstanceState(Bundle savedInstanceState) {
name = savedInstanceState.getString("name"); //被從新建立後恢復緩存的數據
super.onRestoreInstanceState(savedInstanceState);
}
protected void onSaveInstanceState(Bundle outState) {
outState.putString("name", "liming");//被摧毀前緩存一些數據
super.onSaveInstanceState(outState);
}
}


4. Service的生命週期,兩種啓動方法,有什麼區別。

一、Activity的生命週期

情形1、一個單獨的Activity的正常的生命過程是這樣的:onCreate->onStart->onPause->onStop->onDestroy。例如:運行一個Activity,進行了一些簡單操做(不涉及頁面的跳轉等),而後按返回鍵結束。

 

情形2、有兩個Activity(a和b),一開始顯示a,而後由a啓動b,而後在由b回到a,這時候a的生命過程應該是怎麼樣的呢(a被b徹底遮蓋)?

a經歷的過程爲onCreate->onStart->onResume->onPause->onStop->onRestart->onStart->onResume。這個過程說明了圖中,若是Activity徹底被其餘界面遮擋時,進入後臺,並無徹底銷燬,而是停留在onStop狀態,當再次進入a時,onRestart->onStart->onResume,又從新恢復。

 

情形3、基本情形同二同樣,不過此時a被b部分遮蓋(好比給b添加個對話框主題 android:theme="@android:style/Theme.Dialog"

a經歷的過程是:onCreate->onStart->onResume->onPause->onResume

因此當Activity被部分遮擋時,Activity進入onPause,並無進入onStop,從Activity2返回後,執行了onResume

 

情形4、 打開程序,啓動a,點擊a,啓動AlertDialog,按返回鍵從AlertDialog返回。

a經歷的過程是:onCreate->onStart->onResume

當啓動和退出Dialog時,Activity的狀態始終未變,可見,Dialog實際上屬於Acitivity內部的界面,不會影響Acitivty的生命週期。

 

二、Service的生命週期

使用context.startService() 啓動Service

其生命週期爲context.startService() ->onCreate()- >onStart()->Service running-->(若是調用context.stopService() )->onDestroy() ->Service shut down

若是Service尚未運行,則android先調用onCreate()而後調用onStart();
若是Service已經運行,則只調用onStart(),因此一個Service的onStart方法可能會重複調用屢次。 

調用stopService的時候直接onDestroy,
若是是調用者本身直接退出而沒有調用stopService的話,Service會一直在後臺運行。
該Service的調用者再啓動起來後能夠經過stopService關閉Service。

因此調用startService的生命週期爲:onCreate --> onStart(可屢次調用) --> onDestroy

對於bindService()啓動Service會經歷:
context.bindService()->onCreate()->onBind()->Service running-->onUnbind() -> onDestroy() ->Service stop

onBind將返回給客戶端一個IBind接口實例,IBind容許客戶端回調服務的方法,好比獲得Service運行的狀態或其餘操做。
這個時候把調用者(Context,例如Activity)會和Service綁定在一塊兒,Context退出了,
Srevice就會調用onUnbind->onDestroy相應退出。 

因此調用bindService的生命週期爲:onCreate --> onBind(只一次,不可屢次綁定) --> onUnbind --> onDestory。
一但銷燬activity它就結束,若是按home把它放到後臺,那他就不退出。

PS:
在Service每一次的開啓關閉過程當中,只有onStart可被屢次調用(經過屢次startService調用),
其餘onCreate,onBind,onUnbind,onDestory在一個生命週期中只能被調用一次。


5. 怎麼保證service不被殺死。

android應用多線程守護讓你很難殺死它

public class TestserviceActivity extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

System.out.println("activity運行的線程id:"+ Thread.currentThread().getName() +"--"+

Thread.currentThread().getId());

System.out.println("activity的進程id:"+android.os.Process.myPid());

Intent intent = new Intent(this,Service1.class);

startService(intent);

}

}

public class Service1 extends Service {

@Override

public IBinder onBind(Intent intent) {

// TODO Auto-generated method stub

return null;

}

@Override

public void onCreate() {

// TODO Auto-generated method stub

System.out.println("服務1 被開啓");

System.out.println("服務運行的線程id:"+ Thread.currentThread().getName() +"--"+

Thread.currentThread().getId());

System.out.println("服務的進程id:"+android.os.Process.myPid());

super.onCreate();

}

@Override

public void onDestroy() {

Intent intent = new Intent(this,Service2.class);

startService(intent);

super.onDestroy();

}

}

public class Service2 extends Service {

@Override

public IBinder onBind(Intent intent) {

// TODO Auto-generated method stub

return null;

}

@Override

public void onCreate() {

System.out.println("服務2 被開啓");

super.onCreate();

}

@Override

public void onDestroy() {

Intent intent = new Intent(this,Service1.class);

startService(intent);

super.onDestroy();

}

}

四、清單文件中

<service android:name=".Service1"

android:process="cn.it.yqq"

></service>

<service android:name=".Service2"></service>


http://blog.csdn.net/mad1989/article/details/22492519

6. 廣播的兩種註冊方法,有什麼區別。

 以前一直碰到這個問題,都沒有證實回答,如今總結以下:

    經過在配置文件裏面註冊廣播屬於常駐型廣播,意思就是即使你應用程序結束,一旦有了對應的廣播過來,其仍是會被激活;而在代碼裏註冊的廣播則是很是駐型廣播,好比在oncreate方法裏面註冊一個廣播,那麼在ondestroy裏就能夠BroadcastReceiver.abortBroadcast(),好比註冊一個監聽SD卡的廣播。

 

以前有一個概念常常混淆,就是發送廣播sendBroadCast或sendOrderBroadCast是要發送本身定義的廣播時,纔用到的,若是是系統的廣播,根本不須要用到它們,系統的廣播,咱們只須要註冊廣播監聽,如定義intent_filter監聽就能夠了,因此abortBroadcast終止廣播就更用不到了。

 

     1.常駐型廣播
  常駐型廣播,當你的應用程序關閉了,若是有廣播信息來,你寫的廣播接收器一樣的能接受到,
  他的註冊方式就是在你的應用程序中的AndroidManifast.xml進行註冊。一般說這種方式是靜態註冊
  下面是配置例子

Java代碼  複製代碼  收藏代碼
  1.  <!-- 桌面 -->   
  2. <receiver android:name=".widget.DeskWidgeWeather">   
  3. <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_weather_provider" />   
  4. <intent-filter>   
  5.  <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>   
  6.  <action android:name="action_weather"/>   
  7. </intent-filter>   
  8. lt;/receiver>  

 

  2.很是駐型廣播
   當應用程序結束了,廣播天然就沒有了,好比你在activity中的onCreate或者onResume中註冊廣播接收器
   在onDestory中卸載廣播接收器。這樣你的廣播接收器就一個很是駐型的了。這種也叫動態註冊。
   好比寫一個監聽SDcard狀態的廣播接收器

Java代碼  複製代碼  收藏代碼
  1.   SdcardStateChanageReceiver sdcardStateReceiver;   
  2. @Override  
  3. protected void onCreate(Bundle savedInstanceState)   
  4. {   
  5.  super.onCreate(savedInstanceState);   
  6.  IntentFilter filter new IntentFilter();   
  7.  filter.addAction(Intent.ACTION_MEDIA_REMOVED);   
  8.  filter.addAction(Intent.ACTION_MEDIA_EJECT);   
  9.  filter.addAction(Intent.ACTION_MEDIA_MOUNTED);   
  10.  filter.addDataScheme("file");   
  11.  sdcardStateReceiver new SdcardStateChanageReceiver();    
  12.  registerReceiver(sdcardStateReceiver,filter);   
  13. }   
  14. @Override  
  15. protected void onDestroy(){   
  16.  unregisterReceiver(sdcardStateReceiver);   
  17. }   
  18. class SdcardStateChanageReceiver  extends BroadcastReceiver{   
  19.   
  20.  @Override  
  21.  public void onReceive(Context context, Intent intent)   
  22.  {   
  23.   String state=android.os.Environment.getExternalStorageState();   
  24.   System.out.println("SDCard 發生改變! 狀態:"+state);   
  25.   //checkSDCard();   
  26.  }   
  27.  public void checkSDCard(){   
  28.   String state=android.os.Environment.getExternalStorageState();   
  29.   System.out.println(state);   
  30.   if(state.equals(android.os.Environment.MEDIA_REMOVED || state .equals(android.os.Environment.MEDIA_UNMOUNTED)){   
  31.    System.out.println("SDCard 已卸載!");   
  32.   }   
  33.  }   
  34. }   

BroadcastReceiver用於監聽被廣播的事件

必須被註冊,有兩種方法:

一、在應用程序的代碼中註冊

註冊BroadcastReceiver:

registerReceiver(receiver,filter);

取消註冊BroadcastReceiver:

unregisterReceiver(receiver);

當BroadcastReceiver更新UI,一般會使用這樣的方法註冊。啓動Activity時候註冊BroadcastReceiver,Activity不可見時候,取消註冊。

二、在androidmanifest.xml當中註冊

<receiver>

    <intent-filter>

     <action android:name = "android.intent.action.PICK"/>

    </intent-filter>

</receiver>

1)第一種不是常駐型廣播,也就是說廣播跟隨程序的生命週期。

2)第二種是常駐型,也就是說當應用程序關閉後,若是有信息廣播來,程序也會被系統調用自動運行。


使用這樣的方法註冊弊端:它會始終處於活動狀態,畢竟是手機開發,cpu和電源資源比較少,一直處於活動耗費大,不利。



7. Intent的使用方法,能夠傳遞哪些數據類型。


1.Intent的實現過程

  在Android中,Intent不只可用於應用程序之間的交互,也可用於應用程序內部的Activity/Service之間的交互。

  Intent負責對應用中一次操做進行描述,描述內容包括動做以及動做所涉及的數據,Android中的Intent機制則根據此描述,找到對應的組件,將Intent傳遞給該被調用組件,完成對組件的一次調用。

  這即是Intent的實現過程,可見,在Intent中提供了組件互相調用的相關信息,實現了調用者與被調用者之間的解耦。

2.Intent的應用場合

  概括起來,Intent的應用場合主要有如下三種:

2.1啓動一個Activity

  (1)Activity.startActivity(Intent intent);  //啓動一個Activity

  (2)Activity.startActivityForResult(Intent intent, int requestCode);  //啓動一個帶請求碼的Activity,當該Activity結束時將回調原Activity的onActivityResult()方法,並返回一個結果碼。

2.2啓動一個Service

  (1)Context.startService(Intent service); 

  (2)Context.bindService(Intent service, ServiceConnection conn, int flags); 
2.3啓動一個Broadcast

  (1)sendBroadcast(Intent intent); 
    sendBroadcastAsUser(Intent intent, UserHandle user); 

  (2)sendStickyBroadcast(Intent intent); 
         sendStickyBroadcastAsUser(Intent intent, UserHandle user); 

  (3)sendOrderedBroadcast(Intent intent, String receiverPermission); 
      sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
BroadcastReceiver resultReceiver,Handler scheduler, int initialCode, String initialData, Bundle initialExtras); 

3.Intent屬性設置

  Intent的屬性設置包括:Action(要執行的動做)、Data(執行動做所操做的數據)、Type(顯式的指定Intent的數據類型)、Category(執行動做的附加信息)、Component(指定Intent目標組件的類名稱)、Extras(其它全部附加信息的集合)。    

3.1 Action(要執行的動做)

  在SDK中定義了一系列標準動做,其中的一部分如圖1所示。



圖1 部分標準動做


  其中,ACTION_CALL表示調用撥打電話的應用;ACTION_EDIT表示調用編輯器;ACTION_SYNC表示同步數據。

3.2 Data(執行動做所操做的數據)

  在Intent中,Data使用指向數據的URI來表示。好比,在聯繫人應用中,指向聯繫人列表的URI是content://contacts/people/。

3.3 Type(顯式的指定Intent的數據類型)

  對於不一樣的動做,其URI數據的類型是不一樣的。

  一般,Intent的數據類型可以根據其數據自己進行斷定,可是經過設置這個屬性,能夠強制採用顯式指定的類型。

3.4 Category(執行動做的附加信息)

  Category表示執行動做的附加信息。好比,當咱們想要讓所執行的動做被接收後,做爲頂級應用而位於其餘全部應用的最上層,並能夠使用附加信息LAUNCHER_CATEGORY來實現。 

3.5 Component(指定Intent目標組件的類名稱)

  Component用於指定Intent目標組件的類名稱。一般,Android會根據Intent 中所包含的其它屬性信息(好比Action、Data/Type、Category)進行查找,並找到一個與之匹配的目標組件。可是,若是咱們設置了Component屬性,明確的指定了Intent目標組件的類名稱,那麼上述查找過程將不須要執行。 

3.6 Extras(其它全部附加信息的集合)

  使用extras能夠爲組件提供擴展信息。

4.Intent解析過程

  在使用Intent時,根據是否明確的指定Intent對象的接收者,而分爲兩種狀況。一種是顯式的Intent,即在構造Intent對象時就指定其接收者;另外一種是隱式的Intent,即在構造Intent對象時,並不指定其接收者。

  對於顯式的Intent來講,Android不須要解析Intent,由於目標組件已經很明確。對於隱式的Intent來講,Android須要對其進行解析,經過解析,將Intent映射給能夠處理該Intent的Activity、Service或Broadcast。        

  Intent解析機制是經過查找註冊在AndroidManifest.xml文件中的全部IntentFilter,以及IntentFilter所定義的Intent,找到最匹配的Intent。

  在解析過程當中,Android經過判斷Intent的Action、Type、Category這三個屬性,從而找出最匹配的Intent,具體的判斷方法以下:

  (1)若是Intent指明瞭Action,則目標組件IntentFilter的Action列表中就必須包含有這個Action,不然不能匹配;

  (2)若是Intent沒有提供Type,系統將從Data中獲得數據類型。目標組件的數據類型列表中必須包含Intent的數據類型,不然不能匹配。

  (3)若是Intent中的數據不是content: URI,並且Intent也沒有明確指定它的Type,將根據Intent中數據的scheme (好比 http: 或者mailto:) 進行匹配。同理,Intent 的scheme必須出如今目標組件的scheme列表中,不然不能匹配。

  (4)若是Intent指定了一個或多個Category,這些類別必須所有出如今目標組件的類別列表中,不然不能匹配。

5.Intent使用實例

  下面介紹幾個使用Intent的實例。

5.1調用其餘的應用

  經過Intent能夠調用並啓動別的應用程序,好比調用撥打電話的程序,即可以使用以下的方法來完成:

  1. /*
  2.   * Function :  調用撥打電話的程序
  3.   * Author   :  博客園-依舊淡然
  4.   */
  5.   public void intentDemo_Call() {
  6.       Intent intent_call = new Intent();                //建立一個意圖
  7.       intent_call.setAction(Intent.ACTION_CALL);        //設置意圖爲打電話
  8.       intent_call.setData(Uri.parse("tel:110"));        //設置電話號碼
  9.       startActivity(intent_call);                       //啓動意圖
  10.   }
複製代碼


固然了,由於這裏使用到了打電話的功能,咱們還須要在AndroidManifest.xml文件中,添加申請打電話的資源權限,具體實現方法以下:

  1.  <!-- 打電話的權限 -->
  2. 2     <uses-permission 
  3.       android:name="android.permission.CALL_PHONE"    />
複製代碼

有關Android中的權限請求能夠參閱《 Android數據手冊02:android.permission權限請求彙總 》。
5.2跳轉到另外一個Activity

  經過使用Intent不只能夠調用別的應用程序,還能夠實現應用程序內部之間Activity的跳轉。好比以下的代碼便實現了從MainActivity跳轉到SecondaryActivity,並向SecondaryActivity中傳遞了兩個數據name和age。

  1. /*
  2.       * Function  :  跳轉到SecondaryActivity 
  3.       * Author    :  博客園-依舊淡然
  4.       */
  5.      public void intentDemo_GoToSecondaryActivity() {
  6.          Intent intent_toSecondary = new Intent();                      //建立一個意圖
  7.          intent_toSecondary.setClass(this, SecondaryActivity.class);    //指定跳轉到SecondaryActivity
  8.          intent_toSecondary.putExtra("name", "jack");                   //設置傳遞內容name
  9.          intent_toSecondary.putExtra("age", 23);                        //設置傳遞內容age
  10.          startActivity(intent_toSecondary);                             //啓動意圖
  11.      }
複製代碼

 那麼,如何在SecondaryActivity中接收從MainActivity中傳過來的內容(name、age)呢?下面的代碼給出了一種實現方案。

  1. /*
  2.       * Function  :  接收mainActivity中的intent_toSecondary
  3.       * Author    :  博客園-依舊淡然
  4.       */
  5.      public void acceptIntent() {
  6.          Intent intent_accept = getIntent();           //建立一個接收意圖
  7.          Bundle bundle = intent_accept.getExtras();    //建立Bundle對象,用於接收Intent數據
  8.          String name = bundle.getString("name");       //獲取Intent的內容name
  9.          int age = bundle.getInt("age");               //獲取Intent的內容age
  10.      }
複製代碼

5.3發送一個帶回調方法的意圖

  有時,咱們可能須要經過定義在MainActivity中的某一控件來啓動SecondaryActivity,而且當SecondaryActivity結束時,返給MainActivity一個執行結果。

  要實現上述的功能,只須要完成如下三步驟便可。


  (1)在MainActivity中實現向SecondaryActivity發送帶請求碼的意圖,具體實現方法以下:


  1. /*
  2.       * Function  :  向SecondaryActivity發送帶請求碼的意圖
  3.       * Author    :  博客園-依舊淡然
  4.       */
  5.      public void intentDemo_request() {
  6.          Intent intent_request = new Intent();                      //建立一個意圖
  7.          intent_request.setClass(this, SecondaryActivity.class);    //指定跳轉到SecondaryActivity
  8.          startActivityForResult(intent_request, REQUEST_CODE);      //啓動帶請求碼意圖
  9.      }
複製代碼


(2)在SecondaryActivity中接收intent_request,並向意圖中填充要返給MainActivity的內容,最後還須要設置一個返回碼。具體的實現方法以下:


  1. /*
  2.       * Function  :  接收mainActivity中的intent_request並返回一個結果碼
  3.       * Author    :  博客園-依舊淡然 
  4.       */
  5.      public void acceptIntentAndReturn() {
  6.          Intent intent = getIntent();                           //建立一個接收意圖
  7.          intent.putExtra("back", "data of SecondaryActivity");  //設置意圖的內容
  8.          setResult(RESULT_CODE, intent);                        //設置結果碼
  9.          finish();                                              //結束SecondaryActivity,並返回MainActivity
  10.      }
複製代碼
3)當結束SecondaryActivity時,程序將返回到MainActivity界面。此時,MainActivity中的onActivityResult()方法將被回調,而咱們要作的就是實現該方法。在本示例中,該方法的具體實現方法以下:

  1. /*
  2.       * Function  :  onActivityResult回調方法
  3.       * Author    :  博客園-依舊淡然
  4.       */
  5.      protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  6.          if(requestCode == REQUEST_CODE && resultCode == SecondaryActivity.RESULT_CODE) {
  7.              Bundle bundle = data.getExtras();
  8.              String str = bundle.getString("back");
  9.              Toast.makeText(this, "從SecondaryActivity的返回值爲:" + str, Toast.LENGTH_LONG).show();
  10.          }
  11.      }
複製代碼

 以上的代碼,咱們經過判斷requestCode和resultCode即可以惟一的肯定MainActivity中的調用者和SecondaryActivity中的被調用者,創建起了一一對應的關係。而且,咱們經過Bundle對象獲取了從SecondaryActivity中返回給MainActivity的內容,並經過Toast進行了輸出,如圖2所示。

圖2 從SecondaryActivity中返回的內容


  由圖2可見,從SecondaryActivity中返回的內容正是咱們在SecondaryActivity中定義的字符串「data of SecondaryActivity」。


6.總結

  本篇博文主要介紹了Intent的相關概念,以及Intent在Activity中的三種使用方法。有關Intent在Service和Broadcast中的使用方法,將在後續的Service和Broadcast的學習過程當中再作介紹。

原文連接:http://www.cnblogs.com/menlsh/archive/2013/03/30/2991111.html


8. ContentProvider使用方法。

Content Provider爲存儲數據和獲取數據提供了統一的接口,它能夠完成在不一樣應用程序下的數據共享,而在上一篇文章Android開發之SQLite的使用方法講到的SQLite只能在同一個程序中共享數據。另外android爲一些常見的數據,好比說音頻,視頻,圖片,通信錄等提供了Content Provider,這樣咱們就能夠很方便的對這些類型的數據操做了。使用ContentProvider的好處是開發人員不須要考慮數據內部是怎麼存儲的,好比說若是咱們想利用ContenProvider來存數據,只需告訴insert函數該ContentProvider的uri和想存入的數據(包括列名和數值),查詢時也是同樣,只需輸入Uri和查詢的表,列名和查詢條件,至於ContentProvider裏面是怎麼進行這些操做的咱們不須要知道。

  實驗基礎

  在瞭解本實驗的內容,須要用到下面這幾個跟ContentProvider有關的類。

  UriMatcher:

  要了解UriMatcher,首先須要瞭解android中的Uri表示方法,衆所周知,Uri爲通用資源標識符,它表明的是要操做的數據,Android中的每一種資源(好比文本,圖像,視頻等)均可以用Uri來表示。Android中的Uri由如下三部分組成:」content://」(即authory),數據的路徑,資源標識ID(可選),其中若是存在ID,則表示某一個具體的資源,若是不存在ID,則表示路徑下的總體。所以addUri()函數的3個參數也是對應上面的那3個。

  UriMatcher的匹配過程分爲3步,初始化UriMatcher;註冊須要用的Uri;與已經註冊的Uri進行匹配。

  ContentResolver :

  當使用ContentProvider在不一樣的應用程序中共享數據時,其數據的暴露方式是採起相似數據庫中表的方法。而ContentResolver 是剛好是採用相似數據庫的方法來從ContentProvider中存取數據的,它是經過Uri來查詢ContentProvider中提供的數據,查詢時,還需知道目的數據庫的名稱,數據段的數據類型,或者說資源的ID。

  SQLiteQueryBuilder:

  SQLiteQueryBuilder是一個用來生產SQL查詢語句的輔助類,能夠方便的去訪問SQLiteDatabase. 在構造SQL查詢語句時,它一樣也須要指定表名,指定列名,指定where條件等。

  實驗過程

  本文經過一個實例來實現一個ContentProvider,其實通常狀況下咱們是不須要本身來實現的,而是使用andorid內置的ContentProvider,可是本身實現一個之後,對它的原理能更深入的認識,之後使用內置的就駕輕就熟了。這是mars老師的話,本人火候不夠,暫時還沒深入的體會。Mars老師將實現ContentProvider的步驟總結爲以下:

  


 

 

  程序中須要4個java文件,下面就分別來介紹實現這些類需注意的事項:

  FirstProviderMetaData類:

  由於在繼承類FirstContentProvider獲得的子類中要用到不少常量,因此這裏咱們新建了一個類專門用來存儲這些常量的,該類這裏取名爲FirstProviderMetaData,裏面存着authorty名,數據庫名,數據庫版本號,表名,字表名,子表Uri,子表ContentProvider數據類型等等。其中字表是繼承BaseColumns類的,而BaseColumns類中已經有_ID和_COUNT這2列了。

  DatabaseHelper類:

  與android中使用SQLite相似,這裏一樣須要一個繼承SQLiteOpenHelper的子類,子類名爲DatabaseHelper,咱們在子類的回調函數onCreate()中創建了一個表,表名和表中的列名都是引用FirstProviderMetaData類中定義好了的常量的。

  FirstContentProvider類:

  新建一個類,名爲FirstContentProvider,繼承類ContentProvider這個類,且必須重寫父類的下面5個方法,不然會報錯。這5個方法分別爲onCreate(), getType(), insert(), update(), delete().

  onCreate()爲回調函數,是指當ContentProvider建立的時候調用,本程序在該函數中使用DatabaseHelper新建了一個SQLite數據庫。

  在getType()方法完成的功能是根據傳入的Uri,返回該Uri所表示的數據類型。函數內部是使用的UriMatcher來匹配該函數所傳進來的參數,來獲得其數據類型。

  insert()函數給指定的數據庫表中插入一個指定的值,插入完成後必然會生成一個新的記錄,而後利用該記錄和表的Uri從新生成一個新的Uri,這個Uri就表明了插入成功的那條記錄的Uri,該函數返回的也是這個Uri。

  MainActivity類:

  在MainActivity中,主要是有2個按鈕,分爲爲它們綁定了監聽器,來完成插入和查詢操做。

  當單擊插入按鈕時,在監聽器函數中會首先獲得一個ContentResolver,而後當在執行ContentResolver的insert方法時會自動調用ContentProvider的insert方法,由於ContentResolver的insert方法中的第一個參數就爲某個ContentProvider的Uri。

  AndroidManifest.xml:

  ContentProvider的使用須要在AndroidManifest.xml中進行註冊,在activity標籤外加入以下聲明便可:

<provider android:name="com.example.cptest.FirstContentProvider" android:authorities="com.example.cptest.FirstContentProvider" />

  實驗主要部分代碼及註釋:

MainActivity.java:

複製代碼 代碼以下:

package com.example.cptest;

//import com.example.cptest.FirstProviderMetaData;
import com.example.cptest.FirstProviderMetaData.UserTableMetaData;

import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

    private Button insert = null;
    private Button query = null;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        insert = (Button)findViewById(R.id.insert);
        insert.setOnClickListener(new InsertOnClickListener());
        query = (Button)findViewById(R.id.query);
        query.setOnClickListener(new QueryOnClickListener());
        System.out.println(getContentResolver().getType(FirstProviderMetaData.UserTableMetaData.CONTENT_URI));
    }

    //往子表中插入一條記錄
    public class InsertOnClickListener implements OnClickListener{

        public void onClick(View arg0) {
            // TODO Auto-generated method stub   
            ContentValues values = new ContentValues();
            values.put(FirstProviderMetaData.UserTableMetaData.USER_NAME, "tornadomeet");
            //實際上使用的是ContentResolver的insert方法
            //該insert中有2個參數,第一個爲表明了ContentProvider的Uri,第二個參數爲要插入的值。此處的insert函數
            //一執行,則自動調用ContentProvider的insert方法。
            Uri uri = getContentResolver().insert(FirstProviderMetaData.UserTableMetaData.CONTENT_URI,
                    values);
            System.out.println("uri--->" +uri.toString());
        }       
    }

   
    //查詢也是採用的ContentResolver中的query方法。
    public class QueryOnClickListener implements OnClickListener{
        public void onClick(View v) {
            // TODO Auto-generated method stub       
            Cursor c = getContentResolver().query(FirstProviderMetaData.UserTableMetaData.CONTENT_URI,
                    null, null, null, null);
            while(c.moveToNext())
                System.out.println(c.getString(c.getColumnIndex(UserTableMetaData.USER_NAME)));
        }    
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}


DatabaseHelper:
複製代碼 代碼以下:

package com.example.cptest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase.CursorFactory;

public class DatabaseHelper extends SQLiteOpenHelper {

    private static final int  VERSON = 1;//默認的數據庫版本

    //繼承SQLiteOpenHelper類的類必須有本身的構造函數
    //該構造函數4個參數,直接調用父類的構造函數。其中第一個參數爲該類自己;第二個參數爲數據庫的名字;
    //第3個參數是用來設置遊標對象的,這裏通常設置爲null;參數四是數據庫的版本號。
    public DatabaseHelper(Context context, String name, CursorFactory factory, int verson){
        super(context, name, factory, verson);
    }

    //該構造函數有3個參數,由於它把上面函數的第3個參數固定爲null了
    public DatabaseHelper(Context context, String name, int verson){
        this(context, name, null, verson);
    }

    //該構造函數只有2個參數,在上面函數 的基礎山將版本號固定了
    public DatabaseHelper(Context context, String name){
        this(context, name, VERSON);
    }

    //該函數在數據庫第一次被創建時調用
    @Override
    public void onCreate(SQLiteDatabase arg0) {
        // TODO Auto-generated method stub
        System.out.println("create a database");
        //execSQL()爲執行參數裏面的SQL語句,所以參數中的語句須要符合SQL語法,這裏是建立一個表
        //arg0.execSQL("create table user1(id int, name varchar(20))");下面的語句格式是與該句相似
//        arg0.execSQL("create table" + FirstProviderMetaData.USERS_TABLE_NAME
//                + "(" + FirstProviderMetaData.UserTableMetaData._ID
//                + " INTEGER PRIMARY KEY AUTOINCREMENT," +   //ID類型爲自增加的整型
//                FirstProviderMetaData.UserTableMetaData.USER_NAME + " varchar(20));"
//                );
    //    arg0.execSQL("create table user1(id int, name varchar(20))");
        arg0.execSQL("create table" + FirstProviderMetaData.USERS_TABLE_NAME + "("
                + FirstProviderMetaData.UserTableMetaData._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                +FirstProviderMetaData.UserTableMetaData.USER_NAME + " varchar(20))");
        System.out.println("create a database ok");
    }

    @Override
    public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
        // TODO Auto-generated method stub
        System.out.println("update a database");
    }

}


FirstProviderMetaData.java:
複製代碼 代碼以下:

package com.example.cptest;

import android.net.Uri;
import android.provider.BaseColumns;

public class FirstProviderMetaData {

    //這裏的AUTHORTY爲包的全名+ContentProvider子類的全名
    public static final String AUTHORTY = "com.example.cptest.FirstContentProvider";
    //數據庫的名稱
    public static final String DATABASE_NAME = "FisrtProvider.db";
    //數據庫的版本號
    public static final int DATABASE_VERSION = 1;
    //數據庫中的表名
    public static final String USERS_TABLE_NAME = "users";
    //表中的字表
    public static final class UserTableMetaData implements BaseColumns{
        //子表名
        public static final String TABLE_NAME = "users";
        //CONTENT_URI爲常量Uri; parse是將文本轉換成Uri
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORTY + "/users");
        //返回ContentProvider中表的數據類型
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.firstprovider.user";
        //返回ContentProvider表中item的數據類型
        public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.firstprovider.user";
        //子表列名
        public static final String USER_NAME = "name";
        //表中記錄的默認排序算法,這裏是降序排列
        public static final String DEFAULT_SORT_ORDER = "_id desc";

       
    }

}


FirstContentProvider.java:
複製代碼 代碼以下:

package com.example.cptest;

import java.util.HashMap;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;

import com.example.cptest.FirstProviderMetaData.UserTableMetaData;

public class FirstContentProvider extends ContentProvider {

    //定義一個UriMatcher類對象,用來匹配Uri的。
    public static final UriMatcher uriMatcher;
    //組時的ID
    public static final int INCOMING_USER_COLLECTION = 1;
    //單個時的ID
    public static final int INCOMING_USER_SIGNAL = 2;
    private DatabaseHelper dh;//定義一個DatabaseHelper對象
    static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
        uriMatcher.addURI(FirstProviderMetaData.AUTHORTY, "users", INCOMING_USER_COLLECTION);
        uriMatcher.addURI(FirstProviderMetaData.AUTHORTY, "users/#", INCOMING_USER_SIGNAL);//後面加了#表示爲單個
    }

    public static HashMap<String, String> userProjectionMap;//新建一個HashMap,後面執行插入操做時有用
    static
    {
        userProjectionMap = new HashMap<String, String>();
        //這裏能夠直接調用另一個類的public變量,這裏put裏面的2個參數同樣,
        //是由於這裏是給數據庫表中的列取別名,所以取的是同樣的名字
        userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID);
        userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);
    }

    //獲得ContentProvider的數據類型,返回的參數Uri所表明的數據類型
    @Override
    public String getType(Uri arg0) {
        // TODO Auto-generated method stub
        System.out.println("getType");
        switch(uriMatcher.match(arg0)){
        //matcher知足Uri的前2項(即協議+路徑)爲第1種狀況時,switch語句的值爲Uri的第3項,此處爲INCOMING_USER_COLLECTION
        case INCOMING_USER_COLLECTION:
            return UserTableMetaData.CONTENT_TYPE;
        case INCOMING_USER_SIGNAL://同上
            return UserTableMetaData.CONTENT_TYPE_ITEM;
        default:
            throw new IllegalArgumentException("Unknown URI" + arg0);//throw是處理異常的,java中的語法
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        System.out.println("delete");
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        // TODO Auto-generated method stub
        System.out.println("update");
        return 0;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub
        System.out.println("insert");
//        dh = new DatabaseHelper(getContext(), FirstProviderMetaData.DATABASE_NAME);
        SQLiteDatabase db = dh.getWritableDatabase();
        long rowId = db.insert(UserTableMetaData.TABLE_NAME, null, values);
//        System.out.println("insert  OK");
//        System.out.println("" + rowId);
        if(rowId > 0){           
            //發出通知給監聽器,說明數據已經改變
            //ContentUris爲工具類
            Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);
            getContext().getContentResolver().notifyChange(insertedUserUri, null);

            return insertedUserUri;
        }
        throw new SQLException("Failed to insert row into" + uri);
    }

    //回調函數,在ContentProvider建立的時候調用
    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        System.out.println("onCreate");
        dh = new DatabaseHelper(getContext(), FirstProviderMetaData.DATABASE_NAME);//建立1個DatabaseHelper對象
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        // TODO Auto-generated method stub
        System.out.println("query");
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        switch(uriMatcher.match(uri)){
        case INCOMING_USER_COLLECTION:
            qb.setTables(UserTableMetaData.TABLE_NAME);//設置表的名稱
            qb.setProjectionMap(userProjectionMap);//其中userProjectionMap爲上面創建好了的hashmap
            break;
        case INCOMING_USER_SIGNAL:
            qb.setTables(UserTableMetaData.TABLE_NAME);//設置表的名稱
            qb.setProjectionMap(userProjectionMap);//其中userProjectionMap爲上面創建好了的hashmap
            //uri.getPathSegments()獲得Path部分,即把uri的協議+authory部分去掉,把剩下的部分分段獲取,這裏取第
            //一部分
            qb.appendWhere(UserTableMetaData._ID + "=" +uri.getPathSegments().get(1));//設置where條件
            break;
        }
        //排序
        String orderBy;
        if(TextUtils.isEmpty(sortOrder)){
            orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;//傳入的排序參數爲空的時候採用默認的排序
        }
        else{
            orderBy = sortOrder;//不爲空時用指定的排序方法進行排序
        }
        SQLiteDatabase db = dh.getWritableDatabase();
        //採用傳入的參數進行查詢
        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
        //發出通知
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }

}


9. ThreadAsycTaskIntentService的使用場景與特色。

http://www.cnblogs.com/shaweng/p/4024766.html

10. 五種佈局: FrameLayout  LinearLayout  AbsoluteLayout  RelativeLayout TableLayout 各自特色及繪製效率對比。

11. Android的數據存儲形式。

本文介紹Android平臺進行數據存儲的五大方式,分別以下:   

    1 使用SharedPreferences存儲數據

    2 文件存儲數據      

    3 SQLite數據庫存儲數據

    4 使用ContentProvider存儲數據

    5 網絡存儲數據

下面詳細講解這五種方式的特色

第一種: 使用SharedPreferences存儲數據

    適用範圍保存少許的數據,且這些數據的格式很是簡單:字符串型、基本類型的值。好比應用程序的各類配置信息(如是否打開音效、是否使用震動效果、小遊戲的玩家積分等),解鎖口 令密碼等

    核心原理保存基於XML文件存儲的key-value鍵值對數據,一般用來存儲一些簡單的配置信息。經過DDMS的File Explorer面板,展開文件瀏覽樹,很明顯SharedPreferences數據老是存儲在/data/data/<package name>/shared_prefs目錄下。SharedPreferences對象自己只能獲取數據而不支持存儲和修改,存儲修改是經過SharedPreferences.edit()獲取的內部接口Editor對象實現。 SharedPreferences自己是一 個接口,程序沒法直接建立SharedPreferences實例,只能經過Context提供的getSharedPreferences(String name, int mode)方法來獲取SharedPreferences實例,該方法中name表示要操做的xml文件名,第二個參數具體以下:

                 Context.MODE_PRIVATE: 指定該SharedPreferences數據只能被本應用程序讀、寫。

                 Context.MODE_WORLD_READABLE:  指定該SharedPreferences數據能被其餘應用程序讀,但不能寫。

                 Context.MODE_WORLD_WRITEABLE:  指定該SharedPreferences數據能被其餘應用程序讀,

Editor有以下主要重要方法:

                 SharedPreferences.Editor clear():清空SharedPreferences裏全部數據

                 SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key對應的數據,其中xxx 能夠是boolean,float,int等各類基本類型據

                 SharedPreferences.Editor remove(): 刪除SharedPreferences中指定key對應的數據項

                 boolean commit(): 當Editor編輯完成後,使用該方法提交修改

    實際案例:運行界面以下

                       

這裏只提供了兩個按鈕和一個輸入文本框,佈局簡單,故在此不給出界面佈局文件了,程序核心代碼以下:         

複製代碼
class ViewOcl implements View.OnClickListener{

        @Override
        public void onClick(View v) {

            switch(v.getId()){
            case R.id.btnSet:
                //步驟1:獲取輸入值
                String code = txtCode.getText().toString().trim();
                //步驟2-1:建立一個SharedPreferences.Editor接口對象,lock表示要寫入的XML文件名,MODE_WORLD_WRITEABLE寫操做
                SharedPreferences.Editor editor = getSharedPreferences("lock", MODE_WORLD_WRITEABLE).edit();
                //步驟2-2:將獲取過來的值放入文件
                editor.putString("code", code);
                //步驟3:提交
                editor.commit();
                Toast.makeText(getApplicationContext(), "口令設置成功", Toast.LENGTH_LONG).show();
                break;
            case R.id.btnGet:
                //步驟1:建立一個SharedPreferences接口對象
                SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE);
                //步驟2:獲取文件中的值
                String value = read.getString("code", "");
                Toast.makeText(getApplicationContext(), "口令爲:"+value, Toast.LENGTH_LONG).show();
                
                break;
                
            }
        }
        
    }
複製代碼

讀寫其餘應用的SharedPreferences: 步驟以下

                一、在建立SharedPreferences時,指定MODE_WORLD_READABLE模式,代表該SharedPreferences數據能夠被其餘程序讀取

                二、建立其餘應用程序對應的Context:

                    Context pvCount = createPackageContext("com.tony.app", Context.CONTEXT_IGNORE_SECURITY);這裏的com.tony.app就是其餘程序的包名

                三、使用其餘程序的Context獲取對應的SharedPreferences

                    SharedPreferences read = pvCount.getSharedPreferences("lock", Context.MODE_WORLD_READABLE);

                四、若是是寫入數據,使用Editor接口便可,全部其餘操做均和前面一致。

SharedPreferences對象與SQLite數據庫相比,免去了建立數據庫,建立表,寫SQL語句等諸多操做,相對而言更加方便,簡潔。可是SharedPreferences也有其自身缺陷,好比其職能存儲boolean,int,float,long和String五種簡單的數據類型,好比其沒法進行條件查詢等。因此不論SharedPreferences的數據存儲操做是如何簡單,它也只能是存儲方式的一種補充,而沒法徹底替代如SQLite數據庫這樣的其餘數據存儲方式。

 

第二種: 文件存儲數據

 核心原理: Context提供了兩個方法來打開數據文件裏的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),這兩個方法第一個參數 用於指定文件名,第二個參數指定打開文件的模式。具體有如下值可選:

             MODE_PRIVATE:爲默認操做模式,表明該文件是私有數據,只能被應用自己訪問,在該模式下,寫入的內容會覆蓋原文件的內容,若是想把新寫入的內容追加到原文件中。可   以使用Context.MODE_APPEND

             MODE_APPEND:模式會檢查文件是否存在,存在就往文件追加內容,不然就建立新文件。

             MODE_WORLD_READABLE:表示當前文件能夠被其餘應用讀取;

             MODE_WORLD_WRITEABLE:表示當前文件能夠被其餘應用寫入。

 除此以外,Context還提供了以下幾個重要的方法:

             getDir(String name , int mode):在應用程序的數據文件夾下獲取或者建立name對應的子目錄

             File getFilesDir():獲取該應用程序的數據文件夾得絕對路徑

             String[] fileList():返回該應用數據文件夾的所有文件               

實際案例:界面沿用上圖

             核心代碼以下:

複製代碼
public String read() {
        try {
            FileInputStream inStream = this.openFileInput("message.txt");
            byte[] buffer = new byte[1024];
            int hasRead = 0;
            StringBuilder sb = new StringBuilder();
            while ((hasRead = inStream.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, hasRead));
            }

            inStream.close();
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return null;
    }
    
    public void write(String msg){
        // 步驟1:獲取輸入值
        if(msg == null) return;
        try {
            // 步驟2:建立一個FileOutputStream對象,MODE_APPEND追加模式
            FileOutputStream fos = openFileOutput("message.txt",
                    MODE_APPEND);
            // 步驟3:將獲取過來的值放入文件
            fos.write(msg.getBytes());
            // 步驟4:關閉數據流
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複製代碼

openFileOutput()方法的第一參數用於指定文件名稱,不能包含路徑分隔符「/」 ,若是文件不存在,Android 會自動建立它。建立的文件保存在/data/data/<package name>/files目錄,如: /data/data/cn.tony.app/files/message.txt

 下面講解某些特殊文件讀寫須要注意的地方:

讀寫sdcard上的文件

其中讀寫步驟按以下進行:

一、調用Environment的getExternalStorageState()方法判斷手機上是否插了sd卡,且應用程序具備讀寫SD卡的權限,以下代碼將返回true

Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)

二、調用Environment.getExternalStorageDirectory()方法來獲取外部存儲器,也就是SD卡的目錄,或者使用"/mnt/sdcard/"目錄

三、使用IO流操做SD卡上的文件 

注意點:手機應該已插入SD卡,對於模擬器而言,可經過mksdcard命令來建立虛擬存儲卡

           必須在AndroidManifest.xml上配置讀寫SD卡的權限

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

案例代碼:

複製代碼
// 文件寫操做函數
    private void write(String content) {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) { // 若是sdcard存在
            File file = new File(Environment.getExternalStorageDirectory()
                    .toString()
                    + File.separator
                    + DIR
                    + File.separator
                    + FILENAME); // 定義File類對象
            if (!file.getParentFile().exists()) { // 父文件夾不存在
                file.getParentFile().mkdirs(); // 建立文件夾
            }
            PrintStream out = null; // 打印流對象用於輸出
            try {
                out = new PrintStream(new FileOutputStream(file, true)); // 追加文件
                out.println(content);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    out.close(); // 關閉打印流
                }
            }
        } else { // SDCard不存在,使用Toast提示用戶
            Toast.makeText(this, "保存失敗,SD卡不存在!", Toast.LENGTH_LONG).show();
        }
    }

    // 文件讀操做函數
    private String read() {

        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) { // 若是sdcard存在
            File file = new File(Environment.getExternalStorageDirectory()
                    .toString()
                    + File.separator
                    + DIR
                    + File.separator
                    + FILENAME); // 定義File類對象
            if (!file.getParentFile().exists()) { // 父文件夾不存在
                file.getParentFile().mkdirs(); // 建立文件夾
            }
            Scanner scan = null; // 掃描輸入
            StringBuilder sb = new StringBuilder();
            try {
                scan = new Scanner(new FileInputStream(file)); // 實例化Scanner
                while (scan.hasNext()) { // 循環讀取
                    sb.append(scan.next() + "\n"); // 設置文本
                }
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (scan != null) {
                    scan.close(); // 關閉打印流
                }
            }
        } else { // SDCard不存在,使用Toast提示用戶
            Toast.makeText(this, "讀取失敗,SD卡不存在!", Toast.LENGTH_LONG).show();
        }
        return null;
    }
複製代碼

 第三種:SQLite存儲數據

SQLite是輕量級嵌入式數據庫引擎,它支持 SQL 語言,而且只利用不多的內存就有很好的性能。如今的主流移動設備像Android、iPhone等都使用SQLite做爲複雜數據的存儲引擎,在咱們爲移動設備開發應用程序時,也許就要使用到SQLite來存儲咱們大量的數據,因此咱們就須要掌握移動設備上的SQLite開發技巧

SQLiteDatabase類爲咱們提供了不少種方法,上面的代碼中基本上囊括了大部分的數據庫操做;對於添加、更新和刪除來講,咱們均可以使用

1 db.executeSQL(String sql);  
2 db.executeSQL(String sql, Object[] bindArgs);//sql語句中使用佔位符,而後第二個參數是實際的參數集 

除了統一的形式以外,他們還有各自的操做方法:

1 db.insert(String table, String nullColumnHack, ContentValues values);  
2 db.update(String table, Contentvalues values, String whereClause, String whereArgs);  
3 db.delete(String table, String whereClause, String whereArgs);

以上三個方法的第一個參數都是表示要操做的表名;insert中的第二個參數表示若是插入的數據每一列都爲空的話,須要指定此行中某一列的名稱,系統將此列設置爲NULL,不至於出現錯誤;insert中的第三個參數是ContentValues類型的變量,是鍵值對組成的Map,key表明列名,value表明該列要插入的值;update的第二個參數也很相似,只不過它是更新該字段key爲最新的value值,第三個參數whereClause表示WHERE表達式,好比「age > ? and age < ?」等,最後的whereArgs參數是佔位符的實際參數值;delete方法的參數也是同樣

下面給出demo

數據的添加

1.使用insert方法

複製代碼
1 ContentValues cv = new ContentValues();//實例化一個ContentValues用來裝載待插入的數據
2 cv.put("title","you are beautiful");//添加title
3 cv.put("weather","sun"); //添加weather
4 cv.put("context","xxxx"); //添加context
5 String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
6                         .format(new Date());
7 cv.put("publish ",publish); //添加publish
8 db.insert("diary",null,cv);//執行插入操做
複製代碼

2.使用execSQL方式來實現

String sql = "insert into user(username,password) values ('Jack Johnson','iLovePopMuisc');//插入操做的SQL語句
db.execSQL(sql);//執行SQL語句

數據的刪除

一樣有2種方式能夠實現

String whereClause = "username=?";//刪除的條件
String[] whereArgs = {"Jack Johnson"};//刪除的條件參數
db.delete("user",whereClause,whereArgs);//執行刪除

使用execSQL方式的實現

String sql = "delete from user where username='Jack Johnson'";//刪除操做的SQL語句
db.execSQL(sql);//執行刪除操做

數據修改

同上,還是2種方式

ContentValues cv = new ContentValues();//實例化ContentValues
cv.put("password","iHatePopMusic");//添加要更改的字段及內容
String whereClause = "username=?";//修改條件
String[] whereArgs = {"Jack Johnson"};//修改條件的參數
db.update("user",cv,whereClause,whereArgs);//執行修改

使用execSQL方式的實現

String sql = "update user set password = 'iHatePopMusic' where username='Jack Johnson'";//修改的SQL語句
db.execSQL(sql);//執行修改

數據查詢

下面來講說查詢操做。查詢操做相對於上面的幾種操做要複雜些,由於咱們常常要面對着各類各樣的查詢條件,因此係統也考慮到這種複雜性,爲咱們提供了較爲豐富的查詢形式:

1 db.rawQuery(String sql, String[] selectionArgs);  
2 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);  
3 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);  
4 db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);  

上面幾種都是經常使用的查詢方法,第一種最爲簡單,將全部的SQL語句都組織到一個字符串中,使用佔位符代替實際參數,selectionArgs就是佔位符實際參數集;

各參數說明:

  • table:表名稱
  • colums:表示要查詢的列全部名稱集
  • selection:表示WHERE以後的條件語句,能夠使用佔位符
  • selectionArgs:條件語句的參數數組
  • groupBy:指定分組的列名
  • having:指定分組條件,配合groupBy使用
  • orderBy:y指定排序的列名
  • limit:指定分頁參數
  • distinct:指定「true」或「false」表示要不要過濾重複值
  • Cursor:返回值,至關於結果集ResultSet

最後,他們同時返回一個Cursor對象,表明數據集的遊標,有點相似於JavaSE中的ResultSet。下面是Cursor對象的經常使用方法:

複製代碼
 1 c.move(int offset); //以當前位置爲參考,移動到指定行  
 2 c.moveToFirst();    //移動到第一行  
 3 c.moveToLast();     //移動到最後一行  
 4 c.moveToPosition(int position); //移動到指定行  
 5 c.moveToPrevious(); //移動到前一行  
 6 c.moveToNext();     //移動到下一行  
 7 c.isFirst();        //是否指向第一條  
 8 c.isLast();     //是否指向最後一條  
 9 c.isBeforeFirst();  //是否指向第一條以前  
10 c.isAfterLast();    //是否指向最後一條以後  
11 c.isNull(int columnIndex);  //指定列是否爲空(列基數爲0)  
12 c.isClosed();       //遊標是否已關閉  
13 c.getCount();       //總數據項數  
14 c.getPosition();    //返回當前遊標所指向的行數  
15 c.getColumnIndex(String columnName);//返回某列名對應的列索引值  
16 c.getString(int columnIndex);   //返回當前行指定列的值 
複製代碼

實現代碼

複製代碼
String[] params =  {12345,123456};
Cursor cursor = db.query("user",columns,"ID=?",params,null,null,null);//查詢並得到遊標 if(cursor.moveToFirst()){//判斷遊標是否爲空 for(int i=0;i<cursor.getCount();i++){ cursor.move(i);//移動到指定記錄 String username = cursor.getString(cursor.getColumnIndex("username"); String password = cursor.getString(cursor.getColumnIndex("password")); } }
複製代碼

經過rawQuery實現的帶參數查詢

複製代碼
Cursor result=db.rawQuery("SELECT ID, name, inventory FROM mytable");
//Cursor c = db.rawQuery("s name, inventory FROM mytable where ID=?",new Stirng[]{"123456"});     
result.moveToFirst(); 
while (!result.isAfterLast()) { 
    int id=result.getInt(0); 
    String name=result.getString(1); 
    int inventory=result.getInt(2); 
    // do something useful with these 
    result.moveToNext(); 
 } 
 result.close();
複製代碼

 

在上面的代碼示例中,已經用到了這幾個經常使用方法中的一些,關於更多的信息,你們能夠參考官方文檔中的說明。

最後當咱們完成了對數據庫的操做後,記得調用SQLiteDatabase的close()方法釋放數據庫鏈接,不然容易出現SQLiteException。

上面就是SQLite的基本應用,但在實際開發中,爲了可以更好的管理和維護數據庫,咱們會封裝一個繼承自SQLiteOpenHelper類的數據庫操做類,而後以這個類爲基礎,再封裝咱們的業務邏輯方法。

這裏直接使用案例講解:下面是案例demo的界面

SQLiteOpenHelper類介紹

SQLiteOpenHelper是SQLiteDatabase的一個幫助類,用來管理數據庫的建立和版本的更新。通常是創建一個類繼承它,並實現它的onCreate和onUpgrade方法。

方法名 方法描述
SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version)

構造方法,其中

context 程序上下文環境 即:XXXActivity.this;

name :數據庫名字;

factory:遊標工廠,默認爲null,即爲使用默認工廠;

version 數據庫版本號

onCreate(SQLiteDatabase db) 建立數據庫時調用
onUpgrade(SQLiteDatabase db,int oldVersion , int newVersion) 版本更新時調用
getReadableDatabase() 建立或打開一個只讀數據庫
getWritableDatabase() 建立或打開一個讀寫數據庫

首先建立數據庫類

複製代碼
 1 import android.content.Context;
 2 import android.database.sqlite.SQLiteDatabase;
 3 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 4 import android.database.sqlite.SQLiteOpenHelper;
 5 
 6 public class SqliteDBHelper extends SQLiteOpenHelper {
 7 
 8     // 步驟1:設置常數參量
 9     private static final String DATABASE_NAME = "diary_db";
10     private static final int VERSION = 1;
11     private static final String TABLE_NAME = "diary";
12 
13     // 步驟2:重載構造方法
14     public SqliteDBHelper(Context context) {
15         super(context, DATABASE_NAME, null, VERSION);
16     }
17 
18     /*
19      * 參數介紹:context 程序上下文環境 即:XXXActivity.this 
20      * name 數據庫名字 
21      * factory 接收數據,通常狀況爲null
22      * version 數據庫版本號
23      */
24     public SqliteDBHelper(Context context, String name, CursorFactory factory,
25             int version) {
26         super(context, name, factory, version);
27     }
28     //數據庫第一次被建立時,onCreate()會被調用
29     @Override
30     public void onCreate(SQLiteDatabase db) {
31         // 步驟3:數據庫表的建立
32         String strSQL = "create table "
33                 + TABLE_NAME
34                 + "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)";
35         //步驟4:使用參數db,建立對象
36         db.execSQL(strSQL);
37     }
38     //數據庫版本變化時,會調用onUpgrade()
39     @Override
40     public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
41 
42     }
43 }
複製代碼

正如上面所述,數據庫第一次建立時onCreate方法會被調用,咱們能夠執行建立表的語句,當系統發現版本變化以後,會調用onUpgrade方法,咱們能夠執行修改表結構等語句。

 咱們須要一個Dao,來封裝咱們全部的業務方法,代碼以下:

複製代碼
 1 import android.content.Context;
 2 import android.database.Cursor;
 3 import android.database.sqlite.SQLiteDatabase;
 4 
 5 import com.chinasoft.dbhelper.SqliteDBHelper;
 6 
 7 public class DiaryDao {
 8 
 9     private SqliteDBHelper sqliteDBHelper;
10     private SQLiteDatabase db;
11 
12     // 重寫構造方法
13     public DiaryDao(Context context) {
14         this.sqliteDBHelper = new SqliteDBHelper(context);
15         db = sqliteDBHelper.getWritableDatabase();
16     }
17 
18     // 讀操做
19     public String execQuery(final String strSQL) {
20         try {
21             System.out.println("strSQL>" + strSQL);
22             // Cursor至關於JDBC中的ResultSet
23             Cursor cursor = db.rawQuery(strSQL, null);
24             // 始終讓cursor指向數據庫表的第1行記錄
25             cursor.moveToFirst();
26             // 定義一個StringBuffer的對象,用於動態拼接字符串
27             StringBuffer sb = new StringBuffer();
28             // 循環遊標,若是不是最後一項記錄
29             while (!cursor.isAfterLast()) {
30                 sb.append(cursor.getInt(0) + "/" + cursor.getString(1) + "/"
31                         + cursor.getString(2) + "/" + cursor.getString(3) + "/"
32                         + cursor.getString(4)+"#");
33                 //cursor遊標移動
34                 cursor.moveToNext();
35             }
36             db.close();
37             return sb.deleteCharAt(sb.length()-1).toString();
38         } catch (RuntimeException e) {
39             e.printStackTrace();
40             return null;
41         }
42 
43     }
44 
45     // 寫操做
46     public boolean execOther(final String strSQL) {
47         db.beginTransaction();  //開始事務
48         try {
49             System.out.println("strSQL" + strSQL);
50             db.execSQL(strSQL);
51             db.setTransactionSuccessful();  //設置事務成功完成 
52             db.close();
53             return true;
54         } catch (RuntimeException e) {
55             e.printStackTrace();
56             return false;
57         }finally {  
58             db.endTransaction();    //結束事務  
59         }  
60 
61     }
62 }
複製代碼

咱們在Dao構造方法中實例化sqliteDBHelper並獲取一個SQLiteDatabase對象,做爲整個應用的數據庫實例;在增刪改信息時,咱們採用了事務處理,確保數據完整性;最後要注意釋放數據庫資源db.close(),這一個步驟在咱們整個應用關閉時執行,這個環節容易被忘記,因此朋友們要注意。

咱們獲取數據庫實例時使用了getWritableDatabase()方法,也許朋友們會有疑問,在getWritableDatabase()和getReadableDatabase()中,你爲何選擇前者做爲整個應用的數據庫實例呢?在這裏我想和你們着重分析一下這一點。

咱們來看一下SQLiteOpenHelper中的getReadableDatabase()方法:

複製代碼
 1 public synchronized SQLiteDatabase getReadableDatabase() {  
 2     if (mDatabase != null && mDatabase.isOpen()) {  
 3         // 若是發現mDatabase不爲空而且已經打開則直接返回  
 4         return mDatabase;  
 5     }  
 6   
 7     if (mIsInitializing) {  
 8         // 若是正在初始化則拋出異常  
 9         throw new IllegalStateException("getReadableDatabase called recursively");  
10     }  
11   
12     // 開始實例化數據庫mDatabase  
13   
14     try {  
15         // 注意這裏是調用了getWritableDatabase()方法  
16         return getWritableDatabase();  
17     } catch (SQLiteException e) {  
18         if (mName == null)  
19             throw e; // Can't open a temp database read-only!  
20         Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);  
21     }  
22   
23     // 若是沒法以可讀寫模式打開數據庫 則以只讀方式打開  
24   
25     SQLiteDatabase db = null;  
26     try {  
27         mIsInitializing = true;  
28         String path = mContext.getDatabasePath(mName).getPath();// 獲取數據庫路徑  
29         // 以只讀方式打開數據庫  
30         db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);  
31         if (db.getVersion() != mNewVersion) {  
32             throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to "  
33                     + mNewVersion + ": " + path);  
34         }  
35   
36         onOpen(db);  
37         Log.w(TAG, "Opened " + mName + " in read-only mode");  
38         mDatabase = db;// 爲mDatabase指定新打開的數據庫  
39         return mDatabase;// 返回打開的數據庫  
40     } finally {  
41         mIsInitializing = false;  
42         if (db != null && db != mDatabase)  
43             db.close();  
44     }  
45 }
複製代碼

在getReadableDatabase()方法中,首先判斷是否已存在數據庫實例而且是打開狀態,若是是,則直接返回該實例,不然試圖獲取一個可讀寫模式的數據庫實例,若是遇到磁盤空間已滿等狀況獲取失敗的話,再以只讀模式打開數據庫,獲取數據庫實例並返回,而後爲mDatabase賦值爲最新打開的數據庫實例。既然有可能調用到getWritableDatabase()方法,咱們就要看一下了:

複製代碼
public synchronized SQLiteDatabase getWritableDatabase() {  
    if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {  
        // 若是mDatabase不爲空已打開而且不是隻讀模式 則返回該實例  
        return mDatabase;  
    }  
  
    if (mIsInitializing) {  
        throw new IllegalStateException("getWritableDatabase called recursively");  
    }  
  
    // If we have a read-only database open, someone could be using it  
    // (though they shouldn't), which would cause a lock to be held on  
    // the file, and our attempts to open the database read-write would  
    // fail waiting for the file lock. To prevent that, we acquire the  
    // lock on the read-only database, which shuts out other users.  
  
    boolean success = false;  
    SQLiteDatabase db = null;  
    // 若是mDatabase不爲空則加鎖 阻止其餘的操做  
    if (mDatabase != null)  
        mDatabase.lock();  
    try {  
        mIsInitializing = true;  
        if (mName == null) {  
            db = SQLiteDatabase.create(null);  
        } else {  
            // 打開或建立數據庫  
            db = mContext.openOrCreateDatabase(mName, 0, mFactory);  
        }  
        // 獲取數據庫版本(若是剛建立的數據庫,版本爲0)  
        int version = db.getVersion();  
        // 比較版本(咱們代碼中的版本mNewVersion爲1)  
        if (version != mNewVersion) {  
            db.beginTransaction();// 開始事務  
            try {  
                if (version == 0) {  
                    // 執行咱們的onCreate方法  
                    onCreate(db);  
                } else {  
                    // 若是咱們應用升級了mNewVersion爲2,而原版本爲1則執行onUpgrade方法  
                    onUpgrade(db, version, mNewVersion);  
                }  
                db.setVersion(mNewVersion);// 設置最新版本  
                db.setTransactionSuccessful();// 設置事務成功  
            } finally {  
                db.endTransaction();// 結束事務  
            }  
        }  
  
        onOpen(db);  
        success = true;  
        return db;// 返回可讀寫模式的數據庫實例  
    } finally {  
        mIsInitializing = false;  
        if (success) {  
            // 打開成功  
            if (mDatabase != null) {  
                // 若是mDatabase有值則先關閉  
                try {  
                    mDatabase.close();  
                } catch (Exception e) {  
                }  
                mDatabase.unlock();// 解鎖  
            }  
            mDatabase = db;// 賦值給mDatabase  
        } else {  
            // 打開失敗的狀況:解鎖、關閉  
            if (mDatabase != null)  
                mDatabase.unlock();  
            if (db != null)  
                db.close();  
        }  
    }  
}
複製代碼

你們能夠看到,幾個關鍵步驟是,首先判斷mDatabase若是不爲空已打開並非只讀模式則直接返回,不然若是mDatabase不爲空則加鎖,而後開始打開或建立數據庫,比較版本,根據版本號來調用相應的方法,爲數據庫設置新版本號,最後釋放舊的不爲空的mDatabase並解鎖,把新打開的數據庫實例賦予mDatabase,並返回最新實例。

看完上面的過程以後,你們或許就清楚了許多,若是不是在遇到磁盤空間已滿等狀況,getReadableDatabase()通常都會返回和getWritableDatabase()同樣的數據庫實例,因此咱們在DBManager構造方法中使用getWritableDatabase()獲取整個應用所使用的數據庫實例是可行的。固然若是你真的擔憂這種狀況會發生,那麼你能夠先用getWritableDatabase()獲取數據實例,若是遇到異常,再試圖用getReadableDatabase()獲取實例,固然這個時候你獲取的實例只能讀不能寫了

最後,讓咱們看一下如何使用這些數據操做方法來顯示數據,界面核心邏輯代碼:

複製代碼
public class SQLiteActivity extends Activity {

    public DiaryDao diaryDao;

    //由於getWritableDatabase內部調用了mContext.openOrCreateDatabase(mName, 0, mFactory);  
    //因此要確保context已初始化,咱們能夠把實例化Dao的步驟放在Activity的onCreate裏
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        diaryDao = new DiaryDao(SQLiteActivity.this);
        initDatabase();
    }

    class ViewOcl implements View.OnClickListener {

        @Override
        public void onClick(View v) {

            String strSQL;
            boolean flag;
            String message;
            switch (v.getId()) {
            case R.id.btnAdd:
                String title = txtTitle.getText().toString().trim();
                String weather = txtWeather.getText().toString().trim();;
                String context = txtContext.getText().toString().trim();;
                String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                        .format(new Date());
                // 動態組件SQL語句
                strSQL = "insert into diary values(null,'" + title + "','"
                        + weather + "','" + context + "','" + publish + "')";
                flag = diaryDao.execOther(strSQL);
                //返回信息
                message = flag?"添加成功":"添加失敗";
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                break;
            case R.id.btnDelete:
                strSQL = "delete from diary where tid = 1";
                flag = diaryDao.execOther(strSQL);
                //返回信息
                message = flag?"刪除成功":"刪除失敗";
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                break;
            case R.id.btnQuery:
                strSQL = "select * from diary order by publish desc";
                String data = diaryDao.execQuery(strSQL);
                Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show();
                break;
            case R.id.btnUpdate:
                strSQL = "update diary set title = '測試標題1-1' where tid = 1";
                flag = diaryDao.execOther(strSQL);
                //返回信息
                message = flag?"更新成功":"更新失敗";
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                break;
            }
        }
    }

    private void initDatabase() {
        // 建立數據庫對象
        SqliteDBHelper sqliteDBHelper = new SqliteDBHelper(SQLiteActivity.this);
        sqliteDBHelper.getWritableDatabase();
        System.out.println("數據庫建立成功");
    }
}
複製代碼

 

Android sqlite3數據庫管理工具

Android SDK的tools目錄下提供了一個sqlite3.exe工具,這是一個簡單的sqlite數據庫管理工具。開發者能夠方便的使用其對sqlite數據庫進行命令行的操做。

程序運行生成的*.db文件通常位於"/data/data/項目名(包括所處包名)/databases/*.db",所以要對數據庫文件進行操做須要先找到數據庫文件:

一、進入shell 命令

<span style="margin: 0px; padding: 0px; line-height: 1.5; font-family: 'courier new', courier; font-size: 14px;">adb shell</span>

二、找到數據庫文件

<span style="margin: 0px; padding: 0px; line-height: 1.5; font-family: 'courier new', courier; font-size: 14px;">#cd data/data
#ls                --列出全部項目
#cd project_name   --進入所需項目名
#cd databases    
#ls                --列出現寸的數據庫文件</span>

三、進入數據庫

<span style="margin: 0px; padding: 0px; line-height: 1.5; font-family: 'courier new', courier; font-size: 14px;">#sqlite3 test_db   --進入所需數據庫</span>

會出現相似以下字樣:

<span style="margin: 0px; padding: 0px; line-height: 1.5; font-family: 'courier new', courier; font-size: 14px;">SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite></span>

至此,可對數據庫進行sql操做。

四、sqlite經常使用命令

<span style="margin: 0px; padding: 0px; line-height: 1.5; font-family: 'courier new', courier; font-size: 14px;">>.databases        --產看當前數據庫
>.tables           --查看當前數據庫中的表
>.help             --sqlite3幫助
>.schema            --各個表的生成語句</span>

12. Sqlite的基本操做。

   最近開始在某個項目中實習,充分認識到了本身的不足,包括能力和性格等各類方面的缺陷。如何快速掌握開發環境,如何與其餘程序員溝通交流,如何準確知道分配給本身的模塊具體實現的功能等等,都是大問題,更重要的是,本身不能僅僅只是寫代碼而已,還要清楚本身的代碼的應用環境,別人是怎樣用的,本身應該提供哪些接口。這就屬於擴展性的問題,不是一個新手可以立刻明白的,但倒是咱們是否可以「脫農」的關鍵。

      廢話很少說,本文講訴的是我在項目中使用到的新知識---android數據庫的操做。之前並無任何關於android數據庫的開發經歷,全部的東西都是現學現用,因此特地總結一下。

      若是想要在android中使用數據庫,使用SQLite是一個很是好的選擇,由於它是android內置的數據庫,提供了不少支持。

      數據庫的使用無非就是CRUD,也就是"Create,Read,Update,Delete"這四個基本操做。

一.Create

      Create就是建立表,而要想建立表,首先必需要建立或者打開數據庫。

      有兩種方式能夠作到這點:

1.手動建立或者打開數據庫

 SQLiteDatabase database = openOrCreateDatabase("Student.db", MODE_PRIVATE, null);

     調用openOrCreateDatabase()方法,若是有該數據庫,就打開,沒有就建立一個。該方法的具體信息仍是得看源碼,但通常咱們使用的時候,只要指定數據庫的名字和指定該數據庫是私有的就行。

2.使用SQLiteOpenHelper

複製代碼
public class SQLHelper extends SQLiteOpenHelper {

    public SQLHelper(Context context, String name, CursorFactory factory,
                     int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {}

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
複製代碼

        而後咱們再在Activity這樣使用:

SQLHelper helper = new SQLHelper(this, "Student.db", null, 1);

        版本號(不能爲負數)是爲了方便之後升級數據庫,因爲是第一版,版本號就是1。
        SQLiteOpenHelper是一個抽象的數據庫操做類,首先執行的是OnCreate,這裏咱們能夠執行建立表等動做,但該方法並無真正建立數據庫,建立數據庫是在如下的狀況:

SQLiteDatabase database = helper.getWritableDatabase();

        調用getWritableDatabase()或者getReadableDatabase()時,就會真正建立數據庫。

        建立或打開數據庫後,咱們就能夠建表。

        使用數據庫並非一件難事,難就難在如何建模。這裏咱們就只建三個簡單的表:

        Teacher(teacherId, name, classId), Student(studentId,name,classId),Class(classId,className,studentId,teacherId),其中teacherId,studentId,classId分別是Teacher,Student和Class的主鍵。

        SQLite能夠直接執行SQL語句,因此,咱們能夠這樣建表:

 String CREATE_CLASS = "Create Table If Not Exists Class(classId integer primary key,"
                + "className varchar(100),"
                + "studentId integer References Student(studentId),"
                + "teacherId integer References Teacher(teacherId))";
 SQLiteDatabase db = helper.getWritableDatabase();
 db.execSQL(CREATE_CLASS);

        每次使用完數據庫都要記得及時關閉數據庫:      

 db.close();

        按照上面的方法,咱們能夠很快的建好三個表。

二.Updata

        更新這部分包括:插入,修改這兩個動做。

        先講最基本的動做:插入。

        咱們如今要想將學生插入到班級中,像是這樣:

 ClassInfoProvider provider = new ClassInfoProvider(this);
 Student student = new Student(2857, "鄭文彪");
 ClassInfo classInfo = new ClassInfo("電信1班", 1);
 provider.addStudent(student, classInfo);

         這裏咱們有三個類:

複製代碼
public class ClassInfo {
    private String name;
    private int id;

    public ClassInfo() {
    }

    public ClassInfo(String name, int id) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
  }
複製代碼
複製代碼
public class Teacher {
    private int teacherId;
    private String name;
    private String className;

    public Teacher() {
    }

    public Teacher(int teacherId, String name) {
        this.name = name;
        this.teacherId = teacherId;
    }

    public int getTeacherId() {
        return this.teacherId;
    }

    public void setTeacherId(int teacherId) {
        this.teacherId = teacherId;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return this.className;
    }

    public void setClassName(String name) {
        this.className = name;
    }
}
複製代碼
複製代碼
public class Student {
    private int studendtd;
    private String name;
    private String className;

    public Student() {
    }

    public Student(int studentId, String name) {
        this.name = name;
        this.studentId = studentId;
    }

    public int getStudentId() {
        return this.studentId;
    }

    public void setStudendId(int studentId) {
        this.studentId = studendtd;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return this.className;
    }

    public void setClassName(String name) {
        this.className = name;
    }
}
複製代碼

       這三個類就是存放Student,Teacher和Class的基本信息。
       而後咱們開始將學生插入到班級中:

複製代碼
 public void addStudent(Student student, ClassInfo classInfo) {
        String INSERT_STUDENT_INTO_CLASS = "Insert Into Class(className, studentId, classId) Values('" + classInfo.getName() + "',"
                + student.getStudentId() + "," + classInfo.getId() + ")";
        String INSERT_STUDENT = "Insert Into Student(studentId, name, classId) Values(" + student.getStudentId() + ","
                + "'" + student.getName() + "'," + classInfo.getId() + ")";
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL(INSERT_STUDENT);
        db.execSQL(INSERT_STUDENT_INTO_CLASS);
        db.close();
    }
複製代碼

       這是直接執行SQL語句的作法。
       SQLiteOpenHelper封裝了一個insert方法能夠方便咱們執行插入行爲:

複製代碼
 SQLiteDatabase db = helper.getWritableDatabase();
 ContentValues values = new ContentValues();
 values.put("className", classInfo.getName());
 values.put("studentId", student.getStudentId());
 values.put("classId", classInfo.getId());
 db.insert("Class", null, values);
 ContentValues values1 = new ContentValues();
 values1.put("studentId", student.getStudentId());
 values1.put("name", student.getName());
 values1.put("classId", classInfo.getId());
 db.insert("Student", null, values1);
 db.close();
複製代碼

       ContentValues其實就是一個字典Map,key值就是表中的列值,而value就是對應的字段。insert方法的第一個參數是要插入的表名,第二個參數就是相應的列,這裏咱們用null表示全部的列,而後就是咱們要插入的字段。
       這樣的方法確實能夠簡化咱們的操做,至少不用在咱們的代碼中寫那麼長的SQL語句,容易犯錯,又很煩,尤爲是在插入的動做不斷執行的時候。但有個地方值得注意:若是咱們的數據庫是提供接口方法給其餘模塊使用,並且之後要修改的人或者查看的人並非咱們本身,他們可能就必須知道這些方法的參數是什麼,但直接執行SQL語句,只要他有數據庫的基礎知識,就會明白這是在幹嗎,也知道如何修改。更糟糕的狀況就是之後android的接口方法發生變化的話,那麼,這些代碼可能就會出現問題。固然,咱們願意相信他們不會修改接口,由於對接口的修改是一種錯誤的行爲,尤爲在接口已經發布的狀況下。

       如今咱們的表已經有數據了,若是咱們想要修改的話,像是將學生的姓名進行更改,能夠這樣操做:

public void updateStudent(int id, String name) {
        SQLiteDatabase db = helper.getWritableDatabase();
        String UPDATE_STUDENT = "Update Student Set name =" + "'" + name + "' Where id=" + id + "";
        db.execSQL(UPDATE_STUDENT);
        db.close();
    }

       固然,咱們一樣能夠簡化:

 public void updateStudent(int id, String name) {
     SQLiteDatabase db = helper.getWritableDatabase();
     ContentValues values = new ContentValues();
     values.put("name", name);
     db.update("Student", values, "studentId=?", new String[]{id + ""});
db.close(); }

       update方法中,值得注意的是最後面兩個參數,whereClause和whereArgs。whereClause表示要修改哪裏,whereArgs表示修改的字段,不管咱們是要修改一個字段仍是一個以上,這裏都須要一個String[]。

三.Read
      所謂的Read,就是一系列查詢動做。

      咱們要在Student這個表中查詢名爲"鄭文彪"的學號,就是studentId:

複製代碼
  public int getStudentId(String name) {
        int id = 0;
        String SELECT_STUDENTID = "Select studentId From Student Where name=?";
        SQLiteDatabase db = helper.getWritableDatabase();
        Cursor cursor = db.rawQuery(SELECT_STUDENTID, new String[]{name});
        if (cursor.moveToNext()) {
            id = cursor.getInt(0);
        }
        cursor.close();
db.close();
return id; }
複製代碼

      這裏咱們須要利用光標Cursor。Cursor指向當前的數據記錄,而後咱們能夠從光標中獲取相應的數據。
四.Delete

       Delete包括表的刪除,數據記錄的刪除。

       首先是數據記錄的刪除。如今咱們想要刪除姓名爲"鄭文彪"的學生的記錄:

 public void deleteStudent(Student student) {
        SQLiteDatabase db = helper.getWritableDatabase();
        String DELETE_STUDENT = "Delete From Class Where studentId=?";
        db.execSQL(DELETE_STUDENT, new String[]{student.getStudentId() + ""});
        db.close();
    }

        而後這樣調用該方法:

 Student student = new Student(2857, "鄭文彪");
 ClassInfo classInfo = new ClassInfo("電信1班", 1);
 provider.addStudent(student, classInfo);
 provider.deleteStudent(student1);

       一樣能夠簡化:

   db.delete("Class", "studentId=?", new String[]{student.getStudentId() + ""});

      接着是刪除表,這個很簡單:

String DROP_CLASS = "Drop Table Class";
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL(DROP_CLASS);

     在作測試的時候,因爲須要常常運行,因此數據庫中表的數據會很是冗餘,尤爲是將鍵值設爲自增的時候。因此,咱們須要在每次測試後刪除表,這樣下次測試的時候就不會受到影響。
五.結語

     SQLite徹底是現學現作,因此瞭解得並非很深,寫得是通常,但還請各位可以指出不足之處。


13. Android中的MVC模式。

14. MergeViewStub的做用。

FrameLayout 先來看官方文檔的定義:FrameLayout是最簡單的一個佈局對象。它被定製爲你屏幕上的一個空白備用區域,以後你能夠在其中填充一個單一對象 — 好比,一張你要發佈的圖片。全部的子元素將會固定在屏幕的左上角;你不能爲FrameLayout中的一個子元素指定一個位置。後一個子元素將會直接在前一個子元素之上進行覆蓋填充,把它們部份或所有擋住(除非後一個子元素是透明的)。 
個人理解是,把FrameLayout看成畫布canvas,固定從屏幕的左上角開始填充圖片,文字等。看看示例,原來能夠利用 android:layout_gravity來設置位置的:

FrameLayout的佈局結構

 


   

 

效果圖

  


佈局優化 使用tools裏面的hierarchyviewer.bat來查看layout的層次。在啓動模擬器啓動所要分析的程序,再啓動hierarchyviewer.bat,選擇模擬器以及該程序,點擊「Load View Hierarchy」,就會開始分析。能夠save as png。 

<merge> 減小視圖層級結構
從上圖能夠看到存在兩個FrameLayout,紅色框住的。若是能在layout文件中把FrameLayout聲明去掉就能夠進一步優化佈局代碼了。 可是因爲佈局代碼須要外層容器容納,若是
直接刪除FrameLayout則該文件就不是合法的佈局文件。這種狀況下就能夠使用<merge> 標籤了。
修改成以下代碼就能夠消除多餘的FrameLayout了:

複製代碼
 1 01    <?xml
 2 02    version="1.0"
 3 03    encoding="utf-8"?>
 4 04     
 5 05    <merge xmlns:android="http://schemas.android.com/apk/res/android">
 6 06    <ImageView
 7 07    android:id="@+id/image"
 8 08    android:layout_width="fill_parent"
 9 09    android:layout_height="fill_parent"
10 10    android:scaleType="center"
11 11    android:src="@drawable/candle"
12 12    />
13 13    <TextView
14 14    android:id="@+id/text1"
15 15    android:layout_width="wrap_content"
16 16    android:layout_height="wrap_content"
17 17    android:layout_gravity="center"
18 18    android:textColor="#00ff00"
19 19    android:text="@string/hello"
20 20    />
21 21    <Button
22 22    android:id="@+id/start"
23 23    android:layout_width="wrap_content"
24 24    android:layout_height="wrap_content"
25 25    android:layout_gravity="bottom"
26 26    android:text="Start"
27 27    />
28 28    </merge>
複製代碼

 


   

 

<merge>也有一些使用限制: 只能用於xml layout文件的根元素;在代碼中使用LayoutInflater.Inflater()一個以merge爲根元素的
佈局文件時候,須要使用View inflate (int resource, ViewGroup root, boolean attachToRoot)指定一個ViewGroup 做爲其容器,而且要設置attachToRoot 爲true。

<include> 重用layout代碼
若是在某個佈局裏面須要用到另外一個相同的佈局設計,能夠經過<include> 標籤來重用layout代碼:

複製代碼
 1 01    <?xml
 2 02    version="1.0"
 3 03    encoding="utf-8"?>
 4 04     
 5 05    <LinearLayout
 6 06    xmlns:android="http://schemas.android.com/apk/res/android"
 7 07    android:orientation="vertical"
 8 08    android:layout_width="fill_parent"
 9 09    android:layout_height="fill_parent">
10 10     
11 11    <include
12 12    android:id="@+id/layout1"
13 13    layout="@layout/relative"
14 14    />
15 15    <include
16 16    android:id="@+id/layout2"
17 17    layout="@layout/relative"
18 18    />
19 19    <include
20 20    android:id="@+id/layout3"
21 21    layout="@layout/relative"
22 22    />
23 23     
24 24    </LinearLayout>
25  
複製代碼

 


效果圖


這裏要注意的是,"@layout/relative"不是引用Layout的id,而是引用res/layout/relative.xml,其內容是前面文章介紹RelativeLayout的佈局代碼。
另外,經過<include>,除了能夠覆寫id屬性值,還能夠修改其餘屬性值,例如android:layout_width,android:height等。

<viewstub> 延遲加載
(轉自http://rainhomepage.appspot.com/2010/01/use-viewstub-to-optimize-the-layout-of)

ViewStub 是一個不可見的,大小爲0的View,最佳用途就是實現View的延遲加載,在須要的時候再加載View,可Java中常見的性能優化方法延遲加載同樣。

當調用ViewStub的setVisibility函數設置爲可見或則調用 inflate初始化該View的時候,ViewStub引用的資源開始初始化,而後引用的資源替代ViewStub本身的位置填充在ViewStub的 位置。所以在沒有調用setVisibility(int) 或則 inflate()函數以前 ViewStub一種存在組件樹層級結構中,可是因爲ViewStub很是輕量級,這對性能影響很是小。 能夠經過ViewStub的inflatedId屬

複製代碼
1 1    <ViewStub
2 2    android:id="@+id/stub"
3 3     
4 4    android:inflatedId="@+id/subTree"
5 5    android:layout="@layout/mySubTree"
6 6    android:layout_width="120dip"
7 7    android:layout_height="40dip"
8 8    />
複製代碼

 

性來從新定義引用的layout id。 例如:

 
   

 

上面定義的ViewStub ,能夠經過id 「stub」來找到,在初始化資源「mySubTree」後,stub從父組件中刪除,而後"mySubTree"替代stub的位置。初始資源"mySubTree"獲得的組件能夠經過inflatedId 指定的id "subTree"引用。 而後初始化後的資源被填充到一個120dip寬、40dip高的地方。 

推薦使用下面的方式來初始化ViewStub:

1    ViewStub stub = (ViewStub) findViewById(R.id.stub);
2    View inflated = stub.inflate();

 


   

 

當調用inflate()函數的時候,ViewStub 被引用的資源替代,而且返回引用的view。 這樣程序能夠直接獲得引用的view而不用再次調用函數 findViewById()來查找了。

ViewStub目前有個缺陷就是還不支持 <merge /> 標籤。

layoutopt (Layout Optimization工具)
這工具能夠分析所提供的Layout,並提供優化意見。在tools文件夾裏面能夠找到layoutopt.bat。
用法
layoutopt <list of xml files or directories>
參數
一個或多個的Layout xml文件,以空格間隔;或者多個Layout xml文件所在的文件夾路徑
例子
layoutopt G:/StudyAndroid/UIDemo/res/layout/main.xml
layoutopt G:/StudyAndroid/UIDemo/res/layout/main.xml G:/StudyAndroid/UIDemo/res/layout/relative.xml
layoutopt G:/StudyAndroid/UIDemo/res/layout


15. Json有什麼優劣勢。

JSON(Javascript Object Notation) 是一種輕量級的數據交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。它基於Javascript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。JSON採用徹底獨立於語言的文本格式,可是也使用了相似於C語言家族的習慣(包括C, C++, C#, Java, Javascript, Perl, Python等)。這些特性使JSON成爲理想的數據交換語言。
JSON概念很簡單,就是服務器直接生成Javascript語句,客戶端獲取後直接用eval方法來得到這個對象,這樣就能夠省去解析XML的性損失。

如要從後臺載入信息,寫成XML,以下:
<contact>
<friend>
<name>Michael</name>
<email>17bity@gmail.com</email>
<homepage>http://www.jialing.net</homepage>
</friend>
<friend>
<name>John</name>
<email>john@gmail.com</email>
<homepage>http://www.john.com</homepage>
</friend>
<friend>
<name>Peggy</name>
<email>peggy@gmail.com</email>
<homepage>http://www.peggy.com</homepage>
</friend>
</contact>


而寫成JSON:

[
{
name:"Michael",
email:"17bity@gmail.com",
homepage:"http://www.jialing.net"
},
{
name:"John",
email:"john@gmail.com",
homepage:"http://www.jobn.com"
},
{
name:"Peggy",
email:"peggy@gmail.com",
homepage:"http://www.peggy.com"
}
]

簡單的不僅是表達上,最重要的是能夠丟棄讓人暈頭轉向的DOM解析了。由於只要符合Javascript的聲明規範,JavaScrip會自動幫你解析 好 的。Ajax中使用JSON的基本方法是前臺載入後臺聲明Javascript對象的字符串,用eval方法來將它轉爲實際的對象,最後經過 DHTML更新頁面信息。

JSON不只減小了解析XML解析帶來的性能問題和兼容性問題,並且對於Javascript來講很是容易使用,能夠方便的經過遍歷數組以及訪問對象屬性 來獲取數據,其可讀性也不錯,基本具有告終構化數據的性質。不得不說是一個很好的辦法,並且事實上google maps就沒有采用XML傳遞數據,而是採用了JSON方案。

JSON的另一個優點是"跨域性",例如你在www.Web.cn的網頁裏使用



JSON的定義

    一種輕量級的數據交換格式,具備良好的可讀和便於快速編寫的特性。業內主流技術爲其提供了完整的解決方案(有點相似於正則表達式 ,得到了當今大部分語言的支持),從而能夠在不一樣平臺間進行數據交換。JSON採用兼容性很高的文本格式,同時也具有相似於C語言體系的行爲。

 

XML的定義

    擴展標記語言 (Extensible Markup Language, XML) ,用於標記電子文件使其具備結構性的標記語言,能夠用來標記數據、定義數據類型,是一種容許用戶對本身的標記語言進行定義的源語言。 XML是標準通用標記語言 (SGML) 的子集,很是適合 Web 傳輸。XML 提供統一的方法來描述和交換獨立於應用程序或供應商的結構化數據。

[  XML ]
使 用XML做爲傳輸格式的優點:
1. 格式統一, 符合標準
2. 容易與其餘系統進行遠程交互, 數據共享比較方便

缺點:
1. XML文件格式文件龐大, 格式複雜, 傳輸佔用帶寬
2. 服務器端和客戶端都須要花費大量代碼來解析XML, 不論服務器端和客戶端代碼變的異常複雜和不容易維護
3. 客戶端不一樣瀏覽器之間解析XML的方式不一致, 須要重複編寫不少代碼
4. 服務器端和客戶端解析XML花費資源和時間


[ JSON ]
那麼除了XML格式, 還有沒有其餘格式, 有一種叫作JSON (JavaScript Object Notation) 的輕量級數據交換格式可以替代XML的工做.

優勢:
1. 數據格式比較簡單, 易於讀寫, 格式都是壓縮的, 佔用帶寬小
2. 易於解析這種語言, 客戶端JavaScript能夠簡單的經過eval_r()進行JSON數據的讀取
3. 支持多種語言, 包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等語言服務器端語言, 便於服務器端的解析
4. 在PHP世界, 已經有PHP-JSON和JSON-PHP出現了, 便於PHP序列化後的程序直接調用. PHP服務器端的對象、數組等可以直接生JSON格式, 便於客戶端的訪問提取.
5. 由於JSON格式可以直接爲服務器端代碼使用, 大大簡化了服務器端和客戶端的代碼開發量, 可是完成的任務不變, 且易於維護

缺點:
1. 沒有XML格式這麼推廣的深刻人心和使用普遍, 沒有XML那麼通用性
2. JSON格式目前在Web Service中推廣還屬於初級階段

JSON  XML 優缺點的比較

1.       在可讀性方面,JSONXML的數據可讀性基本相同。JSONXML的可讀性可謂不相上下,一邊是建議的語法,一邊是規範的標籤形式,很難分出勝負。

2.       在可擴展性方面,XML天生有很好的擴展性,JSON固然也有,沒有什麼是XML能擴展,JSON不能的。

3.       在編碼難度方面,XML有豐富的編碼工具,好比Dom4jJDom等,JSON也有json.org提供的工具,可是JSON的編碼明顯比XML容易許多,即便不借助工具也能寫出JSON的代碼,但是要寫好XML就不太容易了。

4.       在解碼難度方面,XML的解析得考慮子節點父節點,讓人頭昏眼花,而JSON的解析難度幾乎爲0。這一點XML輸的真是沒話說。

5.       在流行度方面,XML已經被業界普遍的使用,而JSON纔剛剛開始,可是在Ajax這個特定的領域,將來的發展必定是XML讓位於JSON。到時Ajax應該變成Ajaj(Asynchronous Javascript and JSON)了。

6.       JSONXML一樣擁有豐富的解析手段。

7.       JSON相對於XML來說,數據的體積小。

8.       JSONJavaScript的交互更加方便。

9.       JSON對數據的描述性比XML較差。

10.   JSON的速度要遠遠快於XML


  1.數據交換格式比較之關於XML和JSON:

  XML:extensible markup language,一種相似於HTML的語言,他沒有預先定義的標籤,使用DTD(document type definition)文檔類型定義來組織數據;格式統一,跨平臺和語言,早已成爲業界公認的標準。具體的能夠問Google或百度。相比之JSON這種輕量級的數據交換格式,XML能夠稱爲重量級的了。

  JSON : JavaScript Object Notation 是一種輕量級的數據交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。它基於JavaScript Programming Language , Standard ECMA-262 3rd Edition - December 1999 的一個子集。 JSON採用徹底獨立於語言的文本格式,可是也使用了相似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成爲理想的數據交換語言。

  2.數據交換格式比較之關於輕量級和重量級:

  輕量級和重量級是相對來講的,那麼XML相對於JSON的重量級體如今哪呢?我想應該體如今解析上,XML目前設計了兩種解析方式:DOM和 SAX;

  DOM是把一個數據交換格式XML當作一個DOM對象,須要把XML文件整個讀入內存,這一點上JSON和XML的原理是同樣的,可是XML要考慮父節點和子節點,這一點上JSON的解析難度要小不少,由於JSON構建於兩種結構:key/value,鍵值對的集合;值的有序集合,可理解爲數組;

  SAX不須要整個讀入文檔就能夠對解析出的內容進行處理,是一種逐步解析的方法。程序也能夠隨時終止解析。這樣,一個大的文檔就能夠逐步的、一 點一點的展示出來,因此SAX適合於大規模的解析。這一點,JSON目前是作不到得。

  因此,JSON和XML的輕/重量級的區別在於:JSON只提供總體解析方案,而這種方法只在解析較少的數據時才能起到良好的效果;而XML提 供了對大規模數據的逐步解析方案,這種方案很適合於對大量數據的處理。

  3.數據交換格式比較之關於數據格式編碼及解析的難度:

  在編碼上,雖然XML和JSON都有各自的編碼工具,可是JSON的編碼要比XML簡單,即便不借助工具,也能夠寫出JSON代碼,但要寫出好的XML代碼就有點困難;與XML同樣,JSON也是基於文本的,且它們都使用Unicode編碼,且其與數據交換格式XML同樣具備可讀性。

  主觀上來看,JSON更爲清晰且冗餘更少些。JSON網站提供了對JSON語法的嚴格描述,只是描述較簡短。從整體來看,XML比較適合於標記 文檔,而JSON卻更適於進行數據交換處理。

  在解析上,在普通的web應用領域,開發者常常爲XML的解析傷腦筋,不管是服務器端生成或處理XML,仍是客戶端用 JavaScript 解析XML,都經常致使複雜的代碼,極低的開發效率。

  實際上,對於大多數web應用來講,他們根本不須要複雜的XML來傳輸數據,XML宣稱的擴展性在此就不多具備優點;許多Ajax應用甚至直接返回HTML片斷來構建動態web頁面。和返回XML並解析它相比,返回HTML片斷大大下降了系統的複雜性,但同時缺乏了必定的靈活性。同XML或 HTML片斷相比,數據交換格式JSON 提供了更好的簡單性和靈活性。在web serivice應用中,至少就目前來講XML仍有不可動搖的地位。


16. 動畫有哪兩類,各有什麼特色?

  android支持兩種動畫模式,tween animation,frame animation
View Animation(Tween Animation):補間動畫,給出兩個關鍵幀,經過一些算法將給定屬性值在給定的時間內在兩個關鍵幀間漸變。
  View animation只能應用於View對象,並且只支持一部分屬性,這種實現方式能夠使視圖組件移動、放大、縮小以及產生透明度的變化.

另外一種Frame動畫,傳統的動畫方法,經過順序的播放排列好的圖片來實現,相似電影補間動畫和幀動畫。
補間動畫和Frame動畫的定義:
  所謂補間動畫,是指經過指定View的初末狀態和變化時間、方式,對View的內容完成一系列的圖形變換來實現動畫效果。主要包括四種效果:Alpha、Scale、Translate和Rotate。
幀動畫就是Frame動畫,即指定每一幀的內容和停留時間,而後播放動畫。。

17. HandlerLoop消息隊列模型,各部分的做用。

Android系統的消息隊列和消息循環都是針對具體線程的,一個線程能夠存在(固然也能夠不存在)一個消息隊列(Message Queue)和一個消息循環(Looper)。Android中除了UI線程(主線程),建立的工做線程默認是沒有消息循環和消息隊列的。若是想讓該線程具備消息隊列和消息循環,並具備消息處理機制,就須要在線程中首先調用Looper.prepare()來建立消息隊列,而後調用Looper.loop()進入消息循環。如如下代碼所示:

 

Java代碼
  1. class LooperThread extends Thread {   
  2.       public Handler mHandler;   
  3.   
  4.       public void run() {   
  5.           Looper.prepare();   
  6.   
  7.           mHandler = new Handler() {   
  8.               public void handleMessage(Message msg) {   
  9.                   // process incoming messages here   
  10.               }   
  11.           };   
  12.   
  13.           Looper.loop();   
  14.       }   
  15.   }  
[java]  view plain  copy
  1. class LooperThread extends Thread {  
  2.       public Handler mHandler;  
  3.   
  4.       public void run() {  
  5.           Looper.prepare();  
  6.   
  7.           mHandler = new Handler() {  
  8.               public void handleMessage(Message msg) {  
  9.                   // process incoming messages here  
  10.               }  
  11.           };  
  12.   
  13.           Looper.loop();  
  14.       }  
  15.   }  

 

       這樣該線程就具備了消息處理機制了。若是不調用Looper.prepare()來建立消息隊列,會報"java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()"的錯誤。

 

       經過下圖能夠清晰顯示出UI Thread, Worker Thread, Handler, Massage Queue, Looper之間的關係:

解釋上圖中的幾個基本概念:

 

       1.Message

       消息對象,顧名思義就是記錄消息信息的類。這個類有幾個比較重要的字段:

       a.arg1和arg2:咱們能夠使用兩個字段用來存放咱們須要傳遞的整型值,在Service中,咱們能夠用來存放Service的ID。

       b.obj:該字段是Object類型,咱們可讓該字段傳遞某個多項到消息的接受者中。

       c.what:這個字段能夠說是消息的標誌,在消息處理中,咱們能夠根據這個字段的不一樣的值進行不一樣的處理,相似於咱們在處理Button事件時,經過switch(v.getId())判斷是點擊了哪一個按鈕。

       在使用Message時,咱們能夠經過new Message()建立一個Message實例,可是Android更推薦咱們經過Message.obtain()或者Handler.obtainMessage()獲取Message對象。這並不必定是直接建立一個新的實例,而是先從消息池中看有沒有可用的Message實例,存在則直接取出並返回這個實例。反之若是消息池中沒有可用的Message實例,則根據給定的參數new一個新Message對象。經過分析源碼可得知,Android系統默認狀況下在消息池中實例化10個Message對象。

 

       2.MessageQueue

       消息隊列,用來存放Message對象的數據結構,按照「先進先出」的原則存放消息。存放並不是實際意義的保存,而是將Message對象以鏈表的方式串聯起來的。MessageQueue對象不須要咱們本身建立,而是有Looper對象對其進行管理,一個線程最多隻能夠擁有一個MessageQueue。咱們能夠經過Looper.myQueue()獲取當前線程中的MessageQueue。

 

       3.Looper

       MessageQueue的管理者,在一個線程中,若是存在Looper對象,則一定存在MessageQueue對象,而且只存在一個Looper對象和一個MessageQueue對象。假若咱們的線程中存在Looper對象,則咱們能夠經過Looper.myLooper()獲取,此外咱們還能夠經過Looper.getMainLooper()獲取當前應用系統中主線程的Looper對象。在這個地方有一點須要注意,假如Looper對象位於應用程序主線程中,則Looper.myLooper()和Looper.getMainLooper()獲取的是同一個對象。

 

       4.Handler

       消息的處理者。經過Handler對象咱們能夠封裝Message對象,而後經過sendMessage(msg)把Message對象添加到MessageQueue中;當MessageQueue循環到該Message時,就會調用該Message對象對應的handler對象的handleMessage()方法對其進行處理。因爲是在handleMessage()方法中處理消息,所以咱們應該編寫一個類繼承自Handler,而後在handleMessage()處理咱們須要的操做。

 

       另外,咱們知道,Android UI操做並非線程安全的,因此沒法在子線程中更新UI。但Andriod提供了幾種方法,能夠在子線程中通知UI線程更新界面:

 

  • Activity.runOnUiThread( Runnable )
  • View.post( Runnable )
  • View.postDelayed( Runnable, long )
  • Handler

       比較經常使用的是經過Handler,用Handler來接收子線程發送的數據,並用此數據配合主線程更新UI。那麼,只要在主線程中建立Handler對象,在子線程中調用Handler的sendMessage方法,就會把消息放入主線程的消息隊列,而且將會在Handler主線程中調用該handler的handleMessage方法來處理消息。 

 

Java代碼  複製代碼  收藏代碼
  1. package com.superonion;   
  2.   
  3. import android.app.Activity;   
  4. import android.os.Bundle;   
  5. import android.os.Message;   
  6. import android.util.Log;   
  7. import android.os.Handler;   
  8.   
  9. public class MyHandler extends Activity {   
  10.     static final String TAG = "Handler";   
  11.     Handler h = new Handler(){   
  12.         public void handleMessage (Message msg)   
  13.         {   
  14.             switch(msg.what)   
  15.             {   
  16.             case HANDLER_TEST:   
  17.                 Log.d(TAG, "The handler thread id = " + Thread.currentThread().getId() + "\n");   
  18.                 break;   
  19.             }   
  20.         }   
  21.     };   
  22.   
  23.     static final int HANDLER_TEST = 1;   
  24.     /** Called when the activity is first created. */  
  25.     @Override  
  26.     public void onCreate(Bundle savedInstanceState) {   
  27.         super.onCreate(savedInstanceState);   
  28.         Log.d(TAG, "The main thread id = " + Thread.currentThread().getId() + "\n");   
  29.   
  30.         new myThread().start();   
  31.         setContentView(R.layout.main);   
  32.     }   
  33.   
  34.     class myThread extends Thread   
  35.     {   
  36.         public void run()   
  37.         {   
  38.             Message msg = new Message();   
  39.             msg.what = HANDLER_TEST;   
  40.             h.sendMessage(msg);   
  41.             Log.d(TAG, "The worker thread id = " + Thread.currentThread().getId() + "\n");   
  42.         }   
  43.     }   
  44. }  
[java]  view plain  copy
  1. package com.superonion;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.os.Message;  
  6. import android.util.Log;  
  7. import android.os.Handler;  
  8.   
  9. public class MyHandler extends Activity {  
  10.     static final String TAG = "Handler";  
  11.     Handler h = new Handler(){  
  12.         public void handleMessage (Message msg)  
  13.         {  
  14.             switch(msg.what)  
  15.             {  
  16.             case HANDLER_TEST:  
  17.                 Log.d(TAG, "The handler thread id = " + Thread.currentThread().getId() + "\n");  
  18.                 break;  
  19.             }  
  20.         }  
  21.     };  
  22.   
  23.     static final int HANDLER_TEST = 1;  
  24.     /** Called when the activity is first created. */  
  25.     @Override  
  26.     public void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.         Log.d(TAG, "The main thread id = " + Thread.currentThread().getId() + "\n");  
  29.   
  30.         new myThread().start();  
  31.         setContentView(R.layout.main);  
  32.     }  
  33.   
  34.     class myThread extends Thread  
  35.     {  
  36.         public void run()  
  37.         {  
  38.             Message msg = new Message();  
  39.             msg.what = HANDLER_TEST;  
  40.             h.sendMessage(msg);  
  41.             Log.d(TAG, "The worker thread id = " + Thread.currentThread().getId() + "\n");  
  42.         }  
  43.     }  
  44. }  

 

 

       以上代碼中,Handler在主線程中建立後,子線程經過sendMessage()方法就能夠將消息發送到主線程中,並在handleMessage()方法中處理。



18. 怎樣退出終止App

最近兩天爲了解決Android上面退出程序問題折騰了半死,在google & baidu 上面找了好久、好久出來的徹底千篇一概,說的方法有三,可是通過我試驗後所有不行。

      三個方法分別是:

  1. killProcess, 這種方式當你kill後 Activity 會返回到上一個Activity
  2. Android Level 8(包含8)前使用一個API來操做,Level8之後又是另一種,因此不能通用
  3. 使用 FLAG_ACTIVITY_CLEAR_TOP,從 A 到 B
下面介紹本身的方式:
你們都知道 Android 的 Activity 是存着歷史棧的,好比從 A -> B -> C,C 完成 finish 後回到 B,把全部的Activity 都 finish了,程序就天然退出了。 固然在 finish 的同時也須要是否本身程序的其餘資源。因此須要想個辦法把 Activity 給存起來。而後在程序退出的地方調用它們的 finish()方法。
使用全局變量。對了,第一個想到的就是繼承 Application,代碼入下。
[java]  view plain  copy
  1. public class AgentApplication extends Application {  
  2.   
  3. private List<Activity> activities = new ArrayList<Activity>();  
  4.   
  5.     public void addActivity(Activity activity) {  
  6.         activities.add(activity);  
  7.     }  
  8.   
  9.     @Override  
  10.     public void onTerminate() {  
  11.         super.onTerminate();  
  12.           
  13.         for (Activity activity : activities) {  
  14.             activity.finish();  
  15.         }  
  16.           
  17.         onDestroy();  
  18.           
  19.         System.exit(0);  
  20.     }  
  21. }  

而後在 Activity  onCreate 的時候來調用  addActivity (),有人可能想到這個Application須要在全部的 Activity  onCreate的時候都使用,須要作一個單例實例。其實根本不須要。在 Activity 中使用  this.getApplication() 就能夠了。
最後在你須要推出程序的地方調用 application.onTerminate() 就能夠了。記住:super.onTerminate() 必須調用,代碼中的 onDestroy()是我本身的釋放其餘資源的方法,不是系統的。

運行以上代碼後,在LogCat 中會出現一行提示:
Process  包名 (pid  xxxxx)  has died.  證實你的程序退出了。如今你能夠測試了。


19. Asset目錄與res目錄的區別。

*res/raw和assets的相同點:

1.二者目錄下的文件在打包後會原封不動的保存在apk包中,不會被編譯成二進制。


*res/raw和assets的不一樣點:
1.res/raw中的文件會被映射到R.java文件中,訪問的時候直接使用資源ID即R.id.filename;assets文件夾下的文件不會被映射到R.java中,訪問的時候須要AssetManager類。
2.res/raw不能夠有目錄結構,而assets則能夠有目錄結構,也就是assets目錄下能夠再創建文件夾

*讀取文件資源:

1.讀取res/raw下的文件資源,經過如下方式獲取輸入流來進行寫操做

  • InputStream is = getResources().openRawResource(R.id.filename);  

2.讀取assets下的文件資源,經過如下方式獲取輸入流來進行寫操做

  • AssetManager am = null;  
  • am = getAssets();  
  • InputStream is = am.open("filename");  

20. Android怎麼加速啓動Activity

21. Android內存優化方法:ListView優化,及時關閉資源,圖片緩存等等。

22. Android中弱引用與軟引用的應用場景。

若是一個對象只具備軟引用,那麼若是內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存。軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。

若是一個對象只具備弱引用,那麼在垃圾回收器線程掃描的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。弱引用也能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

弱引用與軟引用的根本區別在於:只具備弱引用的對象擁有更短暫的生命週期,可能隨時被回收。而只具備軟引用的對象只有當內存不夠的時候才被回收,在內存足夠的時候,一般不被回收。

在java.lang.ref包中提供了幾個類:SoftReference類、WeakReference類和PhantomReference類,它們分別表明軟引用、弱引用和虛引用。ReferenceQueue類表示引用隊列,它能夠和這三種引用類聯合使用,以便跟蹤Java虛擬機回收所引用的對象的活動。

在Android應用的開發中,爲了防止內存溢出,在處理一些佔用內存大並且聲明週期較長的對象時候,能夠儘可能應用軟引用和弱引用技術。

下面以使用軟引用爲例來詳細說明。弱引用的使用方式與軟引用是相似的。

假設咱們的應用會用到大量的默認圖片,好比應用中有默認的頭像,默認遊戲圖標等等,這些圖片不少地方會用到。若是每次都去讀取圖片,因爲讀取文件須要硬件操做,速度較慢,會致使性能較低。因此咱們考慮將圖片緩存起來,須要的時候直接從內存中讀取。可是,因爲圖片佔用內存空間比較大,緩存不少圖片須要不少的內存,就可能比較容易發生OutOfMemory異常。這時,咱們能夠考慮使用軟引用技術來避免這個問題發生。

首先定義一個HashMap,保存軟引用對象。

複製代碼 代碼以下:

private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

再來定義一個方法,保存Bitmap的軟引用到HashMap。
複製代碼 代碼以下:

 public void addBitmapToCache(String path) {

        // 強引用的Bitmap對象

        Bitmap bitmap = BitmapFactory.decodeFile(path);

        // 軟引用的Bitmap對象

        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

        // 添加該對象到Map中使其緩存

        imageCache.put(path, softBitmap);

    }


獲取的時候,能夠經過SoftReference的get()方法獲得Bitmap對象。
複製代碼 代碼以下:

public Bitmap getBitmapByPath(String path) {

        // 從緩存中取軟引用的Bitmap對象

        SoftReference<Bitmap> softBitmap = imageCache.get(path);

        // 判斷是否存在軟引用

        if (softBitmap == null) {

            return null;

        }

        // 取出Bitmap對象,若是因爲內存不足Bitmap被回收,將取得空

        Bitmap bitmap = softBitmap.get();

        return bitmap;

    }


使用軟引用之後,在OutOfMemory異常發生以前,這些緩存的圖片資源的內存空間能夠被釋放掉的,從而避免內存達到上限,避免Crash發生。

須要注意的是,在垃圾回收器對這個Java對象回收前,SoftReference類所提供的get方法會返回Java對象的強引用,一旦垃圾線程回收該Java對象以後,get方法將返回null。因此在獲取軟引用對象的代碼中,必定要判斷是否爲null,以避免出現NullPointerException異常致使應用崩潰。


經驗分享:

到底何時使用軟引用,何時使用弱引用呢?

我的認爲,若是隻是想避免OutOfMemory異常的發生,則能夠使用軟引用。若是對於應用的性能更在乎,想盡快回收一些佔用內存比較大的對象,則能夠使用弱引用。

還有就是能夠根據對象是否常用來判斷。若是該對象可能會常用的,就儘可能用軟引用。若是該對象不被使用的可能性更大些,就能夠用弱引用。

另外,和弱引用功能相似的是WeakHashMap。WeakHashMap對於一個給定的鍵,其映射的存在並不阻止垃圾回收器對該鍵的回收,回收之後,其條目從映射中有效地移除。WeakHashMap使用ReferenceQueue實現的這種機制。


23. Bitmap的四中屬性,與每種屬性隊形的大小。

咱們知道Android系統分配給每一個應用程序的內存是有限的,Bitmap做爲消耗內存大戶,咱們對Bitmap的管理稍有不當就可能引起OutOfMemoryError,而Bitmap對象在不一樣的Android版本中存在一些差別,今天就給你們介紹下這些差別,並提供一些在使用Bitmap的須要注意的地方。

在Android2.3.3(API 10)及以前的版本中,Bitmap對象與其像素數據是分開存儲的,Bitmap對象存儲在Dalvik heap中,而Bitmap對象的像素數據則存儲在Native Memory(本地內存)中或者說Derict Memory(直接內存)中,這使得存儲在Native Memory中的像素數據的釋放是不可預知的,咱們能夠調用recycle()方法來對Native Memory中的像素數據進行釋放,前提是你能夠清楚的肯定Bitmap已再也不使用了,若是你調用了Bitmap對象recycle()以後再將Bitmap繪製出來,就會出現Canvas: trying to use a recycled bitmap錯誤,而在Android3.0(API 11)以後,Bitmap的像素數據和Bitmap對象一塊兒存儲在Dalvik heap中,因此咱們不用手動調用recycle()來釋放Bitmap對象,內存的釋放都交給垃圾回收器來作,也許你會問,爲何我在顯示Bitmap對象的時候仍是會出現OutOfMemoryError呢?

在說這個問題以前我順便提一下,在Android2.2(API 8)以前,使用的是Serial垃圾收集器,從名字能夠看出這是一個單線程的收集器,這裏的」單線程的意思並不只僅是使用一個CPU或者一條收集線程去收集垃圾,更重要的是在它進行垃圾收集時,必須暫停其餘全部的工做線程,Android2.3以後,這種收集器就被代替了,使用的是併發的垃圾收集器,這意味着咱們的垃圾收集線程和咱們的工做線程互不影響。

簡單的瞭解垃圾收集器以後,咱們對上面的問題舉一個簡單的例子,假如系統啓動了垃圾回收線程去收集垃圾,而此時咱們一會兒產生大量的Bitmap對象,此時是有可能會產生OutOfMemoryError,由於垃圾回收器首先要判斷某個對象是否還存活(JAVA語言判斷對象是否存活使用的是根搜索算法 GC Root Tracing),而後利用垃圾回收算法來對垃圾進行回收,不一樣的垃圾回收器具備不一樣的回收算法,這些都是須要時間的, 發生OutOfMemoryError的時候,咱們要明確究竟是由於內存泄露(Memory Leak)引起的仍是內存溢出(Memory overflow)引起的,若是是內存泄露咱們須要利用工具(好比MAT)查明內存泄露的代碼並進行改正,若是不存在泄露,換句話來講就是內存中的對象確實還必須活着,那咱們能夠看看是否能夠經過某種途徑,減小對象對內存的消耗,好比咱們在使用Bitmap的時候,應該根據View的大小利用BitmapFactory.Options計算合適的inSimpleSize來對Bitmap進行相對應的裁剪,以減小Bitmap對內存的使用,若是上面都作好了仍是存在OutOfMemoryError(通常這種狀況不多發生)的話,那咱們只能調大Dalvik heap的大小了,在Android 3.1以及更高的版本中,咱們能夠在AndroidManifest.xml的application標籤中增長一個值等於「true」的android:largeHeap屬性來通知Dalvik虛擬機應用程序須要使用較大的Java Heap,可是咱們也不鼓勵這麼作。



Bitmap:

(1)     public Bitmap (int width,int height,int stride,     PixelFormat format,IntPtr scan0)

用指定的大小、像素格式和像素數據初始化 Bitmap 類的新實例。

(2)     LockBits():,就是把圖像的內存區域根據格式鎖定,拿到那塊內存的首地址。這樣就能夠直接改寫這段內存了。這個方法的設計是挺好,惋惜都是C++做爲源泉來的,.NET Framework裏面根本就不推薦用指針,須要用Marshal.Copy把內容Copy到一個byte數組裏面,而後處理完了再Copy回去。

(3)     UnlockBits():從系統內存解鎖此 Bitmap。

 

BitmapData:位圖圖像的屬性

(1)   Height獲取或設置 Bitmap 對象的像素高度。有時也稱做掃描行數。

(2)   PixelFormat: 獲取或設置返回此 BitmapData 對象的 Bitmap 對象中像素信息的格式。

(3)   Reserved: 保留。不要使用。

(4)   Scan0: 獲取或設置位圖中第一個像素數據的地址。它也能夠當作是位圖中的第一個掃描行。

(5)   Stride: 獲取或設置 Bitmap 對象的跨距寬度(也稱爲掃描寬度)。

Stride:跨距是單行像素(一個掃描行)的寬度,舍入爲一個 4 字節的邊界。跨距老是大於或等於實際像素寬度。若是跨距爲正,則位圖自頂向下。若是跨距爲負,則位圖顛倒。Stride是指圖像每一行須要佔用的字節數。根據BMP格式的標準,Stride必定要是4的倍數。據個例子,一幅1024*768的24bppRgb的圖像,每行有效的像素信息應該是1024*3 = 3072。由於已是4的倍數,因此Stride就是3072。那麼若是這幅圖像是35*30,那麼一行的有效像素信息是105,可是105不是4的倍數,因此填充空字節,Stride應該是108。這一行計算出來的offset就是3。一要注意必須是4的倍數,二單位是字節!

(6)   Width: 獲取或設置 Bitmap 對象的像素寬度。這也能夠看做是一個掃描行中的像素數。

 

PixelFormat:

(1)     Format24bppRgb,也就是24位色。在這種格式下3個字節表示一種顏色,也就是咱們一般所知道的R,G,B,因此每一個字節表示顏色的一個份量。

(2)     Format32bppArgb,除了RGB,在圖像中還存在一個通道,叫作A。這個A就是用來描述當前像素是透明,半透明,仍是全透明的份量。這個通道是2個叫Catmull和Smith在上世紀70年代初發明的。經過這個份量,咱們能夠進行alpha混合的一些計算。從而使表面的圖像和背景圖像混合,從而形成透明半透明的效果。在這種格式下A做爲一個byte,取值能夠從0到255,那麼0表示圖像徹底透明,則徹底不可見,255則表示圖像徹底不透明。每一個像素均可以實現這種透明或者半透明的效果。更詳細解釋能夠參考http://en.wikipedia.org/wiki/Alpha_compositing,或者去買本數字圖像處理的書回來看。

(3)     Format32bppPArgb,這叫作premultiplied alpha,就是說在RGB份量裏面,alpha份量的數據已經被預先乘進去了。好比說,一個半透明的紅色點,在ARGB下,矢量是(255,0,0,128),而在PARGB下就變成了(128,0,0,128)。這是爲了避免要每次都作乘法。

(4)     Bitmap保存成爲一個文件,那麼必須用png格式,纔可以保存alpha通道的信息。若是你存爲JPG/BMP/GIF,那麼alpha通道的信息將會被丟失。若是存爲BMP,那麼文件格式將變成Format32bppRgb,其中1個字節再也不使用;若是保存爲JPEG,那麼是Format24bppRgb;存爲GIF,格式將變成Format8bppIndexed。根據標準,BMP/JPG原本就不支持透明通道,因此沒有可能保留透明信息。GIF卻是支持透明,可是GIF中顏色的信息都是索引,因此Alpha的解釋對GIF徹底沒有效果,

 

BitmapInfoHeader:

biHeight:說明圖象的高度,以象素爲單位。

    若是該值是一個正數,說明BtimapBottom up DIB,起始點是左下角,也就是從圖像的最下面一行掃描,位圖數組中獲得的第一行數據實際是圖形的最下面的一行。圖像是倒向的;

    若是該值是一個負數,則說明圖像是TopDown DIB,起始點是左上角,圖像從最上面一行掃描,圖像正向的。

   大多數的BMP文件都是倒向的位圖,也就是時,高度值是一個正數。(注:當高度值是一個負數時(正向圖像),圖像將不能被壓縮(也就是說biCompression成員將不能是BI_RLE8BI_RLE4


24. ViewView Group分類。自定義View過程:onMeasure()onLayout()onDraw()

25. Touch事件分發機制。

http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html

Android 編程下 Touch 事件的分發和消費機制

Android 中與 Touch 事件相關的方法包括:dispatchTouchEvent(MotionEvent ev)onInterceptTouchEvent(MotionEvent ev)onTouchEvent(MotionEvent ev);可以響應這些方法的控件包括:ViewGroup 及其子類Activity。方法與控件的對應關係以下表所示:

Touch 事件相關方法   方法功能  
  ViewGroup   
     Activity     
  public boolean dispatchTouchEvent(MotionEvent ev) 事件分發 
 Yes  Yes
  public boolean onInterceptTouchEvent(MotionEvent ev)  
事件攔截 
 Yes  No
  public boolean onTouchEvent(MotionEvent ev) 事件響應 
 Yes  Yes

從這張表中咱們能夠看到 ViewGroup 及其子類對與 Touch 事件相關的三個方法均能響應,而 Activity 對 onInterceptTouchEvent(MotionEvent ev) 也就是事件攔截不進行響應。另外須要注意的是 View 對dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev) 的響應的前提是能夠向該 View 中添加子 View,若是當前的 View 已是一個最小的單元 View(好比 TextView),那麼就沒法向這個最小 View 中添加子 View,也就沒法向子 View 進行事件的分發和攔截,因此它沒有 dispatchTouchEvent(MotionEvent ev) 和 onInterceptTouchEvent(MotionEvent ev),只有 onTouchEvent(MotionEvent ev)

1、Touch 事件分析

▐ 事件分發:public boolean dispatchTouchEvent(MotionEvent ev)

Touch 事件發生時 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法會以隧道方式(從根元素依次往下傳遞直到最內層子元素或在中間某一元素中因爲某一條件中止傳遞)將事件傳遞給最外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法,並由該 View 的 dispatchTouchEvent(MotionEvent ev) 方法對事件進行分發。dispatchTouchEvent 的事件分發邏輯以下:

  • 若是 return true,事件會分發給當前 View 並由 dispatchTouchEvent 方法進行消費,同時事件會中止向下傳遞;
  • 若是 return false,事件分發分爲兩種狀況:
  1. 若是當前 View 獲取的事件直接來自 Activity,則會將事件返回給 Activity 的 onTouchEvent 進行消費;
  2. 若是當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的  onTouchEvent 進行消費。
  • 若是返回系統默認的 super.dispatchTouchEvent(ev),事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。

▐ 事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev) 

外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系統默認的 super.dispatchTouchEvent(ev) 狀況下,事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件攔截邏輯以下:

  • 若是 onInterceptTouchEvent 返回 true,則表示將事件進行攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理;
  • 若是 onInterceptTouchEvent 返回 false,則表示將事件放行,當前 View 上的事件會被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發;
  • 若是 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默認會被攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理。

▐ 事件響應:public boolean onTouchEvent(MotionEvent ev)

在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 而且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的狀況下 onTouchEvent 會被調用。onTouchEvent 的事件響應邏輯以下:

  • 若是事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回了 false,那麼這個事件會從當前 View 向上傳遞,而且都是由上層 View 的 onTouchEvent 來接收,若是傳遞到上面的 onTouchEvent 也返回 false,這個事件就會「消失」,並且接收不到下一次事件。
  • 若是返回了 true 則會接收並消費該事件。
  • 若是返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。

到這裏,與 Touch 事件相關的三個方法就分析完畢了。下面的內容會經過各類不一樣的的測試案例來驗證上文中三個方法對事件的處理邏輯。

2、Touch 案例介紹

一樣在開始進行案例分析以前,我須要說明測試案例的結構,由於全部的測試都是針對這一個案例來進行的,測試中只是經過修改每一個控件中與 Touch 事件相關的三個方法的返回值來體現不一樣的狀況。先來看張圖:

Touch 事件案例佈局 UI

上面的圖爲測試案例的佈局文件 UI 顯示效果,佈局文件代碼以下:

複製代碼
<?xml version="1.0" encoding="utf-8"?>
<cn.sunzn.tevent.TouchEventFather xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#468AD7"
    android:gravity="center"
    android:orientation="vertical" >

    <cn.sunzn.tevent.TouchEventChilds
        android:id="@+id/childs"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:background="#E1110D"
        android:text="@string/hello" />

</cn.sunzn.tevent.TouchEventFather>
複製代碼

藍色背景爲一個自定義控件 TouchEventFather,該控件爲外層 View,繼承自 LinearLayout,實現代碼以下:

複製代碼
package cn.sunzn.tevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class TouchEventFather extends LinearLayout {

    public TouchEventFather(Context context) {
        super(context);
    }

    public TouchEventFather(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("sunzn", "TouchEventFather | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("sunzn", "TouchEventFather | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onInterceptTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.d("sunzn", "TouchEventFather | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

}
複製代碼

紅色背景爲一個自定義控件 TouchEventChilds,該控件爲內層 View,爲 TouchEventFather 的子 View,一樣繼承自 LinearLayout,實現代碼以下:

複製代碼
package cn.sunzn.tevent;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class TouchEventChilds extends LinearLayout {

    public TouchEventChilds(Context context) {
        super(context);
    }

    public TouchEventChilds(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.e("sunzn", "TouchEventChilds | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("sunzn", "TouchEventChilds | onInterceptTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onInterceptTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent ev) {
        Log.d("sunzn", "TouchEventChilds | onTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.onTouchEvent(ev);
    }

}
複製代碼

接着實現 Activity 的代碼,由於控件全部的事件都是經過 Activity 的 dispatchTouchEvent 進行分發的;除此以外還須要重寫 Activity 的 onTouchEvent 方法,這是由於若是一個控件直接從 Activity 獲取到事件,這個事件會首先被傳遞到控件的 dispatchTouchEvent 方法,若是這個方法 return false,事件會以冒泡方式返回給 Activity 的 onTouchEvent 進行消費。實現代碼以下:

複製代碼
package cn.sunzn.tevent;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;

public class TouchEventActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.w("sunzn", "TouchEventActivity | dispatchTouchEvent --> " + TouchEventUtil.getTouchAction(ev.getAction()));
        return super.dispatchTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent event) {
        Log.w("sunzn", "TouchEventActivity | onTouchEvent --> " + TouchEventUtil.getTouchAction(event.getAction()));
        return super.onTouchEvent(event);
    }

}
複製代碼

最後再附上 TouchEventUtil 的代碼,TouchEventUtil 中並無作多少事情,只是將以上 2 個自定義控件中各個方法的 MotionEvent 集中到一個工具類中並將其對應的動做以 String 形式返回,這樣處理更便於實時觀察控件的事件。代碼以下:

複製代碼
package cn.sunzn.tevent;

import android.view.MotionEvent;

public class TouchEventUtil {
    
    public static String getTouchAction(int actionId) {
        String actionName = "Unknow:id=" + actionId;
        switch (actionId) {
        case MotionEvent.ACTION_DOWN:
            actionName = "ACTION_DOWN";
            break;
        case MotionEvent.ACTION_MOVE:
            actionName = "ACTION_MOVE";
            break;
        case MotionEvent.ACTION_UP:
            actionName = "ACTION_UP";
            break;
        case MotionEvent.ACTION_CANCEL:
            actionName = "ACTION_CANCEL";
            break;
        case MotionEvent.ACTION_OUTSIDE:
            actionName = "ACTION_OUTSIDE";
            break;
        }
        return actionName;
    }
    
}
複製代碼

3、Touch 案例分析

 Case 1 

攔截條件
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
TouchEventFather false super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
TouchEventChilds super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
運行結果
Level Time PID Application Tag  Text
 W  05-10 03:41:19.743  414 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_DOWN
 E  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_DOWN
 W  05-10 03:41:19.743  414 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_DOWN
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_UP
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_UP
結果分析
代碼運行後,事件首先由 TouchEventActivity 的 dispatchTouchEvent 方法分發給 TouchEventFather 控件的dispatchTouchEvent,而該控件的 dispatchTouchEvent 返回 false,表示對獲取到的事件中止向下傳遞,同時也不對該事件進行消費。因爲 TouchEventFather 獲取的事件直接來自 TouchEventActivity ,則會將事件返回給 TouchEventActivity  的 onTouchEvent 進行消費,最後直接由 TouchEventActivity 來響應手指移動和擡起事件。

  Case 2

攔截條件
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
TouchEventFather true super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
TouchEventChilds super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
運行結果
Level Time PID Application Tag  Text
 W  05-10 03:41:19.743  414 cn.sunzn.tevent  sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_DOWN
 E  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_DOWN
 W  05-10 03:41:19.743  414 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
 E  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
 E  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
 E  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_MOVE
 W  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_UP
E  05-10 03:41:19.743  414  cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_UP
結果分析
代碼運行後,事件首先由 TouchEventActivity 的 dispatchTouchEvent 方法分發給 TouchEventFather 控件的 dispatchTouchEvent,而該控件的 dispatchTouchEvent 返回 true,表示分發事件到 TouchEventFather 控件並由該控件的 dispatchTouchEvent 進行消費;TouchEventActivity 不斷的分發事件到 TouchEventFather 控件的dispatchTouchEvent,而 TouchEventFather 控件的 dispatchTouchEvent 也不斷的將獲取到的事件進行消費。

 Case 3

攔截條件
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
TouchEventFather super.dispatchTouchEvent(ev) true super.onTouchEvent(ev)
TouchEventChilds super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
運行結果
Level Time PID Application Tag  Text
W 05-10 05:34:46.333 481 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_DOWN
E 05-10 05:34:46.333 481 cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_DOWN
I 05-10 05:34:46.333 481 cn.sunzn.tevent sunzn   TouchEventFather | onInterceptTouchEvent --> ACTION_DOWN
D 05-10 05:34:46.333 481 cn.sunzn.tevent sunzn   TouchEventFather | onTouchEvent --> ACTION_DOWN
W 05-10 05:34:46.333 481 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_DOWN
W 05-10 05:34:46.343 481 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
W 05-10 05:34:46.343 481 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
W 05-10 05:34:46.423 481 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
W 05-10 05:34:46.423 481 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
W 05-10 05:34:46.433 481 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
W 05-10 05:34:46.433 481 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
W 05-10 05:34:46.442 481 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_UP
W 05-10 05:34:46.442 481 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_UP
結果分析
代碼運行後,事件首先由 TouchEventActivity 的 dispatchTouchEvent 方法分發給 TouchEventFather 控件的 dispatchTouchEvent,而該控件的 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev),表示對事件進行分發並向下傳遞給 TouchEventFather 控件的 onInterceptTouchEvent 方法,該方法返回 true 表示對所獲取到的事件進行攔截並將事件傳遞給 TouchEventFather 控件的 onTouchEvent 進行處理,TouchEventFather 控件的 onTouchEvent 返回 super.onTouchEvent(ev) 表示對事件沒有作任何處理直接將事件返回給上級控件,因爲 TouchEventFather 獲取的事件直接來自 TouchEventActivity,因此 TouchEventFather 控件的 onTouchEvent 會將事件以冒泡方式直接返回給 TouchEventActivity 的 onTouchEvent 進行消費,後續的事件則會跳過 TouchEventFather 直接由 TouchEventActivity 的 onTouchEvent 消費來自 TouchEventActivity 自身分發的事件。

 Case 4

攔截條件
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
TouchEventFather super.dispatchTouchEvent(ev) false super.onTouchEvent(ev)
TouchEventChilds super.dispatchTouchEvent(ev) super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
運行結果
Level Time PID Application Tag  Text
W 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_DOWN
E 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventFather | dispatchTouchEvent --> ACTION_DOWN
I 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventFather | onInterceptTouchEvent --> ACTION_DOWN
E 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventChilds | dispatchTouchEvent --> ACTION_DOWN
I 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventChilds | onInterceptTouchEvent --> ACTION_DOWN
D 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventChilds | onTouchEvent --> ACTION_DOWN
D 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventFather | onTouchEvent --> ACTION_DOWN
W 05-10 06:31:47.565 512 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_DOWN
W 05-10 06:31:47.652 512 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
W 05-10 06:31:47.652 512 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
W 05-10 06:31:47.732 512 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
W 05-10 06:31:47.732 512 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
W 05-10 06:31:47.812 512 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_MOVE
W 05-10 06:31:47.812 512 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_MOVE
W 05-10 06:31:47.892 512 cn.sunzn.tevent sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_UP
W 05-10 06:31:47.892 512 cn.sunzn.tevent sunzn   TouchEventActivity | onTouchEvent --> ACTION_UP
結果分析
代碼運行後,事件首先由 TouchEventActivity 的 dispatchTouchEvent 方法分發給 TouchEventFather 控件的 dispatchTouchEvent,而該控件的 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev),表示對事件進行分發並向下傳遞給 TouchEventFather 控件的 onInterceptTouchEvent 方法,該方法返回 false 表示事件會被放行並傳遞到子控件 TouchEventChilds 的 dispatchTouchEvent 方法,一樣 TouchEventChilds 的 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev),表示對事件進行分發並向下傳遞給 TouchEventChilds 控件的 onInterceptTouchEvent 方法TouchEventChilds 的 onInterceptTouchEvent 方法返回 super.onInterceptTouchEvent(ev) 默認會將事件傳遞給 TouchEventChilds 的 onTouchEvent 進行處理,TouchEventChilds 的 onTouchEvent 返回 super.onTouchEvent(ev) 表示對事件沒有作任何處理直接將事件返回給上級控件,因爲 TouchEventChilds 獲取的事件直接來自 TouchEventFather,因此 TouchEventChilds 控件的 onTouchEvent 會將事件以冒泡方式直接返回給 TouchEventFather 的 onTouchEvent 進行消費,而 TouchEventFather 的 onTouchEvent 也返回了 super.onTouchEvent(ev),一樣 TouchEventFather 的 onTouchEvent 也會將事件返回給上級控件,而 TouchEventFather 獲取的事件直接來自 TouchEventActivity,因此 TouchEventFather 控件的 onTouchEvent 會將事件以冒泡方式直接返回給 TouchEventActivity 的 onTouchEvent 進行消費,後續的事件則會跳過 TouchEventFather 和 TouchEventChilds 直接由 TouchEventActivity 的 onTouchEvent 消費來自 TouchEventActivity 自身分發的事件。

 Case 5

攔截條件
控件名稱 dispatchTouchEvent 返回值 onInterceptTouchEvent 返回值 onTouchEvent 返回值
TouchEventActivity super.dispatchTouchEvent(ev) --- super.onTouchEvent(ev)
TouchEventFather super.dispatchTouchEvent(ev) false super.onTouchEvent(ev)
TouchEventChilds true super.onInterceptTouchEvent(ev) super.onTouchEvent(ev)
運行結果
Level Time PID Application Tag  Text
 W  05-10 08:11:18.403 574  cn.sunzn.tevent  sunzn   TouchEventActivity | dispatchTouchEvent --> ACTION_DOWN
 E
相關文章
相關標籤/搜索