2016年4月28日 星期四

【Android】開啟第三方APP

讓APP的內容或服務可以被其他APP開啟跟呼叫,在Android上是很重要的一項功能,──雖然沒什麼人真的在做。

因為技術上它有很多Google進階設計時模糊曖昧的地方!API或文件都沒有精準說明,也沒有範例可以參考。

所以下面的筆記只是個人使用心得,離參考文件還有很長的距離...



在AndroidManifest檔中加入的Activity設定值標籤內,可以再加入intent-filter的標籤。

預備要被第三方APP呼叫的Activity中,必須要在intent-filter標籤內加入額外的參數。(會這樣說是因為基本用法中,除了靠點擊啟動的Activity需要加入main和launcher參數以外,其餘的Activity並不需要加入額外的參數,甚至連intent-filter都可以不需要。)


<intent-filter>
     <action android:name="android.intent.action.SEND"/>
     <action android:name="android.intent.action.VIEW"/>
     <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
SEND跟VIEW似乎只要擇一加入即可,差別只在於Activity中如果有UI要顯示,「使用」這個Activity的Intent中若是傳入SEND,則這個UI不會被顯示。

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//!!!關鍵在這個Flag
intent.addCategory(Intent.CATEGORY_DEFAULT);           
ComponentName cn = new ComponentName("tw.android.demo2", "tw.android.demo2.MainActivity2");           
intent.setComponent(cn);
startActivity(intent);

其實呼叫第三方APP的Activity方法很多,但關鍵應該在於addFlag的動作!

(還沒機會使用到其他型態的Flag,如果有機會用到,會補上結果。)

另外,ComponentName的實體建構子中要傳入兩個參數,一個是第三方APP project名稱,一個則是Activity的完整專案路徑與名稱。

(會強調完整專案路徑與名稱,是因為邏輯上來說,Activity可以分散在多個子project路徑內,例如tw.android.demo2下可以有tw.android.demo2.test,但將Activity放在裏頭,不知道為何AndroidManifest檔內的設定會失敗...一直沒有機會來回頭釐清這一段的正確使用方式和原則。──好像有點本末倒置,不去先釐清更基本的東西,卻在研究比較進階的使用方法。)




另一個奇妙的問題是...當Activity被啟動後,啟動的目的為何?

絕對不是僅僅為了看個UI介面。(有時候會有選單來選擇資料回傳,這時候當然就是要操作UI。但這裡不是指這種情況。)

例如要使用不對外公開的API來跟後台交換資料,這時候就需要建立Thread來完成網路傳輸工作。

奇妙的是...

這種讓第三方啟動的Activity竟然無法在基本生命週期階段中啟動獨立Thread。(寫了個Thread程式,卻無法被啟動,但也沒看到錯誤訊息產生。)

所以這時候就需要使用Service,特別推薦IntentService。(前者可能要等任務完成、關閉Activity後才會跟著關閉,但後者會先在任務完成後關閉自己、同時執行關閉Activity的工作。)



........

怎麼使用IntentService?

別鬧了!去別的地方找資料。

2016年2月22日 星期一

在Android上用座標查詢地址原來這麼簡單!

無意間在StackOverFlow上逛到這篇文章...才知道原來這東西那麼好用!

簡單說:Android預設的物件GeoCoder(點擊可以轉到API)其實就可以幫設計師快速的把這個動作完成!

詭異的是我遲遲沒有找到相關的標準範例!結果原來是因為Google Guide教程中,關於「如何取得GPS座標」的部分已經轉換成使用GoogleApiClient,但後續取得地址的部分...雖然GoogleApiClient也有相關功能,可是在Google Guide中卻還是使用GoeCoder!

(只能說:習慣就好...)



這個物件設計讓我看了要說「好用」的原因...

第一,它直接使用座標,而不是相關系列的物件!

    List<Address> addresses = null;

    try {
        addresses = geocoder.getFromLocation(
                location.getLatitude(),
                location.getLongitude(),
                1);
    } catch (IOException ioException) {
        // Catch network or other I/O problems.
    } catch (IllegalArgumentException illegalArgumentException) {
    }

    
}
(其他時候,Google Guide的範例程式碼很繁雜!但這篇的卻是相較之下精簡,而我上面貼的這段是更精簡。因為這個功能並不需要多做無謂的說明...)


第二,承接第一點,這表示它不需要申請金鑰!在註冊功能上會省去很多手續。(但其實最新的Google Place API For Android在註冊上也很簡單!並沒有多複雜!)

第三,JavaScript的地址查詢API回傳的JSON資料結構非常複雜!

第四,跟Google Place API For Android相比,它可以完全適應自己設計的Thread。因為Google Place API需要使用CallBack,而且如果只是單純的地址查詢,Google Place API並不「精準」,會獲得很多無關的資訊。




補充說明:在Google Place API For Android中獲得PlaceID的方法。

其實就是額外使用JavaScriptAPI去獲得JSON資料而已!

目前這段功能顯然還未有完整物件化。

2016年1月12日 星期二

SurfaceView的動畫會閃爍破碎......用同一個迴圈完成Draw和Post

在Android上,要能夠將Draw和Post分開,必須要另外產生一張Canvas。
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
Post時,只需要在lockCanvas所取到的Canvas上「畫一次」Bitmap(b)即可。

不懂?



如果有十個物件要繪製,從lockCanvas到PostCanvas這中間進行這十次的繪製動作,會導致畫面開始延遲和閃爍。

但如果把這十次縮減成一次,從lockCanvas到PostCanvas這中間所花的時間也就只需要十分之一,畫面更新效率理論上會提升...至少三倍?



野心大的人可能會把進行Draw的Canvas也搬到另一個Thread中進行。

那就會有兩個Thread,一個是DrawThread,一個是PostThread。

但如果畫面更新率不統一,PostThread中要執行Draw時DrawThread也進行Draw了,則Post畫面上使用的Bitmap可能就會被回收......破碎跟黑塊可能就是這樣來的.......



但,在高段的遊戲繪圖領域中,確實是用多個Thread來處理繪圖。

但那恐怕是純Thread的領域,跟繪不繪圖並沒有太直接的關係。

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。

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