Android動態ui介面設計

68,882 views

Published on

Published in: Education
2 Comments
132 Likes
Statistics
Notes
No Downloads
Views
Total views
68,882
On SlideShare
0
From Embeds
0
Number of Embeds
25,940
Actions
Shares
0
Downloads
0
Comments
2
Likes
132
Embeds 0
No embeds

No notes for slide

Android動態ui介面設計

  1. 1. 第6節: Android動態UI介面設計
  2. 2. Android動態UI介面設計 Android動態元件設計  ListView  Gallery  GridView  ImageSwitch Adapter元件  ArrayAdapter  SimpleAdapter  自定Adapter 226
  3. 3. 進階UI控制元件-Adapter使用• Android 框架只負責應用程式的流程架構,框架設計師並不會知道最 終使用框架開發出的Application長的是什麼樣子。• 應用程式設計師並不能直接去修改Android框架的內容,如此會造成 相容性問題,因此無法直接透過框架修改來決定應用程式的長相.• 為解決上述兩個問題,在Android裡設計了Adapter類別來做為 Android框架與應用程式之間的橋樑• 為應付各種不同的資料數據來源,與不同的View元件,因此發展出各 式不同的Adapter元件。為你把關每一道 學習品質 227
  4. 4. 進階UI控制元件-Adapter使用 Adapter• 常見的Adapter的類別 ListAdapter SpinnerAdapter BaseAdapter ArrayAdapter CursorAdapter SimpleAdapter ResourceCursorAdapter• 常見Adapter對應到UI端的類別 SimpleCursorAdapter ViewGroup AdapterView AbsListView AbsSpinner ListView GridView Gallery Spinner為你把關每一道 學習品質 228
  5. 5. 進階UI控制元件-Adapter使用• ArrayAdapter用途 • 作用為陣列與ListView之間的橋梁 • 可將陣列中定義的資料逐一的對應到ListView之中顯示 • 一般ArrayAdapter中顯示的ListView每行通常只有一個TextView ArrayList ListView Jarey Jarey TextView-Two John John ArrayAdapter May TextView-Two Ken May TextView-Two Ken TextView-Two為你把關每一道 學習品質 229
  6. 6. 進階UI控制元件-Adapter使用• SimpleAdapter用途 • simpleAdapter可以定製每一列ListView中要顯示的內容 • 一般ListView中每一列的版面配置(Layout)會撰寫在XML檔中,在 由SimpleAdapter負責將內容(Data)填入Layout中的元件(View)。 ArrayList ListView HashMap Jarey 09222222 Name Jarey John Phone 0922 SimpleAdapter 09333333 HashMap May 09444444 Name John Phone 0933 Ken 095555555為你把關每一道 學習品質 230
  7. 7. 進階UI控制元件-Adapter使用• 建構式ArrayAdapter (Context context, int resource, int textViewResourceId, T[] objects) • contex:要顯示的Context容器 • resource:layout定義,可以使用Android系統內建,或是自定layout. • textViewResourceId:位於layout中的textView,用來顯示data內容. • data:要顯示的資料內容• 建構式SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to) • contex:要顯示的Context容器 • data:基於Map的List. Map中包含了ListView每一列所需要的資料 • resource:顯示的Layout版型,此layout中至少要包含在to參數中所出現的 View。可使系統提供的layout,亦可自行定義 • from:名稱陣列,每一個名稝都對應Data中的索引鍵。 • to:為TextView類形的陣列,必須要在layout中可以被索引到。為你把關每一道 學習品質 231
  8. 8. 範例練習 範例專案名: ex05_01_Spinner 練習目標:  學習使用Spinner,利用ArrayAdapter提供 Spinner內容。 程式撰寫  建置Spinner元件,其內容資料於XML中利用 string array xml標籤編寫內容。  於Activity中讀入string array內容至記憶體,並套 用ArrayAdapter提供資料給Spiiner元件。 操作練習  模擬器練習,分練習XML方式導入靜態資料,與 使用Adapter方式動態載入資料兩種應用方式。為你把關每一道 學習品質 232
  9. 9. 進階UI控制元件-ListView• ListView為Android中用來顯示大量資 料的一個很重要的UI介面• 由於手機受限於螢幕大小,當欲顯示 的資料數量超過一個頁面可以顯示時, 就可以利用ListView來做資料顯示• ListView需搭配Adapter使用, ListView會自動依顯示控制的需求透 過Adapter介面取得資料。• ListView提供OnItemClickListener() 方法,可以取得用戶的動件事件(例 如點選了那一個ListView元件)為你把關每一道 學習品質 233
  10. 10. 進階UI控制元件-ListView• ListView使用方式 • set data source to Adapter • get ListView from R.layout • set adapter to ListView • set click listener for each list item• ListView Event處理函式 • onItemClick(AdapterView<?> parent, View view, int position, long id) • onItemLongClick(AdapterView<?> parent, View view, int position, long id) • onItemSelected(AdapterView<?> parent, View view, int position, long id)為你把關每一道 學習品質 234
  11. 11. 範例練習 範例專案名: ex05_02_Adapter 練習目標:  了解各式adapter的使用  了解ListView與adapter之間的關係與運作 流程 程式撰寫  設計一ListView元件,並練習接上各式不 同的adapter與不同的資料來源串接  SimpleAdapter, ArrayAdapter 操作練習  透過模擬器操作不同的Activity,每一個 Activity代表一個不同Adapter的連接範例。為你把關每一道 學習品質 235
  12. 12. 進階UI控制元件-ListActivity• ListActivity使用時機 • 當一個Activity中只有顯示ListView,而且此ListView又會填滿整 個螢幕,此時可以直接使用ListActivity來代替Activity。 • ListActivity為一個包含了ListView的Activity,以wrapper的方式將 ListVIew包裝起來,方便更容易與直接的控制。• ListActivity 預設ID元件 • ListActivity使用的layout檔中必須定義一個ListView,且此 ListView的id必須固定為[ @id/android:list ] • 另外可定義一個TextView當ListView裡沒有資料時可以顯示,此 TextView的id必須固定為[ @id/android:empty]為你把關每一道 學習品質 236
  13. 13. 進階UI控制元件-ListView• ListActivity使用方式 • Application extended ListActivity • set data source to Adapter • get ListView from ListActivity • set adapter to ListView • set click listener for each list item• ListView Event處理函式 • 在ListActivity中不用註冊Listener,而是直接以覆寫 (Override)來進行事件處理 • 可處理的Event函式與ListView相同為你把關每一道 學習品質 237
  14. 14. 範例練習 範例專案名: ex05_03_ListActivity 練習目標:  了解ListActivity的應用 程式撰寫  修改前一隻範例程式,試著將其改寫成 ListActivity  Activity直接繼承自ListActivity 操作練習  透過模擬器操作,注意ListActivity與ListView在對 於listener event 處理的寫法是否有所不同?為你把關每一道 學習品質 238
  15. 15. 進階UI控制元件-Adapter使用• 自行定義Adapter內容 • 繼承(extended)BaseAdapter • 於建構式接收要處理的資料(Data)來源 • 覆寫(Override)以下四個Method • public int getCount() ; • 傳回Data的數量.(ListView的列數) • public Object getItem(int position); • 傳回指定的Data元件. • public long getItemId(int position); • 傳回Data的識別ID • public View getView(int position, View convertView, ViewGroup parent) • 傳回Layout與Data組合後的View元件(顯示於畫面上) • ※當Data內容有變更時,必須呼叫notifyDataSetChanged才會更新 ListView中的內容。 239為你把關每一道 學習品質
  16. 16. 進階UI控制元件-Adapter使用• Adapter與Android框架運作流程Android Activity AdapterView Adapter 框架 3.getCount() 4.getView()Application 2.setContentView() 1.onCreate() Map MyAdapter ListActivity Array Data Layout Cursor View XML為你把關每一道 學習品質 240
  17. 17. 範例練習 範例專案名: ex05_04_ImageAdapter 練習目標:  了解如何自行擴充Adapter類別  學習如何以高效率,最佳化記憶體方式設計 Adapter 程式撰寫  設計一個ImageAdapter類別,繼承至BaseAdapter 抽像類別  實作BaseAdapter的四個抽像函式  利用系統快取與ViewHolder提高Adapter的效能 操作練習  透過模擬器操作為你把關每一道 學習品質 241
  18. 18. 進階UI控制元件-ListView資料快取問題• ListView為了節省記憶體與提高顯示的效能,會利用快取 機制,在ListView滑動時會重覆利用己使用過,目前不在 畫面上的View元件。 ConvertView ConvertView 重覆利用 ConvertView 由下往上滑動 ConvertView ConvertView為你把關每一道 學習品質 242
  19. 19. 進階UI控制元件-ListView內元件如何監聽事件• ListView本身可以透過註冊setOnItemSelectListener來監 聽用戶點擊了那一條List Item。• 若希望能為List Item中的每一個View元件單獨的設置 Listener事件,那麼需要注意以下二個問題: • ListView本身具有快取,List Item是重覆利用的,這將 導致用戶滑動List後,所點擊的元件可能與你當初註冊 的Listener元件不一致(如點了第5個Item中的一個 Button,但實際上第5個Item等同於重覆利用的第1個 Item)為你把關每一道 學習品質 243
  20. 20. 進階UI控制元件-ListView內元件如何監聽事件 • 由於Item元件是重覆利用,因此你將無法判斷用戶點 擊的Button為第幾個Item選項的Button,同時若你在每 次getView時都為新的Item重新設置新的Listener那麼 將會造成記憶體大量的浪費,同時也會降低顯示的效 能。 重覆利用 點擊第4個Item內的checkbox, 但呼叫的listener將與第1個 item相同。為你把關每一道 學習品質 244
  21. 21. 進階UI控制元件-ListView內元件如何監聽事件• 解決LIstView 內Item元件的監聽事件的方法,可以透過 View.setTag方式,為將該List Item的資料快取在要被監 聽的View元件身上。如此當用戶點擊了CheckBox後,可 以在透過getTag的方式取得該List Item的資料做為判斷。 List Data Set setTag(Object); Profile1 Profile2 重覆利用 Profile3 Profile4為你把關每一道 學習品質 245
  22. 22. 進階UI控制元件-ListView資料快取問題• 重覆使用的View元件會造成顯示不一致的問題,由其是當 View元件中有使用到如CheckBox這類的選單元件。 雖然只勾了一個選項。但往 上滑動時會造成第五個選項 看起來也是被勾選的 重覆利用 由下往上滑動為你把關每一道 學習品質 246
  23. 23. 進階UI控制元件-ListView資料快取問題• 解決View元件顯示不一致的狀況,必須將ListView中每一 條的UI狀態記錄起來,在每次呼叫getView時去重新設定 UI狀態。 List Data狀態表 勾選 由下往上滑動 未勾選重覆利用 未勾選 未勾選 未勾選 在準備顯示到畫面上前,重新將 View元件上的UI狀態做更新,將 勾選取消掉。為你把關每一道 學習品質 247
  24. 24. 範例練習 範例專案名: ex05_04_ImageAdapter 練習目標:  直接修改ImageAdapter範例,加入刪除模式功能  於刪除模式下顯示CheckBox元件提供用戶勾選。 程式撰寫  建置ListData 狀態表記錄每個ListView中每個元件的UI 狀態。  將ListView中的Layout所有的元件forcues狀能設為false 以避免遮避到ListView本身的onItemSelect事件。  註冊CheckBox的Onclick事件,並將狀態資料帶入至 Tag中提供供識別所點選的元件。 操作練習  透過模擬器操作為你把關每一道 學習品質 248
  25. 25. 進階UI控制元件-ImageSwitch&Gallery• ImageSwitch ViewGroup • 主要用來做圖片切換與自動播放用. AdapterView 可支援不同的進場與退場動畫特效 AbsSpinner• Gallery Gallery • 提供一個拖拉式圖片瀏覽與選擇介面 • 僅支援水平擺設方式 • 通常搭配ImageSwitch可用來做成簡 易的圖片瀏覽器。為你把關每一道 學習品質 249
  26. 26. 進階UI控制元件-ImageSwitch&Gallery• ImageSwitch元件 • XML宣告方式 • 程式碼使用方式 功能 程式碼 package Import android.widget.ImageSwitch; Reference ImageSwitch sw1 = (ImageSwitch) findViewById(R.id.imsw1); set sw1.setFactory(ViewSwitch.ViewFactory factory) sw1.setInAnimation(Animation inAnimation) sw1.setOutAnimation(Animation outAnimation) sw1.setImageResource (int resid)為你把關每一道 學習品質 250
  27. 27. 進階UI控制元件-ImageSwitch&Gallery• Gallery元件 • XML宣告方式 • 程式碼使用方式 功能 程式碼 package Import android.widget.Gallery; Reference Gallery gal1 = (Gallery) findViewById(R.id.gallery); set gal1.setAdapter(SpinnerAdapter adapter); Call back gal1.setOnItemSelectedListener(AdapterView.OnItemSelect edListener listener);為你把關每一道 學習品質 251
  28. 28. 範例練習 範例專案名: ex05_05_Gallery 練習目標:  透過Gallery與ImageSwitch設計一個簡易的相片 瀏覽器  復習先前教過的Adapter的應用 程式撰寫  撰寫一個Activity包含ImageSwitch與Gallery元件。  撰寫一個ImageAdapter提供給Gallery。 操作練習  透過模擬器操作為你把關每一道 學習品質 252
  29. 29. 進階UI控制元件-GridView• GridView為網格檢視元件,可以將很很多圖片以 ViewGroup 固定的大小排列顯示在畫面上可用來做為相簿與圖 片瀏覽等應用 AdapterView• GridView的資料取至於ListAdapter,一般要用做 AbsListView 相簿使用需要自行實作ImageAdapter(繼承至 BaseAdapter). GridView為你把關每一道 學習品質 253
  30. 30. 進階UI控制元件-GridView• GridView元件 • XML宣告方式 • 程式碼使用方式 功能 程式碼 package Import android.widget.GridView; Reference GridView grid= (GridView ) findViewById(R.id.grid); set grid.setAdapter(BaseAdapter adapter); Call back grid.setOnItemSelectedListener(AdapterView.OnItemSele ctedListener listener);為你把關每一道 學習品質 254
  31. 31. 第7節: Android 通知元件設計 Android Widget開發
  32. 32. Android 通知元件設計 Android Menu元件設計  Option Menu  Context Menu Android通知元件設計  Dialog  Toast  Notification  Menu AppWidget桌面元件設計  Widget開發上的限制  Remove View的互動方式 256
  33. 33. 進階UI控制元件-Menu選單• Menu選單操作元件 • Option Menu • 按下menu建後顯 示 • Context Menu • 長時間按下一個視 窗後顯示 • Submenu • 子選單,可被 option或context Menu所呼叫為你把關每一道 學習品質 257
  34. 34. 進階UI控制元件-Option Menu• Option Menu • 選單位於整體畫面的下方,透過按下手機上的 Menu按鈕顯示 • 一個畫面最多可以安置6個選單項目,不支援 Checkbox及RadioButton • 超過6個選單的項目將會以擴充列的方式顯示。• Option Menu 使用XML宣告為你把關每一道 學習品質 258
  35. 35. 進階UI控制元件-Option Menu• Inflating Option Menu Resource File • 透過MenuInflater.inflate() 協助載入xml資源檔 • public void inflate (int menuRes, Menu menu) @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.game_menu, menu); return true; }為你把關每一道 學習品質 259
  36. 36. 進階UI控制元件-Option Menu• 建立Option Menu 使用MenuItem add函式 Public boolean onCreateOptionsMenu(Menu menu){ menu.add(0,MENU_ITEM_ID1, 0 “ MENU_ITEM_1”); menu.add(0,MENU_ITEM_ID2, 0 “ MENU_ITEM_2”); return true; }• MenuItem add函式 • public abstract MenuItem add (int groupId, int itemId, int order, int titleRes) • groupid:群組ID • itemid:項目ID • order:排序方式 • titleRes:每一個Item上的標題為你把關每一道 學習品質 260
  37. 37. 進階UI控制元件-Option Menu• 建立Menu• Call Back Function為你把關每一道 學習品質 261
  38. 38. 進階UI控制元件-Context Menu• Context Menu • 與在PC上的滑鼠右鍵功能類似,當按位某一 個View 2秒鐘,就會出現一個浮動式的 Context Menu。 • 較常使用在ListView上。• 建立Option Menu 使用MenuItem add函式為你把關每一道 學習品質 262
  39. 39. 進階UI控制元件-Context Menu• Context Menu的callback函式為你把關每一道 學習品質 263
  40. 40. 進階UI控制元件-Submenu• Submenu • Context Menu與Option Menu可以在內嵌一個 子選單(Submenu)做為擴充。 • 子選單(Submenu)不能在擴充子選單• 建立Submenu 使用xml為你把關每一道 學習品質 264
  41. 41. 進階UI控制元件-Submenu • 建立Submenu 使用SubMenu addSubMenu函式 @Overridepublic boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); SubMenu fileMenu = menu.addSubMenu(GALLERY, SUBMANU01, Menu.NONE, "File"); SubMenu editMenu = menu.addSubMenu(GALLERY, SUBMANU02, Menu.NONE, "Edit"); fileMenu.add(GALLERY, MANU01, Menu.NONE, "new"); fileMenu.add(GALLERY, MANU02, Menu.NONE, "open"); fileMenu.add(GALLERY, MANU03, Menu.NONE, "save"); editMenu.add(GALLERY, MANU04, Menu.NONE, "undo"); editMenu.add(GALLERY, MANU05, Menu.NONE, "redo"); return true;} 為你把關每一道 學習品質 265
  42. 42. 進階UI控制元件-Submenu • 處理Callback函式@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MANU01: case MANU02: case MANU03: case MANU04: case MANU05: String itemid = Integer.toString(item.getItemId()); String title = item.getTitle().toString(); showAlertDialog("項目ID = " + itemid + "n" + "標題 = " + title); return true; } return super.onOptionsItemSelected(item); } 為你把關每一道 學習品質 266
  43. 43. 範例練習 範例專案名: ex06_01_01_OptionsMenu ex06_01_02_ContextMenu ex06_01_03_Submenu 練習目標:  學習各種Menu的應用場合與撰寫方式 程式撰寫  透過覆寫onCreateOptionsMenu函式來加入 Menu選單  透過覆寫onOptionsItemSelected來接收使用者的 選擇 操作練習  透過模擬器操作,透過按下手機上的Menu鍵來 呼叫出Menu選單。為你把關每一道 學習品質 267
  44. 44. Android通知元件-Notifications• Android提供三種不同的通知元件 • Toast • 短暫警示與通知,顯示數秒後會 自動消失 Toast Notification • Dialog • 對話窗可與使用者做簡單交談式 互動,一般用來做進度提示或確 認使用者的需求。 Dialog Notification • Status Bar Notification • 位於訊息列,訊息會長註於狀態 列,點擊後可啟動Activity。一般 Services會透過此通知用戶有事件 發生需返回Activity處理 Status Bar Notification 268為你把關每一道 學習品質
  45. 45. Android通知元件-Dialog元件• Dialog是一切對話框基本類別,並非繼承於View類別.• Dialog也具有生命週期,其生命週期由Activity來誰護。• 開發者可主動呼叫的函式 Activity • showDialog(int id) showDialog(int id) • dismissDialog(int id) Andr oid onCreateDialog(int id) 框架 onPreparDialog(int id, Dialog dialog)• 框架控制Dialog生命週期函式 • onCreateDialog(int id) • onPrepareDialog(int id, Dialog dialog) 269 為你把關每一道 學習品質
  46. 46. Android通知元件-Dialog元件• Dialog於Activity中的建立流程 • Step1: 呼叫Activity的showDialog(id) Button1.setOnClickListener(new OnClickListener(){ public void on click(View v){ showDialog(DIALOG1); } } ); • Step2: 覆寫(override)onCreateDialog(int id)函式 Protected Dialog onCreateDialog(int id){ switch(id){ Case DIALOG1: return buildDialog1(this); } return null; } 270為你把關每一道 學習品質
  47. 47. Android通知元件-Dialog元件 • Step3:建立需要的Dialog類別,將其返return回 Android框架 Private Dialog buildDialog1(Context context){ AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setIcon(R.drawable.alert_dialog_icon); builder.setTitle(R.string.alert_dialog_two_buttons_title); builder.setPositiveButton(R.string.alert_dialog_ok,new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int whichButton){ setTitle(“確認鈕”); } }); return builder.create(); } • Step4:覆寫onPrepareDialog(int id, Dialog dialog) protected void onPrepareDialog (int id, Dialog dialog, Bundle args){ return dialog; } 271為你把關每一道 學習品質
  48. 48. Android通知元件-Dialog元件• AlertDialog • AlertDialog是Dialog的子類別,為最常使用的對話框 • 一個AlertDialog可以有二個至三個按鈕 • 不能直接以建構函式來產生AlertDialog物件,必須透過 AlertDialog.Builder來建構。private Dialog buildDialog2(Context context) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setIcon(R.drawable.alert_dialog_icon); builder.setTitle(R.string.alert_dialog_two_buttons_msg); builder.setMessage(R.string.alert_dialog_two_buttons2_msg); builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { setTitle("點選了對話框上的確定按鈕"); }}); builder.setNeutralButton(略.同上); builder.setNegativeButton(略,同上; return builder.create();} 272為你把關每一道 學習品質
  49. 49. Android通知元件-Dialog元件• 具有View元件的對話框private Dialog buildDialog3(Context context) { LayoutInflater inflater = LayoutInflater.from(this); final View textEntryView = inflater.inflate( R.layout.alert_dialog_text_entry, null); 略 AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setView(textEntryView); builder.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { setTitle("點選了對話框上的確定按鈕:" + ((TextView) textEntryView.findViewById(R.id.username_edit)).getText()); }}); builder.setNegativeButton(略…….); return builder.create();}為你把關每一道 學習品質 273
  50. 50. Android通知元件-Dialog元件• 進度對話框private Dialog buildDialog4(Context context) { ProgressDialog dialog = new ProgressDialog(context); dialog.setTitle("正在下載歌曲"); dialog.setMessage("請稍候……-"); return dialog;}為你把關每一道 學習品質 274
  51. 51. 範例練習 範例專案名: ex06_02_Dialog 練習目標:  學習操作Android各種不同的Dialog對話視窗  學習如何將Dialog改成可以自定Layout元件 的對訊窗 程式撰寫  覆寫onCreateDialog函式,負責管理Activity 中呼叫的Dialog.  透過AlertDialog.Builder建立Dialog物件 操作練習  透過模擬器操作。為你把關每一道 學習品質 275
  52. 52. Android通知元件-Notification• Notification通知元件 • 不會打斷目前使用者的操作,訊息會長註於訊 息列中,待用戶有空時在進行處理,並可直接 啟動對應的Activity. • Notification可加入振動或聲音通知,以加強引 起用戶的注意。為你把關每一道 學習品質 276
  53. 53. Android通知元件-Notification• 如何撰寫Notification事件 • Step1:取得NotificationManager管理元件 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); • Step2:建構Notification物件 • 第一個參數:要顯示的圖片ID • 第二個參數:要顯示的文字 • 第三個參數:顯示的時間,System.currentTimeMillis()代表立 即顯示 Notification notification = new Notification(drawable, tickerText,System.currentTimeMillis());為你把關每一道 學習品質 277
  54. 54. Android通知元件-Notification • Step3:決定Notification如何呈現與處理事件 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, ActivityMain.class), 0); • Context:作用於那一個Activity的Context • requestCode:目前無使用到 • Intent:要被啟動的Activity Intent • Flags: 細部的Intent操作控制,如 FLAG_CANCEL_CURRENT notification.setLatestEventInfo(this, title, content, contentIntent); • Step4:設定Notification預設的表現形式 • Notification.DEFAULT_VIBRATE 振動 • Notification.DEFAULT_SOUND 聲音 • Notification.DEFAULT_ALL 聲音+振動 • 需加入權限才能使用振動功能:Android.permission.VIBRATE notification.defaults = Notification.DEFAULT_ALL; mNotificationManager.notify(NOTIFICATIONS_ID, notification);為你把關每一道 學習品質 278
  55. 55. Android通知元件-Notification• Notification通知更新 • 透過notify()函式來更新通知,可以避免一直不斷產生新的 Notify訊息通知而導致塞滿了用戶的通知視窗 mNotificationManager.notify(NOTIFICATIONS_ID, notification);• 加入自定音效 • 系統預設音效 • notification.defaults |= Notification.DEFAULT_SOUND; • 讀取SD卡的mp3 • notification.sound =Uri.parse("file:///sdcard/notification/test.mp3"); • 從MediaStore的ContentProvider取得 • notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");為你把關每一道 學習品質 279
  56. 56. Android通知元件-Notification• Notification相關FLAG設定 • FLAG_AUTO_CANCEL: • 用戶點了該通知後,系統便會自動清除該訊息 • FLAG_INSISTENT: • 重複發出響聲,直到使用者點選該訊息 • FLAG_ONGOING_EVENT: • 將該訊息放置放”進行中”群組,一般用來代表背景 Service還有在運行中。 • FLAG_NO_CLEAR: • 使用者就算點選了清除訊息,系統也不會把該訊息清 除,必須待應用程式自己發出清除的指令。為你把關每一道 學習品質 280
  57. 57. Android通知元件-Notification • 自定Notification顯示UI元件 • Step1: 利用RemoveView定義元件內容RemoteViews contentView = new RemoteViews(getPackageName(),R.layout.custom_notification_layout);contentView.setImageViewResource(R.id.imageIcon,R.drawable.notification_image);contentView.setTextViewText(R.id.textView, "Hello Notify");notification.contentView = contentView; 為你把關每一道 學習品質 281
  58. 58. Android通知元件-Notification• Step2: 發出通知訊息 • 使用contentIntent設置pendingIntent元件Intent notificationIntent = new Intent(this, MyClass.class);PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);notification.contentIntent = contentIntent;mNotificationManager.notify(0, notification);為你把關每一道 學習品質 282
  59. 59. Android通知元件-Notification• Notification相關FLAG設定 • FLAG_AUTO_CANCEL: • 用戶點了該通知後,系統便會自動清除該訊息 • FLAG_INSISTENT: • 重複發出響聲,直到使用者點選該訊息 • FLAG_ONGOING_EVENT: • 將該訊息放置放”進行中”群組,一般用來代表背景 Service還有在運行中。 • FLAG_NO_CLEAR: • 使用者就算點選了清除訊息,系統也不會把該訊息清 除,必須待應用程式自己發出清除的指令。為你把關每一道 學習品質 283
  60. 60. Android通知元件-Tost通知元件• Tost通知元件 • 為一個短暫提示元件,Toast會直接顯示於螢 幕下方. • Tost內容可以直接為描述一串文字,也可以帶 入View元件。 • Toast的顯示時間長度分為長與短兩種 • Toast.LENGTH_LONG • Toast.LENGTH_SHORT為你把關每一道 學習品質 284
  61. 61. Android通知元件-Tost通知元件• 如何撰寫Tost通知 • Step1:實體化Toast物件 Toast toast = new Toast(this); • Step2:設定Toast內容 • setText(CharSequence s) • setView(View view) LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = vi.inflate(R.layout.toast,null); TextView tv = (TextView) view.findViewById(R.id.content); tv.setText("加入艾鍗,迅速提昇你的開發能力"); toast.setView(view); • Step3:設定顯示時間 toast.setDuration(Toast.LENGTH_SHORT); toast.show();為你把關每一道 學習品質 285
  62. 62. 範例練習 範例專案名: ex06_03_Toast_and_Notification 練習目標:  學習如何建立Notification與Toast通知事件 程式撰寫  透過getSystemService(NOTIFICATION_SERVICE)取得 NotificationManager物件  建立PendingIntent物件,供用戶執選通知後返回Activity  透過notification.defaults決定通知時是否要加振動與音效  建立Toast物件,並傳送一簡單字串於畫面上 操作練習  透過模擬器練習,練習時可注意畫面的最上頂狀態列 收到Notification時的程現與執行後的處理方式為你把關每一道 學習品質 286
  63. 63. Android Widget開發 Widget程式開發  開發上的限制  Widget組成方式  如何設置組態設定Activity Remove View的互動方式 287
  64. 64. Android Widget程式開發• APP Widget為Android 1.5之後所提 供的功能。提供於Android桌面程式 (Home Screen)上安置各式各樣的 常註程式,例如小時鐘、Google Search...• 常註程式新增方式 • 在主畫面上按下觸控面版約2秒, 即會彈出Add to Home Screen 選單,選擇Widget後即會顯現 目前系統所安裝的各式Widget為你把關每一道 學習品質 288
  65. 65. Android Widget程式開發-使用限制• Android Widget可以使用的功能 • 可加載於主畫面(Home Screen) • 同一個常駐程式可以多次啟動 • 常註程式啟動時不會採用全畫面(Full Screen)• Android Widget中不能使用的功能 • Event的呼叫。 • 有限的Layout使用,有限的GUI元件使用。 • 可使用的Layout類別: • Frame Layout • Linear Layout • Relative Layout • 可使用的GUI元件 • AnalogClock ImageButton • Button ProgressBar • Chronometer TextView為你把關每一道 學習品質 289
  66. 66. Android Widget程式開發-使用限制• Android系統對Widget程式的處理方式會有所限制, Widget可以處理的方式選擇 處理方式 說明 指定執行間隔時間 於widget的資訊檔中設置定期執行的時間週期 指定執行時間 於Widget的資訊檔裡設定執行的時間 按下按鈕後才執行 常GUI介面被用戶按下時才執行• Widget由於長駐於主畫面,且會不定時在背景運作, 因此設計時要特別注意評估電池與處理器的消耗狀況。• Widget與Activity還有Services一般,皆有其獨自的生 命週期為你把關每一道 學習品質 290
  67. 67. Android Widget程式開發-生命週期• Widget生命週期 開始 onDelete() onEnabled() onDisabled() onUpdate() App Widget 執行中 關閉為你把關每一道 學習品質 291
  68. 68. Android Widget程式開發-組成結構 • Android Widget應用程式組成結構 BroadcastReceiver AppWidgetManaget AppWidgetProvider 實作生命週期函式 廣播呼叫 onEnable,onUpdate, APPWIDGET_UPDATE onUpdate,onDelete d, myAppWidgetProvider onDisabledAndroid應用程式設定檔 Metadata設定 Widget資訊設定檔 Layout畫面布局檔 AndroidManifest.xml Name:名稱需為 Appwidget_info.xml Layout_sample.xml android.appwidget.建立BroadcastReceiver provider 定義Layout布局 有限的layout元件使用<action>欄位需明確定義為 畫面大小:74倍數-2android.appwidget.action.APPWIDG 有限的GUI元件使用 Resource:指定 執行時間週期設定ET_UPDATE information檔的來 源 為你把關每一道 學習品質 292
  69. 69. Android Widget資料更新• AppWidget主要是依建置在Home Launcher APP之上, 因此Widget畫面的更新與Event互動皆必須透過一個 Proxy來代為處理,而此Proxy就是AppWidgetManager 元件。• 所有待更新的Widget元件,皆必須透過RemoteViews 元件封裝,所有的互動事件則必須透過pending Intent 註冊於RemoteViews中。 Home Launcher• AppWidget元件updateAppWidget函式協助將 RemoteViews更新於Widget元件上。 WidgetsetTextViewText setImageViewResource AppWidget Manager setOnClickPendingIntent RemoteViews為你把關每一道 學習品質 293
  70. 70. 範例練習 範例專案名: ex06_04_MyWidget 練習目標:  了解如何建置Widget元件  學習如何為Widget加入listener與更新UI 程式撰寫  實作一個Widget 的receiver元件,並實作組態設定 Activity,Widget啟動前會現顯示Activity應用程式。  撰寫Appwidget_info.xml檔,決定Widget顯示方式  撰寫Layout_sample.xml,決定Widget顯示的UI樣式。  撰寫RemoteView更新函式,取得Widget觸發事件, 並依事件狀態更新WidgetUI 操作練習  透過模擬器操作為你把關每一道 學習品質 294
  71. 71. Android Widget程式開發-組態設定Activity• AppWidget組態設定Activity • 當用戶新增一個Widget時,系統可以呼叫一個組態設定 Activity,提供用戶對該Widgegt進行設定,如改變顏色,顯示 大小,執行週期..等等資訊。• 如何為AppWidget配置組態設定Activity • AndroidManifest.xml中定義Activity,<action>需定義為: ACTION_APPWIDGET_CONFIGURE <activity android:name="myAppWidgetConfigure"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> </intent-filter> </activity> • 於appwidget_info.xml中指定要呼叫的Activity <appwidget-provider .... android:configure= " com.example.android.appwidget.myAppwidgetConfigures" />為你把關每一道 學習品質 295
  72. 72. Android Widget程式開發-組態設定Activity• AppWidget呼叫組態設定Activity時注意事項 • AppWidget呼叫執行Activity後,此Activity必須要返回一個結果, 而此結果必須要包含Widget ID(存在EXTRA_APPWIDGET_ID) • Activity被建立時,AppWidget中的onUpdate方法將不會被呼叫 到,因此Activity完成組態設定後一定要負責更新AppWidget程式 (可以透過請求AppWIdgetManager協助更新)• 組態設定Activity實作方式 • Step1:透過Intent取得AppWidget ID final Bundle extras = getIntent().getExtras(); If(extras != null){ int mAppWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); }為你把關每一道 學習品質 296
  73. 73. Android Widget程式開發-組態設定Activity• Step2:執行組態設定,等待用戶設定操作• Step3:設定完成後,更新AppWidget UI畫面 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(LifeCycleConfigure.this); RemoteViews views = new RemoteViews(getPackageName(), R.layout.life_cycle); appWidgetManager.updateAppWidget(mAppWidgetId, views);• Step4:最後建立一個Intent返回結果,並結束Activity Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish();為你把關每一道 學習品質 297
  74. 74. 範例練習 範例專案名: ex06_05_Appwidget 練習目標:  了解桌面Widget應用程式的撰寫方式與生命流程  學習如何為Widget加入組態設定用Activity 程式撰寫  實作一個Widget 的receiver元件,並實作組態設定 Activity,Widget啟動前會現顯示Activity應用程式。  撰寫Appwidget_info.xml檔,決定Widget顯示方式  撰寫Layout_sample.xml,決定Widget顯示的UI樣式。 操作練習  透過模擬器操作為你把關每一道 學習品質 298
  75. 75. 第8節: Broadcast Receiver元件 Services元件
  76. 76. Broadcast Receiver與Service元件 BroadCastReceiver元件  廣播的用途與種類  如何發送廣播封包  如何註冊接收廣播封包  廣播封包加入自定權限 Services 元件應用  Local 與 Remote Services  Services生命週期  如何與Services進行溝通  Remote Services設計實作  如何與RemoteServices溝通  Services與Activity通訊架構 設計 300
  77. 77. BroadCast Receiver元件• 廣播接收程式可用來接收其他應用程式所發出的訊息。• 許多的廣播通知訊息都是來自於Android作業系統 • 電池電量快用盡 • 網路連線變化 • 有來電或簡訊通知• 應用程式本身也可以發出廣播訊息,可做為與其他應用程 式互動溝通的管道。 • 需注意的是廣播訊息是公開的,任何應用程式都可以 透過註冊BraodCast Receiver來接收到你所廣播的訊 息。• 一般常用來做為Services應用程式與Activity應用程式溝通 使用。為你把關每一道 學習品質 301
  78. 78. BroadCast Receiver元件• 如何發送廣播 • 在程式中透過Intent建立好希望被那些action filter所接 收,接著透過sendBroadcast函式發送出去。 public static final String NEW_BROADCAST= " com.ittraining.action.NEW_BROADCAST "; Intent intent = new Intent(NEWO_BROADCAST); intent.putExtra(" data1",someData); intent.putExtra(" data2",someData); sendBroadcast(intent);為你把關每一道 學習品質 302
  79. 79. BroadCast Receiver元件• 如何接收廣播 • 想接收並且處理廣播的Intent,就必需要註冊一個 BroadcastReciever,並且設定一個Intent Filter以將不需 要監聽的訊息過慮掉,最後在將其註冊到Android框架 中。• Step1: 繼承BroadcastReceiver類別,並覆寫 onReceive(Context context, Intent intetn)函式 • 需注意onReceive函式內的處理不宜超過5秒,長時間 的處理建議移到Thread中執行。 public class IttrainingAndroidReceiver extends BroadcastReceiver{ @override Public void onReceive(Context context, Intent intent){ //接收後的處理 } }為你把關每一道 學習品質 303
  80. 80. BroadCast Receiver元件• Step2: 註冊BroadCastReceiver • 透過XML註冊 <receiver android:name= " IttrainingAndroidReceiver " > <intent-filter> <action android:name= " com.ittraining.action.NEW_BROADCAST " /> </intent-filter> </receiver> • 透過程式碼註冊 IntentFilter filter =new IntentFilter(NEW_BROADCAST); IttrainingAndroidReceiver receiver=new IttrainingAndroidReceiver(); registerReceiver(receiver , filter); unregisterReceiver(receiver); • 取消註冊 unregisterReceiver(receiver);為你把關每一道 學習品質 304
  81. 81. 範例練習 範例專案名: ex07_01_Broadcastreceiver 練習目標:  學習如何發送廣播訊息  了解如何接收其它應用程式所發出的廣播訊息 程式撰寫  建立一個Activity應用程式與二個Recever程式  由Activity發送broadcast至Recever程式 操作練習  透過模擬器操作。注意兩個receiver的<intent- filter>action name並不相同,透過Activity可透過 不同的action決定要將訊息broadcast至指定的 receiver.為你把關每一道 學習品質 305
  82. 82. BroadCast Receiver元件-安全性管理• Android廣播服務安全性漏洞 • 主要利用Intent Filter做為過濾,任何其它應用 程式只要得知廣播的Action Name就可以註冊 取得該廣播的資料。這將存在一個安全性的漏 洞,若你的廣播中所傳遞的是很重要的資料, 這將有可能被其它惡意軟體給監聽取得。• 增加自訂權限傳送與接收廣播 • 開發者可為自己的APP程式加入自訂權限,任 何的應用程式如果要接收該廣播資料,則必須 宣告取得相關的權限。為你把關每一道 學習品質 306
  83. 83. BroadCast Receiver元件-安全性管理• 增加權限到AndroidManifest.xml定義描述檔 <permission android:name="com.ittraining.ex07_02.SEND_DATA" android:description="@string/permission_aceess_deta" android:label="@string/permission_aceess" android:protectionLevel="normal" > </permission>• name:權限名稱,請求權限與執行權限時使用,必須為唯 一的值。• description:提供詳細說明為何需要此權限,與如何使用• lable:用戶安裝軟體時顯示給使用者看,以提供用戶進行 授權,此字串不應太長。• protectionLevel:權限等級,normal為最基本等級。為你把關每一道 學習品質 307
  84. 84. BroadCast Receiver元件-安全性管理• 於AndroidManifest.xml宣告請求使用者授權 <uses-permission android:name="com.ittraining.ex07_02.SEND_DATA"/>• 發送廣播時於sendBroadcast第二個參數帶入權 限名稱 sendBroadcast(intent, "com.ittraining.ex07_02.SEND_DATA“);• 接收端在註冊Receiver元件時,第三個參數需帶 入權限名稱 registerReceiver(receiver,filter, "com.ittraining.ex07_02.SEND_DATA“,null);為你把關每一道 學習品質 308
  85. 85. Services元件 • Services是在背景運作,不直接與使用者互動,因此並無 UI畫面 • Services無法獨立自行運作,而必須透過Activity或其它 Context物件來呼叫啟動 • Context.startService() • Context.binService() • 若Services的onCreate(),onStart()內需執行一些耗時的處 理,請使用Thread處理,以避免造成UI操件lock. • 使用Services的好處,在於Activity結束後其Services元件 依然會在背景做運行,Services有其獨立的生命週期不會 受Activity生命週期影影。 – 播放音樂 – 背景接收網路訊息為你把關每一道 學習品質 309
  86. 86. Services元件 • Android Services的使用方式可區分為二類 • Local Services (操作自己應用程式的Services) • Remote Services (操作其它應用程式的Services) • 與Services溝通方式 • 透過BroadCast傳送Intent至Services • 透過Binder與Services做Local Services連結 • 透過IPC(interprocess communication)與Remote Services做連結 • 透過Contex.getSystemService(String name),函 式可取得Android預設的Services元件.為你把關每一道 學習品質 310
  87. 87. Services元件-與Activity溝通方式 • Services元件與前景的Activity元件之間的溝通架構設 計模式為你把關每一道 學習品質 311
  88. 88. Services元件 • Android系統內提供的一些Services服務 Services代碼 對應Manager 物件 說明 WINDOW_SERVICE WindowManager 管理視窗畫面 LAYOUT_INFLATER_SERVICE LayoutInflater 載入xml檔所定義的View元件 ACTIVITY_SERVICE ActivityManager 管理應用程式的系統狀態 POWER_SERVICE PowerManger 電源管理 ALARM_SERVICE AlarmManager 鬧鐘管理 NOTIFICATION_SERVICE NotificationManager 狀態列訊息通知服務 KEYGUARD_SERVICE KeyguardManager 鍵盤鎖管理 LOCATION_SERVICE LocationManager 位置服務管理,GPS SEARCH_SERVICE SearchManager 搜尋服務管理 VEBRATOR_SERVICE Vebrator 振動服務管理 CONNECTIVITY_SERVICE Connectivity 網路連線服務管理 WIFI_SERVICE WifiManager WIFI連線服務管理 TELEPHONY_SERVICE TeleponyManager 電話服務管理為你把關每一道 學習品質 312
  89. 89. Services元件-與Activity繼承關係為你把關每一道 學習品質 313
  90. 90. Services元件-生命週期 • Services元件有其獨 立的生命週期 • onCreate() • onStart() • onDestroy() • 啟動Services的方式 不同,對應的生命 週期也不同。 • startServices() • bindServices()為你把關每一道 學習品質 314
  91. 91. Services元件-啟動Services • Step1: 於AndroidManifest.xml中加入Services宣告 <service android:enabled="true" android:name=".TestService" /> • Step2:覆寫Services函式-處理生命週期 public class TestService extends Service { @Override public void onCreate() { Log.e(TAG, "============> TestService.onCreate"); } @Override public void onStart(Intent intent, int startId) { Log.e(TAG, "============> TestService.onStart"); } @Override public void onDestroy() { Log.e(TAG, "============> TestService.onDestroy"); } }為你把關每一道 學習品質 315
  92. 92. Services元件-啟動Services • Step3:覆寫Services函式-提供Bind接口 @Override public IBinder onBind(Intent i) { return new LocalBinder();; } public class LocalBinder extends Binder { TestService getService() { return TestService.this; } } @Override public boolean onUnbind(Intent i) { Log.e(TAG, "============> TestService.onUnbind"); Return true; } @Override public void onRebind(Intent i) { Log.e(TAG, "============> TestService.onRebind"); }為你把關每一道 學習品質 316
  93. 93. Services元件-啟動Service • Step4:透過Activity啟動Service-使用startService() private void startService() { Intent i = new Intent(this, TestService.class); this.startService(i); } private void stopService() { Intent i = new Intent(this, TestService.class); this.stopService(i); } • Step4:透過Activity啟動Services-使用bindService() private void bindService() { Intent it = new Intent(this, TestService.class); bindService(it , _connection, Context.BIND_AUTO_CREATE); _isBound = true; } private void unbindService() { if (_isBound) { unbindService(_connection); _isBound = false; }為你把關每一道}學習品質 317
  94. 94. Services元件-啟動Service • Step6:SerrviceConnection元件建立-透過bindService() private ServiceConnection _connection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service){ _boundService = ((TestService.LocalBinder)service).getService(); Toast.makeText(TestServiceHolder.this, "Service connected:", Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { _boundService = null; Toast.makeText(TestServiceHolder.this, "Service connected", Toast.LENGTH_SHORT).show(); } };為你把關每一道 學習品質 318
  95. 95. BroadcastReceiver與Services綜合演練 • 結合Services服與程式與BroadcastReceiver,實作 一鬧鐘計時器。提供UI畫面讓User設定鬧鐘時 Services在背景進行時間排程倒間設定完成後會通知背景 數,當時間到時透過broadcast訊Services進行倒數計時,並註冊 息,傳送回主程式,並播放一段鬧鐘一個BroadcastReceiver類別 音效與Toast訊息 KitchenTimer.java KitchenTimerService.java KitchenTimer Schedule(long delay) KitchenTimerSdervice.shecule(alarmtimer) Timer.shedule(timerTask,delay) KitchenTimer TimerTask() KitchenTimerReceiver-receiver sendBroadcast(new Intent(ACTION))為你把關每一道 學習品質 319
  96. 96. BroadcastReceiver與Services綜合演練 • Step1:建立Services宣告 <service android:enabled="true" android:name=".KitchenTimerService" /> • Step2:主程式onCreate() //啓動服務程式KitchenTimerService Intent intent = new Intent(this, KitchenTimerService.class); startService(intent); //註冊廣播接收receiver IntentFilter filter = new IntentFilter(KitchenTimerService.ACTION); registerReceiver(receiver, filter); //(Bind)服務程式KitchenTimerService bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);為你把關每一道 學習品質 320
  97. 97. BroadcastReceiver與Services綜合演練 • Step3:主程式onDestroy() //Activity結束時,要將Service相關資源一並結束 @Override public void onDestroy() { super.onDestroy(); unbindService(serviceConnection); //Unbind service unregisterReceiver(receiver); //Unregister Receiver kitchenTimerService.stopSelf(); //Stop Service } • Step4:定義接收廣播類別KitchTimerReceiver private class KitchenTimerReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { Toast toast = Toast.makeText(getApplicationContext(), "Time over!", Toast.LENGTH_LONG); toast.show(); MediaPlayer mp = MediaPlayer.create(KitchenTimer.this, R.raw.alarm); try { mp.start(); } catch (Exception e) { } kitchenTimerService.schedule(alarmtimer); }為你把關每一道 學習品質 } 321
  98. 98. BroadcastReceiver與Services綜合演練 • Step5:定義與Service bind的ServiceConnection private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { kitchenTimerService = ((KitchenTimerService.KitchenTimerBinder)service).getService(); } public void onServiceDisconnected(ComponentName className) { kitchenTimerService = null; } };為你把關每一道 學習品質 322
  99. 99. BroadcastReceiver與Services綜合演練 • Step6:建立Services類:KitchenTimerService public void onCreate() { super.onCreate(); Toast toast = Toast.makeText(getApplicationContext(), "onCreate()", Toast.LENGTH_SHORT); toast.show(); } public void onStart(Intent intent, int startId) { super.onStart(intent, startId); Toast toast = Toast.makeText(getApplicationContext(), "onStart()", Toast.LENGTH_SHORT); toast.show(); } public void onDestroy() { super.onDestroy(); Toast toast = Toast.makeText(getApplicationContext(), "onDestroy()", Toast.LENGTH_SHORT); toast.show(); if (timer != null) { timer.cancel(); timer = null; } }為你把關每一道 學習品質 323
  100. 100. BroadcastReceiver與Services綜合演練 • Step7:覆寫Services Bind相關函式 @Override public IBinder onBind(Intent intent) { Toast toast = Toast.makeText(getApplicationContext(), "onBind()", Toast.LENGTH_SHORT); toast.show(); return new KitchenTimerBinder(); } @Override public void onRebind(Intent intent) { Toast toast = Toast.makeText(getApplicationContext(), "onRebind()", Toast.LENGTH_SHORT); toast.show(); } @Override public boolean onUnbind(Intent intent) { Toast toast = Toast.makeText(getApplicationContext(), "onUnbind()", Toast.LENGTH_SHORT); toast.show(); return true; //當再度自Client接口時,呼叫 onRebind的場合會回覆 true }為你把關每一道 學習品質 324
  101. 101. BroadcastReceiver與Services綜合演練 • Step8:Service發送廣播訊息至主程式 public void schedule(long delay) { if (timer != null) { timer.cancel(); } timer = new Timer(); TimerTask timerTask = new TimerTask() { public void run() { //送出信息給廣播接收程式 sendBroadcast(new Intent(ACTION)); } }; //設定Alarm time,且Time out時會執行timerTask送出信息 timer.schedule(timerTask, delay); }為你把關每一道 學習品質 325
  102. 102. 範例練習 範例專案名: ex07_02_Kitchentimer 練習目標:  學習如何結合BroadCastReceiver與Services的應用 程式撰寫  實作一個Activity並內置一個Receiver類別,當用設定 好時間後將啟動一個Services在背景做倒數計時的動 作。  實作一個Services應用程式,由Activity所呼叫建立, 當指定的計時工作結束後將透過broadcast方式通知 Activity的receiver類別。 操作練習  透過模擬器操作,倒數計時的單位為分鐘。時間到後 會發出聲響,並顯示一段Toast字串於畫面上。為你把關每一道 學習品質 326
  103. 103. Remote Services應用• 要將Services元件跑在獨立的Processe中可透過在 AndroidMainfest.xml中,宣告services元件加上 android:process=":remote" <service android:name=".services.TrackerServices" android:process=":remote">• Services中設置Messenger可以提供與遠端Services元件進 行雙向溝通。 private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { /*接收來自client傳來的訊息*/ } } private Messenger mMessenger = new Messenger(mHandler); @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); }為你把關每一道 學習品質 327
  104. 104. Remote Services應用• Client端在利用Bind與遠端Services連接,並取得 Messenger元件。 private Messenger remoteMessenger; private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { remoteMessenger = new Messenger(service); } public void onServiceDisconnected(ComponentName name) { remoteMessenger = null; } };• 傳送訊息至Remote Services Private Message localMessenger; private void sendMessage() { Message message = Message.obtain(null, 0); message.replyTo = localMessenger; try { rMessenger.send(message); } catch (RemoteException e) { e.printStackTrace(); } }為你把關每一道 學習品質 328
  105. 105. 範例練習 範例專案名: ex07_03_RemoteService 練習目標:  了解RemoteServices與Local Services的差別  學習如何使用Messenger進行IPC跨Process通訊 程式撰寫  撰寫一個Services元件,並將process tag設為:remote  設置Messenger 物件做為IPC通訊管道。  撰寫Activity 元件,利用Bind 與remote services 通 訊。 操作練習  透過模擬為你把關每一道 學習品質 329
  106. 106. IntentService應用• Android預設所有的四大元件都是運行在主執行緒中,包含 Service也是運行在主執行緒中,因此若在Service執行耗時 的工作則將會導致UI lock。避免UI Lock的方式為將Service 設置為remote,或是透過IntentService。• IntentService 為一次性使用的Service元件,並且會自動運 行在子執行緒中,使用者可以免除複雜的Message、Loop 與Handler設計,透過Intentservice可以快速的處理一次性 耗時的工作。• 將工作放置於IntentService的優勢 • 比較不容易被系統Kill • 工作於子執行緒中,不會導致UI Thread Lock • 非常容易使用,使用完即自動回收,不佔用記憶體。為你把關每一道 學習品質 330
  107. 107. 範例練習 範例專案名: ex07_04_IntentService 練習目標:  了解Intent Service、Thread與Service的差別  學習如何利用Intent Service 在背景處理耗時的工作 程式撰寫  繼承IntentService類別,並實作onHandleIntent函式  將耗時的工作寫在onHandleIntent函式中。  撰寫Activity頁面,利用startService函式呼叫 IntentService工作。 操作練習  透過模擬為你把關每一道 學習品質 331
  108. 108. 第9節: SQLite DataBase開發技巧
  109. 109. SQLite DataBase開發技巧 SQLite Database存取技巧  SQLite語言定義與建置工具  SQLiteOpenHelp類別實作  資料庫的基本IO操作方式  設計DataBase Adapter架構 333
  110. 110. SQLite DataBase• SQLite是Android內建的一個輕量化的嵌入式資料庫,可使 用SQL語法進行控制存取。• SQLite的優點 • 免費,無需授權 • 輕量,約150KB左右,很適在嵌入式手機領域使用 • 無需設定、安裝與管理,沒有伺服器,不需組態檔,無 需資料庫管理員 • SQLite只是一個檔案,可以很方便的移動或複製到另一 系統,也能運作的很良好。• Android將SQLite檔案存放於下列路徑中 • /data/data/packagename/databases • 可以使用adb或File Explorer來觀移存取資料庫檔案為你把關每一道 學習品質 334
  111. 111. SQLite DataBase• SQL入門 • SQL描述語法主要包含三種類型 • DLL(資料定義語言) • 修改 • 查尋• DLL資料定義語言: • 資料庫的組成由多個資料表(table)多個資料列(Row) 若干資料欄(column)所組成。 • 每個資料欄具有欄位名稱與資料類形(文字,數字…) Create table mytalbe( _id integer primary key autoincrement, Name text, Phone text) • 第一個欄位指定為主鍵(PRIMARY KEY)為你把關每一道 學習品質 335
  112. 112. SQLite DataBase• 安裝Questoid SQLite Browser Eclipse Plugin,協助以圖形化方式觀看 Android內的SQLite資料表 • http://www.questoid.net/Tools/QuestoidSQLiteBrowser.aspx • 將com.questoid.sqlitebrowser_1.1.0.jar Copy至Eclipse的plugins目 錄中,並重新啟動Eclipse. • 切換至DDMS視圖,利用File Explorer工具找到要查看的SQLite data base檔案.• SQL語法指令查尋與學習資源 • http://www.1keydata.com/tw/sql/sqlhaving.html為你把關每一道 學習品質 336
  113. 113. SQLite DataBase-SQLiteOpenHelper• Android應用程式中提供了SQLiteOpenHelper類別來協助操 作SQLite資料庫並,提供產生與管理資料庫的版本• SQLiteOpenHelper為一抽像類別,需繼承並實作三個函式 • onCreate(SQLiteDatabase db) • 資料庫第一次產生時呼叫。 • onUpdate(SQLiteDatabase db,int oldVersion,int newVersion) • 當資料庫需要升級時,由系統主動呼叫。一般用來刪除舊資料 表,並建立新的資料表 • onOpen(SQLiteDatabase db) • 當資料庫被開啟時的Callback函式,由系統呼叫,一般不會使用到。為你把關每一道 學習品質 337
  114. 114. SQLite DataBase-SQLiteOpenHelper• 如何建立SQLiteOpenHelper類別 private static final String DATABASE_NAME = "dbForTest.db"; private static final int DATABASE_VERSION = 1; private static final String TABLE_NAME = "diary"; private static final String TITLE = "title"; private static final String BODY = "body"; private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE + " text not null, " + BODY + " text not null " + ");"; db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }為你把關每一道 學習品質 338
  115. 115. SQLite DataBase-SQLiteOpenHelper• 取得透過SQLiteOpenHelper取得SQLiteDatabase類別 Public Methods synchronized SQLiteDatabase getReadableDatabase()Create and/or open a database. synchronized SQLiteDatabase getWritableDatabase()Create and/or open a database that will be used for reading and writing.• 利用SQLiteDatabase提供的函式可以存取操作資料庫 • public Cursor rawQuery (String sql, String[] selectionArgs) • 最低階的操作函式,可以直接接受標準SQL語法 • 為方便不熟SQL語法的開發者,另外有提供一系列的API函式,系統 會將API指令自動轉換成標準的SQL語法呼叫資料庫。 • pubilc delete(….) • pubilc insert(….) • pubilc update(….) • public Cursor query (….)為你把關每一道 學習品質 339
  116. 116. SQLite DataBase-資料庫操作• 如何插入資料庫記錄 private void insertItem() { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); String sql1 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY + ") values(haiyang, android的發展真是迅速);"; String sql2 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY + ") values(icesky, android的發展真是迅速);"; try { db.execSQL(sql1); db.execSQL(sql2); setTitle("插入兩條數據成功"); } catch (SQLException e) { setTitle("插入兩條數據失敗"); } }為你把關每一道 學習品質 340
  117. 117. SQLite DataBase-資料庫操作• 如何刪除資料記錄 private void deleteItem() { try { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.delete(TABLE_NAME, " title = haiyang", null); setTitle("刪除title為haiyang的一條記錄"); } catch (SQLException e) { } }• 查尋資料 private void showItems() { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); String col[] = { TITLE, BODY }; Cursor cur = db.query(TABLE_NAME, col, null, null, null, null, null); Integer num = cur.getCount(); setTitle(Integer.toString(num) + " 條記錄"); }為你把關每一道 學習品質 341
  118. 118. SQLite DataBase-資料庫操作• 刪除整個Table private void dropTable() { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); String sql = "drop table " + TABLE_NAME; try { db.execSQL(sql); setTitle("數據表成功刪除︰" + sql); } catch (SQLException e) { setTitle("數據表刪除錯誤"); } }• 建立新的Table private void CreateTable() { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE + " text not null, " + BODY + " text not null " + ");"; try { db.execSQL("DROP TABLE IF EXISTS diary"); db.execSQL(sql); setTitle("數據表成功重建"); } catch (SQLException e) { setTitle("數據表重建錯誤"); }為你把關每一道 學習品質 } 342
  119. 119. 範例練習 範例專案名: ex08_01_Sqlite 練習目標:  學習如何建立與使用SQLite資料庫 程式撰寫  撰寫DataBaseHelper 繼承SQLiteOpenHelper  透過DataBaseHelper取得SQLiteDatabase物件,並實作新 增、刪除、查尋、建立資料表等函式 操作練習  透過模擬器操作,可以藉由DDMS的檔案瀏覽器 將SQLite資料庫檔滙出,或滙入現有的SQLite資 料庫至手機上。為你把關每一道 學習品質 343
  120. 120. SQLite DataBase-Query函式• SQLiteDatabase的query方法 • Cursor query (boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) • distinct : 是否希望每一row都是唯一的 • table:要查尋的資料表(Table) • columns:回傳資料包含的訊息列。不在清單中的其它列將不會取得。 • selection:相當於SQL中的where,如想傳回所有資料請用null • selectionArgs:如select中有使用到[?]變數,此字串將用來取代[?] • groupBy:查尋得到的資料是否要分群組 • having:相當於SQL中的having • orderBy:是否需要排序,若設定為null代表不排序 • limit:限制查尋回的資料組數上限為你把關每一道 學習品質 344
  121. 121. SQLite DataBase-Cursor介面• android.database.Cursor介面 • 由query方法查尋到資料後,將會回傳Cursor物件。 • 透過Cursor可以將資料庫查出來的資料進行讀寫操作。 • Cursor可以連結CursorAdapter並覆寫其bindView()和newView()兩 抽像方法來達到更豐富的顯示方式 Activity SQLiteOpenHelper Cursor Query結果 SQLiteDatabase為你把關每一道 學習品質 345
  122. 122. SQLite DataBase-Cursor介面• Cursor常用的函式 函式 說明 movetoPosition(int pos) 移動Cursor到指定位置 Move(int offset) 向前或向後位移:正數向前,負責向後 moveToFirst() 移動Cursor到最前或最後位置 moveToLast() moveToNext() 將Cursor向前或向後移動一格 moveToLast() isBeforeFirst() 判斷Cursor是否指向第一個數據之前,或最 isAfterLast() 後一個數據之後 isFirst() 判斷Cursor是否指向第一行或最後一行 isLast() getColumnCount() 取得行(列)數目 getCount() getColumnIndexOrThrow(String 根據列的名稱取得索引值(以0開始) columnName) getBlob(int index) 依據資料型別取回Cursor目前所指的資料。 getInt(int index) getLong(int index) getShort(int index) getFloat(int index)為你把關每一道 學習品質 getString(int index) 346
  123. 123. SQLite DataBase-Cursor介面• Cursor與CursorAdapter連接 • public SimpleCursorAdapter (Context contex, int layout, Cursor c, String[]from, int[] to) • context:作用於那個Context物件上 • layout:顯示介面layout樣版,需包含to所指定的View id • c: cursor資料來源 • from:需要從cursor取出的列名稱索引值 • to:layout中需要作用對應於from值的View元件id s = (Spinner)findViewById(R.id.spinner); display = (TextView)findViewById(R.id.display); SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_spinner_item,c,new String[] { MyHelper.COUNTRY}, new int[] {android.R.id.text1}); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_it em); s.setAdapter(adapter); 為你把關每一道 學習品質 347
  124. 124. SQLite DataBase-日記簿運作流程 ActivityDiaryEdit DiaryDbAdapter onCreate () onClick() deleteDiary() createDiary() getAllNotes() EditText EditText Button getDiary() updateDiary() ActivityMain extends ListActivity onMenuItemSelected() onCreateOptionsMenu() DatabaseHelper createDiary() onListItemClick() onCreate () SimpleCursorAdapter NAME:database TABLE: diary Query結 Cursor 果為你把關每一道 學習品質 348
  125. 125. 範例練習 範例專案名: ex08_02_Sqldiary 練習目標:  學習如何利用SQLite開發一個簡單的日記本  學習SimpleCursorAdapter的使用 程式撰寫  實作一個ListActivity,並將SQLite讀取得到的資料透 過SimpleCursorAdapter顯示於ListActivity上.  實作一個DiaryDbAdapter,協助處理與SQLite資料庫 存取有關的所有操作  設計一個日記輸入與編輯的UI頁面 操作練習  透過模擬器進行操作。為你把關每一道 學習品質 349
  126. 126. DataBaseAdapter架構實作 • 設計DataBaseAdapter可提供Activity頁面一個統 一且一致的存取資料庫的介面,並且將SQL語法 隱藏在Adapter內,對外只提供高階的存取介面。 SqlObject SqlObject DataBaseAdapter SqlObject IOInterface Activity SQLite DataBaseOpenHelp為你把關每一道 學習品質 350
  127. 127. DataBaseAdapter架構實作 • 資料庫表單常數定義介面 • DataBaseColumnDefine public interface DataBaseColumnDefine { /* 表單名稱 */ public static final String USERPROFILE_TABLE_NAME = "UserProfileDB"; /* 欄位名稱 */ public static final String USERPROFILE_KEY_ID = "_id"; /*SQL指令*/ public static final String SQL_CREATE="CREATE TABLE "; public static final String SQL_DATA_AUTO=" integer primary key autoincrement"; public static final String SQL_DATA_NUMERIC=" NUMERIC"; public static final String SQL_DATA_TEXT=" TEXT"; /*建立table的sql語法*/ public static final String CREATE_USERPROFILE_TABLE = SQL_CREATE + USERPROFILE_TABLE_NAME+ " (" + USERPROFILE_KEY_ID + SQL_DATA_AUTO+");"; /*Query回來資料的欄位範圍*/ public static String[] userProfileDataSet = { USERPROFILE_KEY_ID,}; }為你把關每一道 學習品質 351
  128. 128. DataBaseAdapter架構實作 • SqlObject抽像介面設計 • 抽像的功能 /** * 取得privateKey值 */ abstract int getPrivateKey(); /** * 設定privateKey值 */ abstract void setPrivateKey(int privateKey); /** * 從資料庫中讀入資料 * @param cursor */ abstract void readProfile(Cursor cursor); /** * 將資料轉換成ContentValues以供資料庫做後操作 */ abstract ContentValues getContentValues();為你把關每一道 學習品質 352
  129. 129. DataBaseAdapter架構實作 • SqlObject抽像介面 • 產生SqlObject的建構工廠 /** * 依需求取得不同型態的SQL Object * @param type * @return */ public static SqlObject createUserDataObject(SqlType type){ switch(type){ } return null; }為你把關每一道 學習品質 353
  130. 130. DataBaseAdapter架構實作 • DataBaseAdapter實作方式 • 使用Singleton架構 private SQLiteDatabase sqlDataBase; private static DataBaseAdapter dbAdapter; private DataBaseAdapter(Context context){ DataBaseHelp dataBaseHelp= new DataBaseHelp(context); try { dataBaseHelp.createDataBase(); } catch (IOException e) { e.printStackTrace(); } sqlDataBase=dataBaseHelp.getWritableDatabase(); } public synchronized static DataBaseInterface getInstence(Context context){ if(dbAdapter==null) dbAdapter=new DataBaseAdapter(context.getApplicationContext()); return dbAdapter; }為你把關每一道 學習品質 354
  131. 131. DataBaseAdapter架構實作 • 透過DataBaseOpenHelp讀寫資料庫(SqlObject) • 新增 mDb.insert(USERPROFILE_TABLE_NAME, null, initialValues); • 刪除 mDb.delete(USERPROFILE_TABLE_NAME, USERPROFILE_KEY_ID + "=" + sqlObject.getPrivateKey(), null); • 修改 mDb.update(USERPROFILE_TABLE_NAME, initialValues, USERPROFILE_KEY_ID + "=" + sqlObject.getPrivateKey(0, null) > 0; • 查尋 Cursor mCursor=mDb.query(USERPROFILE_TABLE_NAME, userProfileDataSet, null, null, null, null,null);為你把關每一道 學習品質 355
  132. 132. DataBaseAdapter架構實作 • 上層Activity使用DataBaseAdapter方式 • 取得DataBaseAdapter物件實體 DataBaseAdapter dbAdapter=DataBaseAdapter.getInstence(this); • 讀取資料 ArrayList<UserProfile>allUser= (ArrayList<UserProfile>) dbAdapter.getAllUser(null); ArrayList<UserProfile>allUser=dbAdapter.getAllUser(allUser); //參照更新 • 新建資料 profile.setPhotoType(1); profile.setUserCompony("test"); profile.setUserName("Jarey"); profile.setUserPhone("09xxx"); long privateKey=dbAdapter.addUserProfile(profile);為你把關每一道 學習品質 356
  133. 133. 範例練習 範例專案名: ex08_03_SqlTemplet 練習目標:  學習如何設計DataBase Adapter  透過DataBase Adapter層將應用層與資料存取層分 程式撰寫  設計一簡單的資料庫表單,並實作DataBase Adapter 元件。  Activity層透過DataBase Adapter對資料庫進行資料存 取。  實作一個BaseAdapter元件,將從DataBaseAdapter 讀出的資料透過BaseAdapter顯示於ListView上。 操作練習  透過模擬器進行操作。為你把關每一道 學習品質 357
  134. 134. 第10節: Content Provider元件 Android Thread程式設計
  135. 135. Content Provider元件 ContentProvider元件設計  ContentProvider應用時機  與系統ContentProvider連接  自建ContentProvider 359
  136. 136. Content Provider元件• ContentProvider作用 • Android程式中所有的資料(包含檔案,資料庫)全都是Private的,不同 的應用程式之間不能直接存取 • ContentProvider提供解決不同應用程式之間交換資料的需求• ContentProvider提供一組標準介面 • query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder) • 透過Uri進行查尋,結果將透過傳回的Cursor物件進行存取. • insert(Uri url,ContentValues values) • 將一組資料插入到Uri指定的位置 • update(Uri url,ContentValues values,String where,String[] selectionArgs) • 更新Uri指定位置的資料 • delete(Uri url,String where,String[] selectionArgs) • 刪除Uri指定並符合條件的資料為你把關每一道 學習品質 360
  137. 137. Content Provider元件• ContentResolver • 外部應用程式可以透過ContentResolver介面來存取 ContentProvider所提供的資料 • ContentResolver物件是透過Activity.getContentResolver()取得• ContentResolver所提供的存取介面 • query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder) • insert(Uri url,ContentValues values) • update(Uri url,ContentValues values,String where,String[] selectionArgs) • delete(Uri url,String where,String[] selectionArgs) Application Application URI Content Content Activity Data Resolver query Provider insert update為你把關每一道 學習品質 delett 361
  138. 138. Content Provider元件• ContentProvider 中使用的Uri形式 • content://contacts/people/ 代表全部聯絡人資料 • content://contacts/people/1 代表ID為1的連絡人資料• Uri的組成結構: content://authority/path/id • 第一部份:content:// • 必要的標準前置碼(prefix) • 第二部份:authority • 提供者的名稱,在此建使用完全套件名稱定義以避免名稱出現衝 突狀況 • 第三部份:path • 為提供者內部的虛擬目錄路徑 • 第四部份:id • 請求之特定記錄的主鍵ID,若要請求所有記錄可以直接以/結尾為你把關每一道 學習品質 362
  139. 139. ContentProvider元件-讀取連絡人資料• Android內建的ContentProvider • content://browser • content://contacts • content://media • content://settings• Step1:於AndroidManifest.xml中加入權限 <uses-permission android:name="android.permission.READ_CONTACTS" />• Step2:取得ContentResolver物件,query連絡人資料 import android.provider.ContactsContract; import android.content.ContentResolver; ContentResolver cr = getContentResolver(); Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);為你把關每一道 學習品質 363
  140. 140. Content Provider元件-讀取連絡人資料• Step3:取得連絡人姓名 String name = cur.getString(cur.getColumnIndex("display_name"));• Ste4:取得連絡人ID (2.0.1後改版,Phone移至不同URI) long id = cur.getLong(cur.getColumnIndex(" _id"));• Step5:透過ID重新查尋得使用者電話 Cursor pcur = getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null ,ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + Long.toString(id), null, null); String strPhoneNumber = pcur.getString( pcur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));為你把關每一道 學習品質 364
  141. 141. 範例練習 範例專案名: ex08_04_Contentprovider 練習目標:  學習如何建立ContentResolver並連接至Android 的連絡人Content Povider,取得用戶的連絡人清 單資料 程式撰寫  透過getContentResolver ()取得ContentResolver 物件  連接至Android CONTENT_URI ,並將資料透過 adapter顯示於ListView上 操作練習  透過模擬器操作為你把關每一道 學習品質 365
  142. 142. ContentProvider元件-自建ContentProvider• 修改先前的日記簿範列,將新增、刪除、修改與查尋操作改以 ContentProvider的方式來實作。 ActivityDiaryEdit EditText EditText Button DiaryContentProvider insertDiary() Content extend ContentProvider updateDiary() Resolver Content query() Provider Insert() delete() ActivityMain extends ListActivity update() onMenuItemSelected() onCreateOptionsMenu() Content onOptionsItemSelected() Resolver onListItemClick() SQLiteOpenHelper SimpleCursorAdapter Diary Query結 NAME:database Cursor BaseColumns為你把關每一道 學習品質 果 TABLE: diary 366

×