01.Android之基礎元件問題

瀟湘劍雨發表於2019-01-19

目錄介紹

  • 1.0.0.1 說下Activity的生命週期?螢幕旋轉時生命週期?異常條件會呼叫什麼方法?
  • 1.0.0.2 後臺的Activity被系統回收怎麼辦?說一下onSaveInstanceState()和onRestoreInstanceState()方法特點?
  • 1.0.0.3 如何避免配置改變時Activity重建?優先順序低的Activity在記憶體不足被回收後怎樣做可以恢復到銷燬前狀態?
  • 1.0.0.4 app切換到後臺,當前activity會走onDestory方法嗎?一般在onstop方法裡做什麼?什麼情況會導致app會被殺死?
  • 1.0.0.5 Activity的啟動過程是有幾種方式?從桌面launcher上點選應用圖示會幹啥,呼叫startActivty()又會做什麼?
  • 1.0.0.6 說下Activity的四種啟動模式?singleTop和singleTask的區別以及應用場景?任務棧的作用是什麼?
  • 1.0.0.7 兩個Activity之間怎麼傳遞資料?intent和bundle有什麼區別?為什麼有了intent還要設計bundle?
  • 1.0.0.8 知道哪些Activity啟動模式的標記位?flag是幹什麼用的,什麼時候用到?
  • 1.0.1.0 同一程式不同的Activity是否可以放在不同的Task任務棧中?
  • 1.0.1.1 介紹一下Service,啟動Service有幾種方式,生命週期是怎樣的?說一下onStartCommand()的作用?service如何殺不死?
  • 1.0.1.2 一個Activty先start一個Service後,再bind時會回撥什麼方法?此時如何做才能回撥Service的destory()方法?
  • 1.0.1.3 bindService是一個非同步的過程嗎?繫結service大概需要經歷那些過程?
  • 1.0.1.4 是否能在Service進行耗時操作?如果非要可以怎麼做,如何避免service執行緒卡頓?service裡面可以彈土司嗎?
  • 1.0.1.5 Activity如何與Service通訊?Service的生命週期與啟動方法有什麼區別?
  • 1.0.2.0 是否瞭解ActivityManagerService,它發揮什麼作用,說一下AMS啟動流程?
  • 1.0.2.1 Android中哪些事件需要用到廣播?廣播的生命週期是怎樣的?
  • 1.0.2.3 廣播有幾種形式?他們分別有什麼特點,如何使用廣播?廣播是怎麼實現不同程式之間通訊的?
  • 1.0.2.8 Fragment與Activity之間是如何傳值的?Fragment與Fragment之間是如何傳值的?
  • 1.0.2.9 Activity建立Fragment的方式是什麼?FragmentPageAdapter和FragmentPageStateAdapter的區別?
  • 1.0.3.0 fragment 特點?說一下Fragment的生命週期?如何解決getActivity為null的異常問題?
  • 1.0.3.1 在fragment中為什麼有時getActivity()會為null?Fragment試圖為什麼有的時候會重疊,怎麼產生的,又如何解決?
  • 1.0.3.2 為什麼fragment傳遞資料不用構造方法傳遞?FragmentManager , add 和 replace 有什麼區別?
  • 1.0.3.9 Activitiy啟動流程中performLaunchActivity的作用?
  • 1.0.4.0 Intent是什麼?Intent可以傳遞哪些資料?傳遞物件的時候為什麼要例項化?
  • 1.0.4.1 mipmap系列中xxxhdpi、xxhdpi、xhdpi、hdpi、mdpi和ldpi存在怎樣的關係?
  • 1.0.4.2 res目錄和assets目錄的區別?R檔案是如何生成的,主要有什麼作用?
  • 1.0.4.3 Context是什麼?Context有哪些型別,分別作用是什麼?Context下有哪些子類?哪些場景只能用activity上下文?
  • 1.0.4.4 ActivityThread的main()的流程大概是怎麼樣的?
  • 1.0.5.0 序列化的方式有哪些?效率對比有何優勢?如何做效能上分析的?
  • 1.0.5.9 介面的重新整理為什麼需16.6ms?畫面的顯示需要哪些步驟?介面保持不變時還會16.6ms重新整理一次螢幕嗎?
  • 1.0.6.0 Android中日誌級別有哪幾種?開發中需要注意什麼問題,列印日誌原始碼分析原理是什麼?

好訊息

  • 部落格筆記大彙總【15年10月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請註明出處,謝謝!
  • 連結地址:github.com/yangchong21…
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!所有的筆記將會更新到GitHub上,同時保持更新,歡迎同行提出或者push不同的看法或者筆記!

1.0.0.1 說下Activity的生命週期?螢幕旋轉時生命週期?異常條件會呼叫什麼方法?

  • 在Activity的生命週期涉及到七大方法,分別是:
    • onCreate()表示Activity 正在建立,常做初始化工作,如setContentView介面資源、初始化資料
    • onStart()表示Activity 正在啟動,這時Activity 可見但不在前臺,無法和使用者互動
    • onResume()表示Activity 獲得焦點,此時Activity 可見且在前臺並開始活動
    • onPause()表示Activity 正在停止,可做 資料儲存、停止動畫等操作
    • onStop()表示activity 即將停止,可做稍微重量級回收工作,如取消網路連線、登出廣播接收器等
    • onDestroy()表示Activity 即將銷燬,常做回收工作、資源釋放
    • onRestart()表示當Activity由後臺切換到前臺,由不可見到可見時會呼叫,表示Activity 重新啟動
  • 螢幕旋轉時生命週期
    • 螢幕旋轉時候,如果不做任何處理,activity會經過銷燬到重建的過程。一般這種效果都不是想要的。比如視訊播放器就經常會涉及螢幕旋轉場景。技術部落格大總結
    • 第一種情況:當前的Activity不銷燬【設定Activity的android:configChanges="orientation|keyboardHidden|screenSize"時,切屏不會重新呼叫各個生命週期,只會執行onConfigurationChanged方法】
      <activity
          android:name=".activity.VideoDetailActivity"
          android:configChanges="orientation|keyboardHidden|screenSize"
          android:screenOrientation="portrait"/>
      複製程式碼
      • 執行該方法
      //重寫旋轉時方法,不銷燬activity
      @Override
      public void onConfigurationChanged(Configuration newConfig) {
      	super.onConfigurationChanged(newConfig);
      }
      複製程式碼
    • 第二種情況:銷燬當前的Activity後重建,這種也儘量避免。【不設定Activity的android:configChanges時,切屏會重新呼叫各個生命週期,預設首先銷燬當前activity,然後重新載入】
  • 異常條件會呼叫什麼方法
    • 當非人為終止Activity時,比如系統配置發生改變時導致Activity被殺死並重新建立、資源記憶體不足導致低優先順序的Activity被殺死,會呼叫 onSavaInstanceState() 來儲存狀態。該方法呼叫在onStop之前,但和onPause沒有時序關係。
    • 有人會問,onSaveInstanceState()與onPause()的區別,onSaveInstanceState()適用於對臨時性狀態的儲存,而onPause()適用於對資料的持久化儲存。
    • 當異常崩潰後App又重啟了,這個時候會走onRestoreInstanceState()方法,可以在該方法中取出onSaveInstanceState()儲存的狀態資料。
  • 什麼時候會引起異常生命週期
    • 資源相關的系統配置發生改變或者資源不足:例如螢幕旋轉,當前Activity會銷燬,並且在onStop之前回撥onSaveInstanceState儲存資料,在重新建立Activity的時候在onStart之後回撥onRestoreInstanceState。其中Bundle資料會傳到onCreate(不一定有資料)和onRestoreInstanceState(一定有資料)。技術部落格大總結
    • 防止螢幕旋轉的時候重建,在清單檔案中新增配置:android:configChanges="orientation"

1.0.0.2 後臺的Activity被系統回收怎麼辦?說一下onSaveInstanceState()和onRestoreInstanceState()方法特點?

  • 後臺的Activity被系統回收怎麼辦?
    • Activity中提供了一個 onSaveInstanceState()回撥方法,這個方法會保證一定在活動被回收之前呼叫,可以通過這個方法來解決活動被回收時臨時資料得不到儲存的問題。onSaveInstanceState()方法會攜帶一個Bundle型別的引數,Bundle提供了一系列的方法用於儲存資料,比如可以使用putString()方法儲存字串,使用putInt()方法儲存整型資料。每個儲存方法需要傳入兩個引數,第一個引數是鍵,用於後面從 Bundle中取值,第二個引數是真正要儲存的內容。技術部落格大總結
  • 說一下onSaveInstanceState()和onRestoreInstanceState()方法特點?
    • Activity的 onSaveInstanceState()和onRestoreInstanceState()並不是生命週期方法,它們不同於onCreate()、onPause()等生命週期方法,它們並不一定會被觸發。
      //儲存資料
      @Override
      protected void onSaveInstanceState(Bundle outBundle) {
      	super.onSaveInstanceState(outBundle);
       	outBundle.putBoolean("Change", mChange);
      }
      
      //取出資料
      @Override 
      protected void onRestoreInstanceState(Bundle savedInstanceState) {
      	super.onRestoreInstanceState(savedInstanceState);
      	mChange = savedInstanceState.getBoolean("Change");
      }
      
      //或者在onCreate方法取資料也可以
      //onCreate()方法其實也有一個Bundle型別的引數。這個引數在一般情況下都是null,
      //但是當活動被系統回收之前有通過 onSaveInstanceState()方法來儲存資料的話,這個參就會帶有之前所儲存的全部資料
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          if (savedInstanceState != null) {
              String data = savedInstanceState.getString("data");
          }
      }
      複製程式碼
  • 什麼時候會觸發走這兩個方法?
    • 當應用遇到意外情況(如:記憶體不足、使用者直接按Home鍵)由系統銷燬一個Activity,onSaveInstanceState() 會被呼叫。但是當使用者主動去銷燬一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被呼叫。除非該activity是被使用者主動銷燬的,通常onSaveInstanceState()只適合用於儲存一些臨時性的狀態,而onPause()適合用於資料的持久化儲存。
  • onSaveInstanceState()被執行的場景有哪些?
    • 系統不知道你按下HOME後要執行多少其他的程式,自然也不知道activityA是否會被銷燬,因此係統都會呼叫onSaveInstanceState(),讓使用者有機會儲存某些非永久性的資料。以下幾種情況的分析都遵循該原則當使用者按下HOME鍵時
      • 長按HOME鍵,選擇執行其他的程式時
      • 鎖屏時
      • 從activity A中啟動一個新的activity時
      • 螢幕方向切換時

1.0.0.3 如何避免配置改變時Activity重建?優先順序低的Activity在記憶體不足被回收後怎樣做可以恢復到銷燬前狀態?

  • 如何避免配置改變時Activity重建
    • 為了避免由於配置改變導致Activity重建,可在AndroidManifest.xml中對應的Activity中設定android:configChanges="orientation|screenSize"。此時再次旋轉螢幕時,該Activity不會被系統殺死和重建,只會呼叫onConfigurationChanged。因此,當配置程式需要響應配置改變,指定configChanges屬性,重寫onConfigurationChanged方法即可。
    • 使用場景,比如視訊播放器橫豎屏切換播放視訊,就需要設定這種屬性。具體可以看我封裝的視訊播放器庫,地址:github.com/yangchong21…
  • 優先順序低的Activity在記憶體不足被回收後怎樣做可以恢復到銷燬前狀態
    • 優先順序低的Activity在記憶體不足被回收後重新開啟會引發Activity重建。Activity被重新建立時會呼叫onRestoreInstanceState(該方法在onStart之後),並將onSavaInstanceState儲存的Bundle物件作為引數傳到onRestoreInstanceState與onCreate方法。因此可通過onRestoreInstanceState(Bundle savedInstanceState)和onCreate((Bundle savedInstanceState)來判斷Activity是否被重建,並取出資料進行恢復。但需要注意的是,在onCreate取出資料時一定要先判斷savedInstanceState是否為空。
  • 如何判斷activity的優先順序?技術部落格大總結
    • 除了在棧頂的activity,其他的activity都有可能在記憶體不足的時候被系統回收,一個activity越處於棧底,被回收的可能性越大.如果有多個後臺程式,在選擇殺死的目標時,採用最近最少使用演算法(LRU)。

1.0.0.4 app切換到後臺,當前activity會走onDestory方法嗎?一般在onstop方法裡做什麼?什麼情況會導致app會被殺死,這時候會走onDestory嗎?

  • app切換到後臺,當前activity會走onDestory方法嗎?
    • 不會走onDestory方法,會先後走onPause和onStop方法。
  • 一般在onstop方法裡做什麼?
    • 比如。寫輪播圖的時候,會在onstop方法裡寫上暫停輪播圖無限輪播,在onStart方法中會開啟自動無限輪播。
    • 再比如,寫視訊播放器的時候,當app切換到後臺,則需要停止視訊播放,也是可以在onstop中處理的。關於視訊播放器,可以看我這個開源專案:視訊播放器
  • 什麼情況會導致app會被殺死,這時候會走onDestory嗎?
    • 系統資源不足,會導致app意外被殺死。應用只有在程式存活的情況下才會按照正常的生命週期進行執行,如果程式突然被kill掉,相當於System.exit(0); 程式被殺死,根本不會走(activity,fragment)生命週期。只有在程式不被kill掉,正常情況下才會執行ondestory()方法。
  • activity被回收如何恢復
    • 當系統記憶體不足時, activity會被回收,我們其實可以覆寫onSaveInstanceState()方法。onSaveInstanceState()方法接受一個Bundle型別的引數, 開發者可以將狀態資料儲存到這個Bundle物件中,這樣即使activity被系統摧毀,當使用者重新啟動這個activity而呼叫它的onCreate()方法時,上述的Bundle物件會作為實參傳遞給onCreate()方法,開發者可以從Bundle物件中取出儲存的資料, 然後利用這些資料將activity恢復到被摧毀之前的狀態。

1.0.0.5 Activity的啟動過程是有幾種方式?從桌面launcher上點選應用圖示會幹啥,呼叫startActivty()又會做什麼?

  • Activity的啟動過程是怎樣的,有幾種方式?
    • 注意是啟動過程,不是生命週期。技術部落格大總結
    • app啟動的過程有兩種情況,第一種是從桌面launcher上點選相應的應用圖示,第二種是在activity中通過呼叫startActivity來啟動一個新的activity。
  • 從桌面launcher上點選應用圖示會幹啥,呼叫startActivty()又會做什麼?
    • 建立一個新的專案,預設的根activity都是MainActivity,而所有的activity都是儲存在堆疊中的,啟動一個新的activity就會放在上一個activity上面,而我們從桌面點選應用圖示的時候,由於launcher本身也是一個應用,當我們點選圖示的時候,系統就會呼叫startActivitySately(),一般情況下,我們所啟動的activity的相關資訊都會儲存在intent中,比如action,category等等。
    • 我們在安裝這個應用的時候,系統也會啟動一個PackaManagerService的管理服務,這個管理服務會對AndroidManifest.xml檔案進行解析,從而得到應用程式中的相關資訊,比如service,activity,Broadcast等等,然後獲得相關元件的資訊。
    • 當我們點選應用圖示的時候,就會呼叫startActivitySately()方法,而這個方法內部則是呼叫startActivty(),而startActivity()方法最終還是會呼叫startActivityForResult()這個方法。而在startActivityForResult()這個方法。因為startActivityForResult()方法是有返回結果的,所以系統就直接給一個-1,就表示不需要結果返回了。
    • 而startActivityForResult()這個方法實際是通過Instrumentation類中的execStartActivity()方法來啟動activity,Instrumentation這個類主要作用就是監控程式和系統之間的互動。而在這個execStartActivity()方法中會獲取ActivityManagerService的代理物件,通過這個代理物件進行啟動activity。啟動會就會呼叫一個checkStartActivityResult()方法,如果說沒有在配置清單中配置有這個元件,就會在這個方法中丟擲異常了。
    • 當然最後是呼叫的是Application.scheduleLaunchActivity()進行啟動activity,而這個方法中通過獲取得到一個ActivityClientRecord物件,而這個ActivityClientRecord通過handler來進行訊息的傳送,系統內部會將每一個activity元件使用ActivityClientRecord物件來進行描述,而ActivityClientRecord物件中儲存有一個LoaderApk物件,通過這個物件呼叫handleLaunchActivity來啟動activity元件,而頁面的生命週期方法也就是在這個方法中進行呼叫。

1.0.0.6 說下Activity的四種啟動模式?singleTop和singleTask的區別以及應用場景?任務棧的作用是什麼?

  • Activity的四種啟動模式
    • standard標準模式:每次啟動一個Activity就會建立一個新的例項
    • singleTop棧頂複用模式:如果新Activity已經位於任務棧的棧頂,就不會重新建立,並回撥 onNewIntent(intent) 方法
    • singleTask棧內複用模式:只要該Activity在一個任務棧中存在,都不會重新建立,並回撥 onNewIntent(intent) 方法。如果不存在,系統會先尋找是否存在需要的棧,如果不存在該棧,就建立一個任務棧,並把該Activity放進去;如果存在,就會建立到已經存在的棧中
    • singleInstance單例項模式:具有此模式的Activity只能單獨位於一個任務棧中,且此任務棧中只有唯一一個例項
  • singleTop和singleTask的區別以及應用場景
    • singleTop:同個Activity例項在棧中可以有多個,即可能重複建立;該模式的Activity會預設進入啟動它所屬的任務棧,即不會引起任務棧的變更;為防止快速點選時多次startActivity,可以將目標Activity設定為singleTop
    • singleTask:同個Activity例項在棧中只有一個,即不存在重複建立;可通過android:taskAffinity設定該Activity需要的任務棧,即可能會引起任務棧的變更;常用於主頁和登陸頁
  • singleTop或singleTask的Activity在以下情況會回撥onNewIntent()
    • singleTop:如果新Activity已經位於任務棧的棧頂,就不會重新建立,並回撥 onNewIntent(intent) 方法
    • singleTask:只要該Activity在一個任務棧中存在,都不會重新建立,並回撥 onNewIntent(intent) 方法
  • 任務棧的作用是什麼?技術部落格大總結
    • 它是存放 Activity 的引用的,Activity不同的啟動模式,對應不同的任務棧的存放;可通過 getTaskId()來獲取任務棧的 ID,如果前面的任務棧已經清空,新開的任務棧ID+1,是自動增長的;首先來看下Task的定義,Google是這樣定義Task的:Task實際上是一個Activity棧,通常使用者感受的一個Application就是一個Task。從這個定義來看,Task跟Service或者其他Components是沒有任何聯絡的,它只是針對Activity而言的。

1.0.0.7 兩個Activity之間怎麼傳遞資料?intent和bundle有什麼區別?為什麼有了intent還要設計bundle?

  • 兩個Activity之間怎麼傳遞資料?
    • 基本資料型別可以通過Intent傳遞資料
    • 把資料封裝至intent物件中
      Intent intent = new Intent(content, MeActivity.class);
      intent.putExtra("goods_id", goods_id);
      content.startActivity(intent);
      複製程式碼
    • 把資料封裝至bundle物件中技術部落格大總結
    • 把bundle物件封裝至intent物件中
      Bundle bundle = new Bundle();
      bundle.putString("malename", "李志");
      intent.putExtras(bundle);
      startActivity(intent); 
      複製程式碼
  • intent和bundle有什麼區別?
    • Intent傳遞資料和Bundle傳遞資料是一回事,Intent傳遞時內部還是呼叫了Bundle。
      public @NonNull Intent putExtra(String name, String value) {
          if (mExtras == null) {
              mExtras = new Bundle();
          }
          mExtras.putString(name, value);
          return this;
      }
      複製程式碼
  • 為什麼有了intent還要設計bundle?
    • 兩者比較
      • Bundle只是一個資訊的載體,內部其實就是維護了一個Map<String,Object>。
      • Intent負責Activity之間的互動,內部是持有一個Bundle的。
    • bundle使用場景
      • Fragment之間傳遞資料;比如,某個Fragment中點選按鈕彈出一個DialogFragment。最便捷的方式就是通過Fragment.setArguments(args)傳遞引數。
      public static void showFragmentDialog(String title, String content, boolean is_open, AppCompatActivity activity) {
          ServiceDialogFragment mainDialogFragment = new ServiceDialogFragment();
          Bundle bundle = new Bundle();
          bundle.putString("title", title);
          bundle.putString("content", content);
          bundle.putBoolean("is_open",is_open);
          mainDialogFragment.setArguments(bundle);
          mainDialogFragment.show(activity.getSupportFragmentManager());
      }
      
      @Override
      public void onCreate(Bundle savedInstanceState) {
          setLocal(Local.CENTER);
          super.onCreate(savedInstanceState);
          Bundle bundle = getArguments();
          if (bundle != null) {
              title = bundle.getString("title");
              content = bundle.getString("content");
              is_open = bundle.getBoolean("is_open");
          }
      }
      複製程式碼

1.0.0.8 知道哪些Activity啟動模式的標記位?flag是幹什麼用的,什麼時候用到?

  • 常見的標記為:
    • FLAG_ACTIVITY_SINGLE_TOP:對應singleTop啟動模式
    • FLAG_ACTIVITY_NEW_TASK :對應singleTask模式

1.0.1.0 同一程式不同的Activity是否可以放在不同的Task任務棧中?

  • 同一程式不同的Activity是否可以放在不同的Task任務棧中?
    • 可以的。比如:啟動模式裡有個Singleinstance,可以執行在另外的單獨的任務棧裡面。用這個模式啟動的activity,在記憶體中只有一份,這樣就不會重複的開啟。
    • 也可以在啟用一個新的activity時候,給intent設定flag,Intent的flag新增FLAG_ACTIVITY_NEW_TASK,這個被啟用的activity就會在新的task棧裡面

1.0.1.1 介紹一下Service,啟動Service有幾種方式,生命週期是怎樣的?說一下onStartCommand()的作用?service如何殺不死?

  • Service分為兩種
    • 本地服務,屬於同一個應用程式,通過startService來啟動或者通過bindService來繫結並且獲取代理物件。如果只是想開個服務在後臺執行的話,直接startService即可,如果需要相互之間進行傳值或者操作的話,就應該通過bindService。
    • 遠端服務(不同應用程式之間),通過bindService來繫結並且獲取代理物件。
  • 對應的生命週期如下:
    • context.startService() ->onCreate()- >onStartCommand()->Service running--呼叫context.stopService() ->onDestroy()
    • context.bindService()->onCreate()->onBind()->Service running--呼叫>onUnbind() -> onDestroy()
  • 注意
    • Service預設是執行在main執行緒的,因此Service中如果需要執行耗時操作(大檔案的操作,資料庫的拷貝,網路請求,檔案下載等)的話應該在子執行緒中完成。
  • Service生命週期解釋技術部落格大總結
    • onCreate():服務第一次被建立時呼叫
    • onStartComand():服務啟動時呼叫
    • onBind():服務被繫結時呼叫
    • onUnBind():服務被解綁時呼叫
    • onDestroy():服務停止時呼叫
  • 說一下onStartCommand()的作用?
  • service如何殺不死?
    • 1.onStartCommand方法,返回START_STICKY(粘性)當service因記憶體不足被kill,當記憶體又有的時候,service又被重新建立
    • 2.設定優先順序,在服務裡的ondestory裡傳送廣播 在廣播裡再次開啟這個服務,雙程式守護

1.0.1.2 一個Activty先start一個Service後,再bind時會回撥什麼方法?此時如何做才能回撥Service的destory()方法?

  • 關於service中onDestroy()什麼時候會被執行?
    • 當呼叫了startService()方法後,又去呼叫stopService()方法,這時服務中的onDestroy()方法就會執行,表示服務已經銷燬了。
    • 類似地,當呼叫了 bindService()方法後,又去呼叫unbindService()方法,onDestroy()方法也會執行,這兩種情況都很好理解。
  • 一個Activty先start一個Service後,再bind時會回撥什麼方法?

  • 先start後bind操作service,此時如何做才能回撥Service的destory()方法?
    • 完全有可能對一個服務既呼叫了startService()方法,又呼叫了bindService()方法的,這種情況下該如何才能讓服務銷燬掉呢?根據Android系統的機制,一個服務只要被啟動或者被繫結了之後,就會一直處於執行狀態,必須要讓以上兩種條件同時不滿足,服務才能被銷燬。
    • 這種情況下要同時呼叫stopService()和unbindService()方法,onDestroy()方法才會執行這樣就把服務的生命週期完整地走了一遍。技術部落格大總結

1.0.1.3 bindService是一個非同步的過程嗎?繫結service大概需要經歷那些過程?

1.0.1.4 是否能在Service進行耗時操作?如果非要可以怎麼做,如何避免service執行緒卡頓?service裡面可以彈土司嗎?

  • 是否能在Service進行耗時操作?
    • 預設情況,如果沒有顯示的指定service所執行的程式,Service和Activity是執行在當前app所在程式的mainThread(UI主執行緒)裡面。
    • service裡面不能執行耗時的操作(網路請求,拷貝資料庫,大檔案),在Service裡執行耗時操作,有可能出現主執行緒被阻塞(ANR)的情況。
  • 如果非要可以怎麼做,如何避免service執行緒卡頓?
    • 需要在子執行緒中執行 new Thread(){}.start();
  • service裡面可以彈土司嗎?
    • 可以,但是有條件。一般很少這樣做……技術部落格大總結
    • 條件是,service裡面彈toast需要新增到主執行緒裡執行。
      @Override  
      public void onCreate(){  
          handler = new Handler(Looper.getMainLooper());                          
          System.out.println("service started");  
          handler.post(new Runnable() {    
              @Override    
              public void run() {    
                  Toast.makeText(getApplicationContext(), "Test",Toast.LENGTH_SHORT).show();
              }
          });
      }
      複製程式碼

1.0.1.5 Activity如何與Service通訊?Service的生命週期與啟動方法有什麼區別?

  • Activity如何與Service通訊?
    • 方法一:
      • 新增一個繼承Binder的內部類,並新增相應的邏輯方法。重寫Service的onBind方法,返回我們剛剛定義的那個內部類例項。Activity中建立一個ServiceConnection的匿名內部類,並且重寫裡面的onServiceConnected方法和onServiceDisconnected方法,這兩個方法分別會在活動與服務成功繫結以及解除繫結的時候呼叫,在onServiceConnected方法中,我們可以得到一個剛才那個service的binder物件,通過對這個binder物件進行向下轉型,得到我們那個自定義的Binder例項,有了這個例項,做可以呼叫這個例項裡面的具體方法進行需要的操作了
    • 方法二
      • 通過BroadCast(廣播)的形式,當我們的進度發生變化的時候我們傳送一條廣播,然後在Activity的註冊廣播接收器,接收到廣播之後更新檢視

1.0.2.0 是否瞭解ActivityManagerService,它發揮什麼作用,說一下AMS啟動流程?

  • ActivityManagerService是Android中最核心的服務,主要負責系統中四大元件的啟動、切換、排程及應用程式的管理和排程等工作,其職責與作業系統中的程式管理和排程模組類似。
  • blog.csdn.net/dutedehuai/…

1.0.2.1 Android中哪些事件需要用到廣播?廣播的生命週期是怎樣的?

  • Android中哪些事件需要用到廣播?
    • Android中:系統在執行過程中,會產生會多事件,那麼某些事件產生時,比如:電量改變、收發簡訊、撥打電話、螢幕解鎖、開機,系統會傳送廣播,只要應用程式接收到這條廣播,就知道系統發生了相應的事件,從而執行相應的程式碼。使用廣播接收者,就可以收聽廣播
  • 廣播的生命週期是怎樣的?
    • a.廣播接收者的生命週期非常短暫的,在接收到廣播的時候建立,onReceive()方法結束之後銷燬;
    • b.廣播接收者中不要做一些耗時的工作,否則會彈出 Application No Response錯誤對話方塊;
    • c.最好也不要在廣播接收者中建立子執行緒做耗時的工作,因為廣播接收者被銷燬後程式就成為了空程式,很容易被系統殺掉;
    • d.耗時的較長的工作最好放在服務中完成;

1.0.2.3 廣播有幾種形式?他們分別有什麼特點,如何使用廣播?廣播是怎麼實現不同程式之間通訊的?

  • 廣播有幾種形式
    • 普通廣播:一種完全非同步執行的廣播,在廣播發出之後,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播訊息,因此它們接收的先後是隨機的。
    • 有序廣播:一種同步執行的廣播,在廣播發出之後,同一時刻只會有一個廣播接收器能夠收到這條廣播訊息,當這個廣播接收器中的邏輯執行完畢後,廣播才會繼續傳遞,所以此時的廣播接收器是有先後順序的,且優先順序(priority)高的廣播接收器會先收到廣播訊息。有序廣播可以被接收器截斷使得後面的接收器無法收到它。
    • 本地廣播:發出的廣播只能夠在應用程式的內部進行傳遞,並且廣播接收器也只能接收本應用程式發出的廣播。
    • 粘性廣播:這種廣播會一直滯留,當有匹配該廣播的接收器被註冊後,該接收器就會收到此條廣播。
  • 廣播的兩種註冊形式技術部落格大總結
    • 廣播的註冊有兩種方法:一種在活動裡通過程式碼動態註冊,另一種在配置檔案裡靜態註冊。兩種方式的相同點是都完成了對接收器以及它能接收的廣播值這兩個值的定義;不同點是動態註冊的接收器必須要在程式啟動之後才能接收到廣播,而靜態註冊的接收器即便程式未啟動也能接收到廣播,比如想接收到手機開機完成後系統發出的廣播就只能用靜態註冊了。
  • 動態註冊
    • 需要使用廣播接收者時,執行註冊的程式碼,不需要時,執行解除註冊的程式碼。安卓中有一些廣播接收者,必須使用程式碼註冊,清單檔案註冊是無效的。
      public class MainActivity extends Activity {
          private IntentFilter intentFilter;
          private NetworkChangeReceiver networkChangeReceiver;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
                 setContentView(R.layout.activity_main);
                 intentFilter = new IntentFilter();
                 intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
                 networkChangeReceiver = new NetworkChangeReceiver();
                 registerReceiver(networkChangeReceiver, intentFilter);
          }
          @Override
          protected void onDestroy() {
                 super.onDestroy();
                 unregisterReceiver(networkChangeReceiver);
          }
          class NetworkChangeReceiver extends BroadcastReceiver {
                  @Override
                  public void onReceive(Context context, Intent intent) {
                       Toast.makeText(context, "network changes",Toast.LENGTH_SHORT).show();
                  }
          }
      }
      複製程式碼
  • 靜態註冊
    • 可以使用清單檔案註冊。廣播一旦發出,系統就會去所有清單檔案中尋找,哪個廣播接收者的action和廣播的action是匹配的,如果找到了,就把該廣播接收者的程式啟動起來。

1.0.2.8 Fragment與Activity之間是如何傳值的?Fragment與Fragment之間是如何傳值的?

  • Fragment與Activity之間是如何傳值的?
    • 1.Activity向Fragment傳值:
      • 步驟:
      • 要傳的值,放到bundle物件裡;
      • 在Activity中建立該Fragment的物件fragment,通過呼叫
      • fragment.setArguments()傳遞到fragment中;
      • 在該Fragment中通過呼叫getArguments()得到bundle物件,就能得到裡面的值。
    • 2.Fragment向Activity傳值:
      • 第一種:
        • 在Activity中呼叫getFragmentManager()得到fragmentManager,,呼叫findFragmentByTag(tag)或者通過findFragmentById(id)
        • FragmentManager fragmentManager = getFragmentManager();
        • Fragment fragment = fragmentManager.findFragmentByTag(tag);
      • 第二種:
        • 通過回撥的方式,定義一個介面(可以在Fragment類中定義),介面中有一個空的方法,在fragment中需要的時候呼叫介面的方法,值可以作為引數放在這個方法中,然後讓Activity實現這個介面,必然會重寫這個方法,這樣值就傳到了Activity中
  • Fragment與Fragment之間是如何傳值的?
    • 第一種:
      • 通過findFragmentByTag得到另一個的Fragment的物件,這樣就可以呼叫另一個的方法了。
    • 第二種:
    • 第三種:
      • 通過setArguments,getArguments的方式。

1.0.2.9 Activity建立Fragment的方式是什麼?FragmentPageAdapter和FragmentPageStateAdapter的區別?

  • Activity建立Fragment的方式是什麼?
    • 靜態建立具體步驟
      • 首先我們同樣需要註冊一個xml檔案,然後建立與之對應的java檔案,通過onCreatView()的返回方法進行關聯,最後我們需要在Activity中進行配置相關引數即在Activity的xml檔案中放上fragment的位置。
    • 動態建立具體步驟
      • (1)建立待新增的碎片例項
      • (2)獲取FragmentManager,在活動中可以直接通過呼叫 getSupportFragmentManager()方法得到。
      • (3)開啟一個事務,通過呼叫beginTransaction()方法開啟。
      • (4)向容器內新增或替換碎片,一般使用repalce()方法實現,需要傳入容器的id和待新增的碎片例項。
      • (5)提交事務,呼叫commit()方法來完成。
  • FragmentPageAdapter和FragmentPageStateAdapter的區別?
    • FragmnetPageAdapter在每次切換頁面時,只是將Fragment進行分離,適合頁面較少的Fragment使用以儲存一些記憶體,對系統記憶體不會多大影響
    • FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的記憶體

1.0.3.0 fragment 特點?說一下Fragment的生命週期?如何解決getActivity為null的異常問題?

  • fragment 特點
    • Fragment可以作為Activity介面的一部分組成出現;
    • 可以在一個Activity中同時出現多個Fragment,並且一個Fragment也可以在多個Activity中使用;
    • 在Activity執行過程中,可以新增、移除或者替換Fragment;
    • Fragment可以響應自己的輸入事件,並且有自己的生命週期,它們的生命週期會受宿主Activity的生命週期影響。
  • Fragment從建立到銷燬整個生命週期中涉及到的方法依次為:
    • onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach(),其中和Activity有不少名稱相同作用相似的方法,而不同的方法有:
      • onAttach():當Fragment和Activity建立關聯時呼叫
      • onCreateView():當Fragment建立檢視時呼叫
      • onActivityCreated():當與Fragment相關聯的Activity完成onCreate()之後呼叫
      • onDestroyView():在Fragment中的佈局被移除時呼叫
      • onDetach():當Fragment和Activity解除關聯時呼叫
  • 如何解決getActivity為null的異常問題技術部落格大總結
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (PhoneNumActivity) context;
    }
    
    @Override
    public void onDetach() {
        super.onDetach();
        activity = null;
    }
    複製程式碼

1.0.3.1 在fragment中為什麼有時getActivity()會為null?Fragment試圖為什麼有的時候會重疊,怎麼產生的,又如何解決?

  • getActivity()空指標:
    • 這種情況一般發生在在非同步任務裡呼叫getActivity(),而Fragment已經onDetach(),此時就會有空指標,解決方案是在Fragment裡使用一個全域性變數mActivity,在onAttach()方法裡賦值,這樣可能會引起記憶體洩漏,但是非同步任務沒有停止的情況下本身就已經可能記憶體洩漏,相比直接crash,這種方式顯得更妥當一些。
  • Fragment檢視重疊:
    • 在類onCreate()的方法載入Fragment,並且沒有判斷saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),導致重複載入了同一個Fragment導致重疊。(PS:replace情況下,如果沒有加入回退棧,則不判斷也不會造成重疊,但建議還是統一判斷下)
    @Override 
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    // 在頁面重啟時,Fragment會被儲存恢復,而此時再載入Fragment會重複載入,導致重疊 ;
        if(saveInstanceState == null){
        // 或者 if(findFragmentByTag(mFragmentTag) == null)
           // 正常情況下去 載入根Fragment 
        } 
    }
    複製程式碼

1.0.3.2 為什麼fragment傳遞資料不用構造方法傳遞?FragmentManager , add 和 replace 有什麼區別?

  • 為什麼fragment傳遞資料不用構造方法傳遞?
    • activity給fragment傳遞資料一般不通過fragment的構造方法來傳遞,會通過setArguments來傳遞,因為當橫豎屏會呼叫fragment的空參建構函式,資料丟失。
  • FragmentManager , add 和 replace 有什麼區別?
    • 使用FragmentTransaction的時候,它提供了這樣兩個方法,一個add,一個replace,add和replace影響的只是介面,而控制回退的,是事務。
    • add 是把一個fragment新增到一個容器container裡。replace是先remove掉相同id的所有fragment,然後在add當前的這個fragment。技術部落格大總結
    • 在大部分情況下,這兩個的表現基本相同。因為,一般,我們們會使用一個FrameLayout來當容器,而每個Fragment被add 或者 replace 到這個FrameLayout的時候,都是顯示在最上層的。所以你看到的介面都是一樣的。但是,使用add的情況下,這個FrameLayout其實有2層,多層肯定要比一層的來得浪費,所以還是推薦使用replace。當然有時候還是需要使用add的。比如要實現輪播圖的效果,每個輪播圖都是一個獨立的Fragment,而他的容器FrameLayout需要add多個Fragment,這樣他就可以根據提供的邏輯進行輪播了。而至於返回鍵的時候,這個跟事務有關,跟使用add還是replace沒有任何關係。
    • replace()方法會將被替換掉的那個Fragment徹底地移除掉,因此最好的解決方案就是使用hide()和show()方法來隱藏和顯示Fragment,這就不會讓Fragment的生命週期重走一遍了。

1.0.3.9 Activitiy啟動流程中performLaunchActivity的作用?Activity啟動流程中handleResumeActivity的作用?

  • Activitiy啟動流程中performLaunchActivity的作用?
    • 從ActivityClientRecord中獲取到待啟動的Activity的元件資訊
    • 使用類載入器建立Activity物件
    • 通過LoadedApk的方法建立Applicayiton物件,該物件唯一,不會重複建立。
    • 會建立ContextImpl並且建立Context和Activity的聯絡,以及建立PhoneWindow,建立Window和Activity的聯絡。
    • 呼叫Activity的onCreate()
  • Activity啟動流程中handleResumeActivity的作用?
    • 執行onStart()、onResume()—利用Instrucmentation
    • 獲取Window
    • 建立DecorView、設定為不可見INVISIBLE、建立DecorView和Activity的聯絡。
    • 獲取Activity的WindowManager
    • 呼叫WindowManager.addView(decorView, ...)將DecorView新增到WM中,完成顯示的工作。
    • image
  • 何時將DecorView設定為VISIBLE?並且顯示出來?技術部落格大總結
    • 也是在handleResumeActivity中
    • 現將DecorView設定為不可見
    • wm.addView(): 將DecorView新增到Window總
    • 然後執行makeVisible讓DecorView可見
    • image

1.0.4.0 Intent是什麼?Intent可以傳遞哪些資料?傳遞物件的時候為什麼要例項化?

  • Intent是一種執行時繫結(run-time binding)機制,它能在程式執行過程中連線兩個不同的元件。
    • 舉例:比如,有一個Activity希望開啟網頁瀏覽器檢視某一網頁的內容,那麼這個Activity只需要發出 WEB_SEARCH_ACTION給Android
    • Android就會根據Intent的請求內容,查詢各元件註冊時宣告的 IntentFilter,找到網頁瀏覽器的Activity來瀏覽網頁
  • Intent可以傳遞的資料基本資料型別的資料,陣列,還有集合,還有序列化的物件
    • 序列化:表示將一個物件轉換成可儲存或可傳輸的狀態
    • Android中序列化物件方式:技術部落格大總結
      • 第一種:JAVA中的Serialize機制,譯成序列化、序列化……,其作用是能將資料物件存入位元組流當中,在需要時重新生成物件。主要應用是利用外部儲存設 備儲存物件狀 態,以及通過網路傳輸物件等。
      • 第二種:在Android系統中,定位為針對記憶體受限的裝置,因此對效能要求更高,另外系統中採用了新的IPC(程式間通訊)機制,必然要求使用效能更出色的物件傳輸方式。

1.0.1.2 Activity如與Service通訊?Service的生命週期與啟動方法由什麼區別?

可以通過bindService的方式,先在Activity裡實現一個ServiceConnection介面,並將該介面傳遞給bindService()方法,在ServiceConnection介面的onServiceConnected()方法
裡執行相關操作。

Service的生命週期與啟動方法由什麼區別?
    startService():開啟Service,呼叫者退出後Service仍然存在。
    bindService():開啟Service,呼叫者退出後Service也隨即退出。

Service生命週期:
    只是用startService()啟動服務:onCreate() -> onStartCommand() -> onDestory
    只是用bindService()繫結服務:onCreate() -> onBind() -> onUnBind() -> onDestory
    同時使用startService()啟動服務與bindService()繫結服務:onCreate() -> onStartCommnad() -> onBind() -> onUnBind() -> onDestory
複製程式碼

1.1.0.4 廣播有哪些註冊方式?有什麼區別?廣播傳送和接收原理是什麼[binder如何運作的]?

  • 廣播有哪些註冊方式?
    • 靜態註冊:常駐系統,不受元件生命週期影響,即便應用退出,廣播還是可以被接收,耗電、佔記憶體。
    • 動態註冊:非常駐,跟隨元件的生命變化,元件結束,廣播結束。在元件結束前,需要先移除廣播,否則容易造成記憶體洩漏。
  • 廣播傳送和接收原理是什麼[binder如何運作的]?
    • 繼承BroadcastReceiver,重寫onReceive()方法。
    • 通過Binder機制向ActivityManagerService註冊廣播。
    • 通過Binder機制向ActivityMangerService傳送廣播。
    • ActivityManagerService查詢符合相應條件的廣播(IntentFilter/Permission)的BroadcastReceiver,將廣播傳送到BroadcastReceiver所在的訊息佇列中。
    • BroadcastReceiver所在訊息佇列拿到此廣播後,回撥它的onReceive()方法。

1.0.4.1 mipmap系列中xxxhdpi、xxhdpi、xhdpi、hdpi、mdpi和ldpi存在怎樣的關係?

  • 表示不同密度的圖片資源,畫素從高到低依次排序為xxxhdpi>xxhdpi>xhdpi>hdpi>mdpi>ldpi,根據手機的dpi不同載入不同密度的圖片

1.0.4.2 res目錄和assets目錄的區別?

  • assets:不會在 R檔案中生成相應標記,存放到這裡的資源在打包時會打包到程式安裝包中。(通過 AssetManager 類訪問這些檔案)
  • res:會在 R 檔案中生成 id標記,資源在打包時如果使用到則打包到安裝包中,未用到不會打入安裝包中。
  • res/anim:存放動畫資源
  • res/raw:和 asset下檔案一樣,打包時直接打入程式安裝包中(會對映到 R檔案中)

1.0.4.3 Context是什麼?Context有哪些型別,分別作用是什麼?Context下有哪些子類?哪些場景只能用activity上下文?

  • Context是什麼?
    • Context是一個抽象基類。在翻譯為上下文,也可以理解為環境,是提供一些程式的執行環境基礎資訊。
  • Context有哪些型別,分別作用是什麼?
    • Context下有兩個子類,ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類。
    • ContextWrapper又有三個直接的子類,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity,所以Activity和Service以及Application的Context是不一樣的,只有Activity需要主題,Service不需要主題。
  • Context下有哪些子類,主要是幹什麼的?
    • Context一共有三種型別,分別是Application、Activity和Service。
    • 這三個類雖然分別各種承擔著不同的作用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種型別的Context都是可以通用的。
    • 不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是系統級別吐司),因此在這種場景下,我們只能使用Activity型別的Context,否則將會出錯。

1.0.4.4 ActivityThread的main()的流程大概是怎麼樣的?

  • ActivityThread的main()的流程大概是怎麼樣的?
    • image

1.0.5.0 序列化的方式有哪些?效率對比有何優勢?如何做效能上分析的?

  • 序列化的方式有哪些
    • Parcelable
      • Parcelable是Android特有的一個實現序列化的介面,在Parcel內部包裝了可序列化的資料,可以在Binder中自由傳輸。序列化的功能由writeToParcel方法來完成,最終通過Parcel的一系列write方法完成。反序列化功能由CREAOR來完成,其內部標明瞭如何建立序列化物件和陣列,並通過Parcel的一系列read方法來完成反序列化的過程。
    • Serializable
      • Serializable是Java提供的一個序列化介面,是一個空介面,用於標示物件是否可以支援序列化,通過ObjectOutputStrean及ObjectInputStream實現序列化和反序列化的過程。注意可以為需要序列化的物件設定一個serialVersionUID,在反序列化的時候系統會檢測檔案中的serialVersionUID是否與當前類的值一致,如果不一致則說明類發生了修改,反序列化失敗。因此對於可能會修改的類最好指定serialVersionUID的值。

1.0.5.9 介面的重新整理為什麼需16.6ms?畫面的顯示需要哪些步驟?介面保持不變時還會16.6ms重新整理一次螢幕嗎?

  • 介面的重新整理為什麼需16.6ms?
    • 系統每16.6ms會發出一個VSYNC訊號,發出訊號後,才會開始進行測量、佈局和繪製。
    • 發出VSYNC訊號時,還會將此時顯示器的buffer緩衝區的資料取出,並顯示在螢幕上。
  • 畫面的顯示需要哪些步驟?
    • CPU計算資料(View樹遍歷並執行三大流程:測量、佈局和繪製),然後將資料交給GPU“
    • GPU渲染處理,然後將資料放到Buffer中。
    • 螢幕(display)從buffer中取出資料,並進行顯示。
  • 介面保持不變時還會16.6ms重新整理一次螢幕嗎?技術部落格大總結
    • 對於底層顯示器,每間隔16.6ms接收到VSYNC訊號時,就會用buffer中資料進行一次顯示。所以一定會重新整理。
  • 介面重新整理的本質流程
    • 通過ViewRootImpl的scheduleTraversals()進行介面的三大流程。
    • 呼叫到scheduleTraversals()時不會立即執行,而是將該操作儲存到待執行佇列中。並給底層的重新整理訊號註冊監聽。
    • 當VSYNC訊號到來時,會從待執行佇列中取出對應的scheduleTraversals()操作,並將其加入到主執行緒的訊息佇列中。
    • 主執行緒從訊息佇列中取出並執行三大流程: onMeasure()-onLayout()-onDraw()

1.0.6.0 Android中日誌級別有哪幾種?開發中需要注意什麼問題,列印日誌原始碼分析原理是什麼?

  • Android中日誌級別有哪幾種?
    • 1.Log.v 的輸出顏色為黑色的,輸出大於或等於VERBOSE日誌級別的資訊,也就是可見級別,一般是最低的資訊提示
    • 2.Log.d的輸出顏色是藍色的,也就是調式級別,一般不會中止程式,一般是程式設計師為了除錯而列印的log
    • 3.Log.i的輸出為綠色,輸出大於或等於INFO日誌級別的資訊,也就是資訊界級別,不會中止程式,一般是系統中執行操作的資訊提示
    • 4.Log.w的輸出為橙色, 輸出大於或等於WARN日誌級別的資訊,也就是警告級別,一般不會中止程式,但是可能會影響程式執行結果
    • 5.Log.e的輸出為紅色,僅輸出ERROR日誌級別的資訊,也就是錯誤級別,一般會中止程式執行,是最嚴重的Log級別。
    • 解釋:
      • verbose
      • debug除錯
      • info資訊
      • warn警告
      • error誤差
  • 通過檢視原始碼我們發現Log類中所有的靜態日誌方法Log.v(),Log.d(),Log.i(),Log.w(),Log.e()等方法都是底層都是呼叫了println方法,然後在原始碼中檢視,其實其內部呼叫的是println_native方法,也就是通過JNI呼叫底層的c++輸出日誌。

關於其他內容介紹

01.關於部落格彙總連結

02.關於我的部落格

相關文章