關(guān)于Android基本優(yōu)化

1、布局優(yōu)化

<1> 盡量減少不必要的嵌套

<2> 簡(jiǎn)單布局使用?LinearLayout和FrameLayout? ?復(fù)雜布局使用 ConstraintLayout?

? ? 盡量不要用?RelativeLayout 因?yàn)橄鄬?duì)布局比較復(fù)雜姥闭,測(cè)繪也想要耗時(shí)丹鸿。

2、繪制優(yōu)化

Android系統(tǒng)是每16ms發(fā)出一次VSYNC信號(hào)棚品,出發(fā)對(duì)UI進(jìn)行渲染靠欢,如果渲染不成功就會(huì)延誤時(shí)間或者跳到下一個(gè)廊敌,給人的視覺(jué)就是會(huì)卡頓跳幀的樣子,View的繪制頻率保證60fps是最佳的门怪,這就要求每幀繪制時(shí)間不超過(guò)16ms(16ms = 1000/60)骡澈,雖然程序很難保證16ms這個(gè)時(shí)間,但是盡量降低onDraw方法中的復(fù)雜度總是切實(shí)有效的掷空。

<1>:?onDraw方法中不要做耗時(shí)的任務(wù)肋殴,也不做過(guò)多的循環(huán)操作,特別是嵌套循環(huán)坦弟,雖然每次循環(huán)耗時(shí)很小护锤,但是大量的循環(huán)勢(shì)必霸占CPU的時(shí)間片,從而造成View的繪制過(guò)程不流暢酿傍。


<2>:onDraw()中不要?jiǎng)?chuàng)建新的局部對(duì)象烙懦,因?yàn)閛nDraw()方法一般都會(huì)頻繁大量調(diào)用,就意味著會(huì)產(chǎn)生大量的臨時(shí)對(duì)象拧粪,不進(jìn)占用過(guò)的內(nèi)存修陡,而且會(huì)導(dǎo)致系統(tǒng)更加頻繁的GC,大大降低程序的執(zhí)行速度和效率可霎。

3、內(nèi)存優(yōu)化

基本可以分為以下四大類: 1宴杀、集合類泄漏 2癣朗、單例/靜態(tài)變量造成的內(nèi)存泄漏 3、匿名內(nèi)部類/非靜態(tài)內(nèi)部類 4旺罢、資源未關(guān)閉造成的內(nèi)存泄漏

<1>集合類泄漏

舉個(gè)栗子:

舉個(gè)栗子:

當(dāng)mList沒(méi)用的時(shí)候旷余,mList內(nèi)部持有者眾多集合元素的對(duì)象,就會(huì)造成泄露扁达。解決辦法:把mList清理掉正卧,然后把它的引用也給釋放掉。

<2>單例/靜態(tài)變量造成的內(nèi)存泄漏

Activity里面使用這個(gè)的時(shí)候跪解,把我們Acitivty的context傳進(jìn)去炉旷,那么,這個(gè)單例就持有這個(gè)Activity的引用叉讥,當(dāng)這個(gè)Activity沒(méi)有用了窘行,需要銷毀的時(shí)候,因?yàn)檫@個(gè)單例還持有Activity的引用图仓,所以無(wú)法GC回收罐盔,所以就出現(xiàn)了內(nèi)存泄漏,也就是生命周期長(zhǎng)的持有了生命周期短的引用救崔,造成了內(nèi)存泄漏惶看。

解決方案:

<3>匿名內(nèi)部類/非靜態(tài)內(nèi)部類

非靜態(tài)內(nèi)部類他會(huì)持有對(duì)應(yīng)外部類的引用捏顺,從圖中我們可以看到非靜態(tài)內(nèi)部類的生命周期可能比外部類更長(zhǎng),如果非靜態(tài)內(nèi)部類的周明周期長(zhǎng)于外部類纬黎,在加上自動(dòng)持有外部類的強(qiáng)引用幅骄,就會(huì)導(dǎo)致泄漏。

舉個(gè)栗子:


開(kāi)始學(xué)這個(gè)的時(shí)候就是這么寫的

MyAscnyTask是一個(gè)非靜態(tài)內(nèi)部類莹桅,如果他處理數(shù)據(jù)的時(shí)間很長(zhǎng)昌执,將它夸大化sleep 100秒,在這期間Activity可能早就關(guān)閉了诈泼,本來(lái)Activity的內(nèi)存應(yīng)該被回收的懂拾,但是我們知道非靜態(tài)內(nèi)部類會(huì)持有外部類的引用,所以Activity也需要陪著非靜態(tài)內(nèi)部類MyAscnyTask一起100秒铐达。這樣岖赋,內(nèi)存就泄漏了。

解決辦法:


把MyAscnyTask變成靜態(tài)類瓮孙,綁定Application唐断,這樣MyAscnyTask就不會(huì)再持有外部類的引用了。兩者也相互獨(dú)立了杭抠。

說(shuō)完非靜態(tài)內(nèi)部類脸甘,再來(lái)看看匿名內(nèi)部類,這個(gè)問(wèn)題很常見(jiàn)偏灿,匿名內(nèi)部類和非靜態(tài)內(nèi)部類有一個(gè)共同的地方丹诀,就是會(huì)只有外部類的強(qiáng)引用,所以這哥倆本質(zhì)是一樣的翁垂。但是處理方法有些不一樣铆遭。但是思路絕對(duì)一樣。換湯不換藥沿猜。


同理枚荣,耗時(shí)過(guò)長(zhǎng),造成內(nèi)部類的生命周期大于外部類啼肩,對(duì)弈非靜態(tài)內(nèi)部類橄妆,我們可以靜態(tài)化,至于匿名內(nèi)部類怎么辦呢疟游?一樣把它變成靜態(tài)內(nèi)部類呼畸,也就是說(shuō)盡量不要用匿名內(nèi)部類。完事了嗎颁虐?很多人不注意這么一件事蛮原,如果我們?cè)趆andleMessage方法里進(jìn)行UI的更新,這個(gè)Handler靜態(tài)化了和Activity沒(méi)啥關(guān)系了另绩,但是比如這個(gè)mText儒陨,怎么說(shuō)花嘶?全寫是activity.mText,看到了吧蹦漠,持有了Activity的引用椭员,也就是說(shuō)Handler費(fèi)勁心思變成靜態(tài)類,自認(rèn)為不持有Activity的引用了笛园,準(zhǔn)確的說(shuō)是不自動(dòng)持有Activity的引用了隘击,但是我們要做UI更新的時(shí)候勢(shì)必會(huì)持有Activity的引用,靜態(tài)類持有非靜態(tài)類的引用研铆,我們發(fā)現(xiàn)怎么又開(kāi)始內(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) 不管內(nèi)存夠不夠也要回收他。不過(guò)GC是一個(gè)優(yōu)先級(jí)很低的線程绘面,也不是太頻繁進(jìn)行欺税,所以弱引用的生活還過(guò)得去,沒(méi)那么提心吊膽揭璃。

虛引用?用的甚少晚凿,我沒(méi)有用過(guò),如果想了解的朋友瘦馍,可以自行谷歌百度歼秽。

所以我們用弱引用來(lái)修飾Activity,這樣GC的時(shí)候情组,該回收的也就回收了燥筷,不會(huì)再有內(nèi)存泄漏了箩祥。很完美。

public class TestActivityextends Activity {

private TextViewmText;

? ? private MyHandlermyHandler =new MyHandler(TestActivity.this);

? ? private MyThreadmyThread =new MyThread();

? ? private static class MyHandlerextends Handler {

WeakReferenceweakReference;

? ? ? ? MyHandler(TestActivity testActivity) {

this.weakReference =new WeakReference(testActivity);

? ? ? ? }

@Override

? ? ? ? public void handleMessage(Message msg) {

super.handleMessage(msg);

? ? ? ? ? ? weakReference.get().mText.setText("do someThing");

? ? ? ? }

}

private static class MyThreadextends 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è)

這些需要記住又開(kāi)就有關(guān),具體做法也很簡(jiǎn)單就不一一贅述了谢揪。給大家介紹幾個(gè)很好用的工具: 1蕉陋、leakcanary傻瓜式操作,哪里有泄漏自動(dòng)給你顯示出來(lái)拨扶,很直接很暴力凳鬓。 2、我們平時(shí)也要多使用Memory Monitor進(jìn)行內(nèi)存監(jiān)控屈雄,這個(gè)分析就有些難度了村视,可以上網(wǎng)搜一下具體怎么使用。 3酒奶、Android Lint 它可以幫助我們發(fā)現(xiàn)代碼機(jī)構(gòu) / 質(zhì)量問(wèn)題蚁孔,同時(shí)提供一些解決方案,內(nèi)存泄露的會(huì)飄黃惋嚎,用起來(lái)很方便杠氢,具體使用方法上網(wǎng)學(xué)習(xí),這里不多做說(shuō)明了另伍。

解決各個(gè)情況下的內(nèi)存泄漏鼻百,注意平時(shí)代碼的規(guī)范。?

4摆尝、啟動(dòng)速度優(yōu)化

不知道大家有沒(méi)有細(xì)心發(fā)現(xiàn)温艇,我們的應(yīng)用啟動(dòng)要比別的大廠的要慢,要花費(fèi)更多的時(shí)間堕汞,明明他們的包體更大勺爱,app更復(fù)雜,怎么啟動(dòng)時(shí)間反而比我們的短呢讯检?

但是這塊的優(yōu)化關(guān)注的人很少琐鲁,因?yàn)锳pp常常伴有閃屏頁(yè),所以這個(gè)問(wèn)題看起來(lái)就不是問(wèn)題了人灼,但是一款好的應(yīng)用是絕對(duì)不允許這樣的围段,我加閃屏頁(yè)是我的事,啟動(dòng)速度慢絕對(duì)不可以投放。

app啟動(dòng)分為冷啟動(dòng)(Cold start)奈泪、熱啟動(dòng)(Hot start)和溫啟動(dòng)(Warm start)三種。

<1>冷啟動(dòng)(Cold start)

冷啟動(dòng)是指應(yīng)用程序從頭開(kāi)始:系統(tǒng)的進(jìn)程在此開(kāi)始之前沒(méi)有創(chuàng)建應(yīng)用程序。冷啟動(dòng)發(fā)生在諸如自設(shè)備啟動(dòng)以來(lái)首次啟動(dòng)應(yīng)用程序或自系統(tǒng)終止應(yīng)用程序以來(lái)段磨。

在冷啟動(dòng)開(kāi)始時(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)程之后债蜜,開(kāi)始創(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í)向胡,用戶可以開(kāi)始使用該應(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)窗口荷辕,允許用戶開(kāi)始與應(yīng)用程序進(jìn)行交互。這就是為什么我們的程序啟動(dòng)時(shí)會(huì)先出現(xiàn)一段時(shí)間的黑屏(白屏)件豌。

如果我們有自己的Application疮方,系統(tǒng)會(huì)onCreate()在我們的Application對(duì)象上調(diào)用該方法。之后茧彤,應(yīng)用程序會(huì)生成主線程(也稱為UI線程)案站,并通過(guò)創(chuàng)建主要活動(dòng)來(lái)執(zhí)行任務(wù)。

從這一點(diǎn)開(kāi)始棘街,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)樗宰罡叩拈_(kāi)銷執(zhí)行工作:加載和膨脹視圖拯腮,以及初始化活動(dòng)運(yùn)行所需的對(duì)象。

<2>熱啟動(dòng)(Hot start)

應(yīng)用程序的熱啟動(dòng)比冷啟動(dòng)要簡(jiǎn)單得多蚁飒,開(kāi)銷也更低动壤。在一個(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)。

<3>溫啟動(dòng)(Warm start)

溫啟動(dòng)包含了冷啟動(dòng)時(shí)發(fā)生的一些操作筛严,與此同時(shí)醉旦,它表示的開(kāi)銷比熱啟動(dòng)少,有許多潛在的狀態(tài)可以被認(rèn)為是溫暖的開(kāi)始桨啃。

場(chǎng)景:

用戶退出您的應(yīng)用车胡,但隨后重新啟動(dòng)它。該過(guò)程可能已繼續(xù)運(yùn)行优幸,但應(yīng)用程序必須通過(guò)調(diào)用從頭開(kāi)始重新創(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)過(guò)程碳却,我們就知道哪里會(huì)影響我們啟動(dòng)的速度了队秩。在創(chuàng)建應(yīng)用程序和創(chuàng)建Activity期間都可能會(huì)出現(xiàn)性能問(wèn)題。

這里是慢的定義:

啟動(dòng)需要5秒或更長(zhǎng)時(shí)間昼浦。

啟動(dòng)需要2秒或更長(zhǎng)時(shí)間馍资。

啟動(dòng)需要1.5秒或更長(zhǎng)時(shí)間。

無(wú)論何種啟動(dòng)关噪,我們的優(yōu)化點(diǎn)都是:?Application鸟蟹、Activity創(chuàng)建以及回調(diào)等過(guò)程

谷歌官方給的建議是: 1、利用提前展示出來(lái)的Window使兔,快速展示出來(lái)一個(gè)界面建钥,給用戶快速反饋的體驗(yàn); 2虐沥、避免在啟動(dòng)時(shí)做密集沉重的初始化(Heavy app initialization)熊经; 3泽艘、避免I/O操作、反序列化镐依、網(wǎng)絡(luò)操作匹涮、布局嵌套等。

針對(duì)1:利用提前展示出來(lái)的Window槐壳,快速展示出來(lái)一個(gè)界面

使用Activity的windowBackground主題屬性來(lái)為啟動(dòng)的Activity提供一個(gè)簡(jiǎn)單的drawable然低。

<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>

這樣在啟動(dòng)的時(shí)候,會(huì)先展示一個(gè)界面宏粤,這個(gè)界面就是Manifest中設(shè)置的Style脚翘,等Activity加載完畢后,再去加載Activity的界面绍哎,而在Activity的界面中来农,我們將主題重新設(shè)置為正常的主題,從而產(chǎn)生一種快的感覺(jué)崇堰。其實(shí)就是個(gè)障眼法而已沃于,提前讓你看到了假的界面。也算是一種不錯(cuò)的方法海诲,但是治標(biāo)不治本繁莹。

針對(duì)2:避免在啟動(dòng)時(shí)做密集沉重的初始化

我們審視一下我們的MyApplication里面的操作。初始化操作有友盟特幔,百度咨演,bugly,數(shù)據(jù)庫(kù)蚯斯,IM薄风,神策,圖片加載庫(kù)拍嵌,網(wǎng)絡(luò)請(qǐng)求庫(kù)遭赂,廣告sdk,地圖横辆,推送撇他,等等,這么多需要初始化狈蚤,Application的任務(wù)太重了困肩,啟動(dòng)不慢才怪呢。

怎么辦呢脆侮?這些還都是必要的僻弹,不能不去初始化啊,那就只能異步加載了他嚷。但是并不是所有的都可以進(jìn)行異步處理。這里分情況給出一些建議: 1、比如像友盟筋蓖,bugly這樣的業(yè)務(wù)非必要的可以的異步加載卸耘。 2、比如地圖粘咖,推送等蚣抗,非第一時(shí)間需要的可以在主線程做延時(shí)啟動(dòng)。當(dāng)程序已經(jīng)啟動(dòng)起來(lái)之后瓮下,在進(jìn)行初始化翰铡。 3、對(duì)于圖片讽坏,網(wǎng)絡(luò)請(qǐng)求框架必須在主線程里初始化了锭魔。

同時(shí)因?yàn)槲覀円话銜?huì)有閃屏頁(yè)面,也可以把延時(shí)啟動(dòng)的地圖路呜,推動(dòng)的啟動(dòng)在這個(gè)時(shí)間段里迷捧,這樣合理安排時(shí)間片的使用。極大的提高了啟動(dòng)速度胀葱。

針對(duì)3:避免I/O操作漠秋、反序列化、網(wǎng)絡(luò)操作抵屿、布局嵌套等庆锦。

這個(gè)不用多說(shuō)了,大家應(yīng)該知道如何去做了轧葛,有些上文也有說(shuō)明搂抒。

利用提前展示出來(lái)的Window,快速展示出來(lái)一個(gè)界面朝群,給用戶快速反饋的體驗(yàn)燕耿;

避免在啟動(dòng)時(shí)做密集沉重的初始化(Heavy app initialization);

避免I/O操作姜胖、反序列化誉帅、網(wǎng)絡(luò)操作、布局嵌套等右莱。?

5蚜锨、包體優(yōu)化

我做過(guò)兩年的海外應(yīng)用產(chǎn)品,深知包體大小對(duì)于產(chǎn)品新增的影響慢蜓,包體小百分之五亚再,可能新增就增加百分之五。如果產(chǎn)品基數(shù)很大晨抡,這個(gè)提升就更可怕了氛悬。不管怎么說(shuō)则剃,我們要減肥,要六塊腹肌如捅,不要九九歸一的大肚子棍现。

既然要瘦身,那么我們必須知道APK的文件構(gòu)成镜遣,解壓apk:

assets文件夾?存放一些配置文件己肮、資源文件,assets不會(huì)自動(dòng)生成對(duì)應(yīng)的 ID悲关,而是通過(guò) AssetManager 類的接口獲取谎僻。

res目錄?res 是 resource 的縮寫,這個(gè)目錄存放資源文件寓辱,會(huì)自動(dòng)生成對(duì)應(yīng)的 ID 并映射到 .R 文件中艘绍,訪問(wèn)直接使用資源 ID。

META-INF?保存應(yīng)用的簽名信息讶舰,簽名信息可以驗(yàn)證 APK 文件的完整性鞍盗。

AndroidManifest.xml?這個(gè)文件用來(lái)描述 Android 應(yīng)用的配置信息,一些組件的注冊(cè)信息跳昼、可使用權(quán)限等般甲。

classes.dex?Dalvik 字節(jié)碼程序,讓 Dalvik 虛擬機(jī)可執(zhí)行鹅颊,一般情況下敷存,Android 應(yīng)用在打包時(shí)通過(guò) Android SDK 中的 dx 工具將 Java 字節(jié)碼轉(zhuǎn)換為 Dalvik 字節(jié)碼。

resources.arsc?記錄著資源文件和資源 ID 之間的映射關(guān)系堪伍,用來(lái)根據(jù)資源 ID 尋找資源锚烦。

我們需要從代碼和資源兩個(gè)方面去減少響應(yīng)的大小。

1帝雇、首先我們可以使用lint工具涮俄,如果有沒(méi)有使用過(guò)的資源就會(huì)打印如下的信息(不會(huì)使用的朋友可以上網(wǎng)看一下)

res/layout/preferences.xml: Warning: The resource R.layout.preferences appears? ? to be unused [UnusedResources]復(fù)制代碼

同時(shí)我們可以開(kāi)啟資源壓縮,自動(dòng)刪除無(wú)用的資源

android {

...

buildTypes {

release {

shrinkResourcestrue

? ? ? ? ? ? minifyEnabledtrue

? ? ? ? ? ? proguardFiles getDefaultProguardFile('proguard-android.txt'),

? ? ? ? ? ? ? ? ? ? 'proguard-rules.pro'

? ? ? ? }

}

無(wú)用的資源已經(jīng)被刪除了,接下來(lái)哪里可以在瘦身呢尸闸?

2彻亲、我們可以使用可繪制對(duì)象,某些圖像不需要靜態(tài)圖像資源;?框架可以在運(yùn)行時(shí)動(dòng)態(tài)繪制圖像吮廉。Drawable對(duì)象(<shape>以XML格式)可以占用APK中的少量空間苞尝。此外,XMLDrawable對(duì)象產(chǎn)生符合材料設(shè)計(jì)準(zhǔn)則的單色圖像宦芦。

上面的話官方宙址,簡(jiǎn)單說(shuō)來(lái)就是,能自己用XML寫Drawable调卑,就自己寫抡砂,能不用公司的UI切圖大咱,就別和他們說(shuō)話,咱們自己造注益,做自己的UI徽级,美滋滋。而且這種圖片占用空間會(huì)很小聊浅。

3、重用資源现使,比如一個(gè)三角按鈕低匙,點(diǎn)擊前三角朝上代表收起的意思,點(diǎn)擊后三角朝下碳锈,代表展開(kāi)顽冶,一般情況下,我們會(huì)用兩張圖來(lái)切換售碳,我們完全可以用旋轉(zhuǎn)的形式去改變

<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糠溜、使用矢量圖形 可以使用矢量圖形來(lái)創(chuàng)建與分辨率無(wú)關(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è)大家太熟悉了。不多說(shuō)了蚂会。

android {

? ? buildTypes {

? ? ? ? release {

? ? ? ? ? ? minifyEnabled true? ? ? ? ? ? proguardFiles getDefaultProguardFile(‘proguard-android.txt'),

? ? ? ? ? ? ? ? ? ? 'proguard-rules.pro'

? ? ? ? }

? ? }


8淋样、插件化。 比如功能模塊放在服務(wù)器上胁住,按需下載趁猴,可以減少安裝包大小。

代碼混淆

插件化

資源優(yōu)化

6彪见、耗電優(yōu)化

我們可能對(duì)耗電優(yōu)化不怎么感冒儡司,沒(méi)事,谷歌這方面做得也不咋地企巢,5.0之后才有像樣的方案枫慷,講實(shí)話這個(gè)優(yōu)化的優(yōu)先級(jí)沒(méi)有前面幾個(gè)那么高,但是我們也要了解一些避免耗電的坑浪规,至于更細(xì)的耗電分析可以使用這個(gè)Battery Historian或听。

Battery Historian 是由Google提供的Android系統(tǒng)電量分析工具,從手機(jī)中導(dǎo)出bugreport文件上傳至頁(yè)面笋婿,在網(wǎng)頁(yè)中生成詳細(xì)的圖表數(shù)據(jù)來(lái)展示手機(jī)上各模塊電量消耗過(guò)程誉裆,最后通過(guò)App數(shù)據(jù)的分析制定出相關(guān)的電量?jī)?yōu)化的方法。

我們來(lái)談一下怎么規(guī)避電老虎吧缸濒。

谷歌推薦使用JobScheduler足丢,來(lái)調(diào)整任務(wù)優(yōu)先級(jí)等策略來(lái)達(dá)到降低損耗的目的。JobScheduler可以避免頻繁的喚醒硬件模塊庇配,造成不必要的電量消耗斩跌。避免在不合適的時(shí)間(例如低電量情況下、弱網(wǎng)絡(luò)或者移動(dòng)網(wǎng)絡(luò)情況下的)執(zhí)行過(guò)多的任務(wù)消耗電量捞慌。

具體功能: 1耀鸦、可以推遲的非面向用戶的任務(wù)(如定期數(shù)據(jù)庫(kù)數(shù)據(jù)更新); 2、當(dāng)充電時(shí)才希望執(zhí)行的工作(如備份數(shù)據(jù))袖订; 3氮帐、需要訪問(wèn)網(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è)懶惰第一的法則:

減少?你的應(yīng)用程序可以刪除冗余操作嗎昂勒?例如,它是否可以緩存下載的數(shù)據(jù)而不是重復(fù)喚醒無(wú)線電以重新下載數(shù)據(jù)舟铜?

推遲?應(yīng)用是否需要立即執(zhí)行操作?例如奠衔,它可以等到設(shè)備充電才能將數(shù)據(jù)備份到云端嗎谆刨?

合并?可以批處理工作,而不是多次將設(shè)備置于活動(dòng)狀態(tài)嗎归斤?例如痊夭,幾十個(gè)應(yīng)用程序是否真的有必要在不同時(shí)間打開(kāi)收音機(jī)發(fā)送郵件?在一次喚醒收音機(jī)期間脏里,是否可以傳輸消息她我?

谷歌在耗電優(yōu)化這方面確實(shí)顯得有些無(wú)力,希望以后可以退出更好的工具和解決方案迫横,不然這方面的優(yōu)化優(yōu)先級(jí)還是很低番舆。付出和回報(bào)所差太大。

使用JobScheduler調(diào)度任務(wù)

使用懶惰法則

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)化关面,不過(guò)還是要理解一下原理的好坦袍。 然后可以對(duì)接受來(lái)的數(shù)據(jù)進(jìn)行分段或者分頁(yè)加載十厢,也可以優(yōu)化性能。

對(duì)于Bitmap捂齐,這個(gè)我們使用的就比較多了蛮放,很容易出現(xiàn)OOM的問(wèn)題,圖片內(nèi)存的問(wèn)題可以看一下我之前寫的這篇文章一張圖片占用多少內(nèi)存奠宜。

Bitmap的優(yōu)化套路很簡(jiǎn)單包颁,粗暴,就是讓壓縮压真。 三種壓縮方式: 1.對(duì)圖片質(zhì)量進(jìn)行壓縮 2.對(duì)圖片尺寸進(jìn)行壓縮 3.使用libjpeg.so庫(kù)進(jìn)行壓縮

對(duì)圖片質(zhì)量進(jìn)行壓縮

public static BitmapcompressImage(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 BitmapcompressImageFromBitmap(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庫(kù)進(jìn)行壓縮?可以參考這篇Android性能優(yōu)化系列之Bitmap圖片優(yōu)化: https://blog.csdn.net/u012124438/article/details/66087785)

ListView使用ViewHolder衩茸,分段证逻,分頁(yè)加載

壓縮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)建和銷毀所帶來(lái)的性能開(kāi)銷,同時(shí)線程池還能有效地控制線程池的最大并發(fā)數(shù)缀皱,避免大量的線程因互相搶占系統(tǒng)資源從而導(dǎo)致阻塞現(xiàn)象發(fā)生斗这。

《Android開(kāi)發(fā)藝術(shù)探索》對(duì)線程池的講解很詳細(xì),不熟悉線程池的可以去了解一下啤斗。

優(yōu)點(diǎn): 1涝影、減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開(kāi)銷。 2争占、如不使用線程池燃逻,有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存以及”過(guò)度切換”。

需要注意的是: 1臂痕、如果線程池中的數(shù)量為達(dá)到核心線程的數(shù)量伯襟,則直接會(huì)啟動(dòng)一個(gè)核心線程來(lái)執(zhí)行任務(wù)。 ? ? ?2握童、如果線程池中的數(shù)量已經(jīng)達(dá)到或超過(guò)核心線程的數(shù)量姆怪,則任務(wù)會(huì)被插入到任務(wù)隊(duì)列中標(biāo)等待執(zhí)行。 ? ? ?3、如果(2)中的任務(wù)無(wú)法插入到任務(wù)隊(duì)列中稽揭,由于任務(wù)隊(duì)列已滿俺附,這時(shí)候如果線程數(shù)量未達(dá)到線程池規(guī)定最大值,則會(huì)啟動(dòng)一個(gè)非核心線程來(lái)執(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)(這里說(shuō)的是特定情景) 如果您不需要訪問(wèn)對(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)語(yǔ)法 增強(qiáng)for循環(huán)(for-each)可用于實(shí)現(xiàn)Iterable接口和數(shù)組的集合哈肖。對(duì)于集合,分配一個(gè)迭代器來(lái)對(duì)hasNext()和進(jìn)行接口調(diào)用next()念秧。使用一個(gè)ArrayList淤井,手寫計(jì)數(shù)循環(huán)快約3倍,但對(duì)于其他集合,增強(qiáng)的for循環(huán)語(yǔ)法將完全等效于顯式迭代器用法币狠。

5游两、避免使用浮點(diǎn)數(shù) 根據(jù)經(jīng)驗(yàn),浮點(diǎn)數(shù)比Android設(shè)備上的整數(shù)慢約2倍

博文原自 :https://juejin.cn/post/6844903641032163336

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末漩绵,一起剝皮案震驚了整個(gè)濱河市贱案,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渐行,老刑警劉巖轰坊,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異祟印,居然都是意外死亡肴沫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蕴忆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颤芬,“玉大人,你說(shuō)我怎么就攤上這事套鹅≌掘穑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵卓鹿,是天一觀的道長(zhǎng)菱魔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吟孙,這世上最難降的妖魔是什么澜倦? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮杰妓,結(jié)果婚禮上藻治,老公的妹妹穿的比我還像新娘。我一直安慰自己巷挥,他們只是感情好桩卵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著倍宾,像睡著了一般雏节。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上高职,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天矾屯,我揣著相機(jī)與錄音,去河邊找鬼初厚。 笑死件蚕,一個(gè)胖子當(dāng)著我的面吹牛孙技,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播排作,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牵啦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了妄痪?” 一聲冷哼從身側(cè)響起哈雏,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衫生,沒(méi)想到半個(gè)月后裳瘪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罪针,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年彭羹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泪酱。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡派殷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墓阀,到底是詐尸還是另有隱情毡惜,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布斯撮,位于F島的核電站经伙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏勿锅。R本人自食惡果不足惜帕膜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粱甫。 院中可真熱鬧,春花似錦作瞄、人聲如沸茶宵。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乌庶。三九已至,卻和暖如春契耿,著一層夾襖步出監(jiān)牢的瞬間瞒大,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工搪桂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留透敌,地道東北人盯滚。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像酗电,于是被迫代替她去往敵國(guó)和親魄藕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 性能優(yōu)化系列閱讀 Android性能優(yōu)化 性能優(yōu)化 - 消除卡頓 性能優(yōu)化- 內(nèi)存優(yōu)化 性能分析工具 - Trac...
    JackChen1024閱讀 1,312評(píng)論 1 20
  • 基礎(chǔ)知識(shí) – 四大組件(生命周期撵术,使用場(chǎng)景背率,如何啟動(dòng))java基礎(chǔ) – 數(shù)據(jù)結(jié)構(gòu),線程嫩与,mvc框架通信 – 網(wǎng)絡(luò)連...
    跑步的小男孩閱讀 233評(píng)論 0 2
  • ??使用過(guò)Android系統(tǒng)手機(jī)的同學(xué)都知道寝姿,Android手機(jī)越用越卡,這個(gè)卡主要體現(xiàn)在手機(jī)系統(tǒng)越用越卡划滋,打開(kāi)A...
    alexlee1987閱讀 1,049評(píng)論 1 14
  • 前言 成為一名優(yōu)秀的Android開(kāi)發(fā)饵筑,需要一份完備的知識(shí)體系,在這里古毛,讓我們一起成長(zhǎng)為自己所想的那樣~翻翩。 本篇是...
    zhx喜籽閱讀 851評(píng)論 0 4
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭稻薇,有人歡樂(lè)有人憂愁嫂冻,有人驚喜有人失落,有的覺(jué)得收獲滿滿有...
    陌忘宇閱讀 8,535評(píng)論 28 53