2012年12月13日木曜日

[安藤]Android studio無痛開發

想到會隨時補上。

環境架設:


  • 安裝eclipse.
  • 選擇help-->install new software 安裝android sdk.
  • 在work-with欄位填上 http://dl-ssl.google.com/android/eclipse/site.xml
  • 必須安裝的套件是Developer Tools-->Android DDMS , Developer Tools-->Android Developer Tools。
  • Window-->android adk manager, 安裝Google Api。版本除了新增api之外,基本api的使用感覺差異不大,建議裝最新的。 有相容性問題的話就使用之前習慣用的版本。


使用習慣:

  • 在左端的project欄位右擊,選擇import-->android-->Existing Android Code Into Workspace來匯入project
  • 建議關掉auto build (在project的menutab選擇),全部手動。因為有時auto build不會更新到code...
  • Android lint 在minSdkVersion設定為比較低的版本,code裡面有用到較高版本才提供的api的時候,會出現「call requires api level X」的問題。 直接點X的地方使用quick fix加上suppress NewAPI的方式解決。
  • Android lint(語法檢查功能)的相關問題,有時更動過之後會發現其他的相同問題不會消失。(最常發生在更改resource的string.xml的時候。)請記得手動build一次,才會被更新。
  • 程式碼遇到中文字出現亂碼問題: eclipse preferences-->general-->workspace-->text file encoding, 改為utf-8。
  • 你的app有用到google map / gcm 等等Google API的話: project's 右擊-->properties-->android-->build target, 把 "Google APIs"打勾
  • 設定 eclipse-->preferences-->run/debug-->launching-->launch operation--> always launch previously application,這樣即使藍色游標還停在xml上面的時候按下debug,也不會導致系統叫出非android的debugger,導致.orig檔的產生。
  • 若是你有匯入其他project,如何避免打開.java的時候成為無法編輯的.class file:
    左擊你的project, 選擇Properties   
    選擇Java Build Path頁面
    選擇Java projects tab
    加上你要導入的project
    選擇 Order and Export tab   
    把你導入的 library project 使用右邊的move按鈕上移到列表的最上方.
  • 用不到的Resource圖檔要拿掉。因為建apk的時候會全部進去...
  • 出現大量import錯誤的時候,可以在Project explorer裡面該project的src目錄上面點擊滑鼠右鍵-->source-->organize imports,就會自動幫你修正該project裡面所有的java檔案的imports!
  • 若是你發現可啓動的AVD裡面沒有你之前加過的版本的機器,應該是你的的PC的usb線有連著實機。



app簽名:

  • keytool -genkey -alias mysign.keystore -keyalg RSA -validity 36500 -keystore mysign.keystore  (需注意,google要求該簽名有效期至少20年才能傳上google play。)
  • 要得到google play可上傳的apk,請使用project-->export的方法加上前一步驟提到的sign。
  • 不小心把debug時的apk傳上去,該package name只能放棄。(除非你一直使用該debug sign下去... 我不知道debug sign什麼時候會消失...)


上傳新版到google play:


  • package name要一致(通常不會有人改,因為你的code的擺放目錄也會跟著大改)
  • androidmanifest.xml 的version number要+1,version name也要變化。
  • 所使用在apk上的簽名要跟之前用的一樣




一些規則:
  • debug在上傳apk時遇到 INSTALL_FAILED_CONFLICTING_PROVIDER ---  請修改 androidmanifest.xml裡面的 android:authorities. 有用到content provider的時候要改的。必須每個package不同,所以建議使用package name。這樣要replace字串也方便。
  • Android Developer Tools版本14以上的resource id已經不是constant, 若是你的library有用到android的resource id, 請把之前用switch case的寫法全部換成if-else。 可以在游標放在switch上面,按ctrl-1讓eclipse幫你改。但是太複雜的switch-case (像是用return的 case,或是沒break的case...) eclipse會無法處理。

AndroidManifest.xml相關:


  • <uses-sdk android:minSdkVersion="10" />此參數必需設定。之前有遇到用較高版本的api,會比較容易出現out of memory error的狀況。 可以看需要決定要用跟build設定的target api版本相同或是較舊的。

    <uses-permission> 跟 <uses-feature> 請放在<application>前面。
  • <action android:name="android.intent.action.MAIN" /> 有幾組,app列表就會看到幾個App Icon
  • 沒寫在Android manifest.xml裡面的Acticity,手動create之後系統會給你exception... 這意味著無法在執行期間新建Activity。
  • 支援所有螢幕大小(放在<application>前面): 
         <supports-screens android:xlargeScreens="true"
                    android:largeScreens="true"
                    android:normalScreens="true"
                    android:smallScreens="true"
                    android:anyDensity="true"/>
  • Sony系列手機的os, app顯示在"應用程式"區的名字,強制抓string resource裡面的「app_name」標籤。所以定義為其他字的話,桌面上的app的名稱將會顯示空白...
    <string name="app_name">Appname</string>
     





Layout file相關:

  • RelativeLayout,若是該view的定位參數有參考到其他view,此view需要移動順序到參考view的下方(把xml定義碼移到參考view的下方)。要不然會complie error。
  • view的id可以重複,但是記住一個規則:同一層layout以下的id儘量不要重複。否則findviewbyid會找不到第一個重名之後的.當然你也可以一層一層往下挖啦。
  • FrameLayout下面的child view 使用layout_gravity參數會有效果。
  • <android.support.v13.app.FragmentTabHost>
    裡面要包兩個tag:<TabWidget>,content用的Layout。
  • include其他layout檔的時候,被include的root layout有指定id的話,以該id為準。沒指定的時候才會使用<include> tag裡面有記載的id。不過在同一個layout檔裡面若是使用relativelayout等等指定物件的相對位置的排版方式,在指定相對位置的時候,卻不會吃到被include的layout檔的layout id。因此還是得指定一個id。但是...就是這個但是。在code裡面只吃<include> tag裡面有記載的id...



widget相關:

  • listview的cell建議加上 adjust view bounds這個屬性,會幫你自動調整view。
  • 有重要訊息會在畫面下方的話,儘量使用Relative view 定義該widget 的Align parent bottom。 要不然面對各種不同的解析度的機子,你會不知道該怎麼測...
  • 不同語系的訊息處理:Resource裡面的strings.xml要定義strings_zh.xml等等
    針對不同語言的訊息。然後程式碼內需要顯示文字的地方全部用context.getResources().getString(R.string.xxxxx)的方式取。
  • 發現按back鍵不會回到上一層的時候(我在使用fragment的時候遇到),在setcontentview()之後加上下列動作:
    view.setFocusableInTouchMode(true); 
    view.requestFocus(); 
  • 令人頭痛的TextView換行碼的問題:建議使用下面的方法替換你的字串:

        str = str.replace("\r\n", "\n");  
        str = str.replace("\r", "\n");

        str = str.replace("\n", System.getProperty("line.separator"));
  • scrollview裡面掛的view請勿設定margin, 會出現切邊的問題。
  • scrollview想強制指定scroll到某個元件的區域,可使用scrollView.smoothScrollTo(xPos, yPos)。但是,取元件的xPos跟yPos的時候,要取scrollView第一層的layout的下一層才會有值。再往下一層的話就沒了...(因為android的xPos, yPos是相對座標)
  • 使用程式碼撰寫layout,各個widget的layoutparams的宣告,請宣告為上一層的widget所使用的格式。例:上一層是LinearLayout, 就宣告為LinearLayout.LayoutParams,上一層是ListView, 就宣告為AbsListView.LayoutParams.
  • 文字加上刪除線的畫法:TextView.setPaintFlags(TextView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
  • ListView若是有加Header或是Footer, setAdapter()之後再getAdapter(),回來的會是「HeaderViewListAdapter」。若是有取回判斷的設計的話請注意。原本的adapter要再用.getWrappedAdapter()才能取得。切記,position參數會加上header跟footer的數量。

    ExpandableListView的話,直接用getExpandableListAdapter()即可。



lib/build path相關:
  • 如何讓想含入的project變成lib: 修改project.properties, 加上
    android.library=true (直接在project properties->android-> Is Library上面打勾也可)
  • java build path的order and export裡面的所有可以打勾的項目都要打勾。不打勾的話可能會在某些static initial的時候出現NoclassDefFoundError.
  • NoclassDefFoundError還有另一個可能,就是主project的build path裡面沒有新增該class有用到的jar並export。只加在lib project裡面,有時候會怪怪的。
  • library jar的增減都需要clean build才會生效。
  • order and export的順序是:jar檔最前面,libproject第二,<project>/src跟<project>/gen為最後。


Resource相關:
  • 檔案名一定要小寫。
  • 多語言的目錄名稱:values / values-zh-rCN / values-zh-rTW
  • 多Layout的目錄名稱:layout / layout-land / layout-ldpi
  • 多解析度的目錄名稱:drawable / drawable-hdpi / drawable-ldpi
  • strings可以有不同檔案。 檔案格式為strings_xxx.xml。
  • layout component 的id: 要使用系統內建的activity,像是ListActivity, 需要綁定的固定id格式為:  @+android:id/list ("id/"之後的字可以看exception的提示), tabhost的固定id為: "@+android:id/tabhost"

    程式碼取用(findViewById之類的動作)的時候的寫法:android.R.id.list / android.R.id.tabhost
  • 一般id的格式為 +id/xxxxx

context相關:

  • dialog是一個常常引發badtokenexception的兇手。但是又不允許使用getApplicationContext()。比較簡單的解決方法:在打開dialog之前使用if (!isFinishing())判斷Activity是不是關了。
  • 若是你用了雙層Tab, 取context是為了要顯示dialog的時候,要取當下context的parent. 也就是要用this.getParent()來取。就可以避開bad token exception.
  • 其實在Activity的框架下,Context是它的subclass. 建議在Activity裡面若是你設計的inner class會需要傳context進去的話,改傳Activity進去. 
  • broadcastreceiver的onReceiveBroadcast()裡面不可以再call registerBroadcastReceiver(),會造成無窮迴圈。

sql相關:

  • like指令要使用select args(問號對應string陣列的方法)處理,需要把萬用字元%跟_加在select args裡面,硬寫在selection裡面的話會失效。
  • DISTINCT指令只能用db的rawquery處理.
  • 數字的判斷若是不用問號對應string陣列的方法列舉硬寫的話,必須加上雙引號。例:"_id = \"100\" "


proguard相關:

  • 直接強制remove Log.x() ---  -assumenosideeffects class android.util.Log { *; }
    使用此法的前提是一定要使用-optimizations參數。建議照一般的建議,在project.properties裡面使用
    #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
    此法無法remove System.println()。
  • 有使用reflect法取得資料結構的話,必須keep該class。proguard報error的時候,也要keep出問題的class。
    使用下面的兩行組來keep你必須keep的東西:
    -dontwarn com.google.android.gms.**
    -keep class com.google.android.gms.** { *;}
    像是apache這個proguard打不散,必須keep的東西...
    -dontwarn org.apache.**
    -keep class org.apache.** { *;}
  • 比較常用的一些指令:
    -libraryjars libs/android-support-v13.jar




memory leak問題:
  • activity裡面別用static variable keep住context。非用static context不可的話請直接拿application的。
  • listview 的cell裡面有用到ImageView的話,要註冊RecyclerListener去處理bitmap的recycle問題或是把imageview跟bitmap之間的連結移除。
  • ViewPager只要記得使用的pageradapter的destroyItem()的Override要處理bitmap的recycle問題即可。
  • 切記,不要用view的background顯示大圖檔。
  • Samsung的Note跟S3在allocate bitmap的時候吃記憶體很重,還會把resource的allocate算在app身上,導致一個app可用的64M記憶體輕鬆被用光。須針對這兩款機型特別做記憶體的相關測試。


Drawable相關:

  • bitmap建議全部改new成bitmapdrawable處理。
  • LayerDrawable在2.3.5版os上面常常會畫錯。我的建議是自己疊。
    Bitmap bitmap = Bitmap.createBitmap( width,height,Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    overlay.draw(canvas);
    bd.draw(canvas);
    畫完之後就可以把舊的bitmap recycle了。
  • selector的按壓變色若是失效,在normal state的item後面加上「 android:state_focused="false" android:state_pressed="false" android:state_selected="false" android:state_checked="false"」這幾個條件。


Javascript Interface相關:
  • android 4.2之後,系統因為安全性的問題禁止了javascriptinterface裡面沒有annotation宣告的method的使用。webview.
    addJavascriptInterface()這個動作有用到的javascript interface class,裡面的method需要加上@JavascriptInterface 這個annotation。否則執行時會給出找不到method的exception.

ActivityResult運作流程:
  • 起始: startActivityForResult(intent, requestCode);
  • 下一層的回傳:
    Intent returnIntent = new Intent();returnIntent.putExtra("result",result); //想傳啥都可以setResult(RESULT_OK, returnIntent);finish();
  • 回傳之後的處理:
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    }
  • 注意點:Activity本身無法判別被指定的requestCode。有點可惜無法好好利用這個參數讓activity對於不同的requestCode有不同的動作。

Acticity lifecycle:

  • 這張圖應該都有看過。
  • 不過兩個acticity之間的互動的流程就比較少人提了。
    定義原本的activity為A,新喚起的Acticity為B。B被心喚起之後,整個Acticity的遷移動作流程:
    A.onPause()->B.onCreate()->B.onStart()->B.onResume()->A.onStop()。

Virtual device emulator相關:

  • CPU使用intel,用eclipse在avd上面跑debug,卻發現Eclipse的console出現錯誤:
  • Installation error: INSTALL_FAILED_MISSING_SHARED_LIBRARY原因通常是intel的android system image裡面沒有google map的package。
    下面是解決方法:
  1. 取得GApps https://goo.im/gapps/
  2. 從GPapps的package裡面取得以下的兩個檔案:
    /system/framework/com.google.android.maps.jar
    /system/framework/com.google.android.maps.xml
  3. 進入 [android-sdk目錄]/sdk/platform-tools
  4. 啟動在avd manager裡面已經建好的cpu使用intel的avd。沒有的話請自己建一個。
    emulator -avd [avd manager裡面建好的avd的名字] -partition-size 512
    (使用partition size參數的原因是不加參數的話,新增的lib會push進不去。)
  5. adb remount
  6. 把步驟2的檔案push進intel的system image裡面。
    adb push [存放檔案ㄌㄨ]/com.google.android.maps.xml /system/etc/permissions
    adb push  [存放檔案路徑]/com.google.android.maps.jar /system/framework
  7. adb reboot

gradle相關:

遇到難解的編譯錯誤的時候,請記得:

  • make跟rebuild編譯成功,並不代表debug / release build會成功。請切記要確認debug build確定會過。
  • 砍掉build目錄,clean build有時不一定能夠完全清乾淨。
  • 執行Android Studio的 MenuBar->File->Invalidate Carches/ Restart,會把整個project的關聯砍掉重新整理過。
  • buildtools版本建議更新。
  • 遇到import找不到的時候,先把其他確定有錯的import先處理乾淨,也許就會恢復正常。
  • 多試幾次... 有時候就會好了。Orz
  • 若是有getMethod()等等reflect的使用需求的話,可能會需要跳過型態檢查。
    請在project根目錄的build.gradle裡面加上:
    allprojects {
        gradle.projectsEvaluated {
            tasks.withType(JavaCompile) {
                options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
            }
        }
    }
  • 執行時遇到com.android.dex.DexIndexOverflowException:在主project的build.gradle的defaultConfig裡面加上「multiDexEnabled true」。
  • 程式碼裡面有用到if (true)或是if (false)做switch的話,請改成if (Boolean.TRUE) / if(Boolean.FALSE)。剛遇到一個非常難解的gradle bug是這種寫法造成的...

Java語法相關:

  • String.contains(string),若是參數給的是空字串,contains測試永遠為真。
 
SimpleDateFormat的parse功能,對於時區的處理: 
  • 以英文顯示的(CST / HST等等),要先設定locale為us才能讀取。
  • android7.0未滿的版本,只支援java 6。ISO 8601的UTC時區「Z」,以SimpleDateFormat格式的「yyyy-MM-dd'T'HH:mm:ss.SSSZ」去parse的話會被忽略,認定為local timezone。必須手動取代為「+0000」。
  • TimeZone.getTimeZone(String),遇到「+0000」的時區無法處理。需要在前面加上「GMT」或是「UTC」




0 件のコメント:

コメントを投稿