android

來源:http://www.lxweimin.com/p/cf5092fa2694

上次寫這篇文章的時候也差不多是一年前了,這一年我兜兜轉轉從android到java又回到android,校招面了很多大廠,阿里、京東、小米、頭條、知乎、騰訊、有贊,也收獲了幾個offer。感謝大家的關注,讓我在簡書上面也混到了一個簡書程序員優秀作者的稱號,所以為了回饋大家,一篇最完全的android面經誕生了。這是我集合了牛客網、百度、簡書等網站的幾十篇面經和我自己面試的經歷的合集,希望大家喜歡。(ps:里面當然會有紕漏,如果有問題歡迎大家留言或者加我QQ討論)

1.android事件分發機制,請詳細說下整個流程

image

2.android view繪制機制和加載過程,請詳細說下整個流程

  • 1.ViewRootImpl會調用performTraversals(),其內部會調用performMeasure()、performLayout、performDraw()。
  • 2.performMeasure()會調用最外層的ViewGroup的measure()-->onMeasure(),ViewGroup的onMeasure()是抽象方法,但其提供了measureChildren(),這之中會遍歷子View然后循環調用measureChild()這之中會用getChildMeasureSpec()+父View的MeasureSpec+子View的LayoutParam一起獲取本View的MeasureSpec,然后調用子View的measure()到View的onMeasure()-->setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默認返回measureSpec的測量數值,所以繼承View進行自定義的wrap_content需要重寫。
  • 3.performLayout()會調用最外層的ViewGroup的layout(l,t,r,b),本View在其中使用setFrame()設置本View的四個頂點位置。在onLayout(抽象方法)中確定子View的位置,如LinearLayout會遍歷子View,循環調用setChildFrame()-->子View.layout()。
  • 4.performDraw()會調用最外層ViewGroup的draw():其中會先后調用background.draw()(繪制背景)、onDraw()(繪制自己)、dispatchDraw()(繪制子View)、onDrawScrollBars()(繪制裝飾)。
  • 5.MeasureSpec由2位SpecMode(UNSPECIFIED、EXACTLY(對應精確值和match_parent)、AT_MOST(對應warp_content))和30位SpecSize組成一個int,DecorView的MeasureSpec由窗口大小和其LayoutParams決定,其他View由父View的MeasureSpec和本View的LayoutParams決定。ViewGroup中有getChildMeasureSpec()來獲取子View的MeasureSpec。
  • 6.三種方式獲取measure()后的寬高:
    • 1.Activity#onWindowFocusChange()中調用獲取
    • 2.view.post(Runnable)將獲取的代碼投遞到消息隊列的尾部。
    • 3.ViewTreeObservable.

3.android四大組件的加載過程,請詳細介紹下

4.Activity的啟動模式

  • 1.standard:默認標準模式,每啟動一個都會創建一個實例,
  • 2.singleTop:棧頂復用,如果在棧頂就調用onNewIntent復用,從onResume()開始
  • 3.singleTask:棧內復用,本棧內只要用該類型Activity就會將其頂部的activity出棧
  • 4.singleInstance:單例模式,除了3中特性,系統會單獨給該Activity創建一個棧,

5.A、B、C、D分別是四種Activity的啟動模式,那么A->B->C->D->A->B->C->D分別啟動,最后的activity棧是怎么樣的

  • 1.這個題目需要深入了解activity的啟動模式
  • 2.最后的答案是:兩個棧,前臺棧是只有D,后臺棧從底至上是A、B、C

6.Activity緩存方法

  • 1.配置改變導致Activity被殺死,橫屏變豎屏:在onStop之前會調用onSaveInstanceState()保存數據在重建Activity之后,會在onStart()之后調用onRestoreInstanceState(),并把保存下來的Bundle傳給onCreate()和它會默認重建Activity當前的視圖,我們可以在onCreate()中,回復自己的數據。
  • 2.內存不足殺掉Activity,優先級分別是:前臺可見,可見非前臺,后臺。

7.Service的生命周期,兩種啟動方法,有什么區別

  • 1.context.startService() ->onCreate()- >onStart()->Service running-->(如果調用context.stopService() )->onDestroy() ->Service shut down
    • 1.如果Service還沒有運行,則調用onCreate()然后調用onStart();
    • 2.如果Service已經運行,則只調用onStart(),所以一個Service的onStart方法可能會重復調用多次。
    • 3.調用stopService的時候直接onDestroy,
    • 4.如果是調用者自己直接退出而沒有調用stopService的話,Service會一直在后臺運行。該Service的調用者再啟動起來后可以通過stopService關閉Service。
  • 2.context.bindService()->onCreate()->onBind()->Service running-->onUnbind() -> onDestroy() ->Service stop
    • 1.onBind將返回給客戶端一個IBind接口實例,IBind允許客戶端回調服務的方法,比如得到Service運行的狀態或其他操作。
    • 2.這個時候會把調用者和Service綁定在一起,Context退出了,Service就會調用onUnbind->onDestroy相應退出。
    • 3.所以調用bindService的生命周期為:onCreate --> onBind(只一次,不可多次綁定) --> onUnbind --> onDestory。

8.怎么保證service不被殺死

  • 1.提升service優先級
  • 2.提升service進程優先級
  • 3.onDestroy方法里重啟service

9.靜態的Broadcast 和動態的有什么區別

  • 1.動態的比靜態的安全
  • 2.靜態在app啟動的時候就初始化了 動態使用代碼初始化
  • 3.靜態需要配置 動態不需要
  • 4.生存期,靜態廣播的生存期可以比動態廣播的長很多
  • 5.優先級動態廣播的優先級比靜態廣播高

10.Intent可以傳遞哪些數據類型

  • 1.Serializable
  • 2.charsequence: 主要用來傳遞String,char等
  • 3.parcelable
  • 4.Bundle

11.Json有什么優劣勢、解析的原理

  • 1.JSON的速度要遠遠快于XML
  • 2.JSON相對于XML來講,數據的體積小
  • 3.JSON對數據的描述性比XML較差
  • 4.解析的基本原理是:詞法分析

12.一個語言的編譯過程

  • 1.詞法分析:將一串文本按規則分割成最小的結構,關鍵字、標識符、運算符、界符和常量等。一般實現方法是自動機和正則表達式
  • 2.語法分析:將一系列單詞組合成語法樹。一般實現方法有自頂向下和自底向上
  • 3.語義分析:對結構上正確的源程序進行上下文有關性質的審查
  • 4.目標代碼生成
  • 5.代碼優化:優化生成的目標代碼,

13.動畫有哪幾類,各有什么特點

  • 1.動畫的基本原理:其實就是利用插值器和估值器,來計算出各個時刻View的屬性,然后通過改變View的屬性來,實現View的動畫效果。
  • 2.View動畫:只是影像變化,view的實際位置還在原來的地方。
  • 3.幀動畫是在xml中定義好一系列圖片之后,使用AnimationDrawable來播放的動畫。
  • 4.View的屬性動畫:
    • 1.插值器:作用是根據時間的流逝的百分比來計算屬性改變的百分比
    • 2.估值器:在1的基礎上由這個東西來計算出屬性到底變化了多少數值的類

14.Handler、Looper消息隊列模型,各部分的作用

  • 1.MessageQueue:讀取會自動刪除消息,單鏈表維護,在插入和刪除上有優勢。在其next()中會無限循環,不斷判斷是否有消息,有就返回這條消息并移除。
  • 2.Looper:Looper創建的時候會創建一個MessageQueue,調用loop()方法的時候消息循環開始,loop()也是一個死循環,會不斷調用messageQueue的next(),當有消息就處理,否則阻塞在messageQueue的next()中。當Looper的quit()被調用的時候會調用messageQueue的quit(),此時next()會返回null,然后loop()方法也跟著退出。
  • 3.Handler:在主線程構造一個Handler,然后在其他線程調用sendMessage(),此時主線程的MessageQueue中會插入一條message,然后被Looper使用。
  • 4.系統的主線程在ActivityThread的main()為入口開啟主線程,其中定義了內部類Activity.H定義了一系列消息類型,包含四大組件的啟動停止。
  • 5.MessageQueue和Looper是一對一關系,Handler和Looper是多對一

15.怎樣退出終止App

  • 1.自己設置一個Activity的棧,然后一個個finish()

16.Android IPC:Binder原理

  • 1.在Activity和Service進行通訊的時候,用到了Binder。
    • 1.當屬于同個進程我們可以繼承Binder然后在Activity中對Service進行操作
    • 2.當不屬于同個進程,那么要用到AIDL讓系統給我們創建一個Binder,然后在Activity中對遠端的Service進行操作。
  • 2.系統給我們生成的Binder:
    • 1.Stub類中有:接口方法的id,有該Binder的標識,有asInterface(IBinder)(讓我們在Activity中獲取實現了Binder的接口,接口的實現在Service里,同進程時候返回Stub否則返回Proxy),有onTransact()這個方法是在不同進程的時候讓Proxy在Activity進行遠端調用實現Activity操作Service
    • 2.Proxy類是代理,在Activity端,其中有:IBinder mRemote(這就是遠端的Binder),兩個接口的實現方法不過是代理最終還是要在遠端的onTransact()中進行實際操作。
  • 3.哪一端的Binder是副本,該端就可以被另一端進行操作,因為Binder本體在定義的時候可以操作本端的東西。所以可以在Activity端傳入本端的Binder,讓Service端對其進行操作稱為Listener,可以用RemoteCallbackList這個容器來裝Listener,防止Listener因為經歷過序列化而產生的問題。
  • 4.當Activity端向遠端進行調用的時候,當前線程會掛起,當方法處理完畢才會喚醒。
  • 5.如果一個AIDL就用一個Service太奢侈,所以可以使用Binder池的方式,建立一個AIDL其中的方法是返回IBinder,然后根據方法中傳入的參數返回具體的AIDL。
  • 6.IPC的方式有:Bundle(在Intent啟動的時候傳入,不過是一次性的),文件共享(對于SharedPreference是特例,因為其在內存中會有緩存),使用Messenger(其底層用的也是AIDL,同理要操作哪端,就在哪端定義Messenger),AIDL,ContentProvider(在本進程中繼承實現一個ContentProvider,在增刪改查方法中調用本進程的SQLite,在其他進程中查詢),Socket

17.描述一次跨進程通訊

  • 1.client、proxy、serviceManager、BinderDriver、impl、service
  • 2.client發起一個請求service信息的Binder請求到BinderDriver中,serviceManager發現BinderDiriver中有自己的請求 然后將clinet請求的service的數據返回給client這樣完成了一次Binder通訊
  • 3.clinet獲取的service信息就是該service的proxy,此時調用proxy的方法,proxy將請求發送到BinderDriver中,此時service的 Binder線程池循環發現有自己的請求,然后用impl就處理這個請求最后返回,這樣完成了第二次Binder通訊
    4.中間client可掛起,也可以不掛起,有一個關鍵字oneway可以解決這個

18.android重要術語解釋

  • 1.ActivityManagerServices,簡稱AMS,服務端對象,負責系統中所有Activity的生命周期
  • 2.ActivityThread,App的真正入口。當開啟App之后,會調用main()開始運行,開啟消息循環隊列,這就是傳說中的UI線程或者叫主線程。與ActivityManagerServices配合,一起完成Activity的管理工作
  • 3.ApplicationThread,用來實現ActivityManagerService與ActivityThread之間的交互。在ActivityManagerService需要管理相關Application中的Activity的生命周期時,通過ApplicationThread的代理對象與ActivityThread通訊。
  • 4.ApplicationThreadProxy,是ApplicationThread在服務器端的代理,負責和客戶端的ApplicationThread通訊。AMS就是通過該代理與ActivityThread進行通信的。
  • 5.Instrumentation,每一個應用程序只有一個Instrumentation對象,每個Activity內都有一個對該對象的引用。Instrumentation可以理解為應用進程的管家,ActivityThread要創建或暫停某個Activity時,都需要通過Instrumentation來進行具體的操作。
  • 6.ActivityStack,Activity在AMS的棧管理,用來記錄已經啟動的Activity的先后關系,狀態信息等。通過ActivityStack決定是否需要啟動新的進程。
  • 7.ActivityRecord,ActivityStack的管理對象,每個Activity在AMS對應一個ActivityRecord,來記錄Activity的狀態以及其他的管理信息。其實就是服務器端的Activity對象的映像。
  • 8.TaskRecord,AMS抽象出來的一個“任務”的概念,是記錄ActivityRecord的棧,一個“Task”包含若干個ActivityRecord。AMS用TaskRecord確保Activity啟動和退出的順序。如果你清楚Activity的4種launchMode,那么對這個概念應該不陌生。

19.理解Window和WindowManager

  • 1.Window用于顯示View和接收各種事件,Window有三種類型:應用Window(每個Activity對應一個Window)、子Window(不能單獨存在,附屬于特定Window)、系統window(Toast和狀態欄)
  • 2.Window分層級,應用Window在1-99、子Window在1000-1999、系統Window在2000-2999.WindowManager提供了增刪改View三個功能。
  • 3.Window是個抽象概念:每一個Window對應著一個View和ViewRootImpl,Window通過ViewRootImpl來和View建立聯系,View是Window存在的實體,只能通過WindowManager來訪問Window。
  • 4.WindowManager的實現是WindowManagerImpl其再委托給WindowManagerGlobal來對Window進行操作,其中有四個List分別儲存對應的View、ViewRootImpl、WindowManger.LayoutParams和正在被刪除的View
  • 5.Window的實體是存在于遠端的WindowMangerService中,所以增刪改Window在本端是修改上面的幾個List然后通過ViewRootImpl重繪View,通過WindowSession(每個應用一個)在遠端修改Window。
  • 6.Activity創建Window:Activity會在attach()中創建Window并設置其回調(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy類創建PhoneWindow實現的。然后通過Activity#setContentView()調用PhoneWindow的setContentView。

20.Bitmap的處理

  • 1.當使用ImageView的時候,可能圖片的像素大于ImageView,此時就可以通過BitmapFactory.Option來對圖片進行壓縮,inSampleSize表示縮小2^(inSampleSize-1)倍。
  • 2.BitMap的緩存:
    • 1.使用LruCache進行內存緩存。
    • 2.使用DiskLruCache進行硬盤緩存。
    • 3.實現一個ImageLoader的流程:同步異步加載、圖片壓縮、內存硬盤緩存、網絡拉取
      • 1.同步加載只創建一個線程然后按照順序進行圖片加載
      • 2.異步加載使用線程池,讓存在的加載任務都處于不同線程
      • 3.為了不開啟過多的異步任務,只在列表靜止的時候開啟圖片加載

21.如何實現一個網絡框架(參考Volley)

  • 1.緩存隊列,以url為key緩存內容可以參考Bitmap的處理方式,這里單獨開啟一個線程。
  • 2.網絡請求隊列,使用線程池進行請求。
  • 3.提供各種不同類型的返回值的解析如String,Json,圖片等等。

22.ClassLoader的基礎知識

  • 1.雙親委托:一個ClassLoader類負責加載這個類所涉及的所有類,在加載的時候會判斷該類是否已經被加載過,然后會遞歸去他父ClassLoader中找。
  • 2.可以動態加載Jar通過URLClassLoader
  • 3.ClassLoader 隔離問題 JVM識別一個類是由:ClassLoader id+PackageName+ClassName。
  • 4.加載不同Jar包中的公共類:
    • 1.讓父ClassLoader加載公共的Jar,子ClassLoader加載包含公共Jar的Jar,此時子ClassLoader在加載公共Jar的時候會先去父ClassLoader中找。(只適用Java)
    • 2.重寫加載包含公共Jar的Jar的ClassLoader,在loadClass中找到已經加載過公共Jar的ClassLoader,也就是把父ClassLoader替換掉。(只適用Java)
    • 3.在生成包含公共Jar的Jar時候把公共Jar去掉。

23.插件化框架描述:dynamicLoadApk為例子

  • 1.可以通過DexClassLoader來對apk中的dex包進行加載訪問
  • 2.如何加載資源是個很大的問題,因為宿主程序中并沒有apk中的資源,所以調用R資源會報錯,所以這里使用了Activity中的實現ContextImpl的getAssets()和getResources()再加上反射來實現。
  • 3.由于系統啟動Activity有很多初始化動作要做,而我們手動反射很難完成,所以可以采用接口機制,將Activity的大部分生命周期提取成接口,然后通過代理Activity去調用插件Activity的生命周期。同時如果像增加一個新生命周期方法的時候,只需要在接口中和代理中聲明一下就行。
  • 4.缺點:
    • 1.慎用this,因為在apk中使用this并不代表宿主中的activity,當然如果this只是表示自己的接口還是可以的。除此之外可以使用that代替this。
    • 2.不支持Service和靜態注冊的Broadcast
    • 3.不支持LaunchMode和Apk中Activity的隱式調用。

24.熱修復:Andfix為例子

  • 1.大致原理:apkpatch將兩個apk做一次對比,然后找出不同的部分。可以看到生成的apatch了文件,后綴改成zip再解壓開,里面有一個dex文件。通過jadx查看一下源碼,里面就是被修復的代碼所在的類文件,這些更改過的類都加上了一個_CF的后綴,并且變動的方法都被加上了一個叫@MethodReplace的annotation,通過clazz和method指定了需要替換的方法。然后客戶端sdk得到補丁文件后就會根據annotation來尋找需要替換的方法。最后由JNI層完成方法的替換。
  • 2.無法添加新類和新的字段、補丁文件很容易被反編譯、加固平臺可能會使熱補丁功能失效

25.線程同步的問題,常用的線程同步

  • 1.sycn:保證了原子性、可見性、有序性
  • 2.鎖:保證了原子性、可見性、有序性
    • 1.自旋鎖:可以使線程在沒有取得鎖的時候,不被掛起,而轉去執行一個空循環。
      • 1.優點:線程被掛起的幾率減少,線程執行的連貫性加強。用于對于鎖競爭不是很激烈,鎖占用時間很短的并發線程。
      • 2.缺點:過多浪費CPU時間,有一個線程連續兩次試圖獲得自旋鎖引起死鎖
    • 2.阻塞鎖:沒得到鎖的線程等待或者掛起,Sycn、Lock
    • 3.可重入鎖:一個線程可多次獲取該鎖,Sycn、Lock
    • 4.悲觀鎖:每次去拿數據的時候都認為別人會修改,所以會阻塞全部其他線程 Sycn、Lock
    • 5.樂觀鎖:每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。cas
    • 6.顯示鎖和內置鎖:顯示鎖用Lock來定義、內置鎖用synchronized。
    • 7.讀-寫鎖:為了提高性能,Java提供了讀
  • 3.volatile
    • 1.只能保證可見性,不能保證原子性
    • 2.自增操作有三步,此時多線程寫會出現問題
  • 4.cas
    • 1.操作:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B并返回true,否則什么都不做并返回false。
    • 2.解釋:本地副本為A,共享內存為V,線程A要把V修改成B。某個時刻線程A要把V修改成B,如果A和V不同那么就表示有其他線程在修改V,此時就表示修改失敗,否則表示沒有其他線程修改,那么把V改成B。
    • 3.局限:如果V被修改成V1然后又被改成V,此時cas識別不出變化,還是認為沒有其他線程在修改V,此時就會有問題
    • 4.局限解決:將V帶上版本。
  • 5.線程不安全到底是怎么回事:
    • 1.一個線程寫,多個線程讀的時候,會造成寫了一半就去讀
    • 2.多線程寫,會造成臟數據

26.Asynctask和線程池,GC相關(怎么判斷哪些內存該GC,GC算法)

  • 1.Asynctask:異步任務類,單線程線程池+Handler
  • 2.線程池:
    • 1.ThreadPoolExecutor:通過Executors可以構造單線程池、固定數目線程池、不固定數目線程池。
    • 2.ScheduledThreadPoolExecutor:可以延時調用線程或者延時重復調度線程。
  • 3.GC相關:重要
    • 1.搜索算法:
      • 1.引用計數
      • 2.圖搜索,可達性分析
    • 2.回收算法:
      • 1.標記清除復制:用于青年代
      • 2.標記整理:用于老年代
    • 3.堆分區:
      • 1.青年區eden 80%、survivor1 10%、survivor2 10%
      • 2.老年區
    • 4.虛擬機棧分區:
      • 1.局部變量表
      • 2.操作數棧
      • 3.動態鏈接
      • 4.方法返回地址
    • 5.GC Roots:
      • 1.虛擬機棧(棧楨中的本地變量表)中的引用的對象
      • 2.方法區中的類靜態屬性引用的對象
      • 3.方法區中的常量引用的對象
      • 4.本地方法棧中JNI的引用的對象

27.網絡

  • 1.ARP協議:在IP以太網中,當一個上層協議要發包時,有了該節點的IP地址,ARP就能提供該節點的MAC地址。
  • 2.HTTP HTTPS的區別:
    • 1.HTTPS使用TLS(SSL)進行加密
    • 2.HTTPS缺省工作在TCP協議443端口
    • 3.它的工作流程一般如以下方式:
      • 1.完成TCP三次同步握手
      • 2.客戶端驗證服務器數字證書,通過,進入步驟3
      • 3.DH算法協商對稱加密算法的密鑰、hash算法的密鑰
      • 4.SSL安全加密隧道協商完成
      • 5.網頁以加密的方式傳輸,用協商的對稱加密算法和密鑰加密,保證數據機密性;用協商的hash算法進行數據完整性保護,保證數據不被篡改
    • 3.http請求包結構,http返回碼的分類,400和500的區別
      • 1.包結構:
        • 1.請求:請求行、頭部、數據
        • 2.返回:狀態行、頭部、數據
      • 2.http返回碼分類:1到5分別是,消息、成功、重定向、客戶端錯誤、服務端錯誤
    • 4.Tcp
      • 1.可靠連接,三次握手,四次揮手
        • 1.三次握手:防止了服務器端的一直等待而浪費資源,例如只是兩次握手,如果s確認之后c就掉線了,那么s就會浪費資源
          • 1.syn-c = x,表示這消息是x序號
          • 2.ack-s = x + 1,表示syn-c這個消息接收成功。syn-s = y,表示這消息是y序號。
          • 3.ack-c = y + 1,表示syn-s這條消息接收成功
      • 2.四次揮手:TCP是全雙工模式
        • 1.fin-c = x , 表示現在需要關閉c到s了。ack-c = y,表示上一條s的消息已經接收完畢
        • 2.ack-s = x + 1,表示需要關閉的fin-c消息已經接收到了,同意關閉
        • 3.fin-s = y + 1,表示s已經準備好關閉了,就等c的最后一條命令
        • 4.ack-c = y + 1,表示c已經關閉,讓s也關閉
      • 3.滑動窗口,停止等待、后退N、選擇重傳
      • 4.擁塞控制,慢啟動、擁塞避免、加速遞減、快重傳快恢復

28.數據庫性能優化:索引和事務,需要找本專門的書大概了解一下

29.13.APK打包流程和其內容

  • 1.流程
    • 1.aapt生成R文件
      • 2.aidl生成java文件
      • 3.將全部java文件編譯成class文件
      • 4.將全部class文件和第三方包合并成dex文件
      • 5.將資源、so文件、dex文件整合成apk
      • 6.apk簽名
      • 7.apk字節對齊
  • 2.內容:so、dex、asset、資源文件

30.網絡劫持的類型原理:可以百度一下了解一下具體概念

  • 1.DNS劫持、欺騙、污染
  • 2.http劫持:重定向、注入js,http注入、報文擴展

31.java類加載過程:

  • 1.加載時機:創建實例、訪問靜態變量或方法、反射、加載子類之前
  • 2.驗證:驗證文件格式、元數據、字節碼、符號引用的正確性
  • 3.加載:根據全類名獲取文件字節流、將字節流轉化為靜態儲存結構放入方法區、生成class對象
  • 4.準備:在堆上為靜態變量劃分內存
  • 5.解析:將常量池中的符號引用轉換為直接引用
  • 6.初始化:初始化靜態變量
  • 7.書籍推薦:深入理解java虛擬機,博客推薦:Java/Android阿里面試JVM部分理解

32.retrofit的了解

  • 1.動態代理創建一個接口的代理類
  • 2.通過反射解析每個接口的注解、入參構造http請求
  • 3.獲取到返回的http請求,使用Adapter解析成需要的返回值。

33.bundle的數據結構,如何存儲

  • 1.鍵值對儲存
  • 2.傳遞的數據可以是boolean、byte、int、long、float、double、string等基本類型或它們對應的數組,也可以是對象或對象數組。
  • 3.當Bundle傳遞的是對象或對象數組時,必須實現Serializable 或Parcelable接口

34.listview內點擊buttom并移動的事件流完整攔截過程:

  • 1.點下按鈕的時候:
    • 1.產生了一個down事件,activity-->phoneWindow-->ViewGroup-->ListView-->botton,中間如果有重寫了攔截方法,則事件被該view攔截可能消耗。
    • 2.沒攔截,事件到達了button,這個過程中建立了一條事件傳遞的view鏈表
    • 3.到button的dispatch方法-->onTouch-->view是否可用-->Touch代理
  • 2.移動點擊按鈕的時候:
    • 1.產生move事件,listView中會對move事件做攔截
    • 2.此時listView會將該滑動事件消費掉
    • 3.后續的滑動事件都會被listView消費掉
  • 3.手指抬起來時候:前面建立了一個view鏈表,listView的父view在獲取事件的時候,會直接取鏈表中的listView讓其進行事件消耗。

35.service的意義:不需要界面,在后臺執行的程序

36.android的IPC通信方式,線程(進程間)通信機制有哪些

  • 1.ipc通信方式:binder、contentprovider、socket
  • 2.操作系統進程通訊方式:共享內存、socket、管道

37.操作系統進程和線程的區別

  • 1.簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
  • 2.線程的劃分尺度小于進程,使得多線程程序的并發性高。
  • 3.另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。
  • 4.多線程的意義在于一個應用程序中,有多個執行部分可以同時執行。有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配

38.HashMap的實現過程:Capacity就是buckets的數目,Load factor就是buckets填滿程度的最大比例。如果對迭代性能要求很高的話不要把capacity設置過大,也不要把load factor設置過小。

  • 1.簡單來說HashMap就是一個會自動擴容的數組鏈表
  • 2.put過程
    • 1.對key的hashCode()做hash,然后再計算index;
    • 2.如果沒碰撞直接放到bucket里;
    • 3.如果碰撞了,以鏈表的形式存在buckets后;
    • 4.如果碰撞導致鏈表過長(大于等于TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹;
    • 5.如果節點已經存在就替換old value(保證key的唯一性)
    • 6.如果bucket滿了(超過load factor*current capacity),就要resize。
  • 3.resize:當put時,如果發現目前的bucket占用程度已經超過了Load Factor所希望的比例,那么就會發生resize。在resize的過程,簡單的說就是把bucket擴充為2倍,之后重新計算index,把節點再放到新的bucket中
  • 4.get過程
    • 1.根據key的hash算出數組下表
    • 2.使用equals遍歷鏈表進行比較

39.mvc、mvp、mvvm:

  • 1.mvc:數據、View、Activity,View將操作反饋給Activity,Activitiy去獲取數據,數據通過觀察者模式刷新給View。循環依賴
    • 1.Activity重,很難單元測試
    • 2.View和Model耦合嚴重
  • 2.mvp:數據、View、Presenter,View將操作給Presenter,Presenter去獲取數據,數據獲取好了返回給Presenter,Presenter去刷新View。PV,PM雙向依賴
    • 1.接口爆炸
    • 2.Presenter很重
  • 3.mvvm:數據、View、ViewModel,View將操作給ViewModel,ViewModel去獲取數據,數據和界面綁定了,數據更新界面更新。
    • 1.viewModel的業務邏輯可以單獨拿來測試
    • 2.一個view 對應一個 viewModel 業務邏輯可以分離,不會出現全能類
    • 3.數據和界面綁定了,不用寫垃圾代碼,但是復用起來不舒服

40.java的線程如何實現

  • 1.Thread繼承
  • 2.Runnale
  • 3.Future
  • 4.線程池

41.ArrayList 如何刪除重復的元素或者指定的元素;

  • 1.刪除重復:Set
  • 2.刪除指定:迭代器

42.如何設計在 UDP 上層保證 UDP 的可靠性傳輸;

  • 1.簡單來講,要使用UDP來構建可靠的面向連接的數據傳輸,就要實現類似于TCP協議的超時重傳,有序接受,應答確認,滑動窗口流量控制等機制,等于說要在傳輸層的上一層(或者直接在應用層)實現TCP協議的可靠數據傳輸機制。
  • 2.比如使用UDP數據包+序列號,UDP數據包+時間戳等方法,在服務器端進行應答確認機制,這樣就會保證不可靠的UDP協議進行可靠的數據傳輸。
  • 3.基于udp的可靠傳輸協議有:RUDP、RTP、UDT

43.Java 中內部類為什么可以訪問外部類

  • 1.因為內部類創建的時候,需要外部類的對象,在內部類對象創建的時候會把外部類的引用傳遞進去

44.設計移動端的聯系人存儲與查詢的功能,要求快速搜索聯系人,可以用到哪些數據結構?數據庫索引,平衡二叉樹(B樹、紅黑樹)

45.紅黑樹特點

  • 1.root節點和葉子節點是黑色
  • 2.紅色節點后必須為黑色節點
  • 3.從root到葉子每條路徑的黑節點數量相同

46.linux異步和同步I/o:

  • 1.同步:對于client,client一直等待,但是client不掛起:主線程調用
  • 2.異步:對于client,client發起請求,service好了再回調client:其他線程調用,調用完成之后進行回調
  • 3.阻塞:對于service,在準備io的時候會將service端掛起,直至準備完成然后喚醒service:bio
  • 3.非阻塞:對于service,在準備io的時候不會將service端掛起,而是service一直去輪詢判斷io是否準備完成,準備完成了就進行操作:nio、linux的select、poll、epoll
  • 4.多路復用io:非阻塞io的一種優化,java nio,用一個線程去輪詢多個 io端口是否可用,如果一個可用就通知對應的io請求,這使用一個線程輪詢可以大大增強性能。
    • 1.我可以采用 多線程+ 阻塞IO 達到類似的效果,但是由于在多線程 + 阻塞IO 中,每個socket對應一個線程,這樣會造成很大的資源占用。
    • 2.而在多路復用IO中,輪詢每個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。
  • 5.異步io:aio,用戶線程完全不感知io的進行,所有操作都交給內核,io完成之后內核通知用戶線程。
    • 1.這種io才是異步的,2、3、4都是同步io,因為內核進行數據拷貝的過程都會讓用戶線程阻塞。
    • 2.異步IO是需要操作系統的底層支持,也就是內核支持,Java 7中,提供了Asynchronous IO

47.ConcurrentHashMap內部實現,HashTable的實現被廢棄的原因:

  • 1.HashTable容器在競爭激烈的并發環境下表現出效率低下的原因,是因為所有訪問HashTable的線程都必須競爭同一把鎖,那假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分數據,那么當多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高并發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。
  • 2.ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲鍵值對數據。一個ConcurrentHashMap里包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment里包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素,每個Segment守護者一個HashEntry數組里的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。

48.HandlerThread是什么

  • 1.MessageQueue + Looper + Handler

49.IntentService是什么

  • 1.含有HandlerThread的Service,可以多次startService()來多次在子線程中進行 onHandlerIntent()的調用。

50.class和dex

  • 1.dvm執行的是dex格式文件,jvm執行的是class文件,android程序編譯完之后生產class文件。然后dex工具會把class文件處理成dex文件,然后把資源文件和.dex文件等打包成apk文件。
  • 2.dvm是基于寄存器的虛擬機,而jvm執行是基于虛擬棧的虛擬機。寄存器存取速度比棧快的多,dvm可以根據硬件實現最大的優化,比較適合移動設備。
  • 3.class文件存在很多的冗余信息,dex工具會去除冗余信息,并把所有的class文件整合到dex文件中。減少了I/O操作,提高了類的查找速度

51.內存泄漏

  • 1.其他線程持有一個Listener,Listener操作activity。那么在線程么有完畢的時候,activity關閉了,原本是要被回收的但是,不能被回收。
  • 2.例如Handler導致的內存泄漏,Handler就相當于Listener。
  • 3.在activity關閉的時候注意停止線程,或者將Listener的注冊取消
  • 3.使用弱引用,這樣即使Listener持有了activity,在GC的時候還是會被回收
  • 4.工具:LeakCanary

52.過度繪制、卡頓優化:

  • 1.過度繪制:
    • 1.移除Window默認的Background:getWidow.setBackgroundDrawable(null);
    • 2.移除XML布局文件中非必需的Background
    • 3.減少布局嵌套(扁平化的一個體現,減少View數的深度,也就減少了View樹的遍歷時間,渲染的時候,前后期的工作,總是按View樹結點來)
    • 4.在引入布局文件里面,最外層可以用merge替代LinearLayout,RelativeLayout,這樣把子UI元素直接銜接在include位置
    • 5.工具:HierarchyViewer 查看視圖層級
  • 2.卡頓優化:16ms數據更新

53.apk瘦身:

  • 1.classes.dex:通過代碼混淆,刪掉不必要的jar包和代碼實現該文件的優化
  • 2.資源文件:通過Lint工具掃描代碼中沒有使用到的靜態資源
  • 3.圖片資源:使用tinypng和webP,下面詳細介紹圖片資源優化的方案,矢量圖
  • 4.SO文件將不用的去掉,目前主流app一般只放一個arm的so包

54.ANR的形成,各個組件上出現ARN的時間限制是多少

  • 1.只要是主線程耗時的操作就會ARN 如io
  • 2.broadcast超時時間為10秒 按鍵無響應的超時時間為5秒 前臺service無響應的超時時間為20秒,后臺service為200秒

55.Serializable和Parcelable 的區別

  • 1.P 消耗內存小
  • 2.網絡傳輸用S 程序內使用P
  • 3.S將數據持久化方便
  • 4.S使用了反射 容易觸發垃圾回收 比較慢

56.Sharedpreferences源碼簡述

  • 1.儲存于硬盤上的xml鍵值對,數據多了會有性能問題
  • 2.ContextImpl記錄著SharedPreferences的重要數據,文件路徑和實例的鍵值對
  • 3.在xml文件全部內加載到內存中之前,讀取操作是阻塞的,在xml文件全部內加載到內存中之后,是直接讀取內存中的數據
  • 4.apply因為是異步的沒有返回值, commit是同步的有返回值能知道修改是否提交成功
  • 5.多并發的提交commit時,需等待正在處理的commit數據更新到磁盤文件后才會繼續往下執行,從而降低效率; 而apply只是原子更新到內存,后調用apply函數會直接覆蓋前面內存數據,從一定程度上提高很多效率。 3.edit()每次都是創建新的EditorImpl對象.
  • 6.博客推薦:全面剖析SharedPreferences

57.操作系統如何管理內存的:

  • 1.使用寄存器進行將進程地址和物理內存進行映射
  • 2.虛擬內存進行內存映射到硬盤上增大內存
  • 3.虛擬內存是進行內存分頁管理
  • 4.頁表實現分頁,就是 頁+地址偏移。
  • 5.如果程序的內存在硬盤上,那么就需要用頁置換算法來將其調入內存中:先進先出、最近未使用最少等等
  • 6.博客推薦:現代操作系統部分章節筆記

58.瀏覽器輸入地址到返回結果發生了什么

  • 1.DNS解析
  • 2.TCP連接
  • 3.發送HTTP請求
  • 4.服務器處理請求并返回HTTP報文
  • 5.瀏覽器解析渲染頁面
  • 6.連接結束

59.java泛型類型擦除發生在什么時候,通配符有什么需要注意的。

60.activity的生命周期

  • 1.a啟動b,后退鍵再到a的生命周期流程:a.create-->a.start-->a.resume-->a.pause-->b.create-->b.start-->b.resume-->b界面繪制-->a.stop-->b.pause-->b.stop-->b.destroy-->a.restart-->a.start-->a.resume
  • 2.意外銷毀會調用saveInstance,重新恢復的時候回調用restoreInstance。儲存數據的時候使用了委托機制,從activity-->window-->viewGroup-->view 會遞歸調用save來保持本view的數據,restore則是遞歸恢復本view數據。我們可以在里面做一些自己需要的數據操作。

61.面試常考的算法

  • 1.快排、堆排序為首的各種排序算法
  • 2.鏈表的各種操作:判斷成環、判斷相交、合并鏈表、倒數K個節點、尋找成環節點
  • 3.二叉樹、紅黑樹、B樹定義以及時間復雜度計算方式
  • 4.動態規劃、貪心算法、簡單的圖論
  • 5.推薦書籍:算法導論,將圖論之前的例子寫一遍

62.Launcher進程啟動另外一個進程的過程:啟動一個app

63.開源框架源碼

  • 1.Fresco
    • 1.mvc框架:
      • 1.Controller控制數據顯示在Hierarchy中的Drawable的顯隱
      • 2.ImagePipeline在Controller中負責進行數據獲取,返回的數據是CloseableImage
      • 3.Drawee把除了初始化之外的操作全部交給Holder去做,Holder持有Controller和Hierarchy
    • 2.Drawable層次以及繪制:
      • 1.如果要繪制一次Drawable就調用invalidateSelf()來觸發onDraw()
      • 2.Drawable分為:容器類(保存一些Drawable)、自我繪制類(進度條)、圖形變換類(scale、rotate、矩陣變換)、動畫類(內部不斷刷新,進行webp和gif的幀繪制)
      • 3.ImagePipeline返回的CloseableImage是由一個個DrawableFactory解析成Drawable的
      • 4.webp和gif動畫是由jni代碼解析的,然后其他靜態圖片是根據不同的android平臺使用BitmapFactory來解析的
    • 3.職責鏈模式:producer不做操作標n,表示只是提供一個consumer。獲取圖片--》解碼圖片緩存Producer--》后臺線程Producer--》client圖片處理producer(n)--》解碼producer(n)--》旋轉或剪裁producer(n)--》編碼圖片內存緩存producer--》讀硬盤緩存producer--》寫硬盤緩存producer(n)--》網絡producer提供CloseableImage《--解碼圖片緩存consumer《--client圖片處理consumer《--解碼consumer《--旋轉或剪裁consumer《--編碼圖片內存緩存consumer《--寫硬盤緩存consumer《--圖片數據
    • 4.內存緩存:
      • 1.一個CountingLruMap保存已經沒有被引用的緩存條目,一個CountingLruMap保存所有的條目包括沒有引用的條目。每當緩存策略改變和一定時間緩存配置的更新的時候,就會將 待銷毀條目Map中的條目一個個移除,直到緩存大小符合配置。
      • 2.這里的引用計數是用Fresco組件實現的引用計數器。
      • 3.緩存有一個代理類,用來追蹤緩存的存取。
      • 4.CountingLruMap是使用LinkedHashMap來儲存數據的。
    • 5.硬盤緩存:
      • 1.DefaultDiskStorage使用Lru策略。
      • 2.為了不讓所有的文件集中在一個文件中,創建很多命名不同的文件夾,然后使用hash算法把緩存文件分散
      • 3.DiskStorageCache封裝了DefaultDiskStorage,不僅進行緩存存取追蹤,并且其在內存里面維持著一個 <key,value> 的鍵值對,因為文件修改頻繁,所有只是定時刷新,因此如果在內存中找不到,還要去硬盤中找一次。
      • 4.刪除硬盤的緩存只出現在硬盤數據大小超限的時候,此時同時也會刪除緩存中的key,所以不會出現內存中有key,但是硬盤上沒有的情況。
      • 5.在插入硬盤數據的時候,采用的是插入器的形式。返回一個Inserter,在Inserter.writeData()中傳入一個CallBack(里面封裝了客戶端插入數據的邏輯和文件引用),讓內部實現調用CallBack的邏輯來插入文件數據,前面寫的文件后綴是.temp,只有調用commit()之后才會修改后綴,讓文件對客戶端可見。
      • 6.使用了java提供的FileTreeVisitor來遍歷文件
    • 6.對象池:
      • 1.使用數組來存儲一個桶,桶內部是一個Queue。數組下標是數據申請內存的byte大小,桶內部的Queue存的是內存塊的。所以數組使用的是稀疏數組
      • 2.申請內存的方式有兩種 1.java堆上開辟的內存 2.ashme 的本地內存中開辟的內存
    • 7.設計模式:Builder、職責鏈、觀察者、代理、組合、享元、適配器、裝飾者、策略、生產者消費者、提供者
    • 8.自定義計數引用:類似c++智能指針
      • 1.使用一個靜態IdentityHashMap <儲存需要被計數引用的對象,其被引用的次數>
      • 2.用SharedReference分裝需要被計數引用的對象,提供一個銷毀資源的銷毀器,提供一個靜態工廠方法來復制自己,復制一個引用計數加一。提供一個方法銷毀自己,表示自己需要變成無人引用的對象了,此時引用計數減一。
      • 3.引用計數歸零,銷毀器將銷毀資源,如bitmap的recycle或者是jni內存調用jni方法歸還內存。
    • 9.博客推薦:Android Fresco源碼文檔翻譯從零開始擼一個Fresco之硬盤緩存從零開始擼一個Fresco之gif和Webp動畫從零開始擼一個Fresco之內存緩存從零開始擼一個Fresco之總結
  • 2.oKhttp:
    • 1.同步和異步:
      • 1.異步使用了Dispatcher來將存儲在 Deque 中的請求分派給線程池中各個線程執行。
      • 2.當任務執行完成后,無論是否有異常,finally代碼段總會被執行,也就是會調用Dispatcher的finished函數,它將正在運行的任務Call從隊列runningAsyncCalls中移除后,主動的把緩存隊列向前走了一步。
    • 2.連接池:
      • 1.一個Connection封裝了一個socket,ConnectionPool中儲存s著所有的Connection,StreamAllocation是引用計數的一個單位
      • 2.當一個請求獲取一個Connection的時候要傳入一個StreamAllocation,Connection中存著一個弱引用的StreamAllocation列表,每當上層應用引用一次Connection,StreamAllocation就會加一個。反之如果上層應用不使用了,就會刪除一個。
      • 3.ConnectionPool中會有一個后臺任務定時清理StreamAllocation列表為空的Connection。5分鐘時間,維持5個socket
    • 3.選擇路線與建立連接
      • 1.選擇路線有兩種方式:
        • 1.無代理,那么在本地使用DNS查找到ip,注意結果是數組,即一個域名有多個IP,這就是自動重連的來源
        • 2.有代理HTTP:設置socket的ip為代理地址的ip,設置socket的端口為代理地址的端口
        • 3.代理好處:HTTP代理會幫你在遠程服務器進行DNS查詢,可以減少DNS劫持。
      • 2.建立連接
        • 1.連接池中已經存在連接,就從中取出(get)RealConnection,如果沒有命中就進入下一步
        • 2.根據選擇的路線(Route),調用Platform.get().connectSocket選擇當前平臺Runtime下最好的socket庫進行握手
        • 3.將建立成功的RealConnection放入(put)連接池緩存
        • 4.如果存在TLS,就根據SSL版本與證書進行安全握手
        • 5.構造HttpStream并維護剛剛的socket連接,管道建立完成
    • 4.職責鏈模式:緩存、重試、建立連接等功能存在于攔截器中網絡請求相關,主要是網絡請求優化。網絡請求的時候遇到的問題
    • 5.博客推薦:Android數據層架構的實現 上篇Android數據層架構的實現 下篇
  • 3.okio
    • 1.簡介;
      • 1.sink:自己--》別人
      • 2.source:別人--》自己
      • 3.BufferSink:有緩存區域的sink
      • 4.BufferSource:有緩存區域的source
      • 5.Buffer:實現了3、4的緩存區域,內部有Segment的雙向鏈表,在在轉移數據的時候,只需要將指針轉移指向就行
    • 2.比java io的好處:
      • 1.減少內存申請和數據拷貝
      • 2.類少,功能齊全,開發效率高
    • 3.內部實現:
      • 1.Buffer的Segment雙向鏈表,減少數據拷貝
      • 2.Segment的內部byte數組的共享,減少數據拷貝
      • 3.SegmentPool的共享和回收Segment
      • 4.sink和source中被實際操作的其實是Buffer,Buffer可以充當sink和source
      • 5.最終okio只是對java io的封裝,所有操作都是基于java io 的

寫在最后:能看到這里的人,我挺佩服你的.這篇文章是我在頭條面試之前整理的,最后80%的題目都命中了,所以祝你好運.

不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限于:科幻、科學、科技、互聯網、程序員、計算機編程。下面是我的微信公眾號:世界上有意思的事,干貨多多等你來看。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374