Android面試準(zhǔn)備(中高級(jí))

Android

Activity生命周期

這里寫圖片描述

onStart()與onResume()有什么區(qū)別拷沸?

onStart()是activity界面被顯示出來的時(shí)候執(zhí)行的丑孩,但不能與它交互;
onResume()是當(dāng)該activity與用戶能進(jìn)行交互時(shí)被執(zhí)行,用戶可以獲得activity的焦點(diǎn),能夠與用戶交互。

Activity啟動(dòng)流程

startActivity最終都會(huì)調(diào)用startActivityForResult薄翅,通過ActivityManagerProxy調(diào)用system_server進(jìn)程中ActivityManagerService的startActvity方法,如果需要啟動(dòng)的Activity所在進(jìn)程未啟動(dòng)虑省,則調(diào)用Zygote孵化應(yīng)用進(jìn)程匿刮,進(jìn)程創(chuàng)建后會(huì)調(diào)用應(yīng)用的ActivityThread的main方法,main方法調(diào)用attach方法將應(yīng)用進(jìn)程綁定到ActivityManagerService(保存應(yīng)用的ApplicationThread的代理對象)并開啟loop循環(huán)接收消息探颈。ActivityManagerService通過ApplicationThread的代理發(fā)送Message通知啟動(dòng)Activity熟丸,ActivityThread內(nèi)部Handler處理handleLaunchActivity,依次調(diào)用performLaunchActivity伪节,handleResumeActivity(即activity的onCreate光羞,onStart,onResume)怀大。
深入理解Activity啟動(dòng)流程

Android類加載器

Android平臺(tái)上虛擬機(jī)運(yùn)行的是Dex字節(jié)碼,一種對class文件優(yōu)化的產(chǎn)物,傳統(tǒng)Class文件是一個(gè)Java源碼文件會(huì)生成一個(gè).class文件纱兑,而Android是把所有Class文件進(jìn)行合并,優(yōu)化化借,然后生成一個(gè)最終的class.dex,目的是把不同class文件重復(fù)的東西只需保留一份,如果我們的Android應(yīng)用不進(jìn)行分dex處理,最后一個(gè)應(yīng)用的apk只會(huì)有一個(gè)dex文件潜慎。
Android中常用的有兩種類加載器,DexClassLoader和PathClassLoader,它們都繼承于BaseDexClassLoader铐炫。區(qū)別在于調(diào)用父類構(gòu)造器時(shí)垒手,DexClassLoader多傳了一個(gè)optimizedDirectory參數(shù),這個(gè)目錄必須是內(nèi)部存儲(chǔ)路徑倒信,用來緩存系統(tǒng)創(chuàng)建的Dex文件科贬。而PathClassLoader該參數(shù)為null北专,只能加載內(nèi)部存儲(chǔ)目錄的Dex文件啦粹。所以我們可以用DexClassLoader去加載外部的apk。

Android消息機(jī)制

  1. 應(yīng)用啟動(dòng)是從ActivityThread的main開始的宝恶,先是執(zhí)行了Looper.prepare()乘综,該方法先是new了一個(gè)Looper對象憎账,在私有的構(gòu)造方法中又創(chuàng)建了MessageQueue作為此Looper對象的成員變量,Looper對象通過ThreadLocal綁定MainThread中卡辰;
  2. 當(dāng)我們創(chuàng)建Handler子類對象時(shí)鼠哥,在構(gòu)造方法中通過ThreadLocal獲取綁定的Looper對象,并獲取此Looper對象的成員變量MessageQueue作為該Handler對象的成員變量看政;
  3. 在子線程中調(diào)用上一步創(chuàng)建的Handler子類對象的sendMesage(msg)方法時(shí),在該方法中將msg的target屬性設(shè)置為自己本身抄罕,同時(shí)調(diào)用成員變量MessageQueue對象的enqueueMessag()方法將msg放入MessageQueue中允蚣;
  4. 主線程創(chuàng)建好之后,會(huì)執(zhí)行Looper.loop()方法呆贿,該方法中獲取與線程綁定的Looper對象嚷兔,繼而獲取該Looper對象的成員變量MessageQueue對象,并開啟一個(gè)會(huì)阻塞(不占用資源)的死循環(huán)做入,只要MessageQueue中有msg冒晰,就會(huì)獲取該msg,并執(zhí)行msg.target.dispatchMessage(msg)方法(msg.target即上一步引用的handler對象)竟块,此方法中調(diào)用了我們第二步創(chuàng)建handler子類對象時(shí)覆寫的handleMessage()方法壶运,之后將該msg對象存入回收池;

Looper.loop()為什么不會(huì)阻塞主線程

Android是基于事件驅(qū)動(dòng)的浪秘,即所有Activity的生命周期都是通過Handler事件驅(qū)動(dòng)的蒋情。loop方法中會(huì)調(diào)用MessageQueue的next方法獲取下一個(gè)message,當(dāng)沒有消息時(shí)耸携,基于Linux pipe/epoll機(jī)制會(huì)阻塞在loop的queue.next()中的nativePollOnce()方法里棵癣,并不會(huì)消耗CPU。

IdleHandler (閑時(shí)機(jī)制)

IdleHandler是一個(gè)回調(diào)接口夺衍,可以通過MessageQueue的addIdleHandler添加實(shí)現(xiàn)類狈谊。當(dāng)MessageQueue中的任務(wù)暫時(shí)處理完了(沒有新任務(wù)或者下一個(gè)任務(wù)延時(shí)在之后),這個(gè)時(shí)候會(huì)回調(diào)這個(gè)接口,返回false河劝,那么就會(huì)移除它壁榕,返回true就會(huì)在下次message處理完了的時(shí)候繼續(xù)回調(diào)。

同步屏障機(jī)制(sync barrier)

同步屏障可以通過MessageQueue.postSyncBarrier函數(shù)來設(shè)置丧裁。該方法發(fā)送了一個(gè)沒有target的Message到Queue中护桦,在next方法中獲取消息時(shí),如果發(fā)現(xiàn)沒有target的Message煎娇,則在一定的時(shí)間內(nèi)跳過同步消息二庵,優(yōu)先執(zhí)行異步消息。再換句話說缓呛,同步屏障為Handler消息機(jī)制增加了一種簡單的優(yōu)先級(jí)機(jī)制催享,異步消息的優(yōu)先級(jí)要高于同步消息。在創(chuàng)建Handler時(shí)有一個(gè)async參數(shù)哟绊,傳true表示此handler發(fā)送的時(shí)異步消息因妙。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保證UI繪制優(yōu)先執(zhí)行票髓。

View的繪制原理

View的繪制從ActivityThread類中Handler的處理RESUME_ACTIVITY事件開始攀涵,在執(zhí)行performResumeActivity之后,創(chuàng)建Window以及DecorView并調(diào)用WindowManager的addView方法添加到屏幕上洽沟,addView又調(diào)用ViewRootImpl的setView方法以故,最終執(zhí)行performTraversals方法,依次執(zhí)行performMeasure裆操,performLayout怒详,performDraw。也就是view繪制的三大過程踪区。
measure過程測量view的視圖大小昆烁,最終需要調(diào)用setMeasuredDimension方法設(shè)置測量的結(jié)果,如果是ViewGroup需要調(diào)用measureChildren或者measureChild方法進(jìn)而計(jì)算自己的大小缎岗。
layout過程是擺放view的過程静尼,View不需要實(shí)現(xiàn),通常由ViewGroup實(shí)現(xiàn)传泊,在實(shí)現(xiàn)onLayout時(shí)可以通過getMeasuredWidth等方法獲取measure過程測量的結(jié)果進(jìn)行擺放茅郎。
draw過程先是繪制背景,其次調(diào)用onDraw()方法繪制view的內(nèi)容或渤,再然后調(diào)用dispatchDraw()調(diào)用子view的draw方法系冗,最后繪制滾動(dòng)條。ViewGroup默認(rèn)不會(huì)執(zhí)行onDraw方法薪鹦,如果復(fù)寫了onDraw(Canvas)方法掌敬,需要調(diào)用 setWillNotDraw(false);清楚不需要繪制的標(biāo)記惯豆。
Android視圖繪制流程完全解析,帶你一步步深入了解View(二)

什么是MeasureSpec

MeasureSpec代表一個(gè)32位int值奔害,高兩位代表SpecMode(測量模式)楷兽,低30位代表SpecSize(具體大小)华临。
SpecMode有三類:

  • UNSPECIFIED 表示父容器不對View有任何限制芯杀,一般用于系統(tǒng)內(nèi)部,表示一種測量狀態(tài)雅潭;
  • EXACTLY 父容器已經(jīng)檢測出view所需的精確大小揭厚,這時(shí)候view的最終大小SpecSize所指定的值,相當(dāng)于match_parent或指定具體數(shù)值扶供。
  • AT_MOST 父容器指定一個(gè)可用大小即SpecSize筛圆,view的大小不能大于這個(gè)值,具體多大要看view的具體實(shí)現(xiàn)椿浓,相當(dāng)于wrap_content太援。

getWidth()方法和getMeasureWidth()區(qū)別呢?

首先getMeasureWidth()方法在measure()過程結(jié)束后就可以獲取到了扳碍,而getWidth()方法要在layout()過程結(jié)束后才能獲取到提岔。另外,getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進(jìn)行設(shè)置的笋敞,而getWidth()方法中的值則是通過視圖右邊的坐標(biāo)減去左邊的坐標(biāo)計(jì)算出來的唧垦。

事件分發(fā)機(jī)制

圖解 Android 事件分發(fā)機(jī)制

requestLayout,invalidate液样,postInvalidate區(qū)別與聯(lián)系

相同點(diǎn):三個(gè)方法都有刷新界面的效果。
不同點(diǎn):invalidate和postInvalidate只會(huì)調(diào)用onDraw()方法巧还;requestLayout則會(huì)重新調(diào)用onMeasure鞭莽、onLayout、onDraw麸祷。

調(diào)用了invalidate方法后澎怒,會(huì)為該View添加一個(gè)標(biāo)記位,同時(shí)不斷向父容器請求刷新阶牍,父容器通過計(jì)算得出自身需要重繪的區(qū)域喷面,直到傳遞到ViewRootImpl中,最終觸發(fā)performTraversals方法走孽,進(jìn)行開始View樹重繪流程(只繪制需要重繪的視圖)惧辈。
調(diào)用requestLayout方法,會(huì)標(biāo)記當(dāng)前View及父容器磕瓷,同時(shí)逐層向上提交盒齿,直到ViewRootImpl處理該事件念逞,ViewRootImpl會(huì)調(diào)用三大流程,從measure開始边翁,對于每一個(gè)含有標(biāo)記位的view及其子View都會(huì)進(jìn)行測量onMeasure翎承、布局onLayout、繪制onDraw符匾。
Android View 深度分析requestLayout叨咖、invalidate與postInvalidate

Binder機(jī)制,共享內(nèi)存實(shí)現(xiàn)原理

為什么使用Binder啊胶?

v2-30dce36be4e6617596b5fab96ef904c6_hd.jpg

概念
進(jìn)程隔離
進(jìn)程空間劃分:用戶空間(User Space)/內(nèi)核空間(Kernel Space)
系統(tǒng)調(diào)用:用戶態(tài)與內(nèi)核態(tài)

原理
跨進(jìn)程通信是需要內(nèi)核空間做支持的甸各。傳統(tǒng)的 IPC 機(jī)制如管道、Socket 都是內(nèi)核的一部分创淡,因此通過內(nèi)核支持來實(shí)現(xiàn)進(jìn)程間通信自然是沒問題的痴晦。但是 Binder 并不是 Linux 系統(tǒng)內(nèi)核的一部分,那怎么辦呢琳彩?這就得益于 Linux 的動(dòng)態(tài)內(nèi)核可加載模塊(Loadable Kernel Module誊酌,LKM)的機(jī)制;模塊是具有獨(dú)立功能的程序露乏,它可以被單獨(dú)編譯碧浊,但是不能獨(dú)立運(yùn)行。它在運(yùn)行時(shí)被鏈接到內(nèi)核作為內(nèi)核的一部分運(yùn)行瘟仿。這樣箱锐,Android 系統(tǒng)就可以通過動(dòng)態(tài)添加一個(gè)內(nèi)核模塊運(yùn)行在內(nèi)核空間,用戶進(jìn)程之間通過這個(gè)內(nèi)核模塊作為橋梁來實(shí)現(xiàn)通信劳较。

在 Android 系統(tǒng)中驹止,這個(gè)運(yùn)行在內(nèi)核空間,負(fù)責(zé)各個(gè)用戶進(jìn)程通過 Binder 實(shí)現(xiàn)通信的內(nèi)核模塊就叫 Binder 驅(qū)動(dòng)(Binder Dirver)观蜗。

那么在 Android 系統(tǒng)中用戶進(jìn)程之間是如何通過這個(gè)內(nèi)核模塊(Binder 驅(qū)動(dòng))來實(shí)現(xiàn)通信的呢臊恋?難道是和前面說的傳統(tǒng) IPC 機(jī)制一樣,先將數(shù)據(jù)從發(fā)送方進(jìn)程拷貝到內(nèi)核緩存區(qū)墓捻,然后再將數(shù)據(jù)從內(nèi)核緩存區(qū)拷貝到接收方進(jìn)程抖仅,通過兩次拷貝來實(shí)現(xiàn)嗎?顯然不是砖第,否則也不會(huì)有開篇所說的 Binder 在性能方面的優(yōu)勢了撤卢。

這就不得不通道 Linux 下的另一個(gè)概念:內(nèi)存映射

Binder IPC 機(jī)制中涉及到的內(nèi)存映射通過 mmap() 來實(shí)現(xiàn)梧兼,mmap() 是操作系統(tǒng)中一種內(nèi)存映射的方法放吩。內(nèi)存映射簡單的講就是將用戶空間的一塊內(nèi)存區(qū)域映射到內(nèi)核空間。映射關(guān)系建立后羽杰,用戶對這塊內(nèi)存區(qū)域的修改可以直接反應(yīng)到內(nèi)核空間屎慢;反之內(nèi)核空間對這段區(qū)域的修改也能直接反應(yīng)到用戶空間瞭稼。
一次完整的 Binder IPC 通信過程通常是這樣:

  1. 首先 Binder 驅(qū)動(dòng)在內(nèi)核空間創(chuàng)建一個(gè)數(shù)據(jù)接收緩存區(qū);
  2. 接著在內(nèi)核空間開辟一塊內(nèi)核緩存區(qū)腻惠,建立內(nèi)核緩存區(qū)和內(nèi)核中數(shù)據(jù)接收緩存區(qū)之間的映射關(guān)系环肘,以及內(nèi)核中數(shù)據(jù)接收緩存區(qū)和接收進(jìn)程用戶空間地址的映射關(guān)系;
  3. 發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用 copyfromuser() 將數(shù)據(jù) copy 到內(nèi)核中的內(nèi)核緩存區(qū)集灌,由于內(nèi)核緩存區(qū)和接收進(jìn)程的用戶空間存在內(nèi)存映射悔雹,因此也就相當(dāng)于把數(shù)據(jù)發(fā)送到了接收進(jìn)程的用戶空間,這樣便完成了一次進(jìn)程間的通信欣喧。

Binder通訊模型
Binder是基于C/S架構(gòu)的腌零,其中定義了4個(gè)角色:Client、Server唆阿、Binder驅(qū)動(dòng)和ServiceManager益涧。

  • Binder驅(qū)動(dòng):類似網(wǎng)絡(luò)通信中的路由器,負(fù)責(zé)將Client的請求轉(zhuǎn)發(fā)到具體的Server中執(zhí)行驯鳖,并將Server返回的數(shù)據(jù)傳回給Client闲询。
  • ServiceManager:類似網(wǎng)絡(luò)通信中的DNS服務(wù)器,負(fù)責(zé)將Client請求的Binder描述符轉(zhuǎn)化為具體的Server地址浅辙,以便Binder驅(qū)動(dòng)能夠轉(zhuǎn)發(fā)給具體的Server扭弧。Server如需提供Binder服務(wù),需要向ServiceManager注冊记舆。
    具體的通訊過程
  1. Server向ServiceManager注冊鸽捻。Server通過Binder驅(qū)動(dòng)向ServiceManager注冊,聲明可以對外提供服務(wù)泽腮。ServiceManager中會(huì)保留一份映射表御蒲。
  2. Client向ServiceManager請求Server的Binder引用。Client想要請求Server的數(shù)據(jù)時(shí)诊赊,需要先通過Binder驅(qū)動(dòng)向ServiceManager請求Server的Binder引用(代理對象)厚满。
  3. 向具體的Server發(fā)送請求。Client拿到這個(gè)Binder代理對象后豪筝,就可以通過Binder驅(qū)動(dòng)和Server進(jìn)行通信了。
  4. Server返回結(jié)果摘能。Server響應(yīng)請求后续崖,需要再次通過Binder驅(qū)動(dòng)將結(jié)果返回給Client。

ServiceManager是一個(gè)單獨(dú)的進(jìn)程团搞,那么Server與ServiceManager通訊是靠什么呢严望?
當(dāng)Android系統(tǒng)啟動(dòng)后,會(huì)創(chuàng)建一個(gè)名稱為servicemanager的進(jìn)程逻恐,這個(gè)進(jìn)程通過一個(gè)約定的命令BINDERSETCONTEXT_MGR向Binder驅(qū)動(dòng)注冊像吻,申請成為為ServiceManager峻黍,Binder驅(qū)動(dòng)會(huì)自動(dòng)為ServiceManager創(chuàng)建一個(gè)Binder實(shí)體。并且這個(gè)Binder實(shí)體的引用在所有的Client中都為0拨匆,也就說各個(gè)Client通過這個(gè)0號(hào)引用就可以和ServiceManager進(jìn)行通信姆涩。Server通過0號(hào)引用向ServiceManager進(jìn)行注冊,Client通過0號(hào)引用就可以獲取到要通信的Server的Binder引用惭每。
寫給 Android 應(yīng)用工程師的 Binder 原理剖析
一篇文章了解相見恨晚的 Android Binder 進(jìn)程間通訊機(jī)制

序列化的方式

Serializable是Java提供的一個(gè)序列化接口骨饿,是一個(gè)空接口,用于標(biāo)示對象是否可以支持序列化台腥,通過ObjectOutputStrean及ObjectInputStream實(shí)現(xiàn)序列化和反序列化的過程宏赘。注意可以為需要序列化的對象設(shè)置一個(gè)serialVersionUID,在反序列化的時(shí)候系統(tǒng)會(huì)檢測文件中的serialVersionUID是否與當(dāng)前類的值一致黎侈,如果不一致則說明類發(fā)生了修改察署,反序列化失敗。因此對于可能會(huì)修改的類最好指定serialVersionUID的值峻汉。
Parcelable是Android特有的一個(gè)實(shí)現(xiàn)序列化的接口贴汪,在Parcel內(nèi)部包裝了可序列化的數(shù)據(jù),可以在Binder中自由傳輸俱济。序列化的功能由writeToParcel方法來完成嘶是,最終通過Parcel的一系列write方法完成。反序列化功能由CREAOR來完成蛛碌,其內(nèi)部標(biāo)明了如何創(chuàng)建序列化對象和數(shù)組聂喇,并通過Parcel的一系列read方法來完成反序列化的過程。

Fragment的懶加載實(shí)現(xiàn)

Fragment可見狀態(tài)改變時(shí)會(huì)被調(diào)用setUserVisibleHint()方法蔚携,可以通過復(fù)寫該方法實(shí)現(xiàn)Fragment的懶加載希太,但需要注意該方法可能在onVIewCreated之前調(diào)用,需要確保界面已經(jīng)初始化完成的情況下再去加載數(shù)據(jù)酝蜒,避免空指針誊辉。
Fragment的懶加載

RecyclerView與ListView(緩存原理,區(qū)別聯(lián)系亡脑,優(yōu)缺點(diǎn))

緩存區(qū)別:

  1. 層級(jí)不同:
    ListView有兩級(jí)緩存堕澄,在屏幕與非屏幕內(nèi)。
    RecyclerView比ListView多兩級(jí)緩存霉咨,支持多個(gè)離屏ItemView緩存(匹配pos獲取目標(biāo)位置的緩存蛙紫,如果匹配則無需再次bindView),支持開發(fā)者自定義緩存處理邏輯途戒,支持所有RecyclerView共用同一個(gè)RecyclerViewPool(緩存池)坑傅。
  2. 緩存不同:
    ListView緩存View。
    RecyclerView緩存RecyclerView.ViewHolder喷斋,抽象可理解為:
    View + ViewHolder(避免每次createView時(shí)調(diào)用findViewById) + flag(標(biāo)識(shí)狀態(tài))唁毒;

優(yōu)點(diǎn)
RecylerView提供了局部刷新的接口蒜茴,通過局部刷新,就能避免調(diào)用許多無用的bindView浆西。
RecyclerView的擴(kuò)展性更強(qiáng)大(LayoutManager粉私、ItemDecoration等)。

Android兩種虛擬機(jī)區(qū)別與聯(lián)系

Android中的Dalvik虛擬機(jī)相較于Java虛擬機(jī)針對手機(jī)的特點(diǎn)做了很多優(yōu)化室谚。
Dalvik基于寄存器毡鉴,而JVM基于棧。在基于寄存器的虛擬機(jī)里秒赤,可以更為有效的減少冗余指令的分發(fā)和減少內(nèi)存的讀寫訪問猪瞬。
Dalvik經(jīng)過優(yōu)化,允許在有限的內(nèi)存中同時(shí)運(yùn)行多個(gè)虛擬機(jī)的實(shí)例入篮,并且每一個(gè) Dalvik應(yīng)用作為一個(gè)獨(dú)立的Linux進(jìn)程執(zhí)行陈瘦。
java虛擬機(jī)運(yùn)行的是java字節(jié)碼。(java類會(huì)被編譯成一個(gè)或多個(gè)字節(jié)碼.class文件潮售,打包到.jar文件中痊项,java虛擬機(jī)從相應(yīng)的.class文件和.jar文件中獲取相應(yīng)的字節(jié)碼)
Dalvik運(yùn)行的是自定義的.dex字節(jié)碼格式。(java類被編譯成.class文件后酥诽,會(huì)通過一個(gè)dx工具將所有的.class文件轉(zhuǎn)換成一個(gè).dex文件鞍泉,然后dalvik虛擬機(jī)會(huì)從其中讀取指令和數(shù)據(jù))
Android開發(fā)之淺談java虛擬機(jī)和Dalvik虛擬機(jī)的區(qū)別

adb常用命令行

查看當(dāng)前連接的設(shè)備:adb devices
安裝應(yīng)用:adb install -r <apk_path> -r表示覆蓋安裝
卸載apk:adb uninstall <packagename>

ADB 用法大全

apk打包流程

  1. aapt工具打包資源文件,生成R.java文件
  2. aidl工具處理AIDL文件肮帐,生成對應(yīng)的.java文件
  3. javac工具編譯Java文件咖驮,生成對應(yīng)的.class文件
  4. 把.class文件轉(zhuǎn)化成Davik VM支持的.dex文件
  5. apkbuilder工具打包生成未簽名的.apk文件
  6. jarsigner對未簽名.apk文件進(jìn)行簽名
  7. zipalign工具對簽名后的.apk文件進(jìn)行對齊處理

Android應(yīng)用程序(APK)的編譯打包過程

apk安裝流程

  1. 復(fù)制APK到/data/app目錄下,解壓并掃描安裝包训枢。
  2. 資源管理器解析APK里的資源文件托修。
  3. 解析AndroidManifest文件,并在/data/data/目錄下創(chuàng)建對應(yīng)的應(yīng)用數(shù)據(jù)目錄恒界。
  4. 然后對dex文件進(jìn)行優(yōu)化睦刃,并保存在dalvik-cache目錄下。
  5. 將AndroidManifest文件解析出的四大組件信息注冊到PackageManagerService中十酣。
  6. 安裝完成后涩拙,發(fā)送廣播。

apk瘦身

APK主要由以下幾部分組成:

  • META-INF/ :包含了簽名文件CERT.SF耸采、CERT.RSA兴泥,以及 manifest 文件MANIFEST.MF。
  • assets/ : 存放資源文件洋幻,這些資源不會(huì)被編譯成二進(jìn)制郁轻。
  • lib/ :包含了一些引用的第三方庫翅娶。
  • resources.arsc :包含res/values/中所有資源文留,例如strings,styles好唯,以及其他未被包含在resources.arsc中的資源路徑信息,例如layout 文件燥翅、圖片等骑篙。
  • res/ :包含res中沒有被存放到resources.arsc的資源。
  • classes.dex :經(jīng)過dx編譯能被android虛擬機(jī)理解的Java源碼文件森书。
  • AndroidManifest.xml :清單文件

其中占據(jù)較大內(nèi)存的是res資源靶端、lib、class.dex凛膏,因此我們可以從下面的幾個(gè)方面下手:

  1. 代碼方面可以通過代碼混淆杨名,這個(gè)一般都會(huì)去做。平時(shí)也可以刪除一些沒有使用類猖毫。
  2. 去除無用資源台谍。使用lint工具來檢測沒有使用到的資源,或者在gradle中配置shrinkResources來刪除包括庫中所有的無用的資源吁断,需要配合proguard壓縮代碼使用趁蕊。這里需要注意項(xiàng)目中是否存在使用getIdentifier方式獲取資源,這種方式類似反射lint及shrinkResources無法檢測情況仔役。如果存在這種方式掷伙,則需要配置一個(gè)keep.xml來記錄使用反射獲取的資源。壓縮代碼和資源
  3. 去除無用國際化支持又兵。對于一些第三庫來說(如support)任柜,因?yàn)閲H化的問題,它們可能會(huì)支持了幾十種語言寒波,但我們的應(yīng)用可能只需要支持幾種語言乘盼,可以通過配置resConfigs提出不要的語言支持。
  4. 不同尺寸的圖片支持俄烁。通常情況下只需要一套xxhpi的圖片就可以支持大部分分辨率的要求了绸栅,因此,我們只需要保留一套圖片页屠。
  5. 圖片壓縮粹胯。 png壓縮或者使用webP圖片,完美支持需要Android版本4.2.1+
  6. 使用矢量圖形辰企。簡單的圖標(biāo)可以使用矢量圖片风纠。

HTTP緩存機(jī)制

圖片來自上述鏈接

緩存的響應(yīng)頭:


20171103144205821.png

Cache-control:標(biāo)明緩存的最大存活時(shí)常;
Date:服務(wù)器告訴客戶端牢贸,該資源的發(fā)送時(shí)間竹观;
Expires:表示過期時(shí)間(該字段是1.0的東西,當(dāng)cache-control和該字段同時(shí)存在的條件下,cache-control的優(yōu)先級(jí)更高)臭增;
Last-Modified:服務(wù)器告訴客戶端懂酱,資源的最后修改時(shí)間;
還有一個(gè)字段誊抛,這個(gè)圖沒給出列牺,就是E-Tag:當(dāng)前資源在服務(wù)器的唯一標(biāo)識(shí),可用于判斷資源的內(nèi)容是否被修改了拗窃。
除以上響應(yīng)頭字段以外瞎领,還需了解兩個(gè)相關(guān)的Request請求頭:If-Modified-since、If-none-Match随夸。這兩個(gè)字段是和Last-Modified九默、E-Tag配合使用的。大致流程如下:
服務(wù)器收到請求時(shí)宾毒,會(huì)在200 OK中回送該資源的Last-Modified和ETag頭(服務(wù)器支持緩存的情況下才會(huì)有這兩個(gè)頭哦)荤西,客戶端將該資源保存在cache中,并記錄這兩個(gè)屬性伍俘。當(dāng)客戶端需要發(fā)送相同的請求時(shí)邪锌,根據(jù)Date + Cache-control來判斷是否緩存過期,如果過期了癌瘾,會(huì)在請求中攜帶If-Modified-Since和If-None-Match兩個(gè)頭觅丰。兩個(gè)頭的值分別是響應(yīng)中Last-Modified和ETag頭的值。服務(wù)器通過這兩個(gè)頭判斷本地資源未發(fā)生變化妨退,客戶端不需要重新下載妇萄,返回304響應(yīng)。

組件化

  • 在gradle.properties聲明一個(gè)變量用于控制是否是調(diào)試模式咬荷,并在dependencies中根據(jù)是否是調(diào)試模式依賴必要組件冠句。
  • 通過resourcePrefix規(guī)范module中資源的命名前綴。
  • 組件間通過ARouter完成界面跳轉(zhuǎn)和功能調(diào)用幸乒。

MVP

三方庫

okhttp原理

OkHttpClient通過newCall可以將一個(gè)Request構(gòu)建成一個(gè)Call懦底,Call表示準(zhǔn)備被執(zhí)行的請求。Call調(diào)用executed或enqueue會(huì)調(diào)用Dispatcher對應(yīng)的方法在當(dāng)前線程或者一步開始執(zhí)行請求罕扎,經(jīng)過RealInterceptorChain獲得最終結(jié)果聚唐,RealInterceptorChain是一個(gè)攔截器鏈,其中依次包含以下攔截器:

  • 自定義的攔截器
  • retryAndFollowUpInterceptor 請求失敗重試
  • BridgeInterceptor 為請求添加請求頭腔召,為響應(yīng)添加響應(yīng)頭
  • CacheInterceptor 緩存get請求
  • ConnectInterceptor 連接相關(guān)的攔截器杆查,分配一個(gè)Connection和HttpCodec為最終的請求做準(zhǔn)備
  • CallServerInterceptor 該攔截器就是利用HttpCodec完成最終請求的發(fā)送

okhttp源碼解析

Retrofit的實(shí)現(xiàn)與原理

Retrofit采用動(dòng)態(tài)代理,創(chuàng)建聲明service接口的實(shí)現(xiàn)對象臀蛛。當(dāng)我們調(diào)用service的方法時(shí)候會(huì)執(zhí)行InvocationHandler的invoke方法亲桦。在這方法中:首先崖蜜,通過method把它轉(zhuǎn)換成ServiceMethod纳猪,該類是對聲明方法的解析桃笙,可以進(jìn)一步將設(shè)定參數(shù)變成Request ;然后搏明,通過serviceMethod, args獲取到okHttpCall 對象闪檬,實(shí)際調(diào)用okhttp的網(wǎng)絡(luò)請求方法就在該類中星著,并且會(huì)使用serviceMethod中的responseConverter對ResponseBody轉(zhuǎn)化;最后虚循,再把okHttpCall進(jìn)一步封裝成聲明的返回對象(默認(rèn)是ExecutorCallbackCall,將原本call的回調(diào)轉(zhuǎn)發(fā)至UI線程)横缔。

Retrofit2使用詳解及從源碼中解析原理
Retrofit2 完全解析 探索與okhttp之間的關(guān)系

ARouter原理

可能是最詳細(xì)的ARouter源碼分析

RxLifecycle原理

在Activity中茎刚,定義一個(gè)Observable(Subject)撤逢,在不同的生命周期發(fā)射不同的事件蚊荣;
通過compose操作符(內(nèi)部實(shí)際上還是依賴takeUntil操作符)互例,定義了上游數(shù)據(jù)媳叨,當(dāng)其接收到Subject的特定事件時(shí)肩杈,取消訂閱;
Subject的特定事件并非是ActivityEvent艘儒,而是簡單的boolean界睁,它已經(jīng)內(nèi)部通過combineLast操作符進(jìn)行了對應(yīng)的轉(zhuǎn)化翻斟。

RxJava

Java

類的加載機(jī)制

程序在啟動(dòng)的時(shí)候逾礁,并不會(huì)一次性加載程序所要用的所有class文件嘹履,而是根據(jù)程序的需要砾嫉,通過Java的類加載機(jī)制(ClassLoader)來動(dòng)態(tài)加載某個(gè)class文件到內(nèi)存當(dāng)中的焕刮,從而只有class文件被載入到了內(nèi)存之后配并,才能被其它c(diǎn)lass所引用荐绝。所以ClassLoader就是用來動(dòng)態(tài)加載class文件到內(nèi)存當(dāng)中用的低滩。
類從被加載到虛擬機(jī)內(nèi)存中開始恕沫,到卸載出內(nèi)存為止婶溯,它的整個(gè)生命周期包括:加載(Loading)迄委、驗(yàn)證(Verification)踪栋、準(zhǔn)備(Preparation)硫狞、解析(Resolution)、初始化(Initialization)倘核、使用(Using)和卸載(Unloading)7個(gè)階段紧唱。其中準(zhǔn)備漏益、驗(yàn)證、解析3個(gè)部分統(tǒng)稱為連接(Linking)稠屠。

  • 加載:查找和導(dǎo)入Class文件权埠;
  • 鏈接:把類的二進(jìn)制數(shù)據(jù)合并到JRE中攘蔽;
    (a) 驗(yàn)證:檢查載入Class文件數(shù)據(jù)的正確性满俗;
    (b) 準(zhǔn)備:給類的靜態(tài)變量分配存儲(chǔ)空間唆垃;
    (c) 解析:將符號(hào)引用轉(zhuǎn)成直接引用辕万;
  • 初始化:對類的靜態(tài)變量渐尿,靜態(tài)代碼塊執(zhí)行初始化操作

什么時(shí)候發(fā)生類初始化

  1. 遇到new砖茸、getstatic渔彰、putstatic或invokestatic這4條字節(jié)碼指令時(shí)恍涂,如果類沒有進(jìn)行過初始化再沧,則需要先觸發(fā)其初始化炒瘸。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實(shí)例化對象的時(shí)候顷扩,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾隘截、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候婶芭,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候犀农。
  2. 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時(shí)候呵哨,如果類沒有進(jìn)行過初始化孟害,則需要先觸發(fā)其初始化纹坐。
  3. 當(dāng)初始化一個(gè)類的時(shí)候耘子,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化谷誓,則需要先觸發(fā)其父類的初始化捍歪。
  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類)恩商,虛擬機(jī)會(huì)先初始化這個(gè)主類怠堪。
  5. 當(dāng)使用JDK 1.7的動(dòng)態(tài)語言支持時(shí)粟矿,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例左后的解析結(jié)果REF_getStatic陌粹、REF_putStatic掏秩、REF_invokeStatic的方法句柄哗讥,并且這個(gè)方法句柄鎖對應(yīng)的類沒有進(jìn)行過初始化時(shí)。

雙親委派模型

Java中存在3種類加載器:
(1) Bootstrap ClassLoader : 將存放于<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的派桩,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別铆惑,如 rt.jar 名字不符合的類庫即使放在lib目錄中也不會(huì)被加載)類庫加載到虛擬機(jī)內(nèi)存中员魏。啟動(dòng)類加載器無法被Java程序直接引用 撕阎。
(2) Extension ClassLoader : 將<JAVA_HOME>\lib\ext目錄下的虏束,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫加載镇匀。開發(fā)者可以直接使用擴(kuò)展類加載器坑律。
(3) Application ClassLoader : 負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫,開發(fā)者可直接使用冀值。
每個(gè)ClassLoader實(shí)例都有一個(gè)父類加載器的引用(不是繼承關(guān)系列疗,是一個(gè)包含的關(guān)系)抵栈,虛擬機(jī)內(nèi)置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器古劲,但是可以用做其他ClassLoader實(shí)例的父類加載器产艾。
當(dāng)一個(gè)ClassLoader 實(shí)例需要加載某個(gè)類時(shí)闷堡,它會(huì)試圖在親自搜索這個(gè)類之前先把這個(gè)任務(wù)委托給它的父類加載器,這個(gè)過程是由上而下依次檢查的踱阿,首先由頂層的類加載器Bootstrap ClassLoader進(jìn)行加載软舌,如果沒有加載到葫隙,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader加載恋脚,如果也沒有找到糟描,則轉(zhuǎn)交給AppClassLoader進(jìn)行加載船响,還是沒有的話见间,則交給委托的發(fā)起者菱蔬,由它到指定的文件系統(tǒng)或者網(wǎng)絡(luò)等URL中進(jìn)行加載類拴泌。還沒有找到的話蚪腐,則會(huì)拋出CLassNotFoundException異常回季。否則將這個(gè)類生成一個(gè)類的定義茧跋,并將它加載到內(nèi)存中诅病,最后返回這個(gè)類在內(nèi)存中的Class實(shí)例對象贤笆。

為什么使用雙親委托模型

JVM在判斷兩個(gè)class是否相同時(shí)芥永,不僅要判斷兩個(gè)類名是否相同埋涧,還要判斷是否是同一個(gè)類加載器加載的劲弦。

  1. 避免重復(fù)加載邑跪,父類已經(jīng)加載了画畅,則子CLassLoader沒有必要再次加載明棍。
  2. 考慮安全因素摊腋,假設(shè)自定義一個(gè)String類兴蒸,除非改變JDK中CLassLoader的搜索類的默認(rèn)算法橙凳,否則用戶自定義的CLassLoader如法加載一個(gè)自己寫的String類,因?yàn)镾tring類在啟動(dòng)時(shí)就被引導(dǎo)類加載器Bootstrap CLassLoader加載了坚踩。

HashMap原理瞬铸,Hash沖突

在JDK1.6,JDK1.7中拦宣,HashMap采用數(shù)組+鏈表實(shí)現(xiàn)鸵隧,即使用鏈表處理沖突掰派,同一hash值的鏈表都存儲(chǔ)在一個(gè)鏈表里系洛。但是當(dāng)位于一個(gè)鏈表中的元素較多描扯,即hash值相等的元素較多時(shí),通過key值依次查找的效率較低恩够。而JDK1.8中蜂桶,HashMap采用位數(shù)組+鏈表+紅黑樹實(shí)現(xiàn),當(dāng)鏈表長度超過閾值(8)時(shí)疆股,將鏈表轉(zhuǎn)換為紅黑樹旬痹,這樣大大減少了查找時(shí)間羡忘。
當(dāng)鏈表數(shù)組的容量超過初始容量*加載因子(默認(rèn)0.75)時(shí)卷雕,再散列將鏈表數(shù)組擴(kuò)大2倍滨嘱,把原鏈表數(shù)組的搬移到新的數(shù)組中太雨。為什么需要使用加載因子吩翻?為什么需要擴(kuò)容呢狭瞎?因?yàn)槿绻畛浔群艽笮芏Вf明利用的空間很多,如果一直不進(jìn)行擴(kuò)容的話亿扁,鏈表就會(huì)越來越長,這樣查找的效率很低牍陌,擴(kuò)容之后,將原來鏈表數(shù)組的每一個(gè)鏈表分成奇偶兩個(gè)子鏈表分別掛在新鏈表數(shù)組的散列位置契讲,這樣就減少了每個(gè)鏈表的長度滑频,增加查找效率捡偏。
HashMap是非線程安全的,HashTable峡迷、ConcurrentHashMap是線程安全的银伟。
HashMap的鍵和值都允許有null存在你虹,而HashTable彤避、ConcurrentHashMap則都不行傅物。
因?yàn)榫€程安全、哈希效率的問題琉预,HashMap效率比HashTable董饰、ConcurrentHashMap的都要高。
HashTable里使用的是synchronized關(guān)鍵字圆米,這其實(shí)是對對象加鎖尖阔,鎖住的都是對象整體,當(dāng)Hashtable的大小增加到一定的時(shí)候榨咐,性能會(huì)急劇下降介却,因?yàn)榈鷷r(shí)需要被鎖定很長的時(shí)間。
ConcurrentHashMap引入了分割(Segment)块茁,可以理解為把一個(gè)大的Map拆分成N個(gè)小的HashTable齿坷,在put方法中,會(huì)根據(jù)hash(paramK.hashCode())來決定具體存放進(jìn)哪個(gè)Segment数焊,如果查看Segment的put操作永淌,我們會(huì)發(fā)現(xiàn)內(nèi)部使用的同步機(jī)制是基于lock操作的,這樣就可以對Map的一部分(Segment)進(jìn)行上鎖佩耳,這樣影響的只是將要放入同一個(gè)Segment的元素的put操作遂蛀,保證同步的時(shí)候,鎖住的不是整個(gè)Map(HashTable就是這么做的)干厚,相對于HashTable提高了多線程環(huán)境下的性能李滴,因此HashTable已經(jīng)被淘汰了。

Java中HashMap底層實(shí)現(xiàn)原理(JDK1.8)源碼分析

什么是Fail-Fast機(jī)制

Fail-Fast是Java集合的一種錯(cuò)誤檢測機(jī)制蛮瞄。當(dāng)遍歷集合的同時(shí)修改集合或者多個(gè)線程對集合進(jìn)行結(jié)構(gòu)上的改變的操作時(shí)所坯,有可能會(huì)產(chǎn)生fail-fast機(jī)制,記住是有可能挂捅,而不是一定芹助。其實(shí)就是拋出ConcurrentModificationException 異常。
集合的迭代器在調(diào)用next()闲先、remove()方法時(shí)都會(huì)調(diào)用checkForComodification()方法状土,該方法主要就是檢測modCount == expectedModCount ? 若不等則拋出ConcurrentModificationException 異常,從而產(chǎn)生fail-fast機(jī)制伺糠。modCount是在每次改變集合數(shù)量時(shí)會(huì)改變的值蒙谓。

Java提高篇(三四)-----fail-fast機(jī)制

Java泛型

Java泛型詳解

Java多線程中調(diào)用wait() 和 sleep()方法有什么不同?

Java程序中wait 和 sleep都會(huì)造成某種形式的暫停退盯,它們可以滿足不同的需要彼乌。wait()方法用于線程間通信泻肯,如果等待條件為真且其它線程被喚醒時(shí)它會(huì)釋放鎖渊迁,而 sleep()方法僅僅釋放CPU資源或者讓當(dāng)前線程停止執(zhí)行一段時(shí)間慰照,但不會(huì)釋放鎖。

volatile的作用和原理

Java代碼在編譯后會(huì)變成Java字節(jié)碼琉朽,字節(jié)碼被類加載器加載到JVM里毒租,JVM執(zhí)行字節(jié)碼,最終需要轉(zhuǎn)化為匯編指令在CPU上執(zhí)行箱叁。
volatile是輕量級(jí)的synchronized(volatile不會(huì)引起線程上下文的切換和調(diào)度)墅垮,它在多處理器開發(fā)中保證了共享變量的“可見性”「可見性的意思是當(dāng)一個(gè)線程修改一個(gè)共享變量時(shí)算色,另外一個(gè)線程能讀到這個(gè)修改的值。
由于內(nèi)存訪問速度遠(yuǎn)不及CPU處理速度螟够,為了提高處理速度灾梦,處理器不直接和內(nèi)存進(jìn)行通信,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存后在進(jìn)行操作妓笙,但操作完不知道何時(shí)會(huì)寫到內(nèi)存若河。普通共享變量被修改之后,什么時(shí)候被寫入主存是不確定的寞宫,當(dāng)其他線程去讀取時(shí)萧福,此時(shí)內(nèi)存中可能還是原來的舊值,因此無法保證可見性辈赋。如果對聲明了volatile的變量進(jìn)行寫操作鲫忍,JVM就會(huì)想處理器發(fā)送一條Lock前綴的指令,表示將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存钥屈。

一個(gè)int變量饲窿,用volatile修飾,多線程去操作++焕蹄,線程安全嗎逾雄?

不安全。volatile只能保證可見性腻脏,并不能保證原子性鸦泳。i++實(shí)際上會(huì)被分成多步完成:1)獲取i的值;2)執(zhí)行i+1永品;3)將結(jié)果賦值給i做鹰。volatile只能保證這3步不被重排序,多線程情況下鼎姐,可能兩個(gè)線程同時(shí)獲取i钾麸,執(zhí)行i+1更振,然后都賦值結(jié)果2,實(shí)際上應(yīng)該進(jìn)行兩次+1操作饭尝。

那如何才能保證i++線程安全肯腕?

可以使用java.util.concurrent.atomic包下的原子類,如AtomicInteger钥平。
其實(shí)現(xiàn)原理是采用CAS自旋操作更新值实撒。CAS即compare and swap的縮寫,中文翻譯成比較并交換涉瘾。CAS有3個(gè)操作數(shù)知态,內(nèi)存值V,舊的預(yù)期值A(chǔ)立叛,要修改的新值B负敏。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B秘蛇,否則什么都不做其做。自旋就是不斷嘗試CAS操作直到成功為止。

CAS實(shí)現(xiàn)原子操作會(huì)出現(xiàn)什么問題彤叉?

  • ABA問題庶柿。因?yàn)镃AS需要在操作之的時(shí)候,檢查值有沒有發(fā)生變化秽浇,如果沒有發(fā)生變化則更新浮庐,但是如果一個(gè)值原來是A,變成柬焕,有變成A审残,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒有發(fā)生變化,但實(shí)際上發(fā)生了變化斑举。ABA問題可以通過添加版本號(hào)來解決搅轿。Java 1.5開始,JDK的Atomic包里提供了一個(gè)類AtomicStampedReference來解決ABA問題富玷。
  • 循環(huán)時(shí)間長開銷大璧坟。pause指令優(yōu)化。
  • 只能保證一個(gè)共享變量的原子操作赎懦∪妇椋可以合并成一個(gè)對象進(jìn)行CAS操作。

synchronized

Java中每個(gè)對象都可以作為鎖:

  • 對于普通同步方法励两,鎖是當(dāng)前實(shí)例對象黎茎;
  • 對于靜態(tài)同步方法,鎖是當(dāng)前類的Class對象当悔;
  • 對于同步方法塊傅瞻,鎖是括號(hào)中配置的對象踢代;

當(dāng)一個(gè)線程試圖訪問同步代碼塊時(shí),它首先必須得到鎖嗅骄,退出或拋出異常時(shí)必須釋放鎖胳挎。synchronized用的鎖是存在Java對象頭里的MarkWord,通常是32bit或者64bit掸读,其中最后2bit表示鎖標(biāo)志位

java對象結(jié)構(gòu)

Java SE1.6為了減少獲得鎖和釋放鎖帶來的性能消耗串远,引入了偏向鎖和輕量級(jí)鎖宏多,在1.6中鎖一共有4種狀態(tài)儿惫,級(jí)別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)伸但、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài)肾请,這幾種狀態(tài)會(huì)隨著競爭情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí)更胖。

偏向鎖

偏向鎖獲取過程:

  1. 訪問Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1铛铁,鎖標(biāo)志位是否為01,確認(rèn)為可偏向狀態(tài)却妨。
  2. 如果為可偏向狀態(tài)饵逐,則測試線程ID是否指向當(dāng)前線程,如果是彪标,進(jìn)入步驟5倍权,否則進(jìn)入步驟3。
  3. 如果線程ID并未指向當(dāng)前線程捞烟,則通過CAS操作競爭鎖薄声。如果競爭成功,則將Mark Word中線程ID設(shè)置為當(dāng)前線程ID题画,然后執(zhí)行5默辨;如果競爭失敗,執(zhí)行4苍息。
  4. 如果CAS獲取偏向鎖失敗缩幸,則表示有競爭。當(dāng)?shù)竭_(dá)全局安全點(diǎn)(safepoint)時(shí)獲得偏向鎖的線程被掛起竞思,偏向鎖升級(jí)為輕量級(jí)鎖表谊,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。(撤銷偏向鎖的時(shí)候會(huì)導(dǎo)致stop the word)
  5. 執(zhí)行同步代碼衙四。

輕量級(jí)鎖

  1. 在代碼進(jìn)入同步塊的時(shí)候铃肯,如果同步對象鎖狀態(tài)為無鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”)传蹈,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間押逼,用于存儲(chǔ)鎖對象目前的Mark Word的拷貝步藕,官方稱之為 Displaced Mark Word。
  2. 拷貝對象頭中的Mark Word復(fù)制到鎖記錄中挑格;
  3. 拷貝成功后咙冗,虛擬機(jī)將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,并將Lock record里的owner指針指向object mark word漂彤。如果更新成功雾消,則執(zhí)行步驟4,否則執(zhí)行步驟5挫望。
  4. 如果這個(gè)更新動(dòng)作成功了立润,那么這個(gè)線程就擁有了該對象的鎖,并且對象Mark Word的鎖標(biāo)志位設(shè)置為“00”媳板,即表示此對象處于輕量級(jí)鎖定狀態(tài)桑腮。
  5. 如果這個(gè)更新操作失敗了,虛擬機(jī)首先會(huì)檢查對象的Mark Word是否指向當(dāng)前線程的棧幀蛉幸,如果是就說明當(dāng)前線程已經(jīng)擁有了這個(gè)對象的鎖破讨,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行。否則說明多個(gè)線程競爭鎖奕纫,輕量級(jí)鎖就要膨脹為重量級(jí)鎖提陶,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖(互斥量)的指針匹层,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)隙笆。 而當(dāng)前線程便嘗試使用自旋來獲取鎖,自旋就是為了不讓線程阻塞又固,而采用循環(huán)去獲取鎖的過程仲器。
    自旋
    如果持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài)仰冠,它們只需要等一等(自旋)乏冀,等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內(nèi)核的切換的消耗洋只。
    但是線程自旋是需要消耗cup的辆沦,說白了就是讓cup在做無用功,如果一直獲取不到鎖识虚,那線程也不能一直占用cup自旋做無用功肢扯,所以需要設(shè)定一個(gè)自旋等待的最大時(shí)間。
    如果持有鎖的線程執(zhí)行的時(shí)間超過自旋等待的最大時(shí)間扔沒有釋放鎖担锤,就會(huì)導(dǎo)致其它爭用鎖的線程在最大等待時(shí)間內(nèi)還是獲取不到鎖蔚晨,這時(shí)爭用線程會(huì)停止自旋進(jìn)入阻塞狀態(tài)。

線程池

好處:1)降低資源消耗;2)提高相應(yīng)速度铭腕;3)提高線程的可管理性银择。
線程池的實(shí)現(xiàn)原理:

  • 當(dāng)提交一個(gè)新任務(wù)到線程池時(shí),判斷核心線程池里的線程是否都在執(zhí)行累舷。如果不是浩考,則創(chuàng)建一個(gè)新的線程執(zhí)行任務(wù)。如果核心線程池的線程都在執(zhí)行任務(wù)被盈,則進(jìn)入下個(gè)流程析孽。
  • 判斷工作隊(duì)列是否已滿。如果未滿只怎,則將新提交的任務(wù)存儲(chǔ)在這個(gè)工作隊(duì)列里袜瞬。如果工作隊(duì)列滿了,則進(jìn)入下個(gè)流程尝盼。
  • 判斷線程池是否都處于工作狀態(tài)吞滞。如果沒有佑菩,則創(chuàng)建一個(gè)新的工作線程來執(zhí)行任務(wù)盾沫。如果滿了,則交給飽和策略來處理這個(gè)任務(wù)殿漠。

假如有n個(gè)網(wǎng)絡(luò)線程赴精,你需要當(dāng)n個(gè)網(wǎng)絡(luò)線程完成之后,再去做數(shù)據(jù)處理绞幌,你會(huì)怎么解決蕾哟?

這題考的其實(shí)是多線程同步的問題。這種情況可以可以使用thread.join()莲蜘;join方法會(huì)阻塞直到thread線程終止才返回谭确。更復(fù)雜一點(diǎn)的情況也可以使用CountDownLatch,CountDownLatch的構(gòu)造接收一個(gè)int參數(shù)作為計(jì)數(shù)器票渠,每次調(diào)用countDown方法計(jì)數(shù)器減一逐哈。做數(shù)據(jù)處理的線程調(diào)用await方法阻塞直到計(jì)數(shù)器為0時(shí)。

Java中interrupted 和 isInterruptedd方法的區(qū)別问顷?

interrupted() 和 isInterrupted()的主要區(qū)別是前者會(huì)將中斷狀態(tài)清除而后者不會(huì)昂秃。Java多線程的中斷機(jī)制是用內(nèi)部標(biāo)識(shí)來實(shí)現(xiàn)的,調(diào)用Thread.interrupt()來中斷一個(gè)線程就會(huì)設(shè)置中斷標(biāo)識(shí)為true杜窄。當(dāng)中斷線程調(diào)用靜態(tài)方法Thread.interrupted()來 檢查中斷狀態(tài)時(shí)肠骆,中斷狀態(tài)會(huì)被清零。而非靜態(tài)方法isInterrupted()用來查詢其它線程的中斷狀態(tài)且不會(huì)改變中斷狀態(tài)標(biāo)識(shí)塞耕。簡單的說就是任何拋 出InterruptedException異常的方法都會(huì)將中斷狀態(tài)清零蚀腿。無論如何,一個(gè)線程的中斷狀態(tài)有有可能被其它線程調(diào)用中斷來改變扫外。

懶漢式單例的同步問題

同步的懶加載雖然是線程安全的莉钙,但是導(dǎo)致性能開銷纱注。因此產(chǎn)生了雙重檢查鎖定。但雙重檢查鎖定存在隱藏的問題胆胰。instance = new Instance()實(shí)際上會(huì)分為三步操作:1)分配對象的內(nèi)存空間狞贱;2)初始化對象;3)設(shè)置instance指向剛分配的內(nèi)存地址蜀涨;由于指令重排序瞎嬉,2和3的順序并不確定。在多線程的情況下厚柳,第一個(gè)線程執(zhí)行了1氧枣,3,此時(shí)第二個(gè)線程判斷instance不為null别垮,但實(shí)際上操作2還沒有執(zhí)行便监,第二個(gè)線程就會(huì)獲得一個(gè)還未初始化的對象,直接使用就會(huì)造成空指針碳想。
解決方案是用volatile修飾instance烧董,在JDK 1.5加強(qiáng)了volatile的語意之后,用volatile修飾instance就阻止了2和3的重排序胧奔,進(jìn)而避免上述情況的發(fā)生逊移。
另一種方式則是使用靜態(tài)內(nèi)部類:

public class Singleton {
    private static class InstanceHolder {
        public static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return InstanceHolder.instance;
    }
}

其原理是利用類初始化時(shí)會(huì)加上初始化鎖確保類對象的唯一性。

什么是ThreadLocal

ThreadLocal即線程變量龙填,它為每個(gè)使用該變量的線程提供獨(dú)立的變量副本胳泉,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對應(yīng)的副本岩遗。從線程的角度看扇商,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思宿礁。ThreadLocal的實(shí)現(xiàn)是以ThreadLocal對象為鍵案铺。任意對象為值得存儲(chǔ)結(jié)構(gòu)。這個(gè)結(jié)構(gòu)被附帶在線程上窘拯,也就是說一個(gè)線程可以根據(jù)一個(gè)ThreadLocal對象查詢到綁定在這個(gè)線程上的一個(gè)值红且。

什么是數(shù)據(jù)競爭

數(shù)據(jù)競爭的定義:在一個(gè)線程寫一個(gè)變量,在另一個(gè)線程讀同一個(gè)變量涤姊,而且寫和讀沒有通過同步來排序暇番。

Java內(nèi)存模型(Java Memory Model JMM)

JM屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果思喊。
線程之間的共享變量存儲(chǔ)在主內(nèi)存中壁酬,每個(gè)線程都有一個(gè)私有的本地內(nèi)存,本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本。本地內(nèi)存是一個(gè)抽象概念舆乔,它涵蓋了緩存岳服、寫緩存區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化希俩。
在執(zhí)行程序時(shí)吊宋,為了提高性能,編譯器和處理器常常會(huì)對指令做重排序颜武。在多線程中重排序會(huì)對程序的執(zhí)行結(jié)果有影響璃搜。
JSR-133內(nèi)存模型采用happens-before的概念來闡述操作之間的內(nèi)存可見性。happens-before會(huì)限制重排序以滿足規(guī)則鳞上。
主要的happens-before規(guī)則有如下:

  • 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作这吻,happens-before于該線程中的任意后續(xù)操作。
  • 監(jiān)視器鎖規(guī)則:對一個(gè)鎖的解鎖篙议,happens-before與鎖隨后對這個(gè)鎖的加鎖唾糯。
  • volatile變量規(guī)則:對一個(gè)volatile域的寫,happens-before與任意后續(xù)對這個(gè)volatile域的讀鬼贱。
  • 傳遞性:如果A happens-before B移怯,且B happens-before C,那么A happens-before C吩愧。

Java內(nèi)存區(qū)域

  • 程序計(jì)數(shù)器:當(dāng)前線程鎖執(zhí)行的字節(jié)碼的行號(hào)指示器芋酌,用于線程切換恢復(fù),是線程私有的雁佳;
  • Java虛擬機(jī)棧(棧):虛擬機(jī)棧也是線程私有的。每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表同云、操作數(shù)棧糖权、動(dòng)態(tài)鏈接、方法出口等信息炸站。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程星澳,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。
  • 本地方法棧:與虛擬機(jī)棧類似旱易,服務(wù)于Native方法禁偎。
  • Java堆:堆是被所有線程共享的一塊內(nèi)存,用于存放對象實(shí)例阀坏。是垃圾收集器管理的主要區(qū)域如暖,也被稱作GC堆。
  • 方法區(qū):與Java堆一樣忌堂,是線程共享的內(nèi)存區(qū)域盒至,用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)常量枷遂、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)樱衷。
  • 運(yùn)行時(shí)常量池:是方法區(qū)的一部分,用于存放編譯器生成的各種字面量和符號(hào)引用酒唉。

判斷對象是否需要回收的方法

  • 引用計(jì)數(shù)算法矩桂。實(shí)現(xiàn)簡單,判定效率高痪伦,但不能解決循環(huán)引用問題耍鬓,同時(shí)計(jì)數(shù)器的增加和減少帶來額外開銷矾柜,JDK1.1以后廢棄了田藐。
  • 可達(dá)性分析算法/根搜索算法 狱杰。根搜索算法是通過一些“GC Roots”對象作為起點(diǎn)笑旺,從這些節(jié)點(diǎn)開始往下搜索泛粹,搜索通過的路徑成為引用鏈(Reference Chain)赔桌,當(dāng)一個(gè)對象沒有被GC Roots 的引用鏈連接的時(shí)候吐葵,說明這個(gè)對象是不可用的册踩。 Java中可作為“GC Root”的對象包括:虛擬機(jī)棧(本地變量表)中引用的對象证薇;方法區(qū)中類靜態(tài)屬性和常量引用的對象度苔。本地方法棧中引用的對象。

引用類型

  • 強(qiáng)引用:默認(rèn)的引用方式浑度,不會(huì)被垃圾回收寇窑,JVM寧愿拋出OutOfMemory錯(cuò)誤也不會(huì)回收這種對象。
  • 軟引用(SoftReference):如果一個(gè)對象只被軟引用指向箩张,只有內(nèi)存空間不足夠時(shí)甩骏,垃圾回收器才會(huì)回收它;
  • 弱引用(WeakReference):如果一個(gè)對象只被弱引用指向先慷,當(dāng)JVM進(jìn)行垃圾回收時(shí)饮笛,無論內(nèi)存是否充足,都會(huì)回收該對象论熙。
  • 虛引用(PhantomReference):虛引用和前面的軟引用福青、弱引用不同,它并不影響對象的生命周期脓诡。如果一個(gè)對象與虛引用關(guān)聯(lián)无午,則跟沒有引用與之關(guān)聯(lián)一樣,在任何時(shí)候都可能被垃圾回收器回收祝谚。虛引用通常和ReferenceQueue配合使用宪迟。
    ReferenceQueue
    作為一個(gè)Java對象,Reference對象除了具有保存引用的特殊性之外踊跟,也具有Java對象的一般性踩验。所以鸥诽,當(dāng)對象被回收之后,雖然這個(gè)Reference對象的get()方法返回null,但這個(gè)SoftReference對象已經(jīng)不再具有存在的價(jià)值箕憾,需要一個(gè)適當(dāng)?shù)那宄龣C(jī)制牡借,避免大量Reference對象帶來的內(nèi)存泄漏。
    在java.lang.ref包里還提供了ReferenceQueue袭异。我們創(chuàng)建Reference對象時(shí)使用兩個(gè)參數(shù)的構(gòu)造傳入ReferenceQueue钠龙,當(dāng)Reference所引用的對象被垃圾收集器回收的同時(shí),Reference對象被列入ReferenceQueue御铃。也就是說碴里,ReferenceQueue中保存的對象是Reference對象,而且是已經(jīng)失去了它所軟引用的對象的Reference對象上真。另外從ReferenceQueue這個(gè)名字也可以看出咬腋,它是一個(gè)隊(duì)列,當(dāng)我們調(diào)用它的poll()方法的時(shí)候睡互,如果這個(gè)隊(duì)列中不是空隊(duì)列根竿,那么將返回隊(duì)列前面的那個(gè)Reference對象。于是我們可以在適當(dāng)?shù)臅r(shí)候把這些失去所軟引用的對象的SoftReference對象清除掉就珠。

垃圾收集算法

  1. 標(biāo)記-清楚算法(Mark-Sweep)
    在標(biāo)記階段寇壳,確定所有要回收的對象,并做標(biāo)記妻怎。清除階段緊隨標(biāo)記階段壳炎,將標(biāo)記階段確定不可用的對象清除。標(biāo)記—清除算法是基礎(chǔ)的收集算法逼侦,有兩個(gè)不足:1)標(biāo)記和清除階段的效率不高匿辩;2)清除后回產(chǎn)生大量的不連續(xù)空間,這樣當(dāng)程序需要分配大內(nèi)存對象時(shí)偿洁,可能無法找到足夠的連續(xù)空間撒汉。
  2. 復(fù)制算法(Copying)
    復(fù)制算法是把內(nèi)存分成大小相等的兩塊,每次使用其中一塊涕滋,當(dāng)垃圾回收的時(shí)候,把存活的對象復(fù)制到另一塊上挠阁,然后把這塊內(nèi)存整個(gè)清理掉宾肺。復(fù)制算法實(shí)現(xiàn)簡單,運(yùn)行效率高侵俗,但是由于每次只能使用其中的一半锨用,造成內(nèi)存的利用率不高。現(xiàn)在的JVM 用復(fù)制方法收集新生代隘谣,由于新生代中大部分對象(98%)都是朝生夕死的增拥,所以會(huì)分成1塊大內(nèi)存Eden和兩塊小內(nèi)存Survivor(大概是8:1:1)啄巧,每次使用1塊大內(nèi)存和1塊小內(nèi)存,當(dāng)回收時(shí)將2塊內(nèi)存中存活的對象賦值到另一塊小內(nèi)存中掌栅,然后清理剩下的秩仆。
  3. 標(biāo)記—整理算法(Mark-Compact)
    標(biāo)記—整理算法和復(fù)制算法一樣,但是標(biāo)記—整理算法不是把存活對象復(fù)制到另一塊內(nèi)存猾封,而是把存活對象往內(nèi)存的一端移動(dòng)澄耍,然后直接回收邊界以外的內(nèi)存。標(biāo)記—整理算法提高了內(nèi)存的利用率晌缘,并且它適合在收集對象存活時(shí)間較長的老年代齐莲。
  4. 分代收集(Generational Collection)
    分代收集是根據(jù)對象的存活時(shí)間把內(nèi)存分為新生代和老年代,根據(jù)各代對象的存活特點(diǎn)磷箕,每個(gè)代采用不同的垃圾回收算法选酗。新生代采用復(fù)制算法,老年代采用標(biāo)記—整理算法岳枷。

內(nèi)存分配策略

  • 對象優(yōu)先在Eden分配芒填。
  • 大對象直接進(jìn)入老年代。 大對象是指需要大量連續(xù)內(nèi)存空間的Java對象嫩舟,最典型的就是那種很長的字符串以及數(shù)組氢烘。
  • 長期存活的對象進(jìn)入老年代。存活過一次新生代的GC家厌,Age+1播玖,當(dāng)達(dá)到一定程度(默認(rèn)15)進(jìn)入老年代。
  • 動(dòng)態(tài)對象年齡判定饭于。如果在Survivor空間中相同Age所有對象大小的總和大于Survivor空間一半蜀踏。那么Age大于等于該Age的對象就可以直接進(jìn)入老年代。
  • 空間分配擔(dān)保掰吕。 在發(fā)生新生代GC之前果覆,會(huì)檢查老年代的剩余空間是否大于新生代所有對象的總和。如果大于則是安全的殖熟,如果不大于有風(fēng)險(xiǎn)局待。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钳榨,一起剝皮案震驚了整個(gè)濱河市纽门,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饼齿,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缕溉,死亡現(xiàn)場離奇詭異,居然都是意外死亡伙菊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門敌土,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镜硕,“玉大人,你說我怎么就攤上這事返干⌒丝荩” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵矩欠,是天一觀的道長财剖。 經(jīng)常有香客問我,道長癌淮,這世上最難降的妖魔是什么躺坟? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮乳蓄,結(jié)果婚禮上咪橙,老公的妹妹穿的比我還像新娘。我一直安慰自己虚倒,他們只是感情好美侦,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魂奥,像睡著了一般菠剩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耻煤,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天具壮,我揣著相機(jī)與錄音,去河邊找鬼哈蝇。 笑死嘴办,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的买鸽。 我是一名探鬼主播眼五,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼批旺,長吁一口氣:“原來是場噩夢啊……” “哼汽煮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鞋囊,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤溜腐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后望众,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黍檩,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年棵里,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了殿怜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柱告,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葵袭,到底是詐尸還是另有隱情坡锡,我是刑警寧澤鹉勒,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站绵疲,受9級(jí)特大地震影響盔憨,放射性物質(zhì)發(fā)生泄漏郁岩。R本人自食惡果不足惜问慎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望穷劈。 院中可真熱鬧歇终,春花似錦追葡、人聲如沸辽俗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翰蠢。三九已至梁沧,卻和暖如春廷支,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藕甩。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工僵娃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留默怨,地道東北人先壕。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓垃僚,卻偏偏與公主長得像谆棺,于是被迫代替她去往敵國和親碍岔。 傳聞我的和親對象是個(gè)殘疾皇子蔼啦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

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

  • 面試必背 會(huì)舍棄、總結(jié)概括——根據(jù)我這些年面試和看面試題搜集過來的知識(shí)點(diǎn)匯總而來 建議根據(jù)我的寫的面試應(yīng)對思路中的...
    luoyangzk閱讀 6,756評論 6 173
  • 初聞涕淚滿衣裳一睁, 待得團(tuán)圓時(shí)候卖局。 定做逍遙物外仙砚偶, 初來苦憔悴。 待得天晴花已老单鹿, 定空埋仲锄、身外芳名儒喊。 初藕花發(fā)怀愧,...
    李波波閱讀 249評論 0 2
  • 屬性選擇器 E【foo】:選擇匹配E元素哈垢,且E元素定義了foo屬性 E【foo=“bar”】:選擇匹配E元素,且E...
    梨啊梨閱讀 433評論 0 0
  • 2013年8月6日洋腮,一支名為《十年》的宣傳片進(jìn)去大家的視線,沒有發(fā)布會(huì)滤灯,沒有記者窒百,他們就這樣出道了豫尽。篙梢。。8歲到18...
    凱LOVE妍閱讀 263評論 0 0