2018年7月16日 星期一

Android Storage System 和 SD Card 存取

在 Android 平台開發中對 SD Card 的存取一直讓我很困惑,Android 提出的 internal storage 跟 external storage 是甚麼意思?Android 在 Context 跟 Environment class 都提供了 method 來對 external storage 的存取,這些 method 有甚麼差別,從 Android Kitkat 提出來的 secondary external storage 又是甚麼?

對 SD Card 存取的諸多混沌不清之處,讓人在開發程式時舉步維艱,因此在這邊整理一下自己的心得與了解,希望可以釐清這些問題,從而對 Android storage system 有一個比較清楚的認識

Android 想的跟你不一樣

首先最容易讓人困惑的大概就是 Android 提出的 internal/external storage 的概念了,我想大部分的人在第一次看到這個 internal/external storage 詞彙時,應該會認為所謂的 internal storage 就是在手機中內建的在主板上的 flash 區域,因為你可以看到,連系統 UI 上都是這麼顯示的

https://commonsware.com/blog/images/2014-04-07-storage-situation-internal-storage-1.png

而 external storage 理所當然的就是外接的 SD Card 了
我一開始也是這樣想的,但很可惜,Android 想的跟你不一樣,來看看 Android 是怎麼定義 internal storage 的



可以看到 Android 認為的 internal storage 跟 UI 上顯示的 internal storage 根本不是同樣的東西,官方文件是說,internal storage 是 app 的私有儲存區域,其他的 app 無法存取這個區域,而這個區域的內的資料會在 app 被移除時一併被刪除,文件裡從頭到尾沒有提到這個區域到底是在主板內建的 flash 還是在 SD Card 上,反之是用一個抽象化的概念來描述

再來看看 external storage 的定義吧



同樣的,external storage 跟 SD Card 也沒有任何關係,external storage 是使用者將裝置插入到 PC 上時被 mount 成 external storage 的區域,並且這個區域有可能是 SD Card,但也有可能不是

看完了 Android 對 internal 和 external 的定義,應該會有一種感覺,那就是 Android 根本不想讓 app 知道現在在用的儲存區域是內建的 flash 還是 SD Card,所以才用 internal/external storage 這種抽象概念把儲存區域包裝起來,實際上 internal 可以是在 SD Card 上,而 external 也可以在內建 flash 上

不清楚 Android 為什麼要這樣做,將儲存區域抽象化感覺對開發並沒有甚麼好處,絕大部分 app 應該都想知道現在實際在用的區域到底是不是 SD Card,另外雖然說 internal 也可以在 SD Card 上,但實際上應該沒有哪家製造商會做這種事情,所以可以預設 internal 都在內建 flash 上

瞭解了 internal/external storage 的觀念之後,接下來就是要講實際開發程式了,雖然 Android 想把 storage 抽象化,但還是有方法可以讓開發者判斷使用的 storage 到底是不是在 SD Card 上面的,尤其是被廣大開發者抗議之後,開始新增了對 SD Card 支援的 API...

在 Android 不同版本中對 storage 有不同的存取設計,所以以下會依照不同 Android 版本對 SD Card 的存取方法來做說明

Before Kitkat...

事實上,在 Kitkat 之前,Android 官方是不支援 SD Card 的,所以你連 SD Card 的路徑在哪都不知道,但是上有政策下有對策,可以用隱藏 API getVolumePaths 來取得系統上所有的 storage 路徑,假如製造商沒有故意修改這個 API,那 SD Card 的路徑應該會包含在其中

只要將取得的路徑跟官方 API 取得的 external storage 路徑比對,那不是 external storage 的路徑十有八九就是 SD Card 了

而因為在這個時期 Android 還沒有正式支援 SD Card,對 SD Card 的存取也就沒有甚麼限制,只要 app 持有 WRITE_EXTERNAL_STORAGE 權限而且你找的到 SD Card 的路徑,那 app 就可以對 SD Card 無限制的存取

Kitkat

Kitkat 對 SD Card 存取相關的 app 來說是一個重要的版本,在 Kitkat 中 Android 第一次對 SD Card 有了正式的支援,並且引入了 SAF 這個新的 storage 存取 API,但是 SAF 在 Kitkat 上還是相當難用的,等到了 Lolipop 才開始能用起來

Kitkat 對 SD Card 的支援主要是加入了 secondary external storage 的概念,而相對應的 primary external storage 其實跟 Kitkat 之前版本中的 external storage 講的是一樣的東西

Kitkat 還將下列這些 API 都加入了複數的版本
  • Context.getExternalCacheDir
  • Context.getExternalFilesDir
  • Context.getObbDirs

這些 API 原本在 Kitkat 之前都只會返回一個 String,也就是 primary external storage,但新加入的複數版的 API 會將 secondary external storage 一起返回,當然前提是你的系統上有 secondary external storage

就實務上來說,這個 secondary external storage 應該就是 SD Card 沒錯了,但實際上是不是 SD Card,在這時期也沒有任何官方 API 能給你答案,別忘了 Android 其實是不想讓你知道你是不是在用 SD Card 的

既然已經正式支援 SD Card 了,Android 也開始對 SD Card 的存取權限作控制,對於用 Context.getExternalFilesDirs API 返回的 secondary external storage 路徑,app 可以自由的寫入,但是這個路徑是專屬於 app 的,通常路徑會長得像這樣 /mnt/sdcard/Android/com.your.app/,而裡面的檔案在 app 移除時也會一併刪除

而 SD Card 中除了這個專屬於你 app 的路徑之外,其他路徑基本上 Android 是限制 app 直接去寫入的,除非使用 SAF 這個新的 storage 存取 API,不過 SAF雖然是可以讓你在 SD Card 的任意位置存取檔案,但是每次存取一個不同的檔案,系統就會跳出一次視窗通知使用者,使用者需同意之後,app 才有權限存取該檔案,這種可以說是擾民的使用方式,應該是沒有開發者會接受的

雖然在 XDA 有人提供了方法讓你不需經過使用者同意就可以在其他路徑寫檔和刪檔,但是這方法未經大量驗證,而且最重要的是它不能建立新資料夾,所以實用性不高

也有人說 ES explorer 可以在 Kitkat 上建立新資料夾,不過它用了甚麼神奇的魔法就不得而知了,網路上對這時期的 Android SD Card 存取絕大部分還是會推薦用一千零一招,root

Lolipop

因為在 Kitkat 上對 SD Card 的存取受到了這麼大的限制,Google 應該是收到了廣大開發者的抗議,所以在 Lolipop 上,Android 改善了對 SD Card 存取的機制

首先是 SAF,新增了一個 Intent 來讓 app 獲得 SD Card 上某個目錄以及其下所有子目錄的存取權限,app 可以透過這個 intent 詢問使用者是否要給予權限,一旦使用者同意,app 就可以對該目錄以及所有子目錄內的檔案做存取而不用重複詢問使用者

前面提到過在 Kitkat 時期沒有官方的 API 能告訴你 secondary external storage 是不是在 SD Card 上,不過 Lolipop 在 Environment class 中新增了 API isExternalStorageRemovable 可詢問任意位置的 storage 是否是 removable,若是 removable 那就是在 SD Card 上面了

基本上到了 Lolipop 時期,對 SD Card 的存取應該可以說沒有甚麼大問題了,不過後來 Android 還在持續修改對 SD Card 存取權限的機制以及又有了新的 storage API,這就之後有空再來分析了

Reference:

2018年7月14日 星期六

java 跟 C# 在 anonymous inner class 中引用外部變數

在 java 中的 anonymous inner class 中引用外部變數時,需要該變數為 final 才能成功引用,這是因為在 java 中這樣的外部變數引用實際上是將變數 copy 一份副本並存在 inner class 中當作 object field 來使用的,這樣做的好處是 compiler 不需要下功夫建立出額外的 code 來讓 inner class 實際持有外部變數

瞭解了 java 引用外部變數的作法之後,試想若是能在 inner class 中將變數修改的話,看起來就會變得很奇怪了,這會讓人以為在 inner class 中的對外部變數的修改會在 inner class 外的 scope 生效,但所謂的引用只是副本,修改的也只是副本而已

既然如此,就乾脆定義只有 final 變數能被 inner class 引用,這樣就不能在 inner class 內修改變數,也不會讓人誤解

不過在 C# 中情況就不一樣了,C# 跟 java 的不同是 C# 為了開發便利,compiler 會幫你做很多事,在 C# 中 inner class 引用外部變數就真的是實際持有這個外部變數,在 inner class 中不但可以修改外部變數,而且修改後的結果在 inner class 外也可以真的看到

2018年7月7日 星期六

github hidapi hid_write fail on windows

windows 跟 linux 都預設支援 USB HID device dirver,也都有提供原生 API 讓開發者可以直接跟 HID device 溝通,github 上有一個專案 hidapi 提供了跨平台的方案,包含 windows 、linux 跟 MAC 上的 library,library 內部使用各平台的原生 HID API,並提供統一的 API 介面,讓開發者利用 hidapi 提供的介面,使程式有了在不同平台上的移植性

照理說那我們用 hidapi 後,程式不需修改只要重新編譯就可以跑在不同平台上,但最近卻遇到程式在 linux 上可以正常運作,在 windows 上卻出現 error 無法跑起來的情況

檢查後發現都是在呼叫 hidapi 的 hid_write 時該 function return error 值 -1,網路上搜尋後發現有很多人遇到類似的問題,有人就直接將 issue 回報給 hidapi 的作者,該 issue 最後也解決了,而且其實 hidapi 的作者在 API header 文件中就有說明呼叫 hid_write 需注意的地方



windows 上會出現 error 的原因是,windows 預期每一個 hid packet 第一個 byte 都是 report ID,假如你的 HID 裝置定義一個 HID report 的話,則那個值就會是 0,而我丟進去的 buffer 卻沒有把 report ID 加上去,所以就出現 fail 了

那為什麼在 linux 上卻沒問題呢?應該是因為 linux 平台的 libusb library 自動幫你把 report ID 0 加上去了,去看 hidapi 在 linux 上使用 libusb 的實作就可以發現,在 report id 是 0 的情況下,hidapi 反而把 report id 從 buffer 中拿掉了,就是因為如此,所以 linux 才不會出現 error



值得一提的是,windows 預設只接收滿足 longest report length 的 data,通常是 65,所以 hidapi 在 windows 的實作還會幫你檢查你傳進來的 buffer 大小是不是小於預期的 report length,如果是的話會幫你 create temp buffer 幫你補足 buffer 長度,因此 hid_write return 的值,也就是實際寫入到 device 的值會跟你傳進去的 buffer 大小不一樣

2018年7月4日 星期三

USBView 替代品

USBView 是 Windows 提供的一個用來檢視系統上所有 USB 裝置的 tool,非常的簡單好用,但是要裝 Win 10 SDK 或 DDK 才會有,若是不想裝這些很肥的軟體,可以在網路上找到很多替代品可以用,因為微軟有將 USBView 的原始碼放出來

例如有一款 USB Device Tree Viewer 就是基於 USBView 原始碼修改而來的,而且據說更穩定 bug 更少,用了一下的確很不錯,有將 USB 裝置的資訊清楚的列出來

2018年7月3日 星期二

USB HID device

USB HID 是 USB 協定中的一部分,它是為了以 USB 為介面的人機通訊裝置例如 USB 滑鼠、USB 鍵盤發展出來的協定,其中包括了定義 USB HID 的 device class,USB host 跟 HID device 溝通的 protocol 等等

windows 跟 linux 預設都支援 USB HID 的 driver,所以對於 USB HID device 來說不需要額外安裝 driver 了,windows 也提供了一組 HID 相關的 API 讓開發者可以跟 USB HID device 通訊,在官網可以看到有關於 HID 與相關 API 的介紹

2018年6月20日 星期三

Nand Flash 簡述

ECC(Error Correction Code)

Nand Flash由於物理特性,Nand Flash中cell的電荷會慢慢漏電,導致資料不正確;抹除和寫資料次數越多,漏電的情形會更嚴重。為了確保資料的正確性,Nand Flash需要ECC功能來偵測糾正Nand Flash內部的隨機位元錯誤。通常Nand Flash本身並不提供ECC功能,而是由SOC的Nand Flash Controller來提供。

Nand Flash 讀寫單位

Nand Flash 中最小的讀寫單位是一個 page,一個 page 的大小通常為 2K bytes(user area) + 64 bytes(spare area),user data 是用來存放一般的儲存資料,spare area 是用來存放一些 meta data,不會用來儲存一般資料

一個 block 通常為 64 個 page,等於 128KB(user area) + 4KB(spare area),一個 block 是 nand flash 進行 erase 動作的一個最小單位

Spare Area

Spare Area 在 linux 系統中的專有名稱為 OOB(Out Of Band),可用來放置 ECC data,還有標記此 block 是否為 bad block,以及一些與 file system 有關的 meta data,Spare Area 從物理上來說跟 User Area 沒有任何差別

Flash 寫入操作

Flash 的寫入稱為 program,並且只能由 1 -> 0,若要寫入由 0 -> 1,則需先執行 erase,erase 完後 flash 內容變為 0xFF,之後便可以將 flash 作 1 -> 0 的 program


Reference
http://cmchao.logdown.com/posts/60216

2018年6月19日 星期二

USB 簡述

USB 是主從關係的 protocol


USB分為 host 跟 device 兩種角色,host 就像是我們的 PC,device 就像是一般的 usb 隨身碟,host 為主,device 為從,所有的通訊都是由 host 所發起,device 都是被動的接收 host 的 command 然後再回應

USB 有階層關係


一個 usb 接口可以接上所謂的 usb hub,usb hub 上提供更多的 usb 接口以連接更多的 usb 裝置,而以 usb host 來說一個接口最多可以這樣串接 127 個 usb 裝置

什麼是 usb root hub 跟 generic usb hub


PC 裝置管理員中可以看到 usb root hub 跟 generic usb hub 兩種裝置,通常 PC 主機版後端會看到幾個 usb 接頭,這個接頭就是 usb root hub 裝置了,並且它跟主機版上的 usb host controller 內建在一起

而常常機殼前置面版上也會有 usb 接口,這些 usb 接口就是 generic usb hub,這些接口沒有跟主機版上 usb host controller 內建在一起,實際上可以看成是外接的 usb hub,只是線路是焊在主機板上並接到機殼的 usb 孔中,這也就是為什麼常常主機版後的 usb 孔會比機殼前置 usb 孔穩定的原因