做Android久了,就會踩很多坑,被坑的多了就有經驗了,閒暇之餘整理了部分,現挑選一些重要或者偏門的“小”經驗做個記錄。
檢視SQLite日誌
1 2 |
adb shell setprop log.tag.SQLiteLog V adb shell setprop log.tag.SQLiteStatements V |
因為實現裡用了Log.isLoggable(TAG, Log.VERBOSE)做了判斷,LessCode的LogLess中也參考了這種機制:LogLess。
使用這種方法就可以在Release版本也能做到檢視應用的列印日誌了。
PNG優化
APK打包會自動對PNG進行無失真壓縮,如果自行無失真壓縮是無效的。
當然進行有失真壓縮是可以的:https://tinypng.com/
Tcpdump抓包
有些模擬器比如genymotion自帶了tcpdump,如果沒有的話,需要下載tcpdump:
http://www.strazzere.com/android/tcpdump
把tcpdump push到/data/local下,抓包命令:
1 |
adb shell /data/local/tcpdump -i any -p -s 0 -w /sdcard/capture.pcap |
檢視簽名
很多開發者服務都需要繫結簽名資訊,用下面的命令可以檢視簽名:
1 |
keytool -list -v -keystore release.jks |
注意,這個是需要密碼的,可以檢視MD5, SHA1,SHA256等等。
單例模式(懶漢式)的更好的寫法
特別說到這個問題,是因為網上很多這樣的程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } |
這種寫法執行緒不安全,改進一下,加一個同步鎖:
1 2 3 4 5 6 7 8 9 10 |
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } |
網上這樣的程式碼更多,可以很好的工作,但是缺點是效率低。
實際上,早在JDK1.5就引入volatile關鍵字,所以又有了一種更好的雙重校驗鎖寫法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } |
注意,別忘記volatile關鍵字哦,否則就是10重,100重也可能還是會出問題。
上面是用的最多的,還有一種靜態內部類寫法更推薦:
1 2 3 4 5 6 7 8 9 10 |
publlic class Singleton { private Singleton() {} private static class SingletonLoader { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonLoader.INSTANCE; } } |
多程式Application
是不是經常發現Application裡的方法執行了多次?百思不得其解。
因為當有多個程式的時候,Application會執行多次,可以通過pid來判斷那些方法只執行一次,避免浪費資源。
隱式啟動Service
這是Android5.0的一個改動,不支援隱式的Service呼叫。下面的程式碼在Android 5.0+上會報錯:Service Intent must be explicit:
1 2 3 |
Intent serviceIntent = new Intent(); serviceIntent.setAction("com.jayfeng.MyService"); context.startService(serviceIntent); |
可改成如下:
1 2 3 |
// 指定具體Service類,或者有packageName也行 Intent serviceIntent = new Intent(context, MyService.class); context.startService(serviceIntent); |
fill_parent的壽命
在Android2.2之後,支援使用match_parent。你的佈局檔案裡是不是既有fill_parent和match_parent顯得很亂?
如果你現在的minSdkVersion是8+的話,就可以忽略fill_parent,統一使用match_parent了,否則請使用fill_parent。
ListView的區域性重新整理
有的列表可能notifyDataSetChanged()代價有點高,最好能區域性重新整理。
區域性重新整理的重點是,找到要更新的那項的View,然後再根據業務邏輯更新資料即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private void updateItem(int index) { int visiblePosition = listView.getFirstVisiblePosition(); if (index - visiblePosition >= 0) { //得到要更新的item的view View view = listView.getChildAt(index - visiblePosition); // 更新介面(示例參考) // TextView nameView = ViewLess.$(view, R.id.name); // nameView.setText("update " + index); // 更新列表資料(示例參考) // list.get(index).setName("Update " + index); } } |
強調一下,最後那個列表資料別忘記更新, 不然資料來源不變,一滾動可能又還原了。
系統日誌中幾個重要的TAG
1 2 3 4 5 6 7 8 |
// 檢視Activity跳轉 adb logcat -v time | grep ActivityManager // 檢視崩潰資訊 adb logcat -v time | grep AndroidRuntime // 檢視Dalvik資訊,比如GC adb logcat -v time | grep "D\/Dalvik" // 檢視art資訊,比如GC adb logcat -v time | grep "I\/art" |
一行居中,多行居左的TextView
這個一般用於提示資訊對話方塊,如果文字是一行就居中,多行就居左。
在TextView外套一層wrap_content的ViewGroup即可簡單實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 套一層wrap_content的ViewGroup --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> </LinearLayout> </RelativeLayout> |
setCompoundDrawablesWithIntrinsicBounds()
網上一大堆setCompoundDrawables()方法無效不顯示的問題,然後解決方法是setBounds,需要計算大小…
不用這麼麻煩,用setCompoundDrawablesWithIntrinsicBounds()這個方法最簡單!
計算程式執行時間
為了計算一段程式碼執行時間,一般的做法是,在程式碼的前面加個startTime,在程式碼的後面把當前時間減去startTime,這個時間差就是執行時間。
這裡提供一種寫起來更方便的方法,完全無時間邏輯,只是加一個列印log就夠了。
1 2 3 4 |
// 測試setContentView()的時間 Log.d("TAG", "Start"); setContentView(R.layout.activity_http); Log.d("TAG", "End"); |
沒有計算時間的邏輯,這能測出來?
把日誌過濾出來,執行命令“adb logcat -v time | grep TAG”:
1 2 |
03-18 14:47:25.477 D/TAG (14600): Start 03-18 14:47:25.478 D/TAG (14600): End |
通過-v time引數,可以比較日誌左邊的時間來算出中間的程式碼執行的時間。
JAVA引用型別一覽表
物件引用:強引用 > 軟引用 > 弱引用 > 虛引用。
引用型別 | 回收時機 | 用途 | 生存時間 |
---|---|---|---|
強引用 | 從來不會 | 物件的一般狀態 | JVM停止執行時終止 |
軟引用 | 在記憶體不足時 | 物件快取 | 記憶體不足時終止 |
弱引用 | 在垃圾回收時 | 物件快取 | GC執行後終止 |
虛引用 | 在垃圾回收時 | 物件跟蹤 | GC執行後終止 |
Context使用場景
為了防止Activity,Service等這樣的Context洩漏於一些生命週期更長的物件,可以使用生命週期更長的ApplicationContext,但是不是所有的Context的都能替換為ApplicationContext
這是網上流傳的一份表格:
Application | Activity | Service | ContentProvider | BroadcastReceiver | |
---|---|---|---|---|---|
Show Dialog | 否 | 是 | 否 | 否 | 否 |
Start Activity | 否 | 是 | 否 | 否 | 否 |
Layout Inflation | 否 | 是 | 否 | 否 | 否 |
Start Service | 是 | 是 | 是 | 是 | 是 |
Bind Service | 是 | 是 | 是 | 是 | 否 |
Send Broadcast | 是 | 是 | 是 | 是 | 是 |
Regist BroadcastReceiver | 是 | 是 | 是 | 是 | 否 |
Load Resource Value | 是 | 是 | 是 | 是 | 是 |
圖片快取大小
現在很多圖片庫需要給圖片設定一個最大快取,但是這個值設定多少合適呢?
高階機和低端機的配置顯然應該不同,可以考慮設定一個動態值。
建議設定為應用可用記憶體的1/8:
1 |
int memoryCache = (int) (Runtime.getRuntime().maxMemory() / 8); |
系統內建的一些工具類
在AOSP原始碼全域性搜了一下包含Util關鍵字的類,整理出這個列表供大家參考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// 系統 ./android/database/DatabaseUtils.java ./android/transition/TransitionUtils.java ./android/view/animation/AnimationUtils.java ./android/view/ViewAnimationUtils.java ./android/webkit/URLUtil.java ./android/bluetooth/le/BluetoothLeUtils.java ./android/gesture/GestureUtils.java ./android/text/TextUtils.java ./android/text/format/DateUtils.java ./android/os/FileUtils.java ./android/os/CommonTimeUtils.java ./android/net/NetworkUtils.java ./android/util/MathUtils.java ./android/util/TimeUtils.java ./android/util/ExceptionUtils.java ./android/util/DebugUtils.java ./android/drm/DrmUtils.java ./android/media/ThumbnailUtils.java ./android/media/ImageUtils.java ./android/media/Utils.java ./android/opengl/GLUtils.java ./android/opengl/ETC1Util.java ./android/telephony/PhoneNumberUtils.java // 設計和支援庫 ./design/src/android/support/design/widget/ViewGroupUtils.java ./design/src/android/support/design/widget/ThemeUtils.java ./design/src/android/support/design/widget/ViewUtils.java ./design/lollipop/android/support/design/widget/ViewUtilsLollipop.java ./design/base/android/support/design/widget/AnimationUtils.java ./design/base/android/support/design/widget/MathUtils.java ./design/honeycomb/android/support/design/widget/ViewGroupUtilsHoneycomb.java ./v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchUIUtil.java ./v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchUIUtilImpl.java ./v7/recyclerview/src/android/support/v7/util/MessageThreadUtil.java ./v7/recyclerview/src/android/support/v7/util/AsyncListUtil.java ./v7/recyclerview/src/android/support/v7/util/ThreadUtil.java ./v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java ./v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java ./v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java ./v7/appcompat/src/android/support/v7/graphics/drawable/DrawableUtils.java ./v7/appcompat/src/android/support/v7/widget/DrawableUtils.java ./v7/appcompat/src/android/support/v7/widget/ThemeUtils.java ./v7/appcompat/src/android/support/v7/widget/ViewUtils.java ./v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java ./v4/jellybean-mr1/android/support/v4/text/TextUtilsCompatJellybeanMr1.java ./v4/jellybean/android/support/v4/app/BundleUtil.java ./v4/jellybean/android/support/v4/app/NavUtilsJB.java ./v4/java/android/support/v4/app/NavUtils.java ./v4/java/android/support/v4/database/DatabaseUtilsCompat.java ./v4/java/android/support/v4/graphics/ColorUtils.java ./v4/java/android/support/v4/text/TextUtilsCompat.java ./v4/java/android/support/v4/util/TimeUtils.java ./v4/java/android/support/v4/util/DebugUtils.java ./v4/java/android/support/v4/content/res/TypedArrayUtils.java |
這麼多工具類,一定可以找到對你有用的。
ClipPadding
這個不多說,ListView的ClipPadding設為false,就能為ListView設定各種padding而不會出現醜陋的滑動“禁區”了。
強大的dumpsys
dumpsys可以檢視系統服務和狀態,非常強大,可通過如下檢視所有支援的子命令:
1 |
dumpsys | grep "DUMP OF SERVICE" |
這裡列舉幾個稍微常用的:
子命令 | 備註 |
---|---|
activity | 顯示所有的activities的資訊 |
cpuinfo | 顯示CPU資訊 |
window | 顯示鍵盤,視窗和它們的關係 |
meminfo | 記憶體資訊(meminfo $package_name or $pid 使用包名或者程式id顯示記憶體資訊) |
alarm | 顯示Alarm資訊 |
statusbar | 顯示狀態列相關的資訊(找出廣告通知屬於哪個應用) |
usagestats | 每個介面啟動的時間 |
bugreport命令
很多人都用過adb logcat,但是如果想要更詳細的資訊,logcat則無能為力。
所以大多數手機廠商測試更多的是用adb bugreport來抓log給開發人員分析。
1 2 3 |
// 除了log,還包括啟動後的系統狀態,包括程式列表,記憶體資訊,VM資訊等等 // 而且不像logcat是一直列印的,bugreport命令輸出到當前時間就停止結束了。 adb bugreport > main.log |
dpi資料夾的換算比例
之前的ldpi基本可以拋棄了,主流的dpi已經從很早之前的mdip轉移到了xhdpi了,特別提醒。
PPI | RESOLUTION | DP | PX |
---|---|---|---|
mdpi(160dp) | 320P | 1 | 1 |
hdpi(240dp) | 480P | 1 | 1.5 |
xhdpi(320dp) | 720P | 1 | 2 |
xxhdpi(480dpi) | 1080P | 1 | 3 |
更新媒體庫檔案
以前做ROM的時候經常碰到一些第三方軟體(某音樂APP)下載了新檔案或刪除檔案之後,但是媒體庫並沒有更新,因為這個是需要第三方軟體主動觸發。
1 2 3 |
// 通知媒體庫更新單個檔案狀態 Uri fileUri = Uri.fromFile(file); sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,fileUri)); |
媒體庫會在手機啟動,SD卡插拔的情況下進行全盤掃描,不是實時的而且代價比較大,所以單個檔案的重新整理很有必要。
Monkey引數
大家都知道,跑monkey的引數設定有一些要注意的地方,比如太快了不行不切實際,太慢了也不行等等,這裡給出一個參考:
1 |
adb shell monkey -p -s 1000 --ignore-crashes --ignore-timeouts --ignore-security-exceptions --pct-trackball 0 --pct-nav 0 --pct-majornav 0 --pct-anyevent 0 -v --throttle 300 1200000000 |
一邊跑monkey,一邊抓log吧。
小結
無論是大經驗還是小經驗,有用就是好經驗。