Android筆記—2018.08.15

1、假設讓你設計一個圖片加載器,你會如何設計?

ImageLoad

大致流程如下:

1、檢查內存緩存,如有,則返回。

2、后臺線程開始后續工作

3、檢查是否在未解碼內存緩存中。如有,解碼,變換,返回,然后緩存到內存緩存中

4、檢查是否在磁盤緩存中,如有,變換,返回,然后緩存到未解碼緩存和內存緩存中。

5、從網絡或者本地加載。加載完成后,解碼,變換,返回。存到各個緩存中。

2、Activity、Window、View三者的關系?

從Activity的源碼中:Activity.attch() -> PolicyManager -> Policy -> PhoneWindow ->??mLayoutInflater.inflate()&mContentParent.addView() ? 這知識一個簡單的跟蹤過程描述,通過源碼就可以明確的看出三者之間的關系。

Activity是整個模型的控制單元,Window屬于承載模型,負責承載視圖,View是視圖顯示模型。

View是Android中的視圖呈現方式,但是View不能單獨存在,它必須依附在Window這個抽象的概念上面,因此有視圖的地方就有Window。包括Activity,Dialog,Toast都是提供視圖的地方,甚至依托Window而實現的視圖,比如PopupWindow,Menu。有視圖的地方就有Window,因此Activity、Dialog、Toast等視圖都對應一個Window。

View綁定到Window上的:Window和View之間的額紐帶是ViewRoot。ViewRoot對應于ViewRootImpl類,它是鏈接WindowManager和Decorview的紐帶,View的三大流程(measure、layout、draw)均是通過ViewRoot來完成的。在ActivityThread中,當Activity對象被創建完成后,會將DecorView添加到Window中,同時會創建ViewRootImpl對象,并將ViewRootImpl對象和DecorView建立關聯。

他們三者關系可以比喻為:Activity像個工匠(控制單元),Window像窗口(承載模型),View像窗花(顯示視圖)。

1)、一個Activiy構造的時候會初始化一個Window,準確的說是一個PhoneWindow。

2)、這個PhoneWindow有一個ViewRoot,ViewRoot是一個View或者說ViewGroup,是最初始得根視圖。

3)、ViewRoot通過addView方法 來添加一個個View。比如TextView,Button等。

4)、這些View的時間監聽,是由WindowManagerService來接受消息,并且回調Activity函數。比如onClickListener、onKeyDown等。

3、Fragment的生命周期

Fragment/Activity LifeCycle

這張圖是Fragment生命周期和Activity生命周期對比圖,可以看出兩者有很多相似的地方。比如都有onCreate(),onStart(),onPause(),onDestroy()等等,因為Fragment是被托管在Activity中的,所以多了兩個onAttach()和onDetach()。

onAttach()? ->? Fragment 和Activity建立關聯的時候調用,被附加到Activity中去。

onCreate()? -> 系統會在創建Fragment的時候調用此方法。可以初始化一段資源文件等等

onCreateView() -> 系統會在Fragment首次繪制其用戶界面的時候調用此方法。要想為Fragment繪制UI,從該方法中返回的VIew必須是Fragment布局的根視圖。如果Fragment未提供UI,可以返回null

onViewCreated() ? ->? 在Fragment被繪制后,調用此方法,可以初始化空間資源。

onActivityCreated() -> 當onCreate(),onCreateView(),onViewCreated()方法執行完后調用,也就是Activity被渲染繪制出來后。

onPause()? ->? 系統將此方法作為用戶離開Fragment的第一個信號(但并不總是意味著此Fragment會被銷毀)進行調用,通常可以在此方法內確認在當前用戶會話結束后仍然有效的任何更改(因為用戶可能不會返回)。

onDestroyView()? ->? Fragment中的布局被移除時調用。

onDetach()? ->? Fragment和Activity解除關聯的時候調用。

注: 除了onCreateView,其他的所有方法如果重寫,必須調用父類對于該方法的實現。

4、自定義Vuew是一種什么樣的流程?

當Activity接收到焦點的時候,它會被請求繪制布局,該請求會Android framework 處理,繪制是從根節點開始,對布局樹進行measure和draw。整個View樹的繪制流程在ViewRoot.java類的performTravesals()函數展開,該函數所做的工作可簡單概括為是否需要重新計算視圖大小(measure)、是否需要重新安置視圖的位置(layout)、以及是否需要重繪(draw)。

自定義View

5、Adnroid中事件機制

1、基礎知識

(1)所有Touch事件都被封裝到MotionEvent對象,包括Touch的位置、事件、歷史記錄以及第幾個手指(多點出發)等

(2)事件類型分為: ??ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL、ACTION_POINTER_DOWN、ACTION_POINTER_UP,每個事件都是以ACTION_DOWN開始ACTION_UP結束。

(3)對事件的處理包括三類:傳遞--->dispatchTouchEvent()函數、攔截--->onInterceptTouchEvent()函數、消費--->onTouchEvent()函數和OnTouchListener()。

2、傳遞流程

(1)事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或者攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子VIew 可以通用onTouchEvent()對事件進行處理。

(2)事件有父View(ViewGroup)傳遞給子View、ViewGroup可以通過onInterceptTouchEvent()對事件進行攔截,停止其往下傳遞。

(3)如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View沒有消費事件,事件會被反向往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最后會到Activity的onTouch()函數。

(4)如果View沒有對ACTION_DOWN進行消費,之后的其他事件不會傳遞過來。

? (5) OnTouchListener 優先于onTouchEvent()對事件進行消費。

以上的消費即相應的函數返回值為? true


view不消費事件


view消費事件

6、Android的啟動模式:區別和應用場景

1)、standard :標準模式,系統默認模式。每次啟動一個Activiy都會創建一個新的實例,不管這個實例是否已經存在。這個模式下,誰啟動了Activity,那么這個Activity就運行在啟動它的那個Activity所在的棧中。

場景:郵件客戶端,在新建一個郵件的時候,適合新建一個新的實例。

2)、singleTop:棧頂復用模式。這種模式下,如果新的Activity已經位于任務棧頂,那么這個Activiy不會被重新創建,同時它的onNewIntent方法會被回掉,通過此方法的參數我們可以取出當前的請求信息。

場景:消息推送,通知欄彈出Notification,點擊Notification添磚到指定的Activity,避免生成重復的頁面。登錄的時候,登錄頁面跳轉主頁,重復點擊登錄按鈕,避免生成兩個主頁。從Activity A啟動了一個service進行耗時操作,或者某種監聽,這個時候按home鍵了,service收集到信息,要返回Activity A。

3)、singleTask:棧內復用模式。這是一種單利模式,在這種模式下,只要Activiy在一個棧中存在,那么多次啟動此Activity都不會創建實例,和singleTop是一樣,系統也會調用onNewIntent。還有一點,就是singleTask有clearTop的效果,會導致棧內已有的Activity全部出棧。

場景:提供給第三方應用調用的頁面,做瀏覽器、微博之類的應用,瀏覽器的主要頁面等等。程序的主頁面,進入多層嵌套之后,一鍵退回,之前的打開的Activity全部出棧。

4)、singleInstance:單一實例模式。這是一種加強版的singleTask模式,它除了具有singleTask的所有特性意外,還加強了一點,那就是具有此模式的Activity只能單獨位于一個任務棧中,比如Activty A是singIeInstance模式,當A啟動后,系統會為它創建一個新的任務棧,然后A獨自在這個新的任務棧中,由于棧內復用的特性,后續均不會創建新的Activiy,除非這個獨特的任務棧被系統銷毀。整個手機操作系統里面只有一個實例存在,不同的應用去打開這個Activity共享公用的同一個Activity。他會運行在自己單獨,獨立的任務棧里面,并且任務棧里面只有他一個實例存在。

場景:呼叫來電界面,打電話、發短信功能。鬧鐘提醒,將鬧鐘提醒和鬧鐘設置分離

7、Android 內存泄漏怎么處理、如何排查

1)、集合類泄露

集合類如果僅僅只有添加元素的方法,而沒有相應的刪除機制的,導致內存被占用。如果這個集合類是全局性的變量(比如類中的靜態屬性,全局性的map等即有靜態引用或final一直指向它),那么沒有相應的刪除機制,很可能導致集合所占內存只增不減。

2)、單例造成的內存泄露

由于單例的靜態特性使得其生命周期跟應用的生命周期一樣長,所以如果使用不恰當的話,很容易造成內存泄漏。

3)非靜態內部類創建靜態實例造成的內存泄漏? 匿名內部類 ? Handler 造成的內存泄漏

Handler的使用造成的內存泄漏問題應該說是最為常見了,很多時候我們為了避免ANR而不在主線程進行耗時操作,在處理網絡任務或者封裝一些請求回掉等API都借助Handler來處理,但是Handler不是萬能的,對于Handler的使用代碼編寫一不規范即有可能造成內存泄漏。另外,我們知道Handler、Message和MessageQueue都是相互關聯在一起的,萬一Handler發送的Message尚未被處理,則該Message及發送它的Handler對象將被線程MessageQueue一直持有。? 由于Handler屬于TLS(Thread Local Storage)變量,生命周期和Activity是不一致的。因此這種實現方式一般很難保證跟View或者Activity的生命周期保持一致,故很容易導致無法正確釋放。

盡量避免使用static成員變量 ? 避免override finalize()

a、finalize方法被執行的時間不確定,不能依賴與它來釋放緊缺的資源。時間不確定的原因是:虛擬機調用GC時間不確定, Finalize daemon線程被調度到的時間不確定。

b、finalize 方法只會被執行一次,即使對象被復活,如果已經執行過了finalize方法,再次被GC時也不會被執行了,原因是:含有finalize方法的object是在new的時候由虛擬機生成了一個finalize reference 在來引用到該Object的,而在finalize方法執行的時候,該object所對應的finalize Reference會被釋放掉,即使在這個時候把object復活(即用強引用引用該object),再第二次被GC的時候由于沒有了finalize reference與之對應,所以finalize 方法不會被執行。

c、含有Finalize方法的object需要至少經過兩輪GC才可能被釋放掉。

對于使用BraodcastReceiver、ContentObserver、File、游標Cursor、Stream、Bitmap等資源的使用,應該再Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。---------------------一些不良代碼造成的內存壓力。有些代碼并不造成內存泄漏,但是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存。

8、反射(Reflection)和注解(Annotation)的作用和原理

1、什么是反射(Reflection)

Java類是被Java虛擬機加載,如果java類不被java虛擬機加載,就不能正常的運行,正常情況下,我們運行所有程序再編譯期時候就已經把那個類被加載了。Java的 反射機制是在編譯時并不確定是哪個類被加載了,而是再程序運行的時候才被加載、探知、自審。使用的是再編譯期并不知道的類,這就是JAVA反射的特點。

2、反射的作用

Java反射機制它知道類的基本結構,這種將Java類結構探知的能力,我們稱為Java類的“自審”。如eclipse中,一按點,編譯工具就會自動的把該對象能夠使用的所有的方法和屬性全部都列出來,供用戶進行選擇,這就是利用反射機制,做到代碼的智能提示。 ? ? ------>> 舉個例子:假如有兩個程序員,一個程序員在寫程序的時候需要使用第二個程序員所寫的類,但是第二個程序員還沒完成他所寫的類。那么第一個程序員的代碼是不能通過編譯的。此時,利用Java反射的機制,就可以讓第一個程序員在沒有得到第二個程序員所寫的類的時候,來完成自身代碼的編譯。

3、反射使用場景

a、工廠模式:Factory類中用反射的話,添加了一個新的類之后,就不需要再修改工廠類Factory了。

b、數據庫JDBC中通過Class.forName(Driver).來獲得數據庫的連接驅動

c、分析類文件:畢竟能得到類中的方法等等。

d、訪問一些不能訪問的變量和屬性:破解別人代碼

4、什么是注解

注解是Java 的一個新特性。注解是插入你代碼中的一種注釋或者說是一種元數據(meta data)。這些注解信息可以再編譯期使用預編譯工具進行處理(pre-compiler tools)。也可以在運行期使用java反射機制進行處理。

Annotation對程式運行沒有影響,它的目的是讓編譯器或分析工具說明程式的某些咨詢,您可以在包、類方法、成員等加上Annotation,每一個Annotation對應于一個實際的Annotation型態,您可以從java.lang.Override,java.lang.Deprecated、java.lang.SuppressWarnings這三個j2SE 中標準的Annotation型態開始了解Annotation的作用。

5、限定Override 父類方法@Override

java.lang.Override是J2SE 5.0中標準的Annotation型態直以,它對編譯期說明某個方法必須是重新定義父類別中的方法,編譯器得知這項資訊后,在編譯程序時如果發現被@Override標示的方法并非重新定義父類別中的方法,就會報錯。

java.lang.Override是Marker Annotation,簡單的說就是用于標示的Annotation,Annotation名稱本身即表示了要給工具程式的資訊,例如Override這個名稱告知編譯器,被@Override標示的方法必須是重新定義父類別中的同名方法。

6、標示方法Deprecated @Deprecated

java.lang.Deprecated也是Mark annotation,簡單的說就是用于表示Annotaion名稱本身即包括了要給工具程式的資訊,例如Deprecated這個名稱告知編譯器,被@Deprecated標示的方法是一個不建議被使用的方法,如果有開發人員不小心使用了被@Deprecated標示的方法,編譯器要提出提示提醒開發人員。

7、抑制編譯器提示@SuppressWarnings

java.lang.SuppressWarnings是J2SE 5.0中標準Annotation型態直以,它對編譯期說明某個方法中若有提示訊息,則加以抑制,不用在編譯完成后出現提示,不過事實上這個功能在Sun JDK 5.0中沒有實現出來。

8、注解使用場景

設計一個原始碼分析工具,分析代碼等;日志信息打

9、進程和線程關系,在Android中activity A和activity B這兩者是不是聽一個線程中。

進程一般是指一個執行的單元,在PC和一定設備上指的是一個程序或者一個應用,一個進程可以包含多個進程,因此進程和線程是包含和被包含的關系,最簡單的情況下,一個進程中可以只有一個線程,即主線程,在Android中主線程也叫UI線程,只有在Ui線程中才能操作界面元素,但不能執行耗時操作任務。 ? 當某個應用組件啟動且應用沒有運行其他任何組件時,Android系統會使用單個執行線程為應用啟動新的Linux進程。默認情況下,用一個應用的所有組件在相同的進程和線程(稱為“主”線程)中運行。如果某個應用組件啟動且該應用已存在進程(因為存在該應用的其他組件),則該組件會在此進程內啟動并使用相同的執行線程。但是 你可以安排應用中的其他組件在單獨的進程中運行,并為任何進程創建額外的線程。

1、進程

Android系統將盡量長時間地保持應用進程,但為了新建進程或運行更重要的進程,最終需要移除舊進程來回收內存。為了確定保留或終止那些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。必要時,系統會首先消除重要性最低的進程,然后時重要性略遜的進程,以此類推,回收系統資源。

2、線程

應用啟動時,系統會為應用創建一個名為“主線程”的執行線程。此線程非常重要,因為它負責將時間分派給相應的用戶界面小部件,其中包括繪圖事件。此外,它也是應用也Android UI工具包組件(來自android.widget和android.view軟件包的組件)進行交互的線程。因此主線程有時也稱為UI線程。系統不會為每個組件實例創建單獨的線程。運行于用一個進程的所有組件均在UI線程中實例化,并且對每個組件的系統調用均由該線程進行分派。因此,響應系統回調方法(例如:報告用戶操作的onKeyDown或生命周期回調方法)始終在進程的UI線程中運行。

例如:當用戶觸摸屏幕上的按鈕時,應用的UI線程會將觸摸事件分派給小部件,而小部件反過來又設置其按下狀態,并將失效請求發布到事件隊列中。UI線程從隊列中取消該請求并通知小部件應該重繪自身。

activity A和activity B是在相同的線程中,都被加載到UI主線程中。

10、hashmap的原理。hashcode和equal的區別

1、什么時候使用HashMap?它的特點

HashMap是基于Map接口的實現,存儲鍵值對時,它可以接收null鍵值,是非同步的,HasMap存儲的這Entry(hash, key,value,next)對象。

2、HashMap工作原理

通過hash的方法,通過put和get存儲和獲取對象。存儲對象時,我們將K/V傳給put方法時,它調用hashCode計算hash從而得到bucket位置,進一步存儲,HashMap會根據當前bucket的占用情況自動調整容量(超過Load Facotr則resize為原來的2倍)。獲取對象時,我們將K傳給get,它調用hashCode計算hash從而得到bucket位置,并進一步調用equals()方法確定鍵值對。如果發生碰撞的時候,Hashmap通過鏈表將產生碰撞沖突的元素組織起來,在Java8中,如果一個bucket中碰撞沖突的元素超過某個限制(默認是8),則使用紅黑樹來代替鏈表,從而提高速度。

3、get和put的原理,equals()和hashCode()的作用

通過對key的hashCode()進行hashing,并計算下表(n-1 & hash),從而獲取buckets的位置。如果產生碰撞,則利用key.equals()方法去鏈表或者樹中查找對應的節點。

4、hash的實現

在Java 1.8的實現中,是通過hashCode()的高16位異或低16實現的:(h=k.hashCode())^(h>>>16),主要是從速度、功效、質量來考慮的,這么做可以在bucket的n比較小的時候,也能保證考慮到高低bit都參與到hash的計算中,同時也不會有太大的開銷。

5、如果HashMap的大小超過了負載因子(load factor)定義的容量, 怎么辦?

如果超過了負載因子(默認0.75),則會重新resize一個原來長度兩倍的HasMap,并且重新調用hash方法。

String、Interger這樣的wrapper類作為HashMap的鍵是再合適不過了,而且String最為常用。應為String是不可變的,也是final的,而且已經重寫了equals()和hashCode()方法了。其他的wrapper類也有這個特點。不可變性是必要的,因為為了要計算hashCode(),就要防止鍵值改變,如果鍵值再放入時和獲取時返回不同的hashcode的話,那么就不能從HashMap中找到你想要的對象。不可變性還有其他的有點如線程安全。如果你可以僅僅通過將某個field聲明成final就能保證hashCode是不變的,那么就可以這么做。因為獲取對象的時候需要同時equals()和hashCode()方法,那么鍵對象正確的重寫是這個方法是非常重要的。如果兩個不相等的對象返回不同的hashcode的話,那么碰撞的幾率就會小些,這樣就能提高HashMap的性能。? 當然針對自定義對象也是可以作為鍵的,只要該對象遵守equals()和hashCode()方法的定義規則,并且當對象插入到Map中之后將不會發生改變。如果這個對象是不可變的那么它就滿足了作為鍵的條件。

CocurrentHashMap和Hashtable

ConcurrentHashMap越來越多的使用,Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因為它僅僅根據同步級別對map的一部分進行上鎖。ConcurrentHashmap是可以替代Hashtable,但是HashTable提供更強的線程安全性。

hashcode和equal區別

在java中任何一個對象都具備equals(Object obj)和hashcode()這兩個方法,因為他們是在Object類中定義的。

equals(Object obj)方法用來判斷兩個對象是否“相同”,如果“相同“則返回true,否則返回false

hashCode()方法返回一個int數,在Object類中默認實現是”將該對象的內部地址轉換成一個整數返回“。

a、如果兩個對象equals,java運行環境會認為他們hashcode一定相等。

b、如果兩個對象不equls,他們的hashcode可能相等。

c、如果兩個對象hashcode相等,他們不一定eqlus。

d、如果兩個對象hashcode不相等,他們一定不equls。

11、設計模式六大原則

1、單一原則。就是說一個類只有一個明確的職責,不易過多職責封裝在一個類里面。

2、開閉原則。對于修改是關閉的,對于擴展是開放的,這樣主要目的是提高可擴展性。

3、依賴倒置原則。高層不依賴于底層,兩者都依賴于抽象,抽象不依賴于細節實現,具體實現依賴于抽象,也就是說要面向接口編程。

4、里氏替換原則。也就是說子類運行的功能,父類也能運行,強調集成的重要性。

5、迪米特原則。一個類要了解另一個類最少的內容,強調低耦合,耦合分解。

6、接口隔離原則。一個類不要實現不需要的接口,接口可拆分,不要冗余在一個總接口中,實現自己所需的接口即可。

12、動態代理

代理是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個對象的訪問。代理類負責為委托類預處理消息,過濾消息并轉發消息,以及進行消息被委托類執行后的后續處理。

代理模式

為了保護行為的一致性,帶里類和委托類通常會實現相同的接口,所以在訪問這看來兩者沒有絲毫的區別。通過代理類這中間層,能有效的控制對委托類對象的直接訪問,也可以很好的隱藏和保護委托類對象,同時也為實施不同控制策略預留了空間,從而在設計上獲得了更大的靈活性。Java動態代理機制以巧妙的方式近乎完美的實踐了代理模式的設計理念。

靜態代理:代理類是在編譯時就實現好的。也就是說Java編譯完成后代理類時一個實際的class文件

動態代理:代理類實在運行時生成的。也就是說Java編譯完之后并沒有實際的class文件,而是在運行時動態生成類字節碼,并加載到JVM中。

一個典型的動態代理創建對象過程可分為四個步驟

1、通過實現InvocationHandler接口創建自己的調用處理器IvocationHandler handler = new InvocationHandlerImpl(....);

2、通過為Proxy類指定ClassLoader對象和一組interface創建動態代理類Class clazz = Proxy.getProxtClass(classLoader,new Class[]{......});

3、通過反射機制獲取動態代理類的構造函數,其參數類型和調用處理器接口類型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通過構造行數創建代理類實例,此時需將調用處理器對象作為參數被傳入Interface proxy=(Interface)constructor.newInstance(new Object[]{handler});

為了簡化對象創建過程,Proxy類中的newInstance方法封裝了2~4,只需要兩步即可完成代理對象的創建。? 生成的ProxySubject繼承Proxy類實現了Subject接口,實現的Subject的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的方法(Object result= method.invoke(proxied,args))

Java實現動態代理的缺點:因為Java的單集成特性(每個代理類都繼承了Proxy類),只能針對接口創建代理類,不能針對類創建代理類。

13、JVM、Java內存機制

JVM(java virtual machine)java虛擬機是運行Java程序必不可少的機制。JVM實現了Java語言最終要的特性:平臺無關性。

編譯后的Java程序指令并不直接在硬件西永的CPU上執行,而是由JVM執行。JVM屏蔽了與具體平臺相關的信息,使Java語言編譯程序只需要生成JVM上運行的目標字節碼(.class),就可以在多種平臺上不加修改的運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。因此實現Java平臺無關性。它是Java程序能在多平臺間進行無縫移植的可靠保證,同時也是Java程序的安全檢驗引擎(還進行安全檢查)。

JVM運行圖

14、android消息循環機制

Handler、Looper、Message 這三者都是Android異步消息處理線程相關的概念。異步消息處理線程啟動后會進入一個無限循環體內,每循環一次,從其內部的消息隊列中取出一個消息,然后回調相應的消息處理函數,執行完成一個消息后則繼續循環。若消息隊列為空,則線程會阻塞等待。

Android消息機制主要就是Handler的運行機制,Handler運行需要底層的MessageQueue和Looper支撐。其中MessageQueue采用的是單鏈表的結構,Looper可以叫做消息循環。由于MessageQueue只是一個消息存儲單元,不能處理消息,而Looper就是專門處理消息的,Looper會以無限循環的形式去查找是否有新消息,如果有的話就處理,否則一直等待著。

Handler創建的時候會采用當前線程的Looper來構造消息循環系統,需要注意的是,線程默認沒有Looper的,如果需要使用Handler就必須為線程創建Looper,因為默認的UI線程,也就是ActivityThread,ActivityThread被創建的時候就會初始化Looper,這也就是在主線程中默認可以使用Handler原因。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容