文章

目前顯示的是 2016的文章

使用非support v4的Fragment放在ViewPager裡該注意的事

圖片
ViewPager算是在Android很常見且相當實用的設計,如果要將ViewPager裡面的每個頁面都是使用Fragment的話,則需要再搭配FragmentPagerAdapter使用,透過FragmentPagerAdapter去分配與生成每個滑動頁裡面的Fragment 早期Android官方為了能夠讓Fragment可以支援Android 3.0以下的版本,所以發展了support v4的Fragment版本,也因此早期的FragmentPagerAdapter是support v4的版本。 加上要生成Fragment需要FragmentManager,所以如果Fragment使用support v4版本的話,連帶的FragmentManager也是只能使用support v4的版本,於是早期在FragmentPagerAdapter整套的import如下: import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; 時至今日,許多的app開發版本都已經限制Android 4.0以上的手機才能安裝了,所以如果app沒有要支援Android 3.0以下的手機,就可以使用一般的Fragment了 這個時候只要把Fragment及FragmentManager在import的地方把 .support.v4 拿掉就能改為一般的版本,再將原本的FragmentPagerAdapter從support.v4改為support.v13就可以使用,或是在一開始新增FragmentPagerAdapter時就直接繼承v13的版本 所以新版的寫法可以改為: import android.app.Fragment; import android.app.FragmentManager; import android.support.v13.app.FragmentPagerAdapter;

JDBC 使用Like 萬用字元(%)

在資料庫做模糊查詢,常會使用Like加上萬用字元的語法: SELECT * FROM Table WHERE column LIKE '%SEARCH_KEYWORD%'; 但是當用在JDBC的時候,就會發生錯誤。 解決的方法是將查詢的關鍵字先加上萬用字元,再傳入沒有加上萬用字元的SQL語法裡 String SEARCH_KEYWORD = "%" + SEARCH_KEYWORD + "%"; SELECT * FROM Table WHERE column LIKE ' SEARCH_KEYWORD '; Reference: http://stackoverflow.com/questions/2857164/cannot-use-a-like-query-in-a-jdbc-prepared-statement

使用Stetho觀察App裡的SharedPreferences與Sqlite資料

圖片
當我們想在手機裡暫存一些資料,方便紀錄使用者在App的設定紀錄時,Android官方提供SharedPreferces的方法(存成XML檔案),或是要以資料庫(可以新增/刪除/修改)的方式儲存更多的資料時,Android也提供了Sqlite的方法(存成db檔案)。 但是如果開發者在debug的過程想要查看所存放的資料內容,比較常見的方法是使用adb shell透過下指令的方式,但是這個方式不是很方便也比較不直覺,於是可以使用Facebook所提供第三方的Library讓我們更方便查看資料內容。 使用這個Library的方法非常的簡單,只要依照下列的幾個步驟即可達成: 1.在Gradle加入dependencies dependencies { compile 'com.facebook.stetho:stetho:1.3.1' } 2.建立Appliction class public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Stetho.initializeWithDefaults(this); } } 3.設定AndroidManifest.xml < android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action and

架設Jenkins伺服器自動建置Android apk檔

圖片
在Android開發的過程難免我們會需要在寫完一定的功能之後,就將目前完成的內容打包成apk交付給其他的人員安裝測試,所以應該是會滿常會遇到:Android build了測試的apk之後,每次都要改版本號,然後再上傳到dropbox空間,然後再發信通知測試人員。 但其實這些機械化的動作,可以交給Jenkins去處理就好。所以最近也就開始研究Jenkins的架設,這篇整理介紹的內容能夠達成以下幾項任務: A.架設Jenkins Server B.自動從Git抓取程式碼 C.自動將Android的code build成apk檔案(並且檔名自動加上Jenkins Build Number) D.自動將apk上傳至Dropbox E.自動發信通知測試人員 一、架設Jenkins Server 1.到Jenkins官網 下載war檔 之後,使用Terminal輸入指令來啟動服務 java -jar jenkins.war 2.開啟瀏覽器,在網址列輸入: localhost:8080 3.輸入預設的管理密碼 這個時候會出現要你輸入啟動安裝的管理者密碼,畫面上會提示放置密碼的位置,通常都是放在使用者資料夾下的.jenkins/screts目錄下的initialAdminPassword檔案裡 所以可以直接用vi去打開來看(關閉vi時,輸入冒號、按q再按Enter的指令),或是直接用文字編輯軟體打開該檔案就能夠看到裡面寫的一長串的文字密碼 4.安裝外掛(plug-in) 接下來會要求安裝一些外掛,在這個階段可以先選擇建議的外掛選項就好,日後也都還可以進到Jenkins後台新增移除外掛 正在開始下載並安裝外掛 5.設定管理者帳號密碼 6.Jenkins已架設完成 二、Jenkins後台設定 1.管理外掛程式 透過這項功能可以將外掛程式新增/刪除/更新。進入的路徑: ①管理Jenkins ②管理外掛程式 進入管理外掛程式的頁面之後: ①選擇「可用的」分頁 ②透過「過濾條件」快速找到想要安裝的外掛,然後勾選下列的外掛程式 Android Emulator Plugin 、 Android Lint Plugin 、 Gradle plugin 、 Email Extension Plugin

Android 6.0實作在Acitivty及Fragment取得權限的方法

最近開始收到一些使用者反映,原本可以使用的功能,手機升級為Android 6.0之後卻不能用了,原因在於Android 6.0的權限管理,將一些列為危險等級的功能權限都預設關閉起來,如此便造成了某些功能因為權限沒有允許而沒有辦法使用。 關於解決的方法在Android官方網站很早就有列出如何透過程式的方法重新取得使用者的授權,只是因為一直沒有遇到需求,所以才會到現在才處理Android 6.0的權限部份。 整個開發的設計流程為: a.透過程式的方法要求所須的權限 b.取得權限成功與失敗的結果 c.如果使用者提供授權就執行該功能(本例為playMusic()) d.如果使用者取消授權就跳出對話框再次詢問(告知為何需要使用者的權限) e.如果使用者依然拒絕提供授權就不執行該功能 以下是開發實作的過程: 1.製作取得權限的公用函式 /** * 取得權限(for Android 6.0) * * @param activity * @param permission * @param permissionCode */ public static void getPermission(Activity activity, String permission, int permissionCode) { if (ContextCompat.checkSelfPermission(activity.getApplicationContext(), permission) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(activity, new String[]{permission}, permissionCode); } } 直接將取得授權寫成一個公用函式的好處,在於可以在不同的頁面隨時都能夠傳入不同的權限參數,就可以觸發取得所屬權限的功能,而且也可以在呼叫程式碼的時候變得簡潔。而其中第一個參數: Activtiy,如果是在Fragment的時候也可以傳入getActivity(),一樣可以使用這個函式。 2.設定權限的requestCode: 自訂的requestCode來做為判斷要求的

Android使用Callback做為傳遞資料/通知的方法

在一個常見的實作上的情境:當使用者在執行某個行為之後,就執行某些行為。例如當API接收成功之後就跳轉到某個頁面,API接收失敗的時候顯示錯誤訊息,除了可以使用BroadcastReceiver,另外也可以使用Callback的方式達成。 以App常見的實作功能-接收API為例,實作的方式如下: 1.先建立一個Interface。 首先在interface裡面製作兩個function,一個是執行結束要執行的行為:loadComplete(),另一個是顯示訊息:showMsg(String msg) /** * Api執行完之後的callback */ public interface ApiCallback { void showMsg(String msg); //要顯示的訊息 void loadComplete(); //api執行完成 } 2.在觸發行為的class裡產生Callback實體。 舉例來說,如果是在API執行之後一定會有該API自己所屬的成功/失敗callback function(或稱method),也就是如果API接收成功了就執行某個function(以Volley來看是onResponse()),而失敗了就執行某個function(以Volley來看是onError())。 於是就可以在Volley的onResponse()加入interface裡的loadComplete(),而在onError()加入showMsg(String msg) public class MyApi{ public ApiCallback mCallback; public DailyNewsApi(Context context) { this.mContext = context; mCallback = (ApiCallback) mContext; } @Override public void onError(VolleyError error) { super.onError(error); mCallback.showMsg("登入失敗"); //傳送失敗訊息內容

使用者刪除手機裡的資料夾,再次建立相同檔名時出現錯誤:open failed: EBUSY (Device or resource busy)

在App開發的過程,如果有個資料夾是App在使用某些功能時必須要存在的,例如像是音樂下載存放後播放。但如果使用者發現手機新增了檔案就直接連同資料夾都一併刪除(或搬移位置),而App為了確保檔案的存在(例如才可以播放音樂),於是在設計上就會在進到該功能頁面的時候又立刻建立資料夾下載檔案,而此時就會發生沒有辦法建立資料夾的錯誤。 E/Error:: /storage/emulated/0/music/my_music.mp3: open failed: EBUSY (Device or resource busy) 會發生這個錯誤是因為相同名稱的資料夾如果被刪除之後又馬上建立相同名稱的資料夾就會被系統擋下來。 所以解決的方法就是建立不同的名稱的資料夾再改回正確的名稱就好。以下提供一個實作的函式: /** * 建立可以快速刪增的資料夾 * * @param path */ public static void createFolder(String path) { //使用系統時間做為名稱的暫時資料夾名稱 final File fakeDir = new File(Environment.getExternalStorageDirectory(), path + System.currentTimeMillis()); //真正想要儲存的資料夾名稱 File realDir = new File(Environment.getExternalStorageDirectory(), path); //如果真正的資料夾不存在才建立暫時資料夾 if (!realDir.exists()) { fakeDir.mkdirs(); fakeDir.renameTo(realDir); //暫時資料夾改名為真正的資料夾名稱 fakeDir.delete(); //刪除暫時資料夾 } } 而在使用上只要傳入路徑名稱就可以了 createFolder( "music" ); //建立一個名為music的資料夾 Reference: http://stackoverflow.com/questions/11539657/open-failed-ebus

如何實作Android文字(TextView)裡的超連結onClick監聽與排版

最近寫了比較多關於TextView的東西,才發現Android的TextView其實功能很強大,不是只有單純的文字格式,換換字型、改粗體、斜體、行距這些基本款,更可以使用Html的語法來做文字排版。 如果要使用Html做為TextView的排版方法如下: 1.將Html格式的文字放在Resource的String裡,並且在html格式的文字前後各加上了 <![CDATA[ 與 ]]> <string name="html"> <![CDATA[ <b><a href="https://www.youtube.com/watch?v=6xc3dHjPje0">說好的幸福呢</a></b> <br> 作詞:方文山<br> 作曲:周杰倫<br> <br> 妳的回話凌亂著 在這個時刻<br> 我想起噴泉旁的白鴿 甜蜜散落了<br> <br> 情緒莫名的拉扯 我還愛妳呢<br> 而妳斷斷續續唱著歌 假裝沒事了<br> <br> 時間過了 走了 愛情面臨選擇 妳冷了 倦了 我哭了<br> 離開時的不快樂 妳用卡片手寫著 有些愛只給到這 真的痛了<br> 怎麼了 妳累了 說好的 幸福呢<br> 我懂了 不說了 愛淡了 夢遠了<br> 開心與不開心一一細數著 妳再不捨<br> 那些愛過的感覺都太深刻 我都還記得<br> <br> 妳不等了 說好的 幸福呢<br> 我錯了 淚乾了 放手了 後悔了<br> 只是回憶的音樂盒還旋轉著 要怎麼停呢<br> ]]&

如何讓Android 文字(TextView)消除上下邊距

Android的Textview預設會依字體大小而在文字留了上邊距與下邊距,如此一來可以讓不同的TextView之間具有預設的間距,讓視覺上不會太過擁擠的感覺。 但如果有排版對齊的需求時就會產生困擾,例如像是在多個TextView使用了不同的字體大小但要又在TextView之間都留下相同間距的時候。 解決的方法,在Layout XML的TextView加上includeFontPadding、marginTop與marginBottom即可 <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom=" -5dp " android:layout_marginTop=" -5dp " android:includeFontPadding=" false " /> Reference: http://stackoverflow.com/questions/4768738/android-textview-remove-spacing-and-padding-on-top-and-bottom

更新Android Studio 2.0之後Gradle版本錯誤

圖片
終於等到官方釋出了Android Studio 2.0穩定版,這次版本推出了好幾項開發過程相當 好用的功能 ,所以是一定要升級的版本。 但從舊有的版本升級之後卻不能build code了,出現了下列的錯誤訊息: Error:(1, 1) A problem occurred evaluating project ':app'. > Failed to apply plugin [id 'com.android.application'] > Gradle version 2.8 is required. Current version is 2.2.1. If using the gradle wrapper, try editing the distributionUrl in /Documents/AndroidDev/source_code/project_name/gradle/wrapper/gradle-wrapper.properties to gradle-2.8-all.zip 上網查的結果,發現也有人提到這幾乎都是每次版本更新時常會發生Gradle版本不適合的老問題了,於是照著錯誤訊息中指出的檔案修改: 在Android Studio的左邊選單找到gradle/wrapper/gradle-wrapper.properties 開啟之後,找到原本的版本名稱(以本例的版本是2.2.1) 修改成2.8,但已經有人發生 版本要求為2.10 了,所以可以直接就先改成2.10了,而改成2.10之後也是確定可以正常使用的 接著還有可能會發生Gradle Plugin版本不適合的問題 Gradle Plugin更新完之後會發現classpath裡的gradle版本已經變成2.0.0 Reference: http://stackoverflow.com/questions/34605667/android-studio-gradle-version-gradle-version-2-8-is-required

Google Play Console上傳解譯混淆對映檔案: mapping.txt

圖片
Android App一直以來都有被反組譯的風險,網路上有很多的反組譯工具教學,甚至還有聽過直接把你的apk download下來之後,直接修改內容再發佈到第三方App商城的事情發生。所以建議在build apk時都要加入混淆的機制,至少能增加一些被破解的難度。 但是當你上傳混淆過後的apk到Google Play上之後,如果App在操作的過程發生了ANR或是閃退時,使用者透過按下了「回報」的機制讓開發者收到錯誤的訊息內容,但在Google Play Console所看到的debug程式碼也都是被混淆過的程式碼,也會增加工程師debug的難度。 於是Google提供了上傳混淆程式碼的函式對映表的方式,當有使用者回報閃退時,其回報的程式碼錯誤區段能夠透過對映表再度解譯為原來的程式碼內容。 取得對映表mapping.txt的方式只要當你執行build release的apk 完成之後,就可以在下列的路徑找到: app/build/outputs/mapping/release/mapping.txt 接著進入到Google Play Console的 當機與ANR/反混淆檔案 選擇所屬版本上傳mapping.txt檔案 上傳mapping.txt檔案之後就會出現如下圖的畫面 請注意: 1.每次Build apk之後所產生的mapping.txt都會產生新的同名檔案覆蓋 2.在Android Studio執行Build/Clean Project會把mapping.txt清除 所以每次build完release版本所產生的mapping.txt檔案要先備份起來,以便之後要上傳到Google Play Console Reference: http://developer.android.com/tools/help/proguard.html#decoding https://support.google.com/googleplay/android-developer/answer/6295281?hl=zh-Hant

如何讓Android 文字(TextView)使用自訂字型不卡頓

圖片
有時為了版面好看,或是想要表現出文青的氣息,會想要把App的字型做個統一的樣式呈現,取代掉Android系統內建的字型,於是就會有必須變更字型的需求。而實作的方式只要透過以下幾個步驟就可以完成: 1.先在assets資料夾建立fonts資料夾,並將字型檔複製到assets/fonts的目錄下 2.將文字設定為自訂的字型 private Typeface fontType; private TextView text; fontType = Typeface.createFromAsset(getActivity().getAssets(),"fonts/NotoSansCJKtc-Regular.otf"); text.setTypeface(fontType); 基本上到步驟2就已經完成文字使用自訂字型的功能了,但如果使用自訂字型的文字同時出現在ListView或是ViewPager,在滑動的時候就會發生卡頓的情形,所以建議繼續實作步驟3 3.新增靜態提供字型的class,保留產生的字型物件 public class TypeFaceProvider { private static Hashtable typeFaces = new Hashtable (3); public static Typeface getTypeFace(Context context, String fileName) { Typeface typeface = typeFaces.get(fileName); if (typeface == null) { String fontPath = "fonts/" + fileName; typeface = Typeface.createFromAsset(context.getAssets(), fontPath); typeFaces.put(fileName, typeface); } return typeface; } } 然後將Typeface改為使用靜態提供字型的class所產生的物件 pr

Android的內嵌網頁(WebView)如何實作Facebook分享

有在寫App的人大概都會知道,如果把網頁包在App裡面,雖然有時可以很快解決版面、內容與網站的頁面共用的好處,但如果網頁有一些使用JavaScript的行為,像是開新視窗(window.open)、警告視窗(alert)...等語法在Android的Webview就會出現一些問題了。 通常會有使用者來跟你說:為什麼同樣的網頁內容,我用手機的瀏覽器去操作,可以出現的功能在你的App裡面就沒有任何反應?(你有頭緒嗎?) 最近就遇到了網頁裡的Facebook分享按鈕在Android的Webview裡面按下後沒有產生任何反應,更可怕的是出現了在iOS裡面是可以正確執行的對照組。所以就只能自己想辦法實作了。 仔細看了一下FB分享按鈕在網頁的語法會發現是觸發了window.open的方法 <a href="javascript: void(0)" onclick="window.open('http://www.facebook.com/sharer.php?sdk=joey&u=http://www.cw.com.tw/article/article.action?id=5075515& display = popup & ref = plugin & src = share_button','_blank','width = 700, height = 650');"></a> 所以解決的方法就是去實作WebChromeClient的Callback: onCreateWindow(),攔截彈出視窗時做出相對應的處理。 /** * 實作WebChromeClient內容 */ private WebChromeClient webChromeClient = new WebChromeClient() { @Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture,Message resultMsg)

如何讓Android App按Home鍵之後再次開啟仍在之前的頁面

通常在Android的世界裡,只要按下App的 icon都會從程式進入的第一個頁面(以下簡稱首頁)啟動,所以不論當你操作App進入到了哪一層、哪個頁面,只要是在桌面再次按下了App的icon就會又回到首頁。 上述的流程可以透過開啟App進入到某一層之後,按Android手機的Home鍵,再按下App icon,就會了解發生了什麼事。 按下App icon就是開啟首頁對於Android使用者一直以來是再習慣也不過的事,但對於iOS的使用者就覺得不對勁了。 原來在iOS的世界裡,App開啟之後,如果按了Home鍵,下次再按了桌面上的icon還是會停留在A pp上次最後的頁面,除非iOS的使用者透過切換程式的方式把該App刷掉(關掉),下次按下icon才會開啟App的首頁。 那麼,如果有天Android工程師收到的需求就是要如同iOS的行為一樣時,該怎麼辦呢? 上網查了一下,解決的方法只要在Activity的onCreate()生命週期裡加入下面的程式碼即可以解決 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) { finish(); return; } //其他原本在此生命週期執行的程式碼... //... //... } Reference: How to return to the latest launched activity when re-launching application after pressing HOME?

Android如何實作強制App版本更新

App上架了一段時間,透過Google Play Console常常會發現有不少的使用者仍然一直停留在舊版本不更新,雖然Android預設會自動更新用戶安裝的App版本,但也是會有使用者把自動更新的功能關閉,如此便會造成App加了新功能用戶無法體驗到或是當初沒考慮到的漏洞沒辦法補起來,基於此,強烈建議App在規劃的初期就要把「強制使用者更新App」的功能實作之後才上架。 實作的方式不難,大致上依照以下幾個流程即可達成: App開啟時檢查版本與Google Play上的版本是否一致 如果不一致就彈出對話框讓使用者執行前往該App的Google Play連結 限制使用者企圖操作不進行版本更新的行為 本例以Volley實作,所以在Callback的部份加上判斷版本是否相符的程式碼: StringRequest appVersion = BaseApi.appVersion(new ResponseListener() { @Override public void onError(VolleyError error) { super.onError(error); intoNextPage(); //有可能網路出錯但仍需讓使用者可以進入下一個頁面 } @Override public void onResponse(String str) { super.onResponse(str); Pattern pattern = Pattern.compile("\"softwareVersion\"\\W*([\\d\\.]+)"); Matcher matcher = pattern.matcher(str); if (matcher.find()) { if (!ValueUtility.getCurrentVersionName().equals(matcher.group(1))) { newVersionDialog.show(); } else {

Eclipse專案移植到Android Studio時發生META-INF/LICENSE.txt與suport-library-v4/v7內容重覆的問題

當我們想把使用Eclipse寫的code移植到Android Studio來開發,如果有當初在Eclipse把整個專案包成別的專案可以使用的Library(在Properties設定為"is Library"),整合之後程式碼都沒有出錯,但要Build apk時就有可能會看到類似的錯誤訊息: Error:Execution failed for task ':library:tools:transformResourcesWithMergeJavaResForRelease'. > com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/LICENSE.txt File1:~/…/[ProjectName]/library/tools/libs/httpcore-4.1.4.jar File2:~/…/[ProjectName]/library/tools/libs/httpclient-4.1.3.jar File3:~/…/[ProjectName]/library/tools/libs/httpmime-4.1.3.jar 解決的方法是在Gradle裡面加上: packagingOptions { exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' } 但是要看發生重覆的檔案位置在哪,本例在Android Studio建立一個Library Project來放置當初在Eclipse的is Library Project,而出現重覆的地方是訊息指出的那三個jar檔,也是在Android Studio的Library專案(

升級SDK Build-tools 23.0.2之後會與Multidex發生衝突

當升級SDK Platform API 23之後,發現已經有Build-tools Version 23.0.2,想說就順便升級一下,結果就發生... Error:Execution failed for task ':app:transformClassesWithDexForDebug'. > com.android.build.transform.api.TransformException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/bin/java'' finished with non-zero exit value 3 查詢的結果發現是與 Multidex 發生衝突了。由於之前在Build-tools 22.0.1就已經有使用了Multidex了,因此建議如果有要使用Multidex就先不要把Build-tools升級到23.0.2,就用Build-tools 22.0.1就好。在此提供升級SDK為23且可以使用Multidex在gradle的設定: android { compileSdkVersion 23 buildToolsVersion ' 22.0.1 ' defaultConfig { applicationId "com.ingree.cwwebsite" minSdkVersion 15 targetSdkVersion 23 // Enabling multidex support. multiDexEnabled true } dependencies { compile ' com.android.support:multidex:1.0.1 ' } 雖然在 Multidex 的Googl