跳到主要內容

發表文章

目前顯示的是有「Programming」標籤的文章

使用 <uses-feature> 的注意事項

<uses-feature> 最早是在 Android 1.6 SDK (API Level 4) 中出現的,他的用途是用來宣告 App 會使用到哪些軟硬體功能(比如 Camera、Bluetooth、OpenGL ES version...)。不過事實上系統本身並不會去檢查這些設定,但 Google Play 確會用這些設定去過濾要呈現哪些 App 給使用者。 比如說我宣告了下面這行,表示我會使用到 Camera 功能。這樣 Google Play 就不會將我的 App 顯示在沒有 Camera 的裝置上。 <uses-feature android:name="android.hardware.camera" /> 不過後來在 Android 2.0 SDK (API Level 5) 中,<uses-feature> 多了一個屬性叫  android:required 。當某功能在 App 中是 必要 時需設定為  true ,若是 非必要 時則設成  false 。咦?...若是不需要的話,我直接省略 <uses-feature> 不是更省事嗎? 在看完落落長的  開發者文件  後才瞭解,嚴格來說,每個 App 都 應該 要清楚宣告哪些功能是必要或非必要。但因為種種原因,開發者可能忽略或未正確宣告。所以 Google Play 除了檢查 <uses-feature> 外,還會參考 <uses-permission> 的設定。當有設定 <uses-permission> 時, Google Play 會假定相關的功能是 必要 的,並加入過濾。 舉例來說,我的 App 會使用到 Camera,但不是必要的。而我只宣告了 Camera 的 <uses-permission>,卻忽略了 <uses-feature>。 <uses-permission android:name="android.permission.CAMERA" /> 此時 Google Play 發現了這個 <uses-permission>,便會將 Camera 視為 必要 而進行過濾,沒有 C...

解決 NetworkOnMainThreadException 的問題

好一陣子沒寫網路相關的程式了,最近有個專案剛好有連網的需求。好吧,把以前的程式拿出來用,但發現在 Android 2.3 的裝置運行正常,在 Android 4.0 的裝置就會出現 NetworkOnMainThreadException 這個錯誤。查了一下原來從 Android 3.0 開始,Thread Policy 加強了限制,只要嘗試在主執行緒中進行網路操作,就會產生這個錯誤。 解決方式有幾個: 1. 把網路操作從主執行緒中移走,你可以自己再開一個 Thread,或使用 AsyncTask 來執行。 2.在 onCreate() 中加入下列程式,透過 StrictMode 重新設定 ThreadPolicy。 if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() // or .detectAll() for all detectable problems .penaltyLog() .build()); } 3.把 AndroidManifest 中的 android:targetSdkVersion 設定成 9,也可以暫時解決,但不建議。 最好的方式還是第一種,能避免耗時的網路操作阻礙主執行緒工作,或產生 ANR 錯誤,我想這也是設計此例外的原因。

在 Android 中使用 Jaudiotagger 的注意事項

在開發  AudioTagFixer  時,使用了  Jaudiotagger  這個第三方函式庫。它主要是用來做音樂檔標籤的存取與修改,並且支援多種音樂檔與標籤格式。使用上很簡單,只要把 Jaudiotagger 的 JAR 加進你的專案,再參考它的程式範例來操作即可。 不過測試時卻發現一個怪問題,在 Android 2.1 以下的裝置運作時,取得的標籤前面都會多出 ��,當然存檔也就有問題。而在 Android 2.2 以上就一切正常,一開始搞不清楚為什麼,只好暫時把 App 的 minSdkVersion 設成 8,以避免出錯。 直到最近才發現似乎是某些 Android 的 Bug 所導致,而 Jaudiotagger 則提供了一個設定: TagOptionSingleton.getInstance().setAndroid(true); 只要加上這行,就能正確的在 Android 2.1 以下存取音樂檔標籤了。 參考資料: http://stackoverflow.com/questions/5447145/jaudiotagger-and-android-change-a-value-in-an-mp3 http://www.jthink.com/jaudiotagger/maven/apidocs/org/jaudiotagger/tag/TagOptionSingleton.html#setAndroid(boolean)

LevelListDrawable 的使用

在開發  Battery Widget  時,需要在不同電量狀態下顯示對應的圖檔,比如電量 100% 時顯示電池全滿的圖,電量 50% 的時候則顯示半滿的圖。那時很單純的用一堆 if else 來判斷,然後在裡面分別 setImageResource()。當然這樣也不是不行,但看到程式碼裡一堆判斷式,就覺得有點討厭。最近發現原來 Android 早就有替類似的需求設計了 LevelListDrawable,而且是 Since: API Level 1。(我真是太不用功了@@) 用法很簡單,範例如下: 1.首先當然得先準備好不同 Level 的圖檔。 2.在 res/drawable 裡新增一個 battery_level.xml 內容如下: <level-list xmlns:android="http://schemas.android.com/apk/res/android">     <item android:maxLevel="20" android:drawable="@drawable/battery_20" />     <item android:maxLevel="40" android:drawable="@drawable/battery_40" />     <item android:maxLevel="60" android:drawable="@drawable/battery_60" />     <item android:maxLevel="80" android:drawable="@drawable/battery_80" />     <item android:maxLevel="100" android:drawable="@drawable/battery_100" /> </level-list> 可以看出是用 <level-list> 標籤來包 <item>,而每個 <...

利用 TouchDelegate 設定元件的觸控區域

開始前請先看看  這篇文章 ~ 您是否有同樣的經驗,想點擊 Listview item 中的 Checkbox 時,卻常常意外點到整個 item,而造成預期外的動作? 嚴格來說這好像也不能怪開發者,誰叫預設的 Checkbox 就是那麼小,更不可能怪使用者說你手指怎麼那麼肥...。 但若想保有優良的使用者體驗,這個小細節是可以被解決的。處理的方法有很多種,比如放大 Checkbox 本身的 Drawable 或從 Layout 下手...但由於這樣做可能擠壓到其他的元件,再加上 Checkbox 本身的缺限,讓過程也有些麻煩。 因此我們可以考慮另一個方法,使用 TouchDelegate 來增加元件的觸控響應區域。 程式範例: final View parent = (View) delegate.getParent(); //delegate是想擴大觸控範圍的元件 parent.post( new Runnable() { public void run() { final Rect r = new Rect(); delegate.getHitRect(r); r.top -= 10; //觸控範圍往上增加 r.bottom += 10; //觸控範圍往下增加 r.left -= 10; //觸控範圍往左增加 r.right += 10; //觸控範圍往右增加 parent.setTouchDelegate( new TouchDelegate( r , delegate)); } }); 簡單來說就是透過父容器 setTouchDelegate,以調整觸控響應區域。這樣就不必動到元件本身或 Layout,一來讓元件維持小巧精美,二來又有足夠的響應區域。 參考連結: http://developer.android.com/reference/android/view/TouchDelegate.html http://www.daggie.be/?p=1006 http://android.cyrilmottier.com/?p=574

如何檢測裝置是否有 Camera?

在做影像處理的應用時,可能會需要用到裝置內的 Camera 來截取影像。而 Android 裝置百百種,不一定每台都有相機,因此如何在沒有相機的裝置上避免程式發生錯誤就是必須考量的。 第一種狀況是當你的應用會上架至 Google Play 商店時,請用 AndroidManifest.xml 中的 <uses-feature> 與 <uses-permission> 標籤宣告會使用 Camera 功能及相關權限: 例如: <uses-feature android:name="android.hardware.camera" /> <uses-permission android:name="android.permission.CAMERA"/> 這樣你的應用程式就不會出現在無 Camera 裝置的 Google Play 商店中,使用者無法下載,自然也就避免了發生錯誤的可能。 第二種狀況是使用者可能從第三方市集或透過其他途徑取得軟體,那就不一定都有完善的篩選機制了,因此你可能必須在程式內部做判斷處理,以避免 Crash。 第三種狀況是 Camera 並非你應用中的主要功能,即使裝置沒有 Camera,還是可以使用其他功能,但使用者介面可能必需做一些調整,比如隱藏呼叫 Camera 的按鍵或是在使用者試圖使用 Camera 相關功能時跳出提示訊息。 而針對狀況二與狀況三,可用的判斷方法如下: PackageManager packageManager = context.getPackageManager(); if (packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) { Log.i("camera", "This device has camera!"); } else { Log.i("camera", "This device has no camera!"); } 簡單說就是利用 PackageManager 檢查裝置是否有 Camera 功能,注意...

如何立即結束前一個 Toast ?

Toast 是一個方便又簡單的工具,可以直接在畫面上顯示簡短的訊息以通知使用者。 最簡單的呼叫方式: Toast.makeText(this, "Hello Toast!", Toast.LENGTH_SHORT).show(); 但這樣會有一個問題,若使用者短時間內連續執行了一堆會產生 Toast 的操作,Toast 會被排在佇列中依序顯示,直到前一個 Toast 結束後才顯示下一個 Toast,一直被累積的結果就是無法立即反應使用者的操作。 那麼要如何讓新的 Toast 能立即被顯示呢? 方法如下: private Toast mToast; private void showToast(String msg) { if (mToast == null) { mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT); } mToast.setText(msg); mToast.show(); } 需要使用 Toast 時,呼叫 showToast() 即可,此時若舊的 Toast 還在,會立刻被更新為新的 Toast 訊息。 參考資料: http://stackoverflow.com/questions/5503682/how-to-cancel-toast-created-in-a-different-method-on-android

利用 Java Reflection 來呼叫被隱藏 {@hide} 的 API

注意:本文的範例於 Android 8 (Oreo) 上已無法執行,而 Google 也表明為了改善應用程式的安全與穩定,未來將逐步限制這些非正規的存取方式。 如果您有研究過 Android Source Code,應該會發現其中有許多函式都被標註了 @hide。也就是這些函式在 SDK 中是被隱藏的,一般情形下無法被呼叫使用。但有時我們又想使用這些功能該怎麼辦呢? 在不更動 Android System 的前提下,我們可以透過 Java 的反射機制 (Java Reflection) 來達成。 範例: 我們在 Android Source Code 中的 PackageManager 類別裡發現了一個函式 getPackageSizeInfo,可以用來取得應用程式的磁碟空間使用量,但在 SDK 內卻找不到此函式。 我們先試著用 getMethods 列出 PackageManager 中所有的函式 PackageManager pm = getPackageManager(); Method[] methods = pm.getClass().getMethods(); for (int i = 0; i < methods.length; i++) { Log.d(TAG, methods[i].getName()); } 看一下結果,沒錯,裡面確實有 getPackageSizeInfo 這個函式 調用方式: Method getPackageSizeInfo = pm.getClass().getMethod( "getPackageSizeInfo", String.class, IPackageStatsObserver.class); getPackageSizeInfo.invoke(pm, "xxx.xxx.xxx", new IPackageStatsObserver.Stub() { @Override public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteExc...

用 ColorMatrix 將 Bitmap 轉成灰階 (Grayscale)

之前  提過 從 RGB 格式計算其灰階明亮度 的方法。 在 Android 中,若想將整張圖片轉成灰階效果其實有更簡便的方式,只要透過 ColorMatrix 類別的 setSaturation 函式將飽和度設為 0 即可。(您也可以試試從 0~1 之間的值,看看不同飽和度的效果) 詳細方法如下: //colorBitmap 為原始 Bitmap,grayBitmap 則用來存放處理過後的灰階 Bitmap Canvas canvas = new Canvas(grayBitmap); Paint paint = new Paint(); ColorMatrix colorMatrix = new ColorMatrix(); colorMatrix.setSaturation(0); ColorMatrixColorFilter colorMatrixFilter = new ColorMatrixColorFilter(colorMatrix); paint.setColorFilter(colorMatrixFilter); canvas.drawBitmap(colorBitmap, 0, 0, paint); 參考資料: http://developer.android.com/reference/android/graphics/ColorMatrix.html

Bitmap copyPixelsFromBuffer 顏色不正確?

最近在做圖像處理的功能時發現一個奇怪的現象,當使用 copyPixelsFromBuffer 直接將 ARGB_8888 順序的 Buffer 丟回給 Bitmap 時,顏色會偏藍。 後來查了一下才發現用 copyPixelsFromBuffer 丟 ARGB_8888 資料時,必須將 R 跟 B 交換,也就是用 ABGR 的順序,顏色才會正確。 將 ARGB 轉 ABGR 的方法: pixel = (R << 0) | (G << 8) | (B << 16) | (A << 24); 或 pixel = (pixel & 0xff00ff00) | ((pixel & 0x00ff0000) >> 16) | ((pixel & 0x000000ff) << 16); 註:若使用 setPixel 或 setPixels 來設定 Bitmap 的話,則不需調換。 參考資料: http://developer.android.com/reference/android/graphics/Bitmap.html#copyPixelsFromBuffer(java.nio.Buffer) http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/BitmapPixels.html