2017年4月30日 星期日

RecyclerView基本用法 - 2/2 Adapter

RecyclerView中最特別的就是需要自訂一個Adapter,代碼中最少要重寫三個部分

  1. onCreateViewHolder
    • 為每一項資料建立一個View
  2. onBindViewHolder
    • 更改每個View的內容
  3. getItemCount
    • 取得資料的總數量
下面的代碼是一個有基本功能的RecyclerView Adapter
Activity中的代碼如下 直接使用上面的代碼已經可以建立一個簡單的RecyclerView,而且Activity已經能捕捉到點擊動作了。
這個範例中RecyclerView中的每項資料只包括一個TextView,如果想再加點東西的話也很簡單,例如想每項資料包括兩個TextView並顯示不同的文字就需要更改以下幾個地方就行了。

  1. 範例中DataSet中每列資料只包括一個String,要自訂一個包括兩個String的封裝並建立ArrayList<自訂>代替ArrayList<String>
  2. 更改item.xml,新增一個TextView
  3. Adapter中定義新增的TextView
  4. Adapter中的onBindViewHolder加入新增TextView的setText代碼

RecyclerView基本用法 - 1/2

從Android 5.0開始新增了RecyclerView,這個View的出現是為了代替舊有的GridView及ListView。GridView及ListView雖然現在還可以正常使用,但官方文件都建議使用RecyclerView,說它的效能較好,所以新建的應用都應該使用RecyclerView。此文章記錄了RecyclerView的基本用法,方便自己下次使用時不用再搜索一番。
RecyclerView的效能較好這一點使用者可能不容易察覺,但另一個特點所有人都能看到,這就是新增或刪除資料時的動畫效果。另外RecyclerView比起GridView及ListView獲取點擊事件的方法也有點不同。
1. 因為RecyclerView是Android Support Library內的一個元件,第一步要先安裝它,在build.gradle(Module: app) 插入以下代碼來安裝
2. 建立一個xml檔案用作RecyclerView內每個項目的樣板,最基本的竟是一個TextView
3. 插入RecyclerView到所需的位置
4. 在代碼中只有兩個東西需要設定:LayoutManager及Adapter
5. 三個LayoutManager的效果圖

6. 如果想要刪除,更新及新增資料的話除了要改變ArrayList的資料外,還要通知Adapter以更新資料同時顯示動畫效果,通知的方法有以下幾種
  • notifyDataSetChanged
    • 整個DataSet重新載入一次,沒有動畫效果
  • notifyItemChanged
    • 通知更新一項資料,有動畫效果
  • notifyItemRangeChanged
    • 通知更新數項資料,有動畫效果
  • notifyItemInserted
    • 通知新增一項資料,有動畫效果
  • notifyItemRangeInserted
    • 通知新增數項資料,有動畫效果
  • notifyItemRemoved
    • 通知刪除一項資料,有動畫效果
  • notifyItemRangeRemoved
    • 通知刪除數項資料,有動畫效果
  • notifyItemMoved
    • 通知移動一項資料,有動畫效果
下面是三種不同排列方法的動畫效果
https://youtu.be/M6LdHBxwPuE

7. 關於RecyclerView的Adapter會記錄在下一篇文章

2017年4月27日 星期四

Android鬧鐘測試 - 3/3 AlarmManager setAlarmClock及set

此文章記錄了Alarm Manager的方法及測試結果。下面所記錄的不包括API level 22及以上所新增的方法。 下面列出跟Alarm有關的方法,自API level 19開始,Alarm會分為準確(exact)和非準確(inexact),使用非準確的Alarm時,系統會自動提早或延遲Alarm以減少喚醒次數來達到省電的效果。例如你想應用定時跟伺服器同步,因為不需要準確在那一分鐘才同步,所以就可以用這種較省電的Alarm了。可是鬧鐘就不行了,一般都是用準確的Alarm。
非準確的Alarm有以下幾款,但因為這文章的重點是測試用作鬧鐘的Alarm,所以不就下面幾個方法測試了
  • set
    • 自API level 19開始變成非準確的Alarm
  • setRepeating
    • 自API level 19開始,所有重複的Alarm都規定是非準確
  • setInexactRepeating
    • 因為API level 19的改變,變成跟setRepeating一樣功能
此文章會測試以下幾個方法
  • setExact
    • 設定準確的Alarm
  • setWindow
    • 設定半準確的Alarm
  • setAlarmClock
    • 自API level 21新增的方法,設定準確的鬧鐘
  • getNextAlarmClock
    • 自API level 21新增的方法,取得下一個以setAlarmClock新增的Alarm
  • cancel
    • 取消Alarm
3個set Alarm中都要傳入一個PendingIntent,setExact及setWindow要傳入Alarm的種類,而setAlarmClock規定了使用RTC_WAKEUP這類Alarm。
Alarm分為4類,當中只有RTC_WAKEUP適合用作鬧鐘
  1. RTC
    • 實際的時間及不會喚醒系統
  2. RTC_WAKEUP
    • 實際的時間及喚醒系統
  3. ELAPSED_REALTIME
    • 自系統啟動後經過的時間及不會喚醒系統
  4. ELAPSED_REALTIME_WAKEUP
    • 自系統啟動後經過的時間及喚醒系統
下面會測試3個set的方法,看看設定鬧鐘後更改系統時間會有什麼效果。
現在的時間是14:30,設定了鬧鐘在15:00響,然後更改系統時間會有以下效果

  1. 更改時間到14:00,鬧鐘會在1小時後(15:00)響
  2. 更改時間到15:00,鬧鐘會立即響
  3. 更改時間到15:30,鬧鐘也會立即響
由此看到當更改系統時間時,鬧鐘是不會自動調整時間

現在的時間是14:30,時區是GMT+0,設定了鬧鐘在15:00響,然後更改系統時區會有以下效果

  1. 更改時區到GMT+1,鬧鐘會在半小時後(16:00)響
  2. 更改時區到GMT-1,鬧鐘會在半小時後(14:00)響
由此看到當更改系統時區時,鬧鐘會自動調整時間

另外當使用setAlarmClock時,系統會自動出現鬧鐘圖示及顯示下個鬧鐘時間

2017年4月23日 星期日

Android鬧鐘測試 - 2/3 PendingIntent requestCode及flags

如果要設一個Alarm,只當中有一個要傳入的參數是PendingIntent,這個參數是取得當所設的時間到達時要做什麼,可以是開啟活動,送出廣播或啟動服務,當中還有一個flag要設定。
以一個鬧鐘的應用來說,一般都是用送出廣播的方式。可是flag要設定那一個官方文件就找不到相關資料,只列出以下解釋。
  • FLAG_CANCEL_CURRENT
    • 如果找到相同的PendingIntent會把舊的刪除然後再建立一個新的
  • FLAG_UPDATE_CURRENT
    • 如果找到相同的PendingIntent會更新intent
  • FLAG_NO_CREATE
    • 如果找不到相同的PendingIntent會返回null,但是不會建立一個新的
  • FLAG_ONE_SHOT
    • PendingIntent只會執行一次
在這次測試中會使用下面三個function,一個是建立Alarm,一個是更新,Alarm,另一個是取消Alarm,測試一下用不同的flag來看對設定鬧鐘有什麼實際的影響。


  • set_alarm使用FLAG_CANCEL_CURRENT
    • cancel_alarm及update_alarm使用FLAG_CANCEL_CURRENT
      • 成功建立,更新及取消Alarm,但當取消時通知列的鬧鐘圖示不會消失,時間到達時才會消失但鬧鐘並不會響
    • cancel_alarm及update_alarm使用FLAG_UPDATE_CURRENT
      • 成功建立,更新及取消Alarm
    • cancel_alarm及update_alarm使用FLAG_NO_CREATE
      • 成功建立Alarm及找到並返回相同的PendingIntent
    • cancel_alarm及update_alarm使用FLAG_ONE_SHOT
      • 成功建立Alarm但不能取消,更新Alarm也不能正常運作,最終建立了兩個鬧鐘
  • set_alarm使用FLAG_UPDATE_CURRENT
    • cancel_alarm及update_alarm使用FLAG_CANCEL_CURRENT
      • 成功建立,更新及取消Alarm,但當取消時通知列的鬧鐘圖示不會消失,時間到達時才會消失但鬧鐘並不會響
    • cancel_alarm及update_alarm使用FLAG_UPDATE_CURRENT
      • 成功建立,更新及取消Alarm
    • cancel_alarm及update_alarm使用FLAG_NO_CREATE
      • 成功建立Alarm及找到並返回相同的PendingIntent
    • cancel_alarm及update_alarm使用FLAG_ONE_SHOT
      • 成功建立Alarm但不能取消,更新Alarm也不能正常運作,最終建立了兩個鬧鐘
  • set_alarm使用FLAG_ONE_SHOT
    • cancel_alarm及update_alarm使用FLAG_CANCEL_CURRENT
      • 成功建立Alarm但不能取消,更新Alarm也不能正常運作,最終建立了兩個鬧鐘
    • cancel_alarm及update_alarm使用FLAG_UPDATE_CURRENT
      • 成功建立Alarm但不能取消,更新Alarm也不能正常運作,最終建立了兩個鬧鐘
    • cancel_alarm及update_alarm使用FLAG_NO_CREATE
      • 成功建立Alarm但找不到相同的PendingIntent,返回null
    • cancel_alarm及update_alarm使用FLAG_ONE_SHOT
      • 成功建立,更新及取消Alarm

    從以上測試結果可看到
    使用FLAG_CANCEL_CURRENT或FLAG_UPDATE_CURRENT建立的Alarm要用FLAG_UPDATE_CURRENT更新或取消。
    使用FLAG_ONE_SHOT建立的Alarm要用FLAG_ONE_SHOT更新或取消。
    以FLAG_NO_CREATE來找有沒有相同Alarm時,只會找到以FLAG_CANCEL_CURRENT或FLAG_UPDATE_CURRENT建立的Alarm。
    另外如果雖要建立多個不同的Alarm時,要使用不同的requestCode,系統就會建立一個新的Alarm而不會動到舊的Alarm。
    在搜索鬧鐘程式範例時,所找到的資料都沒有提到使用不同的flag會有什麼不同的用途,而且官方的文件中都是用0(no flag)去建立Alarm,看來對於Alarm Manager來說這個flag沒有特別用途吧。

2017年4月20日 星期四

Android鬧鐘測試 - 1/3 簡介

關於Android鬧鐘的範例網絡上很容易找到大量資料,於是我根據這些資料來作一個來測試,本以為不用花很多時間就能完成,可是遇到一個很重要的問題,花了很久都找不到完美的解決方法,現在把這問題及暫時的解決方法記錄下來,希望以後能找到解決方法。
這個問題就是當我設定,更改或刪除鬧鐘後,除了等到所設定的時間外,怎樣可以確定鬧鐘真的成功設定了,系統好像不能列出所有已註冊的鬧鐘吧。
一個鬧鐘應用基本要做以下幾件事:

  1. 建立一個接收器,當所設定的時間到了就響鬧鈴。
  2. 向系統建立Alarm,當到了指定時間就通知接收器。
  3. 向系統更改或取消已設定的Alarm。
  4. 當系統重啟時,所有已設定的Alarm都會自動清除,需要在重啟後再建立Alarm。
步驟1及4沒有什麼特別,這裡只記錄代碼,不作進一步測試了。

本文的重點是步驟2及3,向系統建立,更改及取消Alarm,基本代碼如下
在進行測試前,要先找出列出所建立Alarm的方法才能繼續下去,經過搜索一段時間後,看來要列出Alarm的最好方法是便用ADB shell,在platform-tools目錄中輸入以下指令後,所有Alarm會存在dump.txt
......platform-tools>adb shell dumpsys alarm > dump.txt
但是這個辦法很麻煩,而且可讀性很低,再查看AlarmManager的文件時發現了自API level 21開始新增了一個getNextAlarmClock的方法,看來這就是我們所需的。只要設定Alarm時使用setAlarmClock而不用傳統的set,這方法就能找到下一個會執行的Alarm

2017年4月17日 星期一

關於Android權限的重點

我對於Android權限的理解還只是停留左API 1x年代,把權限定義好之後使用者安裝Apps前同意了權限就完成任務了。經過了很多版本更新,到了現左的API 25,請求權限變得跟以前十分不同了。每次實作應用前都要先在網絡搜索關於權限的問題,我對於權限還有點不清晰,現在要花點時間整理一下網路上找到的資料,並記錄下來。

Android Studio 2.3.1
targetSdkVersion 25

1. 當你的應用要增加權限前,先要確定一下是否真的需要這個權限實作這個功能, 例如可否以Intent的方法代替。我認為每個應用的權限越少越好,而且也省卻了很多麻煩。

2. 定義應用所需的方法一直都沒有改變,只是簡單地把所需權限加入AndroidManifest.xml

3. 使用者批准權限的方式自Android 6.0 (API level 23)開始有大幅改變
  • 在Android 5.1 (API level 22)以下的應用,使用者要先同意所有應用中要求的權限才能安裝,安裝後不能再取消權限,代碼中不用特別再聲明權限了
  • 在Android 6.0 (API level 23)開始的應用,使用者在安裝應用時不再需要批准權限了,當應用執行需要特別權限的功能時才向使用者要求權限,同意了才能繼續,使用者不用同意全部權限,而且能隨時在設定中取消已批准權限
4. 權限分為三個保護等級 - 一般權限(normal),危險權限(dangerous)及特別權限(signature/system)
  • 一般權限是對於重要個人資料外泄或影響系統保安方面是低風險的權限,這類型的權限只需要在Manifest定義了就能自動取得,並不需使用者另外批准,例如使用藍牙的權限
  • 危險權限則是相反,是對於重要個人資料外泄或影響系統保安方面是高風險的權限,這類型的權限除了要在Manifest定義了,在應用執行時要取得使用者批准,而且使用者能隨時取消這類型的權限,例子有存取聯絡人的權限
  • 當一個權限沒有被歸納為一般或危險時,會將它分類為特別權限,大部分應用都不會用到這一類的權限,有一些是系統專用,也有一些要取得認證才行,例如是直接修改系統設定的權限
  • 所有權限都有一個所屬的權限群組,當中危險權限的群組比較重要,下面會用實例解釋及就每一類的權限抽取一個分別在Android 6.0 (API level 23)及Android 5.0 (API level 21)上測試和記錄結果
5. 一般權限 - ACCESS_WIFI_STATE (讀取WiFi狀態)

  • Android 5.0 (API level 21) 只需要在安裝前批准權限就能成功取得WiFi狀態
  • Android 6.0 (API level 23) 在安裝前沒有顯示所需權限,安裝後直接能取得WiFi狀態

6. 危險權限 - GET_ACCOUNTS(讀取帳戶資料)

  • Android 5.0 (API level 21) 只需要在安裝前批准權限就能成功取得帳戶

  • Android 6.0 (API level 23) 在安裝前一樣沒有顯示所需權限,但安裝後是不能取得帳戶,因為權限批准的方式改變了,要實時請求權限,加入以下代碼後如果使用者批准就能取得帳戶

  • 值得留意的是雖然只是請求取得帳戶的權限,但因為取得帳戶跟讀寫聯絡人的權限是同一組,請求時顯示是取得整組的權限,如果Manifest也定義了讀寫聯絡人的權限的話,即使請求代碼中沒有寫READ_CONTACTS及WRITE_CONTACTS,也能直接存取帳號及聯絡人
7. 特別權限 - WRITE_SETTINGS (直接更改系統設定)

  • Android 5.0 (API level 21) 只需要在安裝前批准權限就能成功更改設定
  • Android 6.0 (API level 23) 在安裝前一樣沒有顯示所需權限,但安裝後是不能更改設定,而且也不能跟危險權限一樣的方法實時請求權限,要用其他方法,加入以下代碼後會開啟系統頁面,在這裡批准了更改才能取得更改系統設定的權限

2017年4月11日 星期二

Android Calendar Provider的基本用法及範例

如果想在Android App中跟Google日歷互動,其中一個方法就是使用Calendar Provider API。網路上很容易找到相關的資料,但資料都很零碎而且沒有一個完整的基本範例,我把資料整理後記錄下來方便下次使用。

Windows 10
Android 7.1.1 API level 25
Android Studio 2.3.1
範例的影片:
https://youtu.be/DCUlL6GMvQo

每個Google帳戶都能登入Google日歷,預設有一個屬於你帳戶的日歷,你可以把別人的日歷加到你的帳戶,又可以自行新增日歷,每個日歷包括了活動。3款日歷中只有預設及自行新增的日歷能編輯,當中只有預設日歷是真正使用者擁有的,因此在API中找使用者擁有的日歷時,只會找到預設的耶一個,自行新增的是找不到的。
如果要在Android Apps中新增,更改或刪除活動,第一步是先要找出目標帳戶,然後找出目標日歷,之後才編輯活動。下面記錄了從一個新建的Project中加入編輯Google活動的步驟。
此範例中所有功能都是在介面執行緒執行,實際使用時應該在背景執行,本範例只記錄了以下幾個常用的功能:
  1. 找出目標帳戶
  2. 找出目標日歷
  3. 列出活動
  4. 新增活動
  5. 更新活動
  6. 刪除活動

事前準備:
1. 準備一個安裝了Google Calendar的模擬器或實體機
2. 確認在Android Studio SDK Manager已經下載Support Repository目錄下的Google Repository
3. 新增一個Android Studio Project,minimum SDK = API25,選擇Empty Activity

開始:
1. 製作一個簡單介面方便展示功能,包括了Button及EditText,把下面的代碼貼到activity_main.xml

2. 找出目標的Google帳戶 (使用AccountPicker,不需任何Permission)
  • 要使用AccountPicker,先要安裝兩個Google Play Service功能,把以下代碼貼到build.gradle(Module: app)
  • 真正應用時應該要先檢查Google Play Service的版本,如果對應才繼續執行,這範例跳過這步驟
  • 除了使用AccountPicker還有其他方法來取得帳戶,但需要有讀取聯絡人的權限
  • 在MainActivity class中插入以下代碼,每當調用函數時會出現下面的效果
    • 當沒有帳戶登入了手機時,會出現登入畫面,建立或登入帳戶後會從onActivityResult返回帳戶名稱
    • 當已經有帳戶登入了手機時,會出現叫用家選擇帳戶的畫面,然後從onActivityResult返回所選的帳戶名稱
3. 要求使用者給予權限
  • 在MainActivity class中插入以下代碼,每當調用函數時會要求使用者給予權限

4. 找出目標日歷 (使用CalendarProvider,需要讀取日歷的權限)
  • 在此範例會找出使用者所選的帳戶及可以完全控制的日歷
  • 因應Android 6.0 開始改變了獲取權限的方式,從以前安裝前要求獲取所有權限變成執行中才要求獲取所需的權限,所以在開始查詢前要先檢查有沒有所需的權限
  • 在MainActivity class中插入以下代碼,每當調用函數時會找出日歷及要求使用者選擇
5. 列出活動 (使用CalendarProvider,需要讀取日歷的權限)
  • 查詢活動跟查詢日歷差不多,不同的是要定義所查詢的日期範圍
  • 在MainActivity class中插入以下代碼,每當調用函數時會找出活動及要求使用者選擇
4. 新增活動 (使用CalendarProvider,需要寫入日歷的權限)
  • 在MainActivity class中插入以下代碼,每當調用函數時會新增活動及返回新增的活動ID
5. 更新活動 (使用CalendarProvider,需要寫入日歷的權限)
  • 在MainActivity class中插入以下代碼,每當調用函數時會更新活動的標題
6. 刪除活動 (使用CalendarProvider,需要寫入日歷的權限)

2017年4月9日 星期日

Android Studio模擬器安裝 Play Store及Google Calendar

在使用Android Studio開發App過程中,很多人都不會用實體機測試,大部分時間都是用模擬器,在最後階段才會用實體機測試。Android Studio內建了模擬器,直接從官方下載鏡像檔安裝,其中分為兩個版本,一個包含Google APIs 另一個沒有,可是包含Google APIs 那版本是沒有安裝Play Store的,因此要自行安裝。下面記錄了在Window 10 中安裝Play Store的步驟。

事前準備:
1. 在Android Studio 設定中找出Andriod SDK 的位置,下面會統稱SDK_HOME,預設位置是
C:\Users\[User]\AppData\Local\Android\Sdk
2. 建立一個新的模擬器,選擇所需的Android版本(with Google APIs)鏡像檔
3. 下載Google Apps packages,選擇跟鏡像檔一樣的Android版本及pico
http://opengapps.org/
4. 下載及解壓支援.lz檔案7zip,從官方下載的7zip是不支援
    http://download.savannah.gnu.org/releases/lzip/7zip/


5. 用剛下載的7zip開啟步驟2下載的Google Apps packages中找出所需的4個apk及解壓到SDK_HOME\platform-tools\

  1. Play市場 - Phonesky.apk
    • \Core\vending-all.tar.lz\vending-all.tar\vending-all\240-320-480\priv-app\Phonesky\Phonesky.apk
  2. Play服務 - PrebuiltGmsCore.apk
    • \Core\gmscore-x86.tar.lz\gmscore-x86.tar\gmscore-x86\nodpi\priv-app\PrebuiltGmsCore\PrebuiltGmsCore.apk
  3. Google服務框架 - GoogleServicesFramework.apk
    • \Core\gsfcore-all.tar.lz\gsfcore-all.tar\gsfcore-all\nodpi\priv-app\GoogleServicesFramework\GoogleServicesFramework.apk
  4. Google帳戶管理服務 - GoogleLoginService.apk
    • \Core\gsflogin-all.tar.lz\gsflogin-all.tar\gsflogin-all\nodpi\priv-app\GoogleLoginService\GoogleLoginService.apk



開始安裝
1. 開啟Command Windows
  • 按Win+R 之後輸入cmd
2. 列出現有的模擬器
  • 移到SDK_HOME\emulator 目錄後輸入emulator -list-avds
C:\Users\User>cd C:\Users\User\AppData\Local\Android\Sdk\emulator
C:\Users\User\AppData\Local\Android\sdk\emulator>emulator -list-avds
New_Device <--剛新增的模擬器
Nexus_5X_API_21 <--模擬器1的名字
Nexus_5X_API_25 <--模擬器2的名字
3. 以可寫入系統檔案模式開啟之前新增的模擬器
  • 輸入emulator @[模擬器名字] -writable-system
 C:\Users\User\AppData\Local\Android\sdk\emulator>emulator @New_Device -writable-system
4. 開啟另一個Command Windows
  • 按Win+R 之後輸入cmd
5. 移到platform-tools目錄
  • 輸入cd.. 之後輸入cd platform-tools
C:\Users\User\AppData\Local\Android\sdk\emulator> cd..
C:\Users\User\AppData\Local\Android\sdk> cd platform-tools
C:\Users\User\AppData\Local\Android\sdk\platform-tools>
6. 取得 root 權限
  • 輸入adb root
C:\Users\User\AppData\Local\Android\sdk\platform-tools>adb root
7. 重新掛載系統檔案
  • 輸入adb remount
C:\Users\User\AppData\Local\Android\sdk\platform-tools>adb remount
remount succeeded
8. 安裝Play市場,Play服務,Google服務框架及Google帳戶管理服務

  • 輸入adb push Phonesky.apk /system/priv-app/
  • 輸入adb push PrebuiltGmsCore.apk /system/priv-app/PrebuiltGmsCore/
  • 輸入adb push GoogleServicesFramework.apk /system/priv-app/GoogleServicesFramework/
  • 輸入adb push GoogleLoginService.apk /system/priv-app/GoogleLoginService/

C:\Users\User\AppData\Local\Android\sdk\platform-tools>adb push ......

C:\Users\User\AppData\Local\Android\sdk\platform-tools>adb push ......

C:\Users\User\AppData\Local\Android\sdk\platform-tools>adb push ......

C:\Users\User\AppData\Local\Android\sdk\platform-tools>adb push ......

9. 使用Android Emulator 關閉模擬器及使用 Android Studio 重啟模擬器 ,待模擬器完全載入後再重啟一次
10.直接使用Play Store 下載及安裝Google Calendar
12. Google Calendar 已經成功安裝在官方模擬器了