Reason: Broadcast of Intent { act=android.intent.action.TIME_TICK
ActivityManager:
ANR in com.***.***
PID: 16227
Reason: Broadcast of Intent { act=android.intent.action.TIME_TICK flg=0x50000014 (has extras) }
有那么一段時(shí)間我被這個(gè)ANR折磨到每天吃不下飯睡不著覺(jué)日漸憔悴,Read the fucking source code,每天看android源碼各種跟蹤調(diào)試編譯燒寫之后,終于找出了問(wèn)題所在,應(yīng)用層APP的UI線程刷新太頻繁!
不要使用Html.fromHtml()
Textview里的文字設(shè)置多種顏色時(shí)不要偷懶,分兩個(gè)Textview來(lái)寫,使用Html.fromHtml加載時(shí)會(huì)耗時(shí)500+毫秒,用多了引起卡頓。
Handler傳參盡量要使用基礎(chǔ)類型
msg.obj傳遞java類對(duì)象在1秒400次頻率下會(huì)導(dǎo)致數(shù)據(jù)丟失卡頓,并且會(huì)莫名其妙出現(xiàn)數(shù)據(jù)錯(cuò)亂。
UI線程一定要只做刷新UI動(dòng)作
UI線程最好只做界面相關(guān)的動(dòng)作,不要為了偷懶少些幾行代碼就把整個(gè)數(shù)據(jù)拋給UI線程,并發(fā)大的時(shí)候會(huì)丟失數(shù)據(jù)。
數(shù)據(jù)的各種格式和邏輯要在子線程中判斷,把不需要刷新UI的數(shù)據(jù)攔截下來(lái)并拋掉,總之就是不要頻繁刷新UI。
EventBus不適合高并發(fā)數(shù)據(jù)的處理,老老實(shí)實(shí)寫接口用回調(diào)吧
在實(shí)現(xiàn)一些設(shè)計(jì)模式上EventBus更有用,但在高并發(fā)數(shù)據(jù)下即使加了eventBusIndex處理速度還是慢,并且會(huì)越來(lái)越慢。
定時(shí)任務(wù)不要用timer,使用Handler的sendEmptyMessageDelayed()或sendMessageDelayed()
timer耗費(fèi)的資源多,并且一旦異常之后就會(huì)把整個(gè)任務(wù)終結(jié)掉出現(xiàn)各種難以預(yù)料的問(wèn)題。
jni回調(diào)java函數(shù)時(shí),一定要去冗余數(shù)據(jù)
jni層把有用數(shù)據(jù)過(guò)濾精簡(jiǎn)之后再回調(diào)java函數(shù),1000毫秒回調(diào)400多次根本刷新不過(guò)來(lái)。
JNI DETECTED ERROR IN APPLICATION: JNI SetByteArrayRegion called with pending exception 'android.view.ViewRootImpl$CalledFromWrongThreadException' thrown in unknown throw location
剛開(kāi)始遇到這個(gè)錯(cuò)誤我以為jni層SetByteArrayRegion這個(gè)地方byte數(shù)組copy出錯(cuò),單獨(dú)調(diào)試jni的庫(kù)也一點(diǎn)問(wèn)題沒(méi)有。最后感覺(jué)這個(gè)地方又有view又有Thread,那么會(huì)不會(huì)是在jni的子線程里調(diào)用了UI線程導(dǎo)致,十幾個(gè)從jni回調(diào)上來(lái)的地方一個(gè)一個(gè)地方排查,果然是在callback線程直接更新UI所致!一個(gè)地方遺漏掉了,同事以為我改了,我以為他改了,結(jié)果誰(shuí)都沒(méi)改,因?yàn)榘遄記](méi)接收音機(jī)的模塊,CAN總線沒(méi)有發(fā)收音機(jī)的數(shù)據(jù)上來(lái),這個(gè)模塊一直沒(méi)調(diào)試過(guò),直到被測(cè)試發(fā)現(xiàn)之后叼了我們一頓。信息同步很重要??!
Skipped *** frames! The application may be doing too much work on its main thread.
場(chǎng)景是不停的用handler定時(shí)發(fā)delay message,然后分發(fā)給UI來(lái)跑界面,模擬真實(shí)使用場(chǎng)景,跑了一夜,第二天又卡又慢,這個(gè)問(wèn)題不用到處搜索了,寫的清清楚楚明明白白
The application may be doing too much work on its main thread
只能看自己的代碼調(diào)試看看耗時(shí)操作,逐個(gè)場(chǎng)景分析。我這里是因?yàn)檎鎸?shí)數(shù)據(jù)是在子線程分發(fā)出去,然后個(gè)UI線程獲取數(shù)據(jù)之后再用Handler來(lái)更新UI,但是因?yàn)槟M分發(fā)數(shù)據(jù),為了圖方便用了handler的sendMessageDelayed函數(shù),所以相當(dāng)于主線程分發(fā)數(shù)據(jù)之后,UI線程又開(kāi)一個(gè)Handler又在主線程中刷新數(shù)據(jù),造成不必要開(kāi)銷。解決方法就是,加一個(gè)開(kāi)關(guān),如果是模擬數(shù)據(jù),直接更新UI不再發(fā)handler更新UI。
android.view.InflateException: Binary XML file line # *: Error inflating class
自定義view庫(kù)在另外一個(gè)功能,通過(guò)compile project的方式引入,然后在項(xiàng)目工程的layout文件里直接按照包名類名來(lái)引入,總是報(bào)這個(gè)錯(cuò)誤,包名類名路徑都正確,很郁悶,一般也不會(huì)懷疑一個(gè)經(jīng)過(guò)各種驗(yàn)證的自己寫好的封裝view庫(kù),但是原因就在這里。
@RequiresApi(api = Build.VERSION_CODES.N)
public MyView(Context context, AttributeSet attrs, int defStyleAttr)
為了調(diào)用新接口構(gòu)造函數(shù)加了的API版本,而運(yùn)行的機(jī)器低于這個(gè)版本,所以在xml里配置這個(gè)view的時(shí)候就會(huì)報(bào)錯(cuò),這個(gè)地方調(diào)用低版本的API,去掉版本限制就OK了,RequiresApi要慎用。
自定義View抗鋸齒、畫大于180度的扇形問(wèn)題
畫儀表盤的時(shí)候,剛開(kāi)始是用切割畫布的方式實(shí)現(xiàn)clipPath,但是鋸齒太明顯了,后來(lái)用圖層的方式實(shí)現(xiàn),效果非常平滑完美,主要用到PorterDuff.Mode.DST_OUT,但是要先設(shè)置setLayerType為L(zhǎng)AYER_TYPE_SOFTWARE或者LAYER_TYPE_HARDWARE
大于180度的扇形時(shí),由于path的addArc函數(shù)只能添加一個(gè)弧形,如果想要一個(gè)扇形還要切割一個(gè)三角形,和這個(gè)弧形拼湊起來(lái),如果大于180度的時(shí)候,下面會(huì)多出一條線,這時(shí)候要把大于180度的面積,分割成兩個(gè)扇形,分別添加到path中,多說(shuō)無(wú)益,上代碼,示例代碼是從150度開(kāi)始繪制.
補(bǔ)充:后面我再看這段代碼發(fā)現(xiàn)還有優(yōu)化的空間
對(duì)于Dalvik虛擬機(jī)來(lái)說(shuō),要盡量避免頻繁生成臨時(shí)變量特別是onDraw等函數(shù),也要避免產(chǎn)生很多長(zhǎng)生命周期的對(duì)象,但是出于懶的關(guān)系,下面這段代碼并沒(méi)有修改
private void drawProgressIndicator(Canvas canvas, int percent) {
if (percent == 0) {
return;
}
canvas.save();
canvas.translate(0f, 0f);
canvas.drawBitmap(bitmapIndicatorShadow, 0, 0, dPaint);
float startAngle = START_ARC + percent;
dPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
float sX, sY;
float eX, eY;
sX = (float) (CENTER + (2 + CENTER) * Math.cos((startAngle) * Math.PI / 180));
sY = (float) (CENTER + (2 + CENTER) * Math.sin((startAngle) * Math.PI / 180));
eX = (float) (CENTER + (2 + CENTER) * Math.cos((endAngle) * Math.PI / 180));
eY = (float) (CENTER + (2 + CENTER) * Math.sin((endAngle) * Math.PI / 180));
RectF rectF = new RectF(0, 0, mWidth, mWidth);
Path path = new Path();
path.reset();
path.moveTo(CENTER, CENTER);
path.lineTo(sX, sY);
if (startAngle <= 210) {
float cX = (float) (CENTER + (2 + CENTER) * Math.cos((270) * Math.PI / 180));
float cY = (float) (CENTER + (2 + CENTER) * Math.sin((270) * Math.PI / 180));
path.lineTo(cX, cY);
path.lineTo(eX, eY);
path.close();
path.addArc(rectF, startAngle, 270 - startAngle);
path.addArc(rectF, 270, endAngle - 270);
} else {
path.lineTo(eX, eY);
path.close();
path.addArc(rectF, startAngle, endAngle - startAngle);
}
canvas.drawPath(path, dPaint);
dPaint.setXfermode(null);
canvas.restore();
}
優(yōu)化內(nèi)存
1、所有的HashMap盡量改ArrayMap
2、所有的enum盡量改成static變量
3、所有xml布局層級(jí)嵌套超過(guò)3層精簡(jiǎn)一下
4、所有的for或while循環(huán)里,盡量避免new對(duì)象除非邏輯如此
5、所有for或while避免使用Iterator或者for(Object obj:list)形式便利,直接用最原始語(yǔ)句遍歷
6、onDraw()里不要new對(duì)象
7、能用jpg的圖就不要用png,jpg的圖質(zhì)量最低壓縮比例最高,網(wǎng)上搜索一下jpg和png壓縮工具,壓縮圖片大小
8、所有fragment,特別是主界面里的,改成用時(shí)加載不用時(shí)銷毀,不再一直加載,容易被回收
9、編譯時(shí)剪裁系統(tǒng)應(yīng)用主要在./build/target/product下,利用find語(yǔ)句查找并注釋刪減應(yīng)用,在注釋時(shí),# Launcher2 \要把連接符\直接刪除掉,否則注釋語(yǔ)句相當(dāng)于/** Launcher2 \ 后面的一長(zhǎng)串代碼 **/
find . -type f -name '*' | xargs grep 'Launcher2'
find . -name '*' | xargs grep dalvik.vm.heapsize
10、內(nèi)存相關(guān)
adb remount
adb shell
mount -o remount rw /system
dumpsys meminfo
adb shell dumpsys meminfo com.***.***
adb shell dmesg
adb shell cat /proc/kmsg
adb shell procrank
//查看應(yīng)用啟動(dòng)時(shí)間
adb shell am start -W com.***.***/com.***.***.MainActivity
VSS - Virtual Set Size 虛擬耗用內(nèi)存(包含共享庫(kù)占用的內(nèi)存)
RSS - Resident Set Size 實(shí)際使用物理內(nèi)存(包含共享庫(kù)占用的內(nèi)存)
PSS - Proportional Set Size 實(shí)際使用的物理內(nèi)存(比例分配共享庫(kù)占用的內(nèi)存)
USS - Unique Set Size 進(jìn)程獨(dú)自占用的物理內(nèi)存(不包含共享庫(kù)占用的內(nèi)存)
修改framework層應(yīng)用內(nèi)存閾值大小在./frameworks/native/build
下,比如android:largeHeap="true"時(shí),調(diào)整內(nèi)存到512m
PRODUCT_PROPERTY_OVERRIDES += \
dalvik.vm.heapstartsize=8m \
dalvik.vm.heapgrowthlimit=64m \
dalvik.vm.heapsize=512m \
dalvik.vm.heaptargetutilization=0.75 \
dalvik.vm.heapminfree=512k \
dalvik.vm.heapmaxfree=8m
11、固定應(yīng)用方向,使android:screenOrientation="reverseLandscape"無(wú)效,固定在某個(gè)方向,修改frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java
源文件
@Override
public int rotationForOrientationLw(int orientation, int lastRotation) {
if (false) {
Slog.v(TAG, "rotationForOrientationLw(orient="
+ orientation + ", last=" + lastRotation
+ "); user=" + mUserRotation + " "
+ ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED)
? "USER_ROTATION_LOCKED" : "")
);
}
if (mForceDefaultOrientation) {
return Surface.ROTATION_0;
}
修改為固定旋轉(zhuǎn)180度
@Override
public int rotationForOrientationLw(int orientation, int lastRotation) {
if (false) {
Slog.v(TAG, "rotationForOrientationLw(orient="
+ orientation + ", last=" + lastRotation
+ "); user=" + mUserRotation + " "
+ ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED)
? "USER_ROTATION_LOCKED" : "")
);
}
if (mForceDefaultOrientation || true) {
return Surface.ROTATION_180;
}
12、刪減系統(tǒng)服務(wù)
在frameworks/base/services/java/com/android/server/SystemServer.java中刪減服務(wù), 這里也是啟動(dòng)SystemUI的地方