2014年4月20日 星期日

讀 【Why OO Sucks by Joe Armstrong】 ... 這傢伙不是真的懂物件導向(This guy don't know much about OO.)

原文出處似乎已經不可考,附上的聯結內的文章其實也不是原文。

這位Armstrong先生列出了四點「物件導向很爛的原因」。

有些我無法反駁,因為著墨不深、經驗不多,但我明顯感受到不正確的地方,就直接用力的「炮」了!

1.Data structure and functions should not be bound together.
2.Everything has to be an object.
3.In a OOPL data type definitions are spread out all over the place.
4.Objects have private state.

.......仔細看了一眼,基本上每一點都有得「炮」。

或許是因為他的觀點是集中在C++的物件導向,而不是Java的物件導向,所以他對物件導向的觀點很狹隘、想像力更是偏執。(因為跟C++工程師一起合作的經驗很多,所以對他們有這樣的偏見。)\



第一點就大錯特錯!

資料結構跟資料處理功能本來就可以分開!

There is no reason to bound the data structure and functions in one object.

不知道怎麼分開,就表示根本沒搞懂物件導向的極限!

If you don't know how to do it in different object, you don't know OO at all.



第二點...我不懂有什麼好抱怨的,可是我也不覺得對此反感有什麼「不對」。



第三點是最離譜的地方。

因為這點的好壞很主觀!

就好像C++的指標操作也引來很多人詬病一樣!但除此以外的物件導向語言(似乎)多有直接變換取直/取記憶體的功能。(在C/C++...連矩陣的操作都可以用指標亂入,某個「指標參數」的遞增並不是遞增這個指標的值,而是指「從指標01遞增到指標02」。──雖然程式碼和語法上有「特徵」,但整體結構卻沒有。)





第四點......不太懂!(難道我英文這麼差?)沒人強制設計者宣告任何private參數!

這又是個不懂OO的人會有的偏見。

private的功能和意義是避免特定資料備不受控制的修改,而不是把這個資料的型態藏起來!getter和setter就是這個概念的延伸物!

2014年4月17日 星期四

【Android】UsbManager 的 requestPermission() (UsbHost元件的使用心得2)

在Google官方的Android API中的範例教學裡,有一段如和動態的使用程式碼設定/註冊BroadCastReciever去偵測「Usb插上/拔除」的「事件」。(就是告訴你的程式Usb裝置是否還插在手機上。)

這段程式碼如下........

UsbManager mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
private static final String ACTION_USB_PERMISSION =
    "com.android.example.USB_PERMISSION";
...
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
mPermissionIntent是什麼?要做什麼用的?

下面的文字說明中有說,是要給requestPermission使用。

UsbDevice device;
...
mUsbManager.requestPermission(device, mPermissionIntent);
問題是...何時要使用?



APP啟動後,(或是在使用過程中,)發生了「拔除再插上(不一定是同一部Usb裝置)」時,會發現UsbManager雖然可以正常取得UsbDevice,但這個UsbDevice卻沒有「權限/Permission」。

這個時候就要使用requestPermission。



聽起來好像是很簡單的事情。但是如果是走範例中「XML設定」的人,就很有可能會跳過這一整段程式,而誤以為「XML設定會幫使用者取得穩定可靠的Permission」。

但其實並不是。

至少,如果希望自己寫的APP有「拔除後不用退出就可以切換到下一台Usb裝置」的功能,就需要使用requestPermission()。



另外一個要注意的地方是requestPerssion()可以多次重複進行。每做一次、畫面就會彈出一個徵詢使用者「是否要讓這個裝置連線嗎」的視窗。

一般來說,會因為沒有Permission而出錯的時機,是在「取得UsbConnection」時。

也就是說即使沒有Permission,UsbManager還是可以正常取得UsbDevice。

但是(讀上一篇)取得UsbConnection後,就要按時/定時把它關閉/釋放,如果資料傳輸、溝通的量很大、或次數很頻繁,取得UsbConnection的動作勢必要寫在迴圈/Thread中。

也就是說使用者根本沒有辦法監控每一次的取得動作。

如果迴圈/Thread不停的跑回來查詢UsbDevice的Permission,在使用者按下那個「確認」以前,程式都會一直彈出詢問視窗。

所以?

自己各憑本事想個機制去「只問一次」啦!



最笨的方式就是在迴圈/Thread後面寫個無窮迴圈去「等使用者選擇『同意連線』」,因為...如果使用者不小心點到「不同意」時就GG了!

但這也開啟了另一個問題就是「如果只問一次,但是使用者不小心回答了『不同意』,要怎麼再次建立Usb連線?

...........

這其實不是問題。

1.拔掉再插上就好。

2.在Menu/選單中增加一個「偵測連線」選項,......目的是什麼?如果懂了應該就知道該寫怎樣的功能了......

2014年4月9日 星期三

【Android】hardware/usb (UsbHost元件的使用心得)

雖然我只有使用了UsbHost系列的功能,UsbAccessory的部分還沒用過,但經驗相信是差不多,而且UsbHost部分的元件複雜多了。

要在這裡附上我使用的程式做範例是不太可能的,我只能盡量做到引用官方API,然後每個細節補充



要在Android上使用USB、或稱為OTG,需要使用Android建立在  android.hardware.usb  這個package下的一系列元件,還有在APP的AndroidManifest檔中寫下類似下面這樣的設定:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk android:minSdkVersion="12" />
    ...
    <application>
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>
</manifest>
(希望進來看到這篇文的人看得懂怎麼修改Manifest,因為我討厭寫基本教學。)

特別注意一下:
android:resource="@xml/device_filter"
這行是要另外搭配一個XML檔,(在res目錄下直接新增一個目錄「xml」,然後在裡頭新增檔案,──有興趣的人也可以取不同的名字,只要不會造成混亂就好。)檔案的名稱就取為device_filter:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" class="255" subclass="66" protocol="1" />
</resources>
要特別注意的是這個範例有點「過於」詳盡,其實vendor-id/product-id/class/subclass/protocol....並不全是必須的。(像下面這段也是可以的。這些XML檔都是直接引用自官方API的教學內容。)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

這些屬性/數值是每一台Usb裝置的「身分證」,我不是這方面的專家,無法非常精準正確的講述其中的細節,但是基本上......同一家公司生產的同一系列商品,基本上都有相同的vendor-id和product-id。

設定這個device-filter和Manifest,這樣裝置插上手機後,系統才會自動偵測「要不要啟動你的APP」。(也有不自動偵測,改走別的路徑的方式,就是自己設定BroadCastReciever去獲得裝置連線的Permission,但這方面的使用經驗還不多,改天再講吧。)

如果你/妳的APP要針對特定Usb裝置,就盡量把device_filter定義的詳細一點。如果不針對特定程式,就乾脆什麼都不要寫。


Android的Usb連線溝通方式,是以UsbDevice類別為基礎。每一個類別的物件代表一台Usb裝置。

獲得UsbDevice物件的方式,最簡單的就是......
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
但是這個方法在官網中的說明很模糊、很「曖昧」,像最基本的...這個intent是什麼?怎麼獲取?(根據後來的BroadCastReciever範例,這個Intent根本不是在Activity中獲得。)

所以我並沒有用它。



我用的是下面這段方法...(幾乎是官方API照抄。)

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
... //Android
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while(deviceIterator.hasNext()){
    UsbDevice device = deviceIterator.next()
    //your code
}
但其實這段的作法是有很多彈性的...deviceList中的Key值其實就是UsbDevice的DeviceName。這段程式碼中獲得UsbDevice後,可以將DeviceName存起來,然後下次跑到這段時,直接使用DeviceName去獲得UsbDevice,就不用跑while迴圈了。

官方API教學程式碼如下。注意最後一行程式碼中的那個「"deviceName"」,這個地方並不是要使用者輸入「"deviceName"」,而是要輸入上一面的DeviceName!

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
...  HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
UsbDevice device = deviceList.get("deviceName");


菜鳥、初學Android的人可能會被API這種寫法給害死,就傻傻的一直輸入「"deviceName"」,然後好奇為何HashMap永遠要不到資料。因為直覺沒有敏銳到發現「我幹嘛在一個HashMap的get()中輸入deviceName這個字?想也知道這地方的內容是彈性的!

雖然Key值可以直接請HashMap吐給你/妳,可是用OTG時,是沒有adb功能的,(因為Usb接頭接著Usb裝置而不是電腦,)又某種原因,wifi-adb根本靠不住,(官方推薦改用wifi輸出Log資訊到電腦上,)所以吐出來的Key值...作夢想想就好。(或是自己在自己的APP中設置背景資訊的輸出功能,等到開發完畢在關閉這個功能就好。)

(因為Manifest中加了intent-filter,所以不需要經由BroadCastReciever去獲得Permission,因此我這裡不說明官方API中那段的使用方式。)



接下來是直接跟Usb機器「溝通」的部分。

跟機器「溝通」實在是個很模糊的概念,因為最終都是「送出Byte陣列資料」。

譬如...這裡有段Byte矩陣要送進特殊的隨身碟中存起來,只要矩陣開頭和結尾的Byte值是「100,101」,隨身碟就會把(「100,101」捨棄後)中間的值存起來。

(或是個喇叭,會把中間的值轉成音效,然後發出噪音。)

搞不懂的人可能要去研究一下網路傳輸或I/O原理,或就單純的自己用Java架個簡單的測試伺服器,然後練習用手機跟這個伺服器溝通看看。

就先......假設看到這裡的人都懂怎麼作,(遙遠的未來,或許我也來寫寫I/O相關的心得文吧。)



這段程式碼就是傳輸和接收資料的方法。(注意!Android的Usb元件能夠傳送和接收的資料有長度限制。上限是16K。)
private Byte[] bytes //要傳輸的資料  特別注意的是這個bytes的長度不能超過16K 也就是 16 * 1024  因為這是它每次能傳輸的資料量極限 
private static int TIMEOUT = 0; //每次傳輸的等待時間 (如果Usb機器超過這個時間沒回傳  就視為傳輸失敗)
private boolean forceClaim = true;
...
UsbInterface intf = device.getInterface(0);
UsbEndpoint endpoint = intf.getEndpoint(0);//又是個API害死人的例子
UsbDeviceConnection connection = mUsbManager.openDevice(device); 
connection.claimInterface(intf, forceClaim);
connection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT); //do in another thread
最後一行connection.bulkTransfer中,這個用法跟使用File元件讀取檔案的使用方法很像。(所以細節我也不說明了。)

關鍵在於每個UsbInterface中都會有兩組UsbEndpoint,一組為傳輸、一組為接收。(哪組在先、哪組在後,似乎並沒有絕對的標準和規則。所以我在上面的程式碼中補註明了「又是個API害死人的例子」。)

UsbConstants(這是一個UsbHost的元件)中可以看到相關的參數UsbConstants.USB_DIR_IN/UsbConstants.USB_DIR_OUT。

去判讀每組Endpoint的Direction,(使用指令getDirection(),可以得到參數,判讀參數是IN或OUT,就可以知道Endpoint的屬性和功能。)把傳輸的丟進去就會傳輸資料給Usb機器,把接收的丟進去就會從Usb機器接收資料。



最後,官方文件輕輕帶過、但困擾了我整整一個月的部分.......

UsbDeviceConnection和UsbInterface元件使用完,要進行「釋放」和「關閉」的動作。

為何要特別強調?

因為這段方式沒有「標準作法」!

一般人會以為「我不再需要使用Usb功能了,就把它關閉,」.......錯!這些元件雖然基本上是ParceLable介面的實作,(跟Bundle一樣,)但其實它們都混合了JNI,所以經常資料傳輸到一半,這兩個元件(其中一個?或兩個同時發生?)會變成「Null」!(我的解釋是「它自己把自己回收掉了。)

碰到這種情況時,就要重作一次獲取的動作。

可是隨著這種情況發生、多次獲取後,經常會連獲取都失敗!(UsbManager的openDevice指令回傳Null?UsbDevice的getInterface也回傳Null?)

因為UsbManager用一組「很奇特」的HashMap來存放這些元件的資料,這組HashMap功能異常(資料不見?資料錯誤?存放資料總數超過上限?)就會回傳Null。(可以用IDE打開UsbManager,但對於理解HashMap會異常的原因還是沒有幫助,而且修改SDK的程式碼對未來APP在實機上的運作其實沒什麼幫助。)

所以不能等到整個功能都用完了才去作釋放動作,必須要定時的釋放。

「每次傳輸/接收完,都釋放一次?」......還是錯!

這樣只是把「獲取失敗」發生的時間往後推遲而以。(我清清楚楚的記得...從不釋放,程式會在第540~550之間轉為「獲取失敗」。每次釋放,程式會在第1050~1100之間轉為「獲取失敗」。非常穩定、且準時的兩組數字。)



我的設定是「傳輸+接收」的次數「每十次」就進行一次釋放、再獲取的動作。

但這個數字是我碰運氣、靠靈感隨便決定的,(當然也在平版實機上進行了無數次的測試。)

如果你/妳要使用Android的Usb連線功能,而且不是用在最常見、制式標準的隨身碟這類儲存工具上,最後這個步驟記得多花點時間測試。

2014年4月1日 星期二

【Android】Handler.post(Runnable run) 讓這個Runnable跑久一點

標題是個Android的API。

簡單來說,Handler可以允許使用者輸入一個Runnable,並執行這個Runnable的內容。

所以.......用Handler來啟動Thread/Runnable不是也不錯嗎?



如果你曾經有過這個念頭,表示你跟我一樣蠢。

因為Handler會導向主線程,所以Handler啟動了這個Runnable後,MainThread/UIThread就被占住了。(有趣的是,如果在Runnable中直接寫入操作UI的相關指令/程式碼,系統不會擋下這個Runnable。但直接使用Thread來啟動這個Runnable,第一時間就被系統攔阻下來。)

這個APP不會對使用者的任何操作產生反應,連關都關不掉,最後是等系統通知「此APP無回應」,才順利關閉。



========================================



反向思考一下,其實Runnable並不等於Thread!

它只是種接口,而且不是Thread獨佔使用。

Handler.post這個用法應該不是另外開啟一個Thread,而是將Runnable的內容差入Main Thread中執行。

能幹嘛........參透中,真的不能理解這樣做跟實作一個有獨特功能的Handler的差異。終究只是今日一個無聊實驗的無聊結果。