本文由玉剛說寫作平臺(tái)提供寫作贊助,版權(quán)歸玉剛說微信公眾號(hào)所有
原作者:Mr.s(豬_隊(duì)友)
版權(quán)聲明:未經(jīng)玉剛說許可上煤,不得以任何形式轉(zhuǎn)載
文章之前的一些話:
本文尿點(diǎn)比較多,酌情看自己想看的。
什么是性能
這張圖很好詮釋了什么性能。
快护戳,穩(wěn),省垂睬,小媳荒,這四點(diǎn)很形象的代表了性能的四個(gè)方面,同時(shí)也讓我們知道我們App現(xiàn)在是否是款性能良好的APP驹饺,如果有一項(xiàng)不達(dá)標(biāo)钳枕,那么說明我們的應(yīng)用有待優(yōu)化。
很多時(shí)候我們注重功能實(shí)現(xiàn)逻淌,保證能用么伯,但是我們會(huì)發(fā)現(xiàn)疟暖,這樣的應(yīng)用很難拿的出手卡儒,里面的槽點(diǎn)太多了,性能很差俐巴,但是又不知道從哪里下手進(jìn)行優(yōu)化骨望,那么我們就一步一步來,看看我們到底應(yīng)該怎么優(yōu)化我們的APP欣舵。
1 擎鸠、布局優(yōu)化
和UI相關(guān)的首先就是布局,特別是在開發(fā)一些復(fù)雜界面的時(shí)候缘圈,通常我們都是采用布局嵌套的方法劣光,每個(gè)人的布局思路不太一樣袜蚕,寫出的也不太一樣,绢涡,所以就可能造成嵌套的層級(jí)過多牲剃。
官方:
屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次。在多層次的UI結(jié)構(gòu)里面雄可,如果不可見的UI也在做繪制的操作凿傅,這就會(huì)導(dǎo)致某些像素區(qū)域被繪制了多次。這就浪費(fèi)大量的CPU以及GPU資源数苫。白話:
顯示一個(gè)布局就好比我們蓋一個(gè)房子聪舒,首先我們要測(cè)量房子的大小,還要測(cè)量房間里面各個(gè)家具的大小虐急,和位置箱残,然后進(jìn)行擺放同時(shí)也要對(duì)房子進(jìn)行裝修,如果我們是一層戏仓,都在明面上疚宇,干起活來敞亮也輕松,可是有的人的房子赏殃,喜歡各種隔斷敷待,分成一個(gè)一個(gè)的大隔斷間,每個(gè)大隔斷間里還有小隔斷間仁热,小隔斷間里有小小隔斷間榜揖,還有小小小隔斷間。抗蠢。举哟。N層隔斷間
看到這些頭皮發(fā)麻吧,而且是一個(gè)大隔斷間里面所有的小隔斷迅矛,小小隔斷等等都測(cè)量完擺放好妨猩,才能換另外一個(gè)大隔斷,天呢秽褒,太浪費(fèi)時(shí)間了壶硅,不能都直接都放外面嗎?也好擺放啊销斟,這么搞我怎么擺庐椒,每個(gè)隔斷間都要裝修一遍,太浪費(fèi)時(shí)間了啊蚂踊。
我們的Android虛擬機(jī)也會(huì)這么抱怨约谈,咱們家本來就不富裕,什么都要省著用,你這么搞棱诱,肯定運(yùn)轉(zhuǎn)有問題啊泼橘,那么多嵌套的小隔斷間需要處理,都會(huì)占用cpu計(jì)算的時(shí)間和GPU渲染的時(shí)間迈勋。顯示GPU過度繪制侥加,分層如下如所示
通過顏色我們可以知道我們應(yīng)用是否有多余層次的繪制,如果一路飄紅粪躬,那么我們就要相應(yīng)的處理了担败。
所以我們有了第一個(gè)優(yōu)化版本:
- 優(yōu)化 1.0
1、如果父控件有顏色镰官,也是自己需要的顏色提前,那么就不必在子控件加背景顏色
2、如果每個(gè)自控件的顏色不太一樣泳唠,而且可以完全覆蓋父控件狈网,那么就不需要再父控件上加背景顏色
3、盡量減少不必要的嵌套
4笨腥、能用LinearLayout和FrameLayout拓哺,就不要用RelativeLayout,因?yàn)镽elativeLayout控件相對(duì)比較復(fù)雜脖母,測(cè)繪也想要耗時(shí)士鸥。
做到了以上4點(diǎn)只能說恭喜你,入門級(jí)優(yōu)化已經(jīng)實(shí)現(xiàn)了谆级。
針對(duì)嵌套布局烤礁,谷歌也是陸續(xù)出了一些新的方案。
對(duì)就是<include>,<merge>,<ViewStub>三兄弟
<include>可以提高布局的復(fù)用性肥照,大大方便我們的開發(fā)脚仔,有人說這個(gè)沒有減少布局的嵌套吧,對(duì)舆绎,<include>確實(shí)沒有鲤脏,但是<include>和<merge>聯(lián)手搭配,效果那是杠杠滴吕朵。
<merge>的布局取決于父控件是哪個(gè)布局猎醇,使用<merge>相當(dāng)于減少了自身的一層布局,直接采用父<include>的布局边锁,當(dāng)然直接在父布局里面使用意義不大姑食,所以會(huì)和<include>配合使用波岛,既增加了布局的復(fù)用性茅坛,用減少了一層布局嵌套。
<ViewStub>它可以按需加載,什么意思贡蓖?用到他的時(shí)候喊他一下曹鸠,再來加載,不需要的時(shí)候像空氣一樣斥铺,在一邊靜靜的呆著彻桃,不吃你的米,也不花你家的錢晾蜘。等需要的時(shí)候ViewStub中的布局才加載到內(nèi)存邻眷,多節(jié)儉持家啊。
對(duì)于一些進(jìn)度條剔交,提示信息等等八百年才用一次的功能肆饶,使用<ViewStub>是極其合適的。這就是不用不知道岖常,一用戒不了驯镊。
我們開始進(jìn)化我們的優(yōu)化
- 優(yōu)化1.1
5、使用<include>和<merge> 增加復(fù)用竭鞍,減少層級(jí)
6板惑、<ViewStub>按需加載,更加輕便
可能又有人說了:
背景復(fù)用了偎快,嵌套已經(jīng)很精簡(jiǎn)了冯乘,再精簡(jiǎn)就實(shí)現(xiàn)了不了復(fù)雜視圖了,可是還是一路飄紅晒夹,這個(gè)怎么辦往湿?面對(duì)這個(gè)問題谷歌給了我們一個(gè)新的布局ConstraintLayout
。
ConstraintLayout可以有效地解決布局嵌套過多的問題惋戏。
ConstraintLayout使用約束的方式來指定各個(gè)控件的位置和關(guān)系的领追,它有點(diǎn)類似于 RelativeLayout,但遠(yuǎn)比RelativeLayout要更強(qiáng)大响逢。(照抄隔壁IOS的約束布局)
所以簡(jiǎn)單布局簡(jiǎn)單處理绒窑,復(fù)雜布局ConstraintLayout很好使。提升性能從布局做起舔亭。
再次進(jìn)化:
- 優(yōu)化1.2
7些膨、復(fù)雜界面可選擇ConstraintLayout,可有效減少層級(jí)
2钦铺、繪制優(yōu)化
我們把布局優(yōu)化了订雾,但是和布局息息相關(guān)的還有繪制。這是直接影響顯示的兩個(gè)根本因素矛洞。
其實(shí)布局優(yōu)化了對(duì)于性能提升影響不算很大洼哎,但是是我們最容易下手烫映,最直接接觸的優(yōu)化,所以不管能提升多少噩峦,哪怕只有百分之一的提升锭沟,我們也要做,因?yàn)橛绊懶阅艿牡胤教嗔耸恫梗總€(gè)部分都提升一點(diǎn)族淮,我們應(yīng)用就可以提升很多了。
我們平時(shí)感覺的卡頓問題最主要的原因之一是因?yàn)殇秩拘阅芷就浚驗(yàn)樵絹碓綇?fù)雜的界面交互祝辣,其中可能添加了動(dòng)畫,或者圖片等等切油。我們希望創(chuàng)造出越來越炫的交互界面较幌,同時(shí)也希望他可以流暢顯示,但是往往卡頓就發(fā)生在這里白翻。
這個(gè)是Android的渲染機(jī)制造成的乍炉,Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染滤馍,但是渲染未必成功岛琼,如果成功了那么代表一切順利,但是失敗了可能就要延誤時(shí)間巢株,或者直接跳過去槐瑞,給人視覺上的表現(xiàn),就是要么卡了一會(huì)阁苞,要么跳幀困檩。
View的繪制頻率保證60fps是最佳的,這就要求每幀繪制時(shí)間不超過16ms(16ms = 1000/60)那槽,雖然程序很難保證16ms這個(gè)時(shí)間悼沿,但是盡量降低onDraw方法中的復(fù)雜度總是切實(shí)有效的。
這個(gè)正常情況下骚灸,每隔16ms draw()一下糟趾,很整齊,很流暢甚牲,很完美义郑。
往往會(huì)發(fā)生如下圖的情況,有個(gè)便秘的家伙霸占著丈钙,一幀畫面拉的時(shí)間那么長(zhǎng)非驮,這一下可不就卡頓了嘛。把后面的時(shí)間給占用了雏赦,后面只能延后劫笙,或者直接略過了芙扎。
既然問題找到了,那么我們肯定要有相應(yīng)的解決辦法邀摆,根本做法是 減輕onDraw()的負(fù)擔(dān)。所以
第一點(diǎn):
onDraw方法中不要做耗時(shí)的任務(wù)伍茄,也不做過多的循環(huán)操作栋盹,特別是嵌套循環(huán),雖然每次循環(huán)耗時(shí)很小敷矫,但是大量的循環(huán)勢(shì)必霸占CPU的時(shí)間片例获,從而造成View的繪制過程不流暢。
第二點(diǎn):
除了循環(huán)之外曹仗,onDraw()中不要?jiǎng)?chuàng)建新的局部對(duì)象榨汤,因?yàn)閛nDraw()方法一般都會(huì)頻繁大量調(diào)用,就意味著會(huì)產(chǎn)生大量的零時(shí)對(duì)象怎茫,不進(jìn)占用過的內(nèi)存收壕,而且會(huì)導(dǎo)致系統(tǒng)更加頻繁的GC,大大降低程序的執(zhí)行速度和效率轨蛤。
其實(shí)這兩點(diǎn)在android的UI線程中都適用蜜宪。
升級(jí)進(jìn)化:
- 優(yōu)化2.0
8、onDraw中不要?jiǎng)?chuàng)建新的局部對(duì)象
9祥山、onDraw方法中不要做耗時(shí)的任務(wù)
其實(shí)從渲染優(yōu)化里我們也牽扯出了另一個(gè)優(yōu)化圃验,那就是內(nèi)存優(yōu)化。
3缝呕、內(nèi)存優(yōu)化
內(nèi)存泄漏指的是那些程序不再使用的對(duì)象無法被GC識(shí)別澳窑,這樣就導(dǎo)致這個(gè)對(duì)象一直留在內(nèi)存當(dāng)中,占用了沒來就不多的內(nèi)存空間供常。
內(nèi)存泄漏是一個(gè)緩慢積累的過程摊聋,一點(diǎn)一點(diǎn)的給你,溫水煮青蛙一般栈暇,我們往往很難直觀的看到栗精,只能最后內(nèi)存不夠用了,程序奔潰了瞻鹏,才知道里面有大量的泄漏悲立,但是到底是那些地方?估計(jì)是狼煙遍地新博,千瘡百孔薪夕,都不知道如何下手。怎么辦赫悄?最讓人難受的是內(nèi)存泄漏情況那么多原献,記不住馏慨,理解也不容易,關(guān)鍵是老會(huì)忘記姑隅。怎么辦呢写隶?老這么下去也不是事,總不能面試的時(shí)候突擊讲仰,做項(xiàng)目的時(shí)候不知所措吧慕趴。所以一定要記住了解GC原理,這樣才可以更準(zhǔn)確的理解內(nèi)存泄漏的場(chǎng)景和原因鄙陡。不懂GC原理的可以先看一下這個(gè)JVM初探:內(nèi)存分配冕房、GC原理與垃圾收集器
本來GC的誕生是為了讓java程序員更加輕松(這一點(diǎn)隔壁C++痛苦的一匹),趁矾,java虛擬機(jī)會(huì)自動(dòng)幫助我們回收那些不再需要的內(nèi)存空間耙册。通過引用計(jì)數(shù)法,可達(dá)性分析法等等方法毫捣,確認(rèn)該對(duì)象是否沒有引用详拙,是否可以被回收。
有人會(huì)說真么強(qiáng)悍的功能看起來無懈可擊啊蔓同,對(duì)溪厘,理論上可以達(dá)到消除內(nèi)存泄漏,但是很多人不按常理出牌啊牌柄,往往很多時(shí)候畸悬,有的對(duì)象還保持著引用,但邏輯上已經(jīng)不會(huì)再用到珊佣。就是這一類對(duì)象蹋宦,游走于GC法律的邊緣,我沒用了咒锻,但是你又不知道我沒用了冷冗,就是這么賴著不走,空耗內(nèi)存惑艇。
因?yàn)橛袃?nèi)存泄漏蒿辙,所以內(nèi)存被占用越來越多,那么GC會(huì)更容易被觸發(fā)滨巴,GC會(huì)越來越頻發(fā)思灌,但是當(dāng)GC的時(shí)候所有的線程都是暫停狀態(tài)的,需要處理的對(duì)象數(shù)量越多耗時(shí)越長(zhǎng)恭取,所以這也會(huì)造成卡頓泰偿。
那么什么情況下會(huì)出現(xiàn)這樣的對(duì)象呢?
基本可以分為以下四大類:
1蜈垮、集合類泄漏
2耗跛、單例/靜態(tài)變量造成的內(nèi)存泄漏
3裕照、匿名內(nèi)部類/非靜態(tài)內(nèi)部類
4、資源未關(guān)閉造成的內(nèi)存泄漏
1调塌、集合類泄漏:
集合類添加元素后晋南,仍引用著集合元素對(duì)象,導(dǎo)致該集合中的元素對(duì)象無法被回收羔砾,從而導(dǎo)致內(nèi)存泄露负间。
舉個(gè)栗子:
static List<Object> mList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Object obj = new Object();
mList.add(obj);
obj = null;
}
當(dāng)mList沒用的時(shí)候,我們?nèi)绻蛔鎏幚淼脑捬亚眩@就是典型的占著茅坑不拉屎唉擂,mList內(nèi)部持有者眾多集合元素的對(duì)象餐屎,不泄露天理難容啊檀葛。解決這個(gè)問題也超級(jí)簡(jiǎn)單。把mList清理掉腹缩,然后把它的引用也給釋放掉屿聋。
mList.clear();
mList = null;
2、單例/靜態(tài)變量造成的內(nèi)存泄漏:
單例模式具有其 靜態(tài)特性藏鹊,它的生命周期 等于應(yīng)用程序的生命周期润讥,正是因?yàn)檫@一點(diǎn),往往很容易造成內(nèi)存泄漏盘寡。
先來一個(gè)小栗子:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context){
this.mContext = context;
}
public static SingleInstance newInstance(Context context){
if(mInstance == null){
mInstance = new SingleInstance(context);
}
return sInstance;
}
}
當(dāng)我們?cè)贏ctivity里面使用這個(gè)的時(shí)候楚殿,把我們Acitivty的context傳進(jìn)去,那么竿痰,這個(gè)單例就持有這個(gè)Activity的引用脆粥,當(dāng)這個(gè)Activity沒有用了,需要銷毀的時(shí)候影涉,因?yàn)檫@個(gè)單例還持有Activity的引用变隔,所以無法GC回收,所以就出現(xiàn)了內(nèi)存泄漏蟹倾,也就是生命周期長(zhǎng)的持有了生命周期短的引用匣缘,造成了內(nèi)存泄漏。
所以我們要做的就是生命周期長(zhǎng)的和生命周期長(zhǎng)的玩鲜棠,短的和短的玩肌厨。就好比你去商場(chǎng),本來就是傳個(gè)話的豁陆,話說完就要走了夏哭,突然保安過來非要拉著你的手,說要和你天長(zhǎng)地久献联。只要商場(chǎng)在一天竖配,他就要陪你一天何址。天呢?太可怕了进胯。叔叔我們不約用爪,我有我的小伙伴,我還要上學(xué)呢胁镐,你趕緊找你的保潔阿姨去吧偎血。你在商場(chǎng)的生命周期本來可能就是1分鐘,而保安的生命周期那是要和商場(chǎng)開關(guān)門一致的盯漂,所以不同生命周期的最好別一起玩的好颇玷。
解決方案也很簡(jiǎn)單:
public class SingleInstance {
private static SingleInstance mInstance;
private Context mContext;
private SingleInstance(Context context){
this.mContext = context.getApplicationContext();
}
public static SingleInstance newInstance(Context context){
if(mInstance == null){
mInstance = new SingleInstance(context);
}
return sInstance;
}
}
還有一個(gè)常用的地方就是Toast。你應(yīng)該知道和誰玩了吧就缆。
3帖渠、匿名內(nèi)部類/非靜態(tài)內(nèi)部類
這里有一張寶圖:
非靜態(tài)內(nèi)部類他會(huì)持有他外部類的引用,從圖我們可以看到非靜態(tài)內(nèi)部類的生命周期可能比外部類更長(zhǎng)竭宰,這就是二樓的情況一致了空郊,如果非靜態(tài)內(nèi)部類的周明周期長(zhǎng)于外部類,在加上自動(dòng)持有外部類的強(qiáng)引用切揭,我的乖乖狞甚,想不泄漏都難啊。
我們?cè)賮砼e個(gè)栗子:
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
我們經(jīng)常會(huì)用這個(gè)方法去異步加載廓旬,然后更新數(shù)據(jù)哼审。貌似很平常,我們開始學(xué)這個(gè)的時(shí)候就是這么寫的孕豹,沒發(fā)現(xiàn)有問題啊涩盾,但是你這么想一想,MyAscnyTask是一個(gè)非靜態(tài)內(nèi)部類巩步,如果他處理數(shù)據(jù)的時(shí)間很長(zhǎng)旁赊,極端點(diǎn)我們用sleep 100秒,在這期間Activity可能早就關(guān)閉了椅野,本來Activity的內(nèi)存應(yīng)該被回收的终畅,但是我們知道非靜態(tài)內(nèi)部類會(huì)持有外部類的引用,所以Activity也需要陪著非靜態(tài)內(nèi)部類MyAscnyTask一起天荒地老竟闪。好了离福,內(nèi)存泄漏就形成了。
怎么辦呢炼蛤?
既然MyAscnyTask的生命周期可能比較長(zhǎng)妖爷,那就把它變成靜態(tài),和Application玩去吧,這樣MyAscnyTask就不會(huì)再持有外部類的引用了絮识。兩者也相互獨(dú)立了绿聘。
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
//改了這里 注意一下 static
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
說完非靜態(tài)內(nèi)部類,我再來看看匿名內(nèi)部類次舌,這個(gè)問題很常見熄攘,匿名內(nèi)部類和非靜態(tài)內(nèi)部類有一個(gè)共同的地方,就是會(huì)只有外部類的強(qiáng)引用彼念,所以這哥倆本質(zhì)是一樣的挪圾。但是處理方法有些不一樣。但是思路絕對(duì)一樣逐沙。換湯不換藥哲思。
舉個(gè)灰常熟悉的栗子:
public class TestActivity extends Activity {
private TextView mText;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//do something
mText.setText(" do someThing");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mText = findVIewById(R.id.mText);
// 匿名線程持有 Activity 的引用,進(jìn)行耗時(shí)操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
mHandler. sendEmptyMessageDelayed(0, 100000);
}
想必這兩個(gè)方法是我們經(jīng)常用的吧吩案,很熟悉棚赔,也是這么學(xué)的,沒感覺不對(duì)啊务热,老師就是這么教的忆嗜,通過我們上面的分析己儒,還這么想嗎崎岂?關(guān)鍵是 耗時(shí)時(shí)間過長(zhǎng),造成內(nèi)部類的生命周期大于外部類闪湾,對(duì)弈非靜態(tài)內(nèi)部類冲甘,我們可以靜態(tài)化,至于匿名內(nèi)部類怎么辦呢途样?一樣把它變成靜態(tài)內(nèi)部類江醇,也就是說盡量不要用匿名內(nèi)部類。完事了嗎何暇?很多人不注意這么一件事陶夜,如果我們?cè)趆andleMessage方法里進(jìn)行UI的更新,這個(gè)Handler靜態(tài)化了和Activity沒啥關(guān)系了裆站,但是比如這個(gè)mText条辟,怎么說?全寫是activity.mText,看到了吧宏胯,持有了Activity的引用羽嫡,也就是說Handler費(fèi)勁心思變成靜態(tài)類,自認(rèn)為不持有Activity的引用了肩袍,準(zhǔn)確的說是不自動(dòng)持有Activity的引用了杭棵,但是我們要做UI更新的時(shí)候勢(shì)必會(huì)持有Activity的引用,靜態(tài)類持有非靜態(tài)類的引用氛赐,我們發(fā)現(xiàn)怎么又開始內(nèi)存泄漏了呢魂爪?處處是坑啊先舷,怎么辦呢?我們這里就要引出弱引用的概念了滓侍。
引用分為強(qiáng)引用密浑,軟引用,弱引用粗井,虛引用尔破,強(qiáng)度一次遞減。
強(qiáng)引用:
我們平時(shí)不做特殊處理的一般都是強(qiáng)引用浇衬,如果一個(gè)對(duì)象具有強(qiáng)引用懒构,GC寧可OOM也絕不會(huì)回收它≡爬蓿看出多強(qiáng)硬了吧胆剧。
軟引用(SoftReference):
如果內(nèi)存空間足夠,GC就不會(huì)回收它醉冤,如果內(nèi)存空間不足了秩霍,就會(huì)回收這些對(duì)象的內(nèi)存。
弱引用(WeakReference):
弱引用要比軟引用,更弱一個(gè)級(jí)別蚁阳,內(nèi)存不夠要回收他铃绒,GC的時(shí)候不管內(nèi)存夠不夠也要回收他,簡(jiǎn)直是弱的一匹螺捐。不過GC是一個(gè)優(yōu)先級(jí)很低的線程颠悬,也不是太頻繁進(jìn)行,所以弱引用的生活還過得去定血,沒那么提心吊膽赔癌。
虛引用:
用的甚少,我沒有用過澜沟,如果想了解的朋友灾票,可以自行谷歌百度。
所以我們用弱引用來修飾Activity茫虽,這樣GC的時(shí)候刊苍,該回收的也就回收了,不會(huì)再有內(nèi)存泄漏了席噩。很完美班缰。
public class TestActivity extends Activity {
private TextView mText;
private MyHandler myHandler = new MyHandler(TestActivity.this);
private MyThread myThread = new MyThread();
private static class MyHandler extends Handler {
WeakReference<TestActivity> weakReference;
MyHandler(TestActivity testActivity) {
this.weakReference = new WeakReference<TestActivity>(testActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
weakReference.get().mText.setText("do someThing");
}
}
private static class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
mText = findViewById(R.id.mText);
myHandler.sendEmptyMessageDelayed(0, 100000);
myThread.start();
}
//最后清空這些回調(diào)
@Override
protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
4、資源未關(guān)閉造成的內(nèi)存泄漏
- 網(wǎng)絡(luò)悼枢、文件等流忘記關(guān)閉
- 手動(dòng)注冊(cè)廣播時(shí)埠忘,退出時(shí)忘記 unregisterReceiver()
- Service 執(zhí)行完后忘記 stopSelf()
- EventBus 等觀察者模式的框架忘記手動(dòng)解除注冊(cè)
這些需要記住又開就有關(guān),具體做法也很簡(jiǎn)單就不一一贅述了。給大家介紹幾個(gè)很好用的工具:
1莹妒、leakcanary傻瓜式操作名船,哪里有泄漏自動(dòng)給你顯示出來。很直接很暴力旨怠。
2渠驼、我們平時(shí)也要多使用Memory Monitor進(jìn)行內(nèi)存監(jiān)控,這個(gè)分析就有些難度了鉴腻,可以上網(wǎng)搜一下具體怎么使用淀歇。
3量淌、Android Lint 它可以幫助我們發(fā)現(xiàn)代碼機(jī)構(gòu) / 質(zhì)量問題烟逊,同時(shí)提供一些解決方案衷旅,內(nèi)存泄露的會(huì)飄黃,用起來很方便课锌,具體使用方法上網(wǎng)學(xué)習(xí)厨内,這里不多做說明了。
so
- 優(yōu)化3.0
10渺贤、解決各個(gè)情況下的內(nèi)存泄漏雏胃,注意平時(shí)代碼的規(guī)范。
4志鞍、啟動(dòng)速度優(yōu)化
不知道大家有沒有細(xì)心發(fā)現(xiàn)瞭亮,我們的應(yīng)用啟動(dòng)要比別的大廠的要慢,要花費(fèi)更多的時(shí)間述雾,明明他們的包體更大街州,app更復(fù)雜兼丰,怎么啟動(dòng)時(shí)間反而比我們的短呢玻孟?
但是這塊的優(yōu)化關(guān)注的人很少,因?yàn)锳pp常常伴有閃屏頁鳍征,所以這個(gè)問題看起來就不是問題了黍翎,但是一款好的應(yīng)用是絕對(duì)不允許這樣的,我加閃屏頁是我的事艳丛,啟動(dòng)速度慢絕對(duì)不可以匣掸。
app啟動(dòng)分為冷啟動(dòng)(Cold start)和熱啟動(dòng)(Hot start),溫啟動(dòng)(Warm start)三種氮双。
冷啟動(dòng)(Cold start):
官方:
冷啟動(dòng)是指應(yīng)用程序從頭開始:系統(tǒng)的進(jìn)程在此開始之前沒有創(chuàng)建應(yīng)用程序碰酝。冷啟動(dòng)發(fā)生在諸如自設(shè)備啟動(dòng)以來首次啟動(dòng)應(yīng)用程序或自系統(tǒng)終止應(yīng)用程序以來。
在冷啟動(dòng)開始時(shí)戴差,系統(tǒng)有三個(gè)任務(wù)送爸。這些任務(wù)是:
1、 加載并啟動(dòng)應(yīng)用程序。
2袭厂、 啟動(dòng)后立即顯示應(yīng)用程序的空白啟動(dòng)窗口墨吓。
3、 創(chuàng)建應(yīng)用程序 進(jìn)程
當(dāng)系統(tǒng)為我們創(chuàng)建了應(yīng)用進(jìn)程之后纹磺,開始創(chuàng)建應(yīng)用程序?qū)ο蟆?/p>
1帖烘、啟動(dòng)主線程。
2橄杨、創(chuàng)建主Activity秘症。
3、加載布局
4式矫、屏幕布局
5历极、執(zhí)行初始繪制。
應(yīng)用程序進(jìn)程完成第一次繪制后衷佃,系統(tǒng)進(jìn)程會(huì)交換當(dāng)前顯示的背景窗口趟卸,將其替換為主活動(dòng)。此時(shí)氏义,用戶可以開始使用該應(yīng)用程序锄列。至此啟動(dòng)完成。
Application創(chuàng)建
當(dāng)Application啟動(dòng)時(shí)惯悠,空白的啟動(dòng)窗口將保留在屏幕上邻邮,直到系統(tǒng)首次完成繪制應(yīng)用程序。此時(shí)克婶,系統(tǒng)進(jìn)程會(huì)交換應(yīng)用程序的啟動(dòng)窗口筒严,允許用戶開始與應(yīng)用程序進(jìn)行交互。這就是為什么我們的程序啟動(dòng)時(shí)會(huì)先出現(xiàn)一段時(shí)間的黑屏(白屏)情萤。
如果我們有自己的Application鸭蛙,系統(tǒng)會(huì)onCreate()
在我們的Application對(duì)象上調(diào)用該方法。之后筋岛,應(yīng)用程序會(huì)生成主線程(也稱為UI線程)娶视,并通過創(chuàng)建主要活動(dòng)來執(zhí)行任務(wù)。
從這一點(diǎn)開始睁宰,App就按照他的 應(yīng)用程序生命周期階段進(jìn)行肪获。
Activity創(chuàng)建
應(yīng)用程序進(jìn)程創(chuàng)建活動(dòng)后,活動(dòng)將執(zhí)行以下操作:
- 初始化值柒傻。
- 調(diào)用構(gòu)造函數(shù)孝赫。
- 調(diào)用回調(diào)方法,例如 Activity.onCreate()红符,對(duì)應(yīng)Activity的當(dāng)前生命周期狀態(tài)青柄。
通常劫映,該 onCreate()方法對(duì)加載時(shí)間的影響最大,因?yàn)樗宰罡叩拈_銷執(zhí)行工作:加載和膨脹視圖刹前,以及初始化活動(dòng)運(yùn)行所需的對(duì)象泳赋。
熱啟動(dòng)(Hot start):
官方:
應(yīng)用程序的熱啟動(dòng)比冷啟動(dòng)要簡(jiǎn)單得多,開銷也更低喇喉。在一個(gè)熱啟動(dòng)中祖今,系統(tǒng)都會(huì)把你的Activity帶到前臺(tái)。如果應(yīng)用程序的Activity仍然駐留在內(nèi)存中拣技,那么應(yīng)用程序可以避免重復(fù)對(duì)象初始化千诬、布局加載和渲染。
熱啟動(dòng)顯示與冷啟動(dòng)方案相同的屏幕行為:系統(tǒng)進(jìn)程顯示空白屏幕膏斤,直到應(yīng)用程序完成呈現(xiàn)活動(dòng)徐绑。
溫啟動(dòng)(Warm start)
溫啟動(dòng)包含了冷啟動(dòng)時(shí)發(fā)生的一些操作;與此同時(shí),它表示的開銷比熱啟動(dòng)少莫辨。有許多潛在的狀態(tài)可以被認(rèn)為是溫暖的開始傲茄。
場(chǎng)景:
- 用戶退出您的應(yīng)用,但隨后重新啟動(dòng)它沮榜。該過程可能已繼續(xù)運(yùn)行盘榨,但應(yīng)用程序必須通過調(diào)用從頭開始重新創(chuàng)建Activity 的onCreate()。
- 系統(tǒng)將您的應(yīng)用程序從內(nèi)存中逐出蟆融,然后用戶重新啟動(dòng)它草巡。需要重新啟動(dòng)進(jìn)程和活動(dòng),但是在調(diào)用onCreate()的時(shí)候可以從Bundle(savedInstanceState)獲取數(shù)據(jù)型酥。
了解完啟動(dòng)過程山憨,我們就知道哪里會(huì)影響我們啟動(dòng)的速度了。
在創(chuàng)建應(yīng)用程序和創(chuàng)建Activity期間都可能會(huì)出現(xiàn)性能問題弥喉。
這里是慢的定義:
- 冷啟動(dòng)需要5秒或更長(zhǎng)時(shí)間郁竟。
- 溫啟動(dòng)需要2秒或更長(zhǎng)時(shí)間。
- 熱啟動(dòng)需要1.5秒或更長(zhǎng)時(shí)間档桃。
無論何種啟動(dòng)枪孩,我們的優(yōu)化點(diǎn)都是:
Application、Activity創(chuàng)建以及回調(diào)等過程
谷歌官方給的建議是:
1藻肄、利用提前展示出來的Window,快速展示出來一個(gè)界面拒担,給用戶快速反饋的體驗(yàn)嘹屯;
2、避免在啟動(dòng)時(shí)做密集沉重的初始化(Heavy app initialization)从撼;
3州弟、避免I/O操作钧栖、反序列化、網(wǎng)絡(luò)操作婆翔、布局嵌套等拯杠。
具體做法:
針對(duì)1:利用提前展示出來的Window,快速展示出來一個(gè)界面
使用Activity的windowBackground主題屬性來為啟動(dòng)的Activity提供一個(gè)簡(jiǎn)單的drawable啃奴。
Layout XML file:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@android:color/white"/>
<!-- Your product logo - 144dp color version of your app icon -->
<item>
<bitmap
android:src="@drawable/product_logo_144dp"
android:gravity="center"/>
</item>
</layer-list>
Manifest file:
<activity ...
android:theme="@style/AppTheme.Launcher" />
這樣在啟動(dòng)的時(shí)候潭陪,會(huì)先展示一個(gè)界面,這個(gè)界面就是Manifest中設(shè)置的Style最蕾,等Activity加載完畢后依溯,再去加載Activity的界面,而在Activity的界面中瘟则,我們將主題重新設(shè)置為正常的主題黎炉,從而產(chǎn)生一種快的感覺。其實(shí)就是個(gè)障眼法而已醋拧,提前讓你看到了假的界面慷嗜。也算是一種不錯(cuò)的方法,但是治標(biāo)不治本丹壕。
針對(duì)2:避免在啟動(dòng)時(shí)做密集沉重的初始化
我們審視一下我們的MyApplication里面的操作洪添。
初始化操作有友盟,百度雀费,bugly干奢,數(shù)據(jù)庫,IM盏袄,神策忿峻,圖片加載庫,網(wǎng)絡(luò)請(qǐng)求庫辕羽,廣告sdk逛尚,地圖,推送刁愿,等等绰寞,這么多需要初始化,Application的任務(wù)太重了铣口,啟動(dòng)不慢才怪呢滤钱。
怎么辦呢?這些還都是必要的脑题,不能不去初始化啊件缸,那就只能異步加載了。
但是并不是所有的都可以進(jìn)行異步處理叔遂。這里分情況給出一些建議他炊。
1争剿、比如像友盟,bugly這樣的業(yè)務(wù)非必要的可以的異步加載痊末。
2蚕苇、比如地圖,推送等凿叠,非第一時(shí)間需要的可以在主線程做延時(shí)啟動(dòng)涩笤。當(dāng)程序已經(jīng)啟動(dòng)起來之后,在進(jìn)行初始化幔嫂。
3辆它、對(duì)于圖片,網(wǎng)絡(luò)請(qǐng)求框架必須在主線程里初始化了履恩。
同時(shí)因?yàn)槲覀円话銜?huì)有閃屏頁面锰茉,也可以把延時(shí)啟動(dòng)的地圖,推動(dòng)的啟動(dòng)在這個(gè)時(shí)間段里切心,這樣合理安排時(shí)間片的使用飒筑。極大的提高了啟動(dòng)速度。
針對(duì)3:避免I/O操作绽昏、反序列化协屡、網(wǎng)絡(luò)操作、布局嵌套等全谤。
這個(gè)不用多說了肤晓,大家應(yīng)該知道如何去做了,有些上文也有說明认然。
so
- 優(yōu)化4.0
11补憾、利用提前展示出來的Window,快速展示出來一個(gè)界面卷员,給用戶快速反饋的體驗(yàn)盈匾;
12、避免在啟動(dòng)時(shí)做密集沉重的初始化(Heavy app initialization)毕骡;
13削饵、避免I/O操作、反序列化未巫、網(wǎng)絡(luò)操作窿撬、布局嵌套等。
5橱赠、包體優(yōu)化
我做過兩年的海外應(yīng)用產(chǎn)品尤仍,深知包體大小對(duì)于產(chǎn)品新增的影響,包體小百分之五狭姨,可能新增就增加百分之五宰啦。如果產(chǎn)品基數(shù)很大,這個(gè)提升就更可怕了饼拍。不管怎么說赡模,我們要減肥,要六塊腹肌师抄。不要九九歸一的大肚子漓柑。
既然要瘦身,那么我們必須知道APK的文件構(gòu)成叨吮,解壓apk辆布。
1、assets文件夾茶鉴。存放一些配置文件锋玲、資源文件,assets不會(huì)自動(dòng)生成對(duì)應(yīng)的 ID涵叮,而是通過 AssetManager 類的接口獲取惭蹂。
2、res割粮。res 是 resource 的縮寫盾碗,這個(gè)目錄存放資源文件,會(huì)自動(dòng)生成對(duì)應(yīng)的 ID 并映射到 .R 文件中舀瓢,訪問直接使用資源 ID廷雅。
3、META-INF京髓。保存應(yīng)用的簽名信息航缀,簽名信息可以驗(yàn)證 APK 文件的完整性。
4朵锣、AndroidManifest.xml谬盐。這個(gè)文件用來描述 Android 應(yīng)用的配置信息,一些組件的注冊(cè)信息诚些、可使用權(quán)限等飞傀。
5、classes.dex诬烹。Dalvik 字節(jié)碼程序砸烦,讓 Dalvik 虛擬機(jī)可執(zhí)行,一般情況下绞吁,Android 應(yīng)用在打包時(shí)通過 Android SDK 中的 dx 工具將 Java 字節(jié)碼轉(zhuǎn)換為 Dalvik 字節(jié)碼幢痘。
6、resources.arsc家破。記錄著資源文件和資源 ID 之間的映射關(guān)系颜说,用來根據(jù)資源 ID 尋找資源购岗。
我們需要從代碼和資源兩個(gè)方面去減少響應(yīng)的大小。
1门粪、首先我們可以使用lint
工具喊积,如果有沒有使用過的資源就會(huì)打印如下的信息(不會(huì)使用的朋友可以上網(wǎng)看一下)
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears
to be unused [UnusedResources]
同時(shí)我們可以開啟資源壓縮,自動(dòng)刪除無用的資源
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
無用的資源已經(jīng)被刪除了,接下來哪里可以在瘦身呢玄妈?
2乾吻、我們可以使用可繪制對(duì)象,某些圖像不需要靜態(tài)圖像資源; 框架可以在運(yùn)行時(shí)動(dòng)態(tài)繪制圖像拟蜻。Drawable對(duì)象(<shape>
以XML格式)可以占用APK中的少量空間绎签。此外,XML Drawable對(duì)象產(chǎn)生符合材料設(shè)計(jì)準(zhǔn)則的單色圖像酝锅。
上面的話官方诡必,簡(jiǎn)單說來就是,能自己用XML寫Drawable屈张,就自己寫擒权,能不用公司的UI切圖,就別和他們說話阁谆,咱們自己造碳抄,做自己的UI,美滋滋场绿。而且這種圖片占用空間會(huì)很小剖效。
3、重用資源焰盗,比如一個(gè)三角按鈕璧尸,點(diǎn)擊前三角朝上代表收起的意思,點(diǎn)擊后三角朝下熬拒,代表展開爷光,一般情況下,我們會(huì)用兩張圖來切換澎粟,我們完全可以用旋轉(zhuǎn)的形式去改變
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_thumb_up"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="180" />
比如同一圖像的著色不同蛀序,我們可以用android:tint和tintMode屬性,低版本(5.0以下)可以使用ColorFilter活烙。
4徐裸、壓縮PNG和JPEG文件
您可以減少PNG文件的大小,而不會(huì)丟失使用工具如圖像質(zhì)量 pngcrush啸盏,pngquant重贺,或zopflipng。所有這些工具都可以減少PNG文件的大小,同時(shí)保持感知的圖像質(zhì)量气笙。
5次企、使用WebP文件格式
可以使用圖像的WebP文件格式,而不是使用PNG或JPEG文件健民。WebP格式提供有損壓縮(如JPEG)以及透明度(如PNG)抒巢,但可以提供比JPEG或PNG更好的壓縮贫贝。
可以使用Android Studio將現(xiàn)有的BMP秉犹,JPG,PNG或靜態(tài)GIF圖像轉(zhuǎn)換為WebP格式稚晚。
6崇堵、使用矢量圖形
可以使用矢量圖形來創(chuàng)建與分辨率無關(guān)的圖標(biāo)和其他可伸縮Image。使用這些圖形可以大大減少APK大小客燕。一個(gè)100字節(jié)的文件可以生成與屏幕大小相關(guān)的清晰圖像鸳劳。
但是,系統(tǒng)渲染每個(gè)VectorDrawable對(duì)象需要花費(fèi)大量時(shí)間 也搓,而較大的圖像需要更長(zhǎng)的時(shí)間才能顯示在屏幕上赏廓。因此,請(qǐng)考慮僅在顯示小圖像時(shí)使用這些矢量圖形傍妒。
不要把AnimationDrawable用于創(chuàng)建逐幀動(dòng)畫幔摸,因?yàn)檫@樣做需要為動(dòng)畫的每個(gè)幀包含一個(gè)單獨(dú)的位圖文件,這會(huì)大大增加APK的大小颤练。
7既忆、代碼混淆
使用proGuard 代碼混淆器工具,它包括壓縮嗦玖、優(yōu)化患雇、混淆等功能。這個(gè)大家太熟悉了宇挫。不多說了苛吱。
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android.txt'),
'proguard-rules.pro'
}
}
8、插件化器瘪。
比如功能模塊放在服務(wù)器上翠储,按需下載,可以減少安裝包大小娱局。
so
優(yōu)化5.0
14彰亥、代碼混淆。
15衰齐、插件化
16任斋、資源優(yōu)化
6、耗電優(yōu)化
我們可能對(duì)耗電優(yōu)化不怎么感冒,沒事废酷,谷歌這方面做得也不咋地瘟檩,5.0之后才有像樣的方案,講實(shí)話這個(gè)優(yōu)化的優(yōu)先級(jí)沒有前面幾個(gè)那么高澈蟆,但是我們也要了解一些避免耗電的坑墨辛,至于更細(xì)的耗電分析可以使用這個(gè)Battery Historian。
Battery Historian 是由Google提供的Android系統(tǒng)電量分析工具趴俘,從手機(jī)中導(dǎo)出bugreport文件上傳至頁面睹簇,在網(wǎng)頁中生成詳細(xì)的圖表數(shù)據(jù)來展示手機(jī)上各模塊電量消耗過程,最后通過App數(shù)據(jù)的分析制定出相關(guān)的電量?jī)?yōu)化的方法寥闪。
我們來談一下怎么規(guī)避電老虎吧太惠。
谷歌推薦使用JobScheduler,來調(diào)整任務(wù)優(yōu)先級(jí)等策略來達(dá)到降低損耗的目的疲憋。
JobScheduler可以避免頻繁的喚醒硬件模塊凿渊,造成不必要的電量消耗。
避免在不合適的時(shí)間(例如低電量情況下缚柳、弱網(wǎng)絡(luò)或者移動(dòng)網(wǎng)絡(luò)情況下的)執(zhí)行過多的任務(wù)消耗電量埃脏;
具體功能:
1、可以推遲的非面向用戶的任務(wù)(如定期數(shù)據(jù)庫數(shù)據(jù)更新)秋忙;
2彩掐、當(dāng)充電時(shí)才希望執(zhí)行的工作(如備份數(shù)據(jù));
3翰绊、需要訪問網(wǎng)絡(luò)或 Wi-Fi 連接的任務(wù)(如向服務(wù)器拉取配置數(shù)據(jù))佩谷;
4、零散任務(wù)合并到一個(gè)批次去定期運(yùn)行监嗜;
5谐檀、當(dāng)設(shè)備空閑時(shí)啟動(dòng)某些任務(wù);
6裁奇、只有當(dāng)條件得到滿足, 系統(tǒng)才會(huì)啟動(dòng)計(jì)劃中的任務(wù)(充電桐猬、WIFI...);
同時(shí)谷歌針對(duì)耗電優(yōu)化也提出了一個(gè)懶惰第一的法則
1刽肠、減少:你的應(yīng)用程序可以刪除冗余操作嗎溃肪?例如,它是否可以緩存下載的數(shù)據(jù)而不是重復(fù)喚醒無線電以重新下載數(shù)據(jù)音五?
2惫撰、推遲:應(yīng)用是否需要立即執(zhí)行操作?例如躺涝,它可以等到設(shè)備充電才能將數(shù)據(jù)備份到云端嗎厨钻?
3、合并:可以批處理工作,而不是多次將設(shè)備置于活動(dòng)狀態(tài)嗎夯膀?例如诗充,幾十個(gè)應(yīng)用程序是否真的有必要在不同時(shí)間打開收音機(jī)發(fā)送郵件?在一次喚醒收音機(jī)期間诱建,是否可以傳輸消息蝴蜓?
谷歌在耗電優(yōu)化這方面確實(shí)顯得有些無力,希望以后可以退出更好的工具和解決方案俺猿,不然這方面的優(yōu)化優(yōu)先級(jí)還是很低茎匠。付出和回報(bào)所差太大。
so
優(yōu)化6.0
17辜荠、使用JobScheduler調(diào)度任務(wù)
18汽抚、使用懶惰法則
6、ListView和 Bitmap優(yōu)化
針對(duì)ListView優(yōu)化
ViewHolder的使用
創(chuàng)建一個(gè)內(nèi)部類ViewHolder伯病,里面的成員變量和view中所包含的組件個(gè)數(shù)、類型相同否过,在convertview為null的時(shí)候午笛,把findviewbyId找到的控件賦給ViewHolder中對(duì)應(yīng)的變量,就相當(dāng)于先把它們裝進(jìn)一個(gè)容器苗桂,下次要用的時(shí)候药磺,直接從容器中獲取。
現(xiàn)在我們現(xiàn)在一般使用RecyclerView煤伟,自帶這個(gè)優(yōu)化癌佩,不過還是要理解一下原理的好。
然后可以對(duì)接受來的數(shù)據(jù)進(jìn)行分段或者分頁加載便锨,也可以優(yōu)化性能围辙。
對(duì)于Bitmap,這個(gè)我們使用的就比較多了放案,很容易出現(xiàn)OOM的問題姚建,圖片內(nèi)存的問題可以看一下我之前寫的這篇文章一張圖片占用多少內(nèi)存。
Bitmap的優(yōu)化套路很簡(jiǎn)單吱殉,粗暴掸冤,就是讓壓縮。
三種壓縮方式:
1.對(duì)圖片質(zhì)量進(jìn)行壓縮
2.對(duì)圖片尺寸進(jìn)行壓縮
3.使用libjpeg.so庫進(jìn)行壓縮
- 對(duì)圖片質(zhì)量進(jìn)行壓縮
public static Bitmap compressImage(Bitmap bitmap){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//質(zhì)量壓縮方法友雳,這里100表示不壓縮稿湿,把壓縮后的數(shù)據(jù)存放到baos中
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int options = 100;
//循環(huán)判斷如果壓縮后圖片是否大于50kb,大于繼續(xù)壓縮
while ( baos.toByteArray().length / 1024>50) {
//清空baos
baos.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
options -= 10;//每次都減少10
}
//把壓縮后的數(shù)據(jù)baos存放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
//把ByteArrayInputStream數(shù)據(jù)生成圖片
Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);
return newBitmap;
}
- 對(duì)圖片尺寸進(jìn)行壓縮
/**
* 按圖片尺寸壓縮 參數(shù)是bitmap
* @param bitmap
* @param pixelW
* @param pixelH
* @return
*/
public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
if( os.toByteArray().length / 1024>512) {//判斷如果圖片大于0.5M,進(jìn)行壓縮避免在生成圖片(BitmapFactory.decodeStream)時(shí)溢出
os.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//這里壓縮50%,把壓縮后的數(shù)據(jù)存放到baos中
}
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Bitmap.Config.RGB_565;
BitmapFactory.decodeStream(is, null, options);
options.inJustDecodeBounds = false;
options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
is = new ByteArrayInputStream(os.toByteArray());
Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
return newBitmap;
}
/**
* 動(dòng)態(tài)計(jì)算出圖片的inSampleSize
* @param options
* @param minSideLength
* @param maxNumOfPixels
* @return
*/
public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
return lowerBound;
}
if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
- 使用libjpeg.so庫進(jìn)行壓縮
可以參考這篇Android性能優(yōu)化系列之Bitmap圖片優(yōu)化押赊。
so
- 優(yōu)化7.0
18饺藤、ListView使用ViewHolder,分段,分頁加載
19策精、壓縮Bitmap
8舰始、響應(yīng)速度優(yōu)化
影響響應(yīng)速度的主要因素是主線程有耗時(shí)操作,影響了響應(yīng)速度咽袜。所以響應(yīng)速度優(yōu)化的核心思想是避免在主線程中做耗時(shí)操作丸卷,把耗時(shí)操作異步處理。
9询刹、線程優(yōu)化
線程優(yōu)化的思想是采用線程池谜嫉,避免在程序中存在大量的Thread。線程池可以重用內(nèi)部的線程凹联,從而避免了現(xiàn)場(chǎng)的創(chuàng)建和銷毀所帶來的性能開銷沐兰,同時(shí)線程池還能有效地控制線程池的最大并發(fā)數(shù),避免大量的線程因互相搶占系統(tǒng)資源從而導(dǎo)致阻塞現(xiàn)象發(fā)生蔽挠。
《Android開發(fā)藝術(shù)探索》對(duì)線程池的講解很詳細(xì)住闯,不熟悉線程池的可以去了解一下。
優(yōu)點(diǎn):
1澳淑、減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開銷比原。
2、如不使用線程池杠巡,有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及”過度切換”量窘。需要注意的是:
1、如果線程池中的數(shù)量為達(dá)到核心線程的數(shù)量氢拥,則直接會(huì)啟動(dòng)一個(gè)核心線程來執(zhí)行任務(wù)蚌铜。
2、如果線程池中的數(shù)量已經(jīng)達(dá)到或超過核心線程的數(shù)量嫩海,則任務(wù)會(huì)被插入到任務(wù)隊(duì)列中標(biāo)等待執(zhí)行冬殃。
3、如果(2)中的任務(wù)無法插入到任務(wù)隊(duì)列中出革,由于任務(wù)隊(duì)列已滿造壮,這時(shí)候如果線程數(shù)量未達(dá)到線程池規(guī)定最大值,則會(huì)啟動(dòng)一個(gè)非核心線程來執(zhí)行任務(wù)骂束。
4耳璧、如果(3)中線程數(shù)量已經(jīng)達(dá)到線程池最大值,則會(huì)拒絕執(zhí)行此任務(wù)展箱,ThreadPoolExecutor會(huì)調(diào)用RejectedExecutionHandler的rejectedExecution方法通知調(diào)用者旨枯。
10、微優(yōu)化
這些微優(yōu)化可以在組合時(shí)提高整體應(yīng)用程序性能混驰,但這些更改不太可能導(dǎo)致顯著的性能影響攀隔。選擇正確的算法和數(shù)據(jù)結(jié)構(gòu)應(yīng)始終是我們的首要任務(wù)皂贩,以提高代碼效率。
- 編寫高效代碼有兩個(gè)基本規(guī)則:
1昆汹、不要做你不需要做的工作明刷。
2、如果可以避免满粗,請(qǐng)不要分配內(nèi)存辈末。
1、避免創(chuàng)建不必要的對(duì)象
對(duì)象創(chuàng)建永遠(yuǎn)不是免費(fèi)的映皆,雖然每一個(gè)的代價(jià)不是很大挤聘,但是總歸是代價(jià)的不是嗎?能不創(chuàng)建何必要浪費(fèi)資源呢捅彻?
2组去、首選靜態(tài)(這里說的是特定情景)
如果您不需要訪問對(duì)象的字段,請(qǐng)使您的方法保持靜態(tài)步淹。調(diào)用速度將提高約15%-20%从隆。這也是很好的做法,因?yàn)槟憧梢詮姆椒ê灻锌闯鱿涂酰{(diào)用方法不能改變對(duì)象的狀態(tài)
3广料、對(duì)常量使用static final
此優(yōu)化僅適用于基本類型和String常量,而不適用于 任意引用類型幼驶。盡管如此,static final
盡可能聲明常量是一種好習(xí)慣韧衣。
4盅藻、使用增強(qiáng)的for循環(huán)語法
增強(qiáng)for
循環(huán)(for-each)可用于實(shí)現(xiàn)Iterable接口和數(shù)組的集合。對(duì)于集合畅铭,分配一個(gè)迭代器來對(duì)hasNext()
和進(jìn)行接口調(diào)用next()
氏淑。使用一個(gè) ArrayList,手寫計(jì)數(shù)循環(huán)快約3倍硕噩,但對(duì)于其他集合假残,增強(qiáng)的for循環(huán)語法將完全等效于顯式迭代器用法。
5炉擅、避免使用浮點(diǎn)數(shù)
根據(jù)經(jīng)驗(yàn)辉懒,浮點(diǎn)數(shù)比Android設(shè)備上的整數(shù)慢約2倍
結(jié)尾
本文篇幅有限,性能優(yōu)化的方面很多谍失,每一項(xiàng)深入下去眶俩,不寫個(gè)幾十萬字是結(jié)束不了,所以很多都是淺嘗輒止快鱼,希望可以拋磚引玉颠印,用我的拙劣的文章纲岭,給大家一些幫助。性能優(yōu)化需要走的路還很遠(yuǎn)线罕,希望能和各位同學(xué)一同前行止潮、一起進(jìn)步。
大家可以點(diǎn)個(gè)關(guān)注钞楼,告訴我大家想要深入探究哪些問題喇闸,希望看到哪方面的文章,我可以免費(fèi)給你寫專題文章窿凤。仅偎。哈哈。雳殊。橘沥。
希望大家多多支持。夯秃。你的一個(gè)關(guān)注座咆,是我堅(jiān)持的最大動(dòng)力。仓洼。
參考:
Android APP 性能優(yōu)化的一些思考
https://www.cnblogs.com/cr330326/p/8011523.html
谷歌官方
http://developer.android.com/topic/performance/
Android性能優(yōu)化系列之Bitmap圖片優(yōu)化
https://blog.csdn.net/u012124438/article/details/66087785
Android 內(nèi)存泄漏總結(jié)
https://yq.aliyun.com/articles/3009