2016年1月12日 星期二

SurfaceView的透明背景一片黑...要將Bitmap屬性設為Config.ARGB_4444

1.在ViewHolder上設定setFormat(PixelFormat.TRANSLUCENT);
2.在畫上想要的內容前先執行canvas.drawColor(Color.TRANSPARENT,Mode.CLEAR);

執行了這兩個步驟後,還是經常有人拿到一片黑的背景。

這是因為作為繪圖用的Bitmap,Bitmap.createBitmap()這個函數中傳入的可能是RGB_565。

改為ARGB_4444後就解決了。



對了!

記得打硬體加速的設定打開!

在AndroidManifest檔中,在Application標籤內加入android:hardwareAccelerated="true"。

2016年1月6日 星期三

HashMap的Key和Value

雖然Key也可以丟物件型態的資料進去,包含String,但令人驚訝(但其實又不驚訝)的是「它不會比對內容」。

例如用字串「Test」當Key值,丟進去了一個物件A,之後再用一個新的、但內容一樣的字串「Test」去搜尋,會發現找不到物件A。

(也就是說:它是用HashKey值作為Key......所以叫「Hash」Map?)



這也挺合理。

今天如果是用String以外的物件(假設為類別1)做Key,然後內有N個參數,然後用實體A來做為Key值存放Value,──HashMap怎麼知道當使用者修改了參數值後,實體A還是同一個Key值?而接下來存入的資料要存放在新欄位?還是取代舊資料?

如果要讓HashMap的Key值有辨識內容的功能,這大概會變成無限迴圈跑不完,(萬一類別1的參數又有物件型態的資料時,是否又要再分析一次...)



根據以上特性,HashMap除了作為存放跟取用的容器以外,是否也可以快速地幫忙比對資料?

譬如類別2內有M組參數,但我們只需要比對其中M1/M2/M3/M4...

再設計一個類別3,內有N組參數可以存放M1/M2/M3/M4...

用類別3的實體物件為Key、類別2的實體物件為Value,類別3實體物件參數值設定跟類別2實體物件一樣,然後直接判讀Key來判斷是否需要取用內容?

public static class KeyObj2{
  
 int a, b, c, d, e, f, g;
  
}
 
public static class KeyObj3{
  
 int a, b, c, d, e;
  
}


類似這樣兩組物件...另外再準備一個矩陣,長度為5,剛好可以存放N組參數。

這同時也需要兩個HashMap(A/B),一個是以矩陣為Key,另一個是以類別3為Key,但Value都是類別2。

然後在準備一個單純的List,用來直接存放類別2。

將HashMap的Key值已Set<T>的方式取出來,(怕有人不知道:它有內建函數可以輸出Set<T>。)然後再用for(T : Set)的方式判讀裡面的結果........

至於List的內容,就直接用for(... : ...)判讀即可,不需要再取出值。



以物件為Key值,執行的效率比以矩陣為Key值快了.......一成左右!

聽起來好像很不賴,但List直接取個別值出來判讀內容,只需要以物件為Key值的2成...不是快兩成,是只需要兩成。

顯然不管怎麼設計,用HashMap就是無法做到快速比對內容!(與其試圖智慧化,不如單一化、線性化,這真是程式設計的黃金法則!)



另外,物件的Class值也是個Hash,而且只會以實體物件宣告的方式為主,不會被多型性所干擾。(但這是Java,在其他也有提供Collection類別的程式語言上,不知道情況如何。)

所以用HashMap<Class, ObjectA>的方式在ObjectA中設置一個static型態的HashMap,就可以用來存放所有ObjectA的子類別的SingleTon參數,不需要再每個子類別中設計不一樣的static參數或static函數去取得SingleTon參數。

2015年12月22日 星期二

Android手機上GCM的設定方法

【注意!如果是在找iPhone,或純網頁版的GCM功能,建議別在我這篇文章裡浪費時間了,除非你搞不懂Key和SenderID之間的關係,弄不懂自己到底哪裡出錯。】

所謂的GCM,就是Android會時不時從網路上收到由「Google系統」所發出「針對你這支手機、要給你的資訊。」

它的結構並不難!只是有些資訊因為版本不停更換,不管是API說明,或是Console端介面,所以說明變得越來越模糊籠統,對新人來說越來越不友善。

但GCM只是種「如何實現非Google系統也能針對特定手機發送針對這支手機所發出的資訊」的機制,它的具體應用內容目前並沒有太多規範與限制!

所以要試著把「自己想做的事情和應用」給排除、專注在理解「發送GCM」上,反而會容易理解GCM的機制與應用方法



雖然網頁教學強調「Server Key」和「SenderID」是應用的基礎,但......

「Google Developer Console端設定」才是這項功能的核心!

(官網上的教學完全假設使用者已經對Console端運作的邏輯和每個欄位每個值得意義滾瓜爛熟......)

每一支GCM功能都要在Console端設定一項專案!

意思是說...一支網頁如果有發送GCM功能,它是否只能針對一項GCM功能?一支手機APP一次是否只能囊括一項GCM功能?...以上答案都是「不」!

理論上來說,一支APP裡面同時可以應用到很多個GCM功能。

比如A購物應用和B購物應用都有自己的GCM內容,能否做一支APP同時把兩支應用的GCM都改由同一支APP接收?(「我不想要購物,但我想要知道廣告消息?」)...

理論上來說,可以!

(2015/12/23:我自己是有測試過,成功了。但未來是否會針對這項特性做出限制?...不保證!)



GCM服務在Google Console上可以獨立成為一項專案嗎?

理論上來說可以,只是不知道為何,我連測試它的動力都沒有。可能是因為我是使用GAE平台來施作Service,而我的GCM服務就直接附屬GAE專案在裏頭。但對不使用GAE的使用者來說,這樣做可能反而很合理。

這是Google Console設定的邏輯。一項專案可以單純的只是一串Google API應用權限的集合,設定完之後任何網頁APP或手機APP都能使用這項專案的權限設定。

再講精準一點、講的懶人一點,一支手機APP中,可以使用兩組以上的Google API專案?...答案當然也是「可以」。(關鍵點在這組專案的這項功能是否讓特定APP使用。如果不行?那就是沒有權限!如果將特定APP的權限加入後,自然就可以使用了。)



這是很多菜鳥在思維跟管理上會犯的錯。(對!我是菜鳥!)

GCM服務並不屬於任何Web Service或是APP,而是Web Service或APP需要使用特定GCM專案時,需要將自己的權限加入GCM服務專案中。

將Web Service加入GCM服務專案後,就會產生一組Server Key。這組Key就是Authorization欄位中所要帶入的參數值。(我不會用XMPP,所以那段怎麼做,請自行研究一下。)

至於APP端需要的SenderID,其實就是這個GCM服務專案的專案ID!

(2015/12/23:不知道為何,中文版的Console上,每個專案都有兩組專案ID,一組是文字,另一組則是編號。這地方要取用編號。)

如果Service Key跟SenderID來自不同的GCM專案,就會產生「MismatchSenderID」的結果!

如果為了方便開發,APP端跟WebService可能會分頭進行,為了測試就用GAE設置了測試用的簡單WebService,這時候就很容易忽略了這個步驟.......

因為是GAE,所以一定會在Console上設置一組Google API專案。就很順手的把GCM服務也歸屬在這項專案中,也從中取得ServiceKey和加入APP權限......但不知道為何,使用的SenderID還是正式WebService的專案ID。



對.......

其實這整篇文只是想解釋MismatchSenderID造成的原因。

2015年12月7日 星期一

檢查參數內容是否是同一個物件實體?

假設物件A內有兩個參數,分別是字串B和長整數C。

物件A生成兩個實體參數,分別是實體一和實體二......

可以將實體一和實體二的B和C都設為同樣的數值內容,但如果用if(實體一 == 實體二)來檢查結果...

在Java中一定會得到false。

除非逐一針對B和C參數比對,不然永遠都會是false,──但這不是重點!



只要善用這個特性,可以輕鬆地完成「資料是否有更新」的比對。



最近碰到一個功能,(我保留了細節,只講大概,)要在Activity A中用網路去查詢資料,查詢完後將資料在Activity B鐘用列表(ListView)顯示,並且用彈出式視窗告知使用者「現在有幾筆資料」,然後在Activity C中可以看各筆資料的詳細內容.......並且重新設定查詢條件後再次查詢、查詢完後退回「上一頁(也就是Activity B)」。


用彈出式視窗告知使用者「現在有幾筆資料」是後來追加的功能,所以我設計的簡單,就是在Activity B的onResume中把列表的資料數顯示出來就是...

但問題是在Activity C中,使用者大部分只是單純的看完細節就退回Activity B,並不會重新設定查詢。

所以「判斷是否有重新設定查詢」仍是有必要的一件事。



方法很簡單。

資料要能跨三個Activity間傳遞並操作,方法就是使用一個static型態的ArrayList參數。

(在同一個APP中只要這樣做即可,Intent是很多餘的東西。)

每次只要有「查詢」,就會將這個static ArrayList參數重新產生一個實體。

然後在Activity B中設置一個物件類別專屬的ArrayList參數三,並在onResume中做if(實體一 == 實體二)的檢查,就可以快速精準地知道「使用者是否重新使用過查詢功能」。

(怕有人不知道:通常,將static ArrayList設為實體一,可以省去檢查實體二是否為null的動作。)



但如果ListView的Adapter直接使用參數三,這個方法「可能」會導致APP當機。

怕有人不知道......

安全的程序應該是再產生ArrayList參數四,然後用addAll()的方式將參數三內的資料全部倒進參數四中,然後再使用參數四來產生ListView的Adapter。

(資料量不要太誇張...一般來說這個動作是不吃什麼效能的。如果資料量很誇張,擔心記憶體,不要擔心效能。)

2015年8月10日 星期一

顯示圖片所遭遇的OOM...「原來要用WebView」

一個APP在Android上可以使用的記憶體空間最大為24MB。

但一張圖片轉成Bitmap「物件」後,實際大小會是檔案的四倍以上!

也就是說如果圖片有N張、總大小為4MB,那它轉為Bitmap物件後在記憶體中吃掉將近16MB的大小。

這時候如果加上APP本身其他功能在記憶體中佔的大小,很容易會發生「Out Of Memory」的Error!



或許有人會想:但Bitmap物件在Android APP中無法顯示或作用,需要丟給ImageView之類的UI元件後才會顯示,或許這些UI元件並不會保留Bitmap物件、會將它快速丟入Garbage回收列中。

如果是這樣...對一張已經轉給ImageView顯示的Bitmap進行recycle()指令應該會成功才對!但實際上log訊息卻跳出錯誤碼「不能對一張ImageView正在執行顯示的Bitmap執行recycle()」。



也就是說...

顯示圖片要遭遇的問題並不單單是「將圖檔從resource狀態轉成Bitmap的過程」,轉成Bitmap後存在記憶體中的實際大小本身就是問題的根源。



先解釋一下關於:將圖檔從resource狀態轉成Bitmap的過程...

Bitmap物件並沒有建構子,必須使用內建的static型態參數createBitmap()來產生,或是使用BitmapFactory物件的static型態參數decodeStream。

關於後者,顧名思義就是使用Stream來將resource檔轉成InputStream,然後........(不知道怎麼形容這個過程,反正就是接收Stream資料串就是了。Stream的資料串吐出資料的「開啟」機制一直是我覺得這個物件很「謎」的地方。大家可以直接點連結參考一下怎麼用Stream轉成Bitmap物件。)(連結一)

ImageView有setImageResource()的功能,但這功能本身也是將圖檔從resource索引值轉出成Bitmap物件,它的過程和會遭遇的問題跟上面在討論的並沒有不同。

網路上常見的「標準」解決方法(連結一),其實僅僅只是將圖檔製作縮圖,取得一個比較小的Bitmap物件。



但...「挑戰」之所以是「挑戰」,就是因為永遠會有人、會有狀況來測驗標準/穩定方法的極限!

如果APP的程序中存在著一次要顯示/讀取多張大尺寸Bitmap,則這個方法(連結一)的穩定性會變得跟紙糊的沒兩樣!

大家用動態的流程想像一下...

假設我用縮放尺寸A來讀取的圖片1,然後將圖片1設給ImageView1,接著我要用相同的縮放尺寸A來讀取圖片2.......抱歉,OOM了!因為記憶體的環境已經跟讀取圖片1時的環境不同了!而這樣的環境已經無法用縮放尺寸A來讀取圖片2。



我曾經試過動態的變化縮放尺寸,讓每一張圖片都有「適合」自己的數值。

結果圖片的縮放品質會開始無底線的探底!

如果圖片是「不太講究品質的小icon」,那OOM的問題根本從一開始就不會發生!也就是說會發生OOM,一定都是解析度要講究、內容必須要可以讓使用者精確閱覽的圖片!



所以用Stream來縮放圖片(連結一)雖然符合Java解決問題的精神原則,但實際上根本沒有解決問題!

(其他還有一些「幻燈片/跑馬燈切換」的方式來展示多張圖片,但這都是從根本上限制「APP設計」,碰到「不行!我們就是要同時展示多張圖片!」時,依賴這種技巧等於讓程式設計師陷入一個叫天天不應、求地地不靈的死局。)


真正治標又治本的方式,就是製作一個存在手機端的小網頁,然後將圖片插入這個網頁中,在轉到WebView上顯示!(連結二)



不要聽到WebView就啞然失笑,「用WebAPP?效能不是很差嗎?」

這個方法除了圖片的顯示以外,其他都還是Java Code,效能等同於Native APP!



只是要注意一些小細節:

1.不要使用String.format來組成Html Tag。(執行後,經常會跳出Error,告訴我「Html少了個「"」。)
2.連結二中是Asset資源圖檔的版本,res中的resource資源檔有不同的file路徑寫法。(連結三)

3.如果有辦法精確控制WebView大小會更好,也可以輕鬆顯示多張圖片。(我有自己的秘訣,但.....解釋起來挺複雜的。如果能夠從「用Java動態設置」為方向去思考,會發現不難。)

2015年8月6日 星期四

關於Thread多線程

過去幾個禮拜在做「在Google Map上記錄使用者行走路徑」的功能...

(基於業務需求,不能附上詳細的程式碼。)

製作的過程中碰到最重要的幾個問題:

1.GPS的精準度和效率。
2.如何在行走過程中,開啟和關閉地圖,(地圖只是記錄行走路徑的附屬功能,可以獨立開啟跟關閉,)但又不會影響紀錄的效率和整體操作的流暢性。
3.紀錄行走的路徑資料量吃光了記憶體。

關於1,結果有點無解。

關於3,用即時的SQL讀寫來解決。

關於2,解決的過程中刷新了我對Thread的應用和設計心得.......




Thread的意義在於「不要讓單一項工作/任務吃光所有CPU的運算資源」,所以它會建議設計師在迴圈或程式碼中埋下Thread.sleep,來讓目前的工作/任務先暫時停止工作、把結果保留下來,然後處理其他工作。



我個人接觸Thread的初體驗是遊戲設計中的「畫面更新」功能。

畫面更新速率要穩定!(不穩定可以視為Thread設計失敗!)

但這是「畫面更新」的要點,而不是「Thread設計」的基本技巧,導致我一直以來在Thread的設計上,都會將Thread設計得過於僵硬強勢!──Thread結構都類似「畫面更新」的「無止盡的執行下去、直到程式關閉為止。」(這樣的Thread執行或許很穩定,但卻很難關掉、很難即刻反映APP的生命週期或開關。)

我想這是因為遊戲中都會將要運算和更新的資料量控制在一個穩定的範圍內,例如敵人數量要穩定、圖層數量要穩定。

所以不管是遊戲數值的運算、或畫面的更新,這兩個Thread的運算效率通常都會很穩定。(偶爾會有「爆擊」、「連續技」、「超大範圍攻擊」等事件發生,.......能否處理好就看工程師的功力了。)



但這次在更新地圖路徑的繪圖需求上,卻是完全彈性的!

資料量從一開始可能十筆二十筆,在五分鐘後會爆增到一百筆兩百筆,再十分鐘後又會增加到八百筆九百筆。

資料的量非常不穩定,所以「遊戲軟體」中「畫面更新」的Thread設計原則必然不適用!

將「數據運算」和「畫面更新」做成兩個完全平行、且穩定持續運作的Thread並不適合在這個運算資料量一直增加的設計中。



所以兩個Thread要從平行改為主從式設計。

一個Thread為穩定持續運作,另一個則被前面的Thread動態的生成啟動或關閉,...但在我這次的工作中,只有啟動、不需要關閉。(「畫面更新」Thread不需要循環執行,單一的功能執行完、則整個Thread關閉丟入記憶體回收列中.....,下次要更新畫面時,重啟一個Thread。)

這還是需要一些參數上的設計,例如「畫面更新開始」和「畫面更新結束」,畫面更新如果開始,就主Thread就不會再發出畫面更新的Thread,直到畫面更新結束為止。

(其實這是可以同一個參數解決的。)



這樣設計後,畫面的更新效率或許不太理想,時不時「使用者當下位置」跟「路徑線」會有點差距.......

但APP的整體操作完全不受影響。

2015年7月19日 星期日

正規的使用Handler更新畫面並且避免Handler Leaking發生

【可以先參考官方教學與範例。

真正讓我想出不用Handler、而用BroadCast的方式更新畫面的原因...在於Handler很難用!



產生Handler的程式碼如果不是直接在Activity類別內,這個Handler最後不被執行的可能性...八成!

最發生的現象就是...在一個獨立在Activity外寫成的Thread/Runnable類別內,有一段生成/宣告Handler的程式碼,不管這段程式碼是宣告一個匿名物件、或確實寫一個class,也不管它寫在run()內或run()外,.......經驗來判斷是:「這個Handler一律都不會被執行。」。

Android開發者稱這個現象叫做「Handler Leaking」。

在Log訊息中可以看到系統發出關於這個現象的警告,意思大概是說「這個Handler不是在Activity內產生的......」

這個「直接在Activity類別內」...意思是說連Fragment和View本身也涵蓋在其中!

其他可能會發生的現象還有...在Activity中寫了個匿名物件,內有產生Handler的程式碼,但這段程式碼卻不是在Activity內被執行的!

(我個人模糊的形容方式是:要滿足以下兩個條件才「絕對」不會發生HandlerLeaking:

1.類別在Activity中設置為內類別/巢狀類別。2.在Activity中宣告產生實體物件,包含匿名物件。

門檻其實挺高。)

雖然官方的教學範本裡面有說到要在Handler的建構子中傳入Looper.getMainLooper(),但這個方法也不管用!(沒有成功過!)



所以要用自己的變化與創意補足這塊了!

網路上看到過很多人使用Activity內增加Inner Class擴充Handler的方式來處理這個狀況,但我不採用。

因為這變成在Activity外使用Handler時,還要取得Activity當作參數傳入這個自設的Handler類別的建構子中。

現階段沒有辦法直接做到「隨意的產生Handler,然後又隨意決定Handler的功能,」只能做到最接近、最不花費力氣的方式...





這兩個介面/函數和Handler仍然需要在Activity中實作,但全部只有21行程式碼,而且只要做一次。


然後用上圖的方式,就可以在任何可以取道Context或Activity的地方產生帶有特定效果的Handler。




如果只是單純的要傳一個數值參數,用emptyMessage(int what)即可,為何還要有個Object參數?

仔細想想,Message類別中帶有一個Object參數讓使用者可以隨意傳入物件,真正的意義恐怕就在這裡。