Android M Dialer完全總結

歡迎轉(zhuǎn)載评矩,但請保留作者鏈接:http://www.reibang.com/p/ca4ab4e9817f
作為Dialer Owner块请,作一下基于M版本的總結吧榜掌。
在線源碼閱讀:http://androidxref.com

總體輪廓

手機之所以被稱為手機窘游,是因為它是一個通訊工具,而完成這一核心功能的軟件模塊么伯,即為Telephony。
Telephony包含的范圍非常廣泛卡儒,單拿上層來說田柔,大致可以劃分成五大部分:Telephony應用(Dialer俐巴、ContactsMms)硬爆,service Telephony和service Telecomm欣舵,framework Telephony和framework Telecomm。
現(xiàn)在這一架構的主要變化是從L版本開始的缀磕,相較舊版的主要變遷可以參考:Android 4.4 Kitkat Phone工作流程淺析(十二)__4.4小結與5.0概覽


圖片資料

本文只關注Dialer缘圈,那么先看幾張Nexus 6p的實機截圖來個感性的認識:

Dialer_示例1

Dialer_示例2

Dialer_示例3

架構分析

Dialer主要涉及的包有:
1)/packages/apps下
DialerInCallUI袜蚕,ContactsCommon糟把,PhoneCommonVoiceDialer

憑借makefile牲剃,分包可以非常的自由隨意遣疯,看如下片斷:

6incallui_dir := ../InCallUI
7contacts_common_dir := ../ContactsCommon
8phone_common_dir := ../PhoneCommon
9
10src_dirs := src \
11    $(incallui_dir)/src \
12    $(contacts_common_dir)/src \
13    $(phone_common_dir)/src

DialerInCallUI凿傅,PhoneCommon缠犀,ContactsCommon全都在src_dirs路徑下了,于是最終的Dialer.apk由這四個包下的代碼編譯生成聪舒。

VoiceDialer提供語音相關功能辨液,入口看下圖:

VoiceDialer

但是,此功能侵略性過強过椎,在天朝是基本殘廢的室梅,在海外多數(shù)運營商也不喜歡表示要去除,所以不予關注疚宇。

2)/packages/services下
Mms亡鼠,TelephonyTelecomm敷待,生成MmsService.apk间涵,Telecom.apk與TeleService.apk,對Dialer來說是提供通話菜單功能的榜揖。
應該說不管從邏輯還是物理上勾哩,切分出來都是大有好處,這樣才能讓Android能夠良好支持第三方通訊類應用举哟。

3)/packages/providers下
TelephonyProvider思劳,ContactsProvider,數(shù)據(jù)創(chuàng)建及查詢妨猩,當然也是要切分的部分潜叛。

4)frameworks/opt和frameworks/base下
telephony等和上面類似的眼熟名字,具體關系到各種功能點如MmiCode,Clear Code威兜,Number match销斟,Number format,DTMF椒舵,F(xiàn)DN等等等等蚂踊。


具體分析

看完整體架構之后,單單一個Dialer包的定位也變得很清晰了:它就只是一個撥號器而已笔宿。

1.層次結構

Dialer的UI是否美觀是個見仁見智的問題犁钟,我個人還是挺喜歡的。

這里我們只談其實現(xiàn)原理措伐。
這是Dialer的主layout dialtacts_activity.xml:

16<FrameLayout
17    xmlns:android="http://schemas.android.com/apk/res/android"
18    android:id="@+id/dialtacts_mainlayout"
19    android:layout_width="match_parent"
20    android:layout_height="match_parent"
21    android:orientation="vertical"
22    android:focusable="true"
23    android:focusableInTouchMode="true"
24    android:clipChildren="false"
25    android:background="@color/background_dialer_light">
26
27    <FrameLayout
28        android:id="@+id/dialtacts_container"
29        android:layout_width="match_parent"
30        android:layout_height="match_parent"
31        android:clipChildren="false">
32        <!-- The main contacts grid -->
33        <FrameLayout
34            android:layout_height="match_parent"
35            android:layout_width="match_parent"
36            android:id="@+id/dialtacts_frame"
37            android:clipChildren="false" />
38    </FrameLayout>
39
40    <FrameLayout
41        android:id="@+id/floating_action_button_container"
42        android:background="@drawable/fab_blue"
43        android:layout_width="@dimen/floating_action_button_width"
44        android:layout_height="@dimen/floating_action_button_height"
45        android:layout_marginBottom="@dimen/floating_action_button_margin_bottom"
46        android:layout_gravity="center_horizontal|bottom">
47
48        <ImageButton
49            android:id="@+id/floating_action_button"
50            android:background="@drawable/floating_action_button"
51            android:layout_width="match_parent"
52            android:layout_height="match_parent"
53            android:contentDescription="@string/action_menu_dialpad_button"
54            android:src="@drawable/fab_ic_dial"/>
55
56    </FrameLayout>
57
58    <!-- Host container for the contact tile drag shadow -->
59    <FrameLayout
60        android:id="@+id/activity_overlay"
61        android:layout_height="match_parent"
62        android:layout_width="match_parent">
63        <ImageView
64            android:id="@+id/contact_tile_drag_shadow_overlay"
65            android:layout_width="wrap_content"
66            android:layout_height="wrap_content"
67            android:visibility="gone"
68            android:importantForAccessibility="no" />
69    </FrameLayout>
70
71</FrameLayout>

雖然不同情況下顯示的部分不同特纤,但總得來說以z軸從大到小做一個側(cè)向剖面圖排列的話,主要元素是這樣的:

Dialer UI剖面圖
層次結構
  • FAB button(FloatingActionButton)
    layout中R.id.floating_action_button_container位置即是侥加,并不是真正的FloatingActionButton,Google工程師手搓了一個看上去有著類似效果的控件而已粪躬。用來控制Dialpad的展開和收起担败。

  • Dialpad(Fragment)
    實現(xiàn)類為xref: /packages/apps/Dialer/src/com/android/dialer/dialpad/DialpadFragment.java,填充進R.id.dialtacts_container中镰官,自帶號碼輸入條提前,按下?lián)芴栤o后會構造相應Intent然后啟動service Telecomm中的不可見Activity如UserCallActivity(L中對應為CallActivity,M中為實現(xiàn)AFW泳唠,Android for Work模式引入)開始撥號處理流程狈网。

  • SearchUI(Fragment)
    填充在R.id.dialtacts_frame中,注意展開與收起Dialpad時笨腥,雖然肉眼感知不到拓哺,但卻會使用不同的Fragment來提供搜索結果頁面。一個是SmartDialSearchFragment脖母,另一個是RegularSearchFragment士鸥。
    這是因為,其在設計上還支持更強大的搜索功能谆级,能使用輸入法進行輸入:

    RegularSearchFragment

    國內(nèi)的一般都把這功能直接做掉了-_-||烤礁。

  • Content pages(Fragment)
    實現(xiàn)類為xref: /packages/apps/Dialer/src/com/android/dialer/list/ListsFragment.java,填充進R.id.dialtacts_frame肥照,因為時間上肯定比SearchUI要早脚仔,所以在其下面。ListsFragment中使用ViewPager又組織著三個Fragment作為之前圖示中的三個Tab頁(當滿足情況時舆绎,第四個頁面會出現(xiàn))鲤脏。這樣的嵌套是否是一個好的設計值得商榷。


2.撥號盤

實現(xiàn)代碼為/packages/apps/Dialer/src/com/android/dialer/dialpad/DialpadFragment.java
撥號盤分三個亿蒸,全都使用了PhoneCommon包中的同一套資源:

  • Dialer中一個
  • InCallUI中一個
  • KeyGuard中有個“緊急呼叫”按鈕凑兰,會調(diào)用到service Telephony中的一個弱化版撥號盤

關于UI:Google原生設計上這三處使用了一致的UI掌桩,所以直接復用即可。但是姑食,其他手機設計有很多都是不一樣的波岛,所以這里是一個客制化比較麻煩的點。

關于雙卡撥號:Google對于雙卡的支持就是:選擇默認卡->Dialer中按下?lián)芴?>使用默認卡撥號音半。國內(nèi)很多廠商的做法卻都是在Dialpad上提供兩個按鈕则拷,如卡一“中國移動”卡二“中國聯(lián)通”,需要按哪個鍵就用哪張卡進行撥號曹鸠。其實在撥號流程中的service Telecomm中的一環(huán)有使用一個關鍵值PhoneAccountHandle來進行判定使用哪張SIM卡煌茬,所以實現(xiàn)的方法也就只是很簡單地Intent傳值、取值彻桃、處理即可坛善。L中撥號流程為CallActivity#processOutgoingCallIntent->CallReceiver#processOutgoingCallIntent->CallsManager#startOutgoingCall,M中略有變動邻眷,開始的變?yōu)榱?code>UserCallActivity眠屎,往下找即可。

關于長按數(shù)字鍵快速撥號

華為手機樣圖1
華為手機樣圖2

如設置2鍵撥號119肆饶,則長按2能夠立即進行撥號改衩。這個功能很長一段時間以來由MTK的MTK Plugin提供,然而在L版本中Google提供了SpeedDial驯镊,即Dialer_示例1中的Tab頁面葫督,對聯(lián)系人收藏之后就變成了這個樣子,多了一個小卡片可以直接點擊呼叫:

SpeedDial

雖然本身完全不是同一個東西板惑,但MTK表示此功能將廢棄橄镜。
如果要自行實現(xiàn)的話,實現(xiàn)原理大略是這樣:遵循Fragment寄宿于Activity的思路寫一個類似組件洒放,關鍵回調(diào)中調(diào)用同名方法蛉鹿。建立一個數(shù)據(jù)庫,用戶設置相應鍵位的快速撥號時往湿,如果是設置號碼妖异,那就簡單保存號碼;如果設置的是聯(lián)系人领追,那么置標志位他膳,保存聯(lián)系人條目的主鍵,每次撥號或顯示時之前绒窑,使用該主鍵查詢數(shù)據(jù)庫獲取所需信息棕孙。


3.設置

設置頁的UI非常糟糕,毫無設計可言,而且層次多得不像話蟀俊,以下演示的是如何進行通話帳戶設置:

Dialer_示例4
Dialer_示例5
Dialer_示例6
Dialer_示例7

示例5跟6都只是簡單羅列多行單調(diào)的文字钦铺,而且風格還不統(tǒng)一,一個有分隔線另一個沒有(示例5在Dialer包中肢预,示例6則在services/Telephony中矛洞,可以推斷不是同一撥人做的,而且沒有溝通好烫映,于是出現(xiàn)了如此明顯的差異)沼本,只有示例7看出了點兒Material Design的影子,但是只有一個分類的話Category又變得沒有意義了锭沟。
我期待的設置頁是層次合理抽兆,有分類帶說明文字的,下圖來自于我的開源練習之作PureNote

示例5的頁面由Dialer中的DialerSettingsActivity.java提供族淮,并且辫红,會根據(jù)單雙卡而添加不同的Header,而往后的通話設置則主要由service Telephony提供支持祝辣,參見如下代碼:

59            // Show "Call Settings" if there is one SIM and "Phone Accounts" if there are more.
60            if (telephonyManager.getPhoneCount() <= 1) {
61                Header callSettingsHeader = new Header();
62                Intent callSettingsIntent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
63                callSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
64
65                callSettingsHeader.titleRes = R.string.call_settings_label;
66                callSettingsHeader.intent = callSettingsIntent;
67                target.add(callSettingsHeader);
68            } else {
69                Header phoneAccountSettingsHeader = new Header();
70                Intent phoneAccountSettingsIntent =
71                        new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
72                phoneAccountSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
73
74                phoneAccountSettingsHeader.titleRes = R.string.phone_account_settings_label;
75                phoneAccountSettingsHeader.intent = phoneAccountSettingsIntent;
76                target.add(phoneAccountSettingsHeader);
77            }

單卡為CallFeaturesSetting厉熟,雙卡為PhoneAccountSettingsActivityPhoneAccountSettingsActivity只是顯示兩行卡名较幌,如行一中國移動行二中國聯(lián)通,然后點擊后再復用CallFeaturesSetting白翻。


4.數(shù)據(jù)獲取

Dialer特色的自然就是CallLog部分還有SearchUI部分了乍炉。
關于SearchUI,可以參考這篇文章Android撥號搜索機制源碼分析(原)滤馍。
對于CallLog岛琼,其數(shù)據(jù)查詢與更新采用的是AsyncQueryHandler+ContentObserver,Google應該考慮用Loader來取代它們巢株。
參考Android4.4 Telephony流程分析——撥號應用(Dialer)的通話記錄加載過程槐瑞、Handler官方范例AsyncQueryHandler源碼解析

我只想說:讀CallLog的代碼(這里指得是數(shù)據(jù)獲取+內(nèi)容顯示),不啻于去地獄走一遭阁苞,一坨一坨的極為恐怖困檩。


代碼細節(jié)

Dialer中的一些代碼細節(jié)。

組合模式

設置監(jiān)聽器分兩種情況:setOnXXXListener那槽,addOnXXXListener悼沿,通常來講,后者要優(yōu)于前者骚灸,所以許多類都增加了add方法而廢棄了set方法糟趾。可假如說你使用的這個類現(xiàn)在只有set方法可得怎么辦呢?只需要使用組合模式即可達成效果义郑,具體使用場景可以參考ListsFragment蝶柿。以下代碼為一個示例:

public class ViewPagerListenersUtil implements ViewPager.OnPageChangeListener {
    private ArrayList<OnPageChangeListener> mOnPageChangeListeners = new rrayList<OnPageChangeListener>();

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        final int count = mOnPageChangeListeners.size();
        for (int i = 0; i < count; i++) {
            mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset,positionOffsetPixels);
        }
    }

    @Override
    public void onPageSelected(int position) {
        final int count = mOnPageChangeListeners.size();
        for (int i = 0; i < count; i++) {
            mOnPageChangeListeners.get(i).onPageSelected(position);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        final int count = mOnPageChangeListeners.size();
        for (int i = 0; i < count; i++) {
            mOnPageChangeListeners.get(i).onPageScrollStateChanged(state);
        }
    }

    public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
        if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
            mOnPageChangeListeners.add(onPageChangeListener);
        }
    }
}

異步設置TextWatcher

DialpadFragment中為了實現(xiàn)i18n號碼處理,需要給號碼輸入條添加一個TextWatcher非驮,Google工程師是這樣做的交汤,其中mDigits為TextView

370        PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);

跟蹤代碼:

26public final class PhoneNumberFormatter {
27    private PhoneNumberFormatter() {}
28
29    /**
30     * Load {@link TextWatcherLoadAsyncTask} in a worker thread and set it to a {@link TextView}.
31     */
32    private static class TextWatcherLoadAsyncTask extends
33            AsyncTask<Void, Void, PhoneNumberFormattingTextWatcher> {
34        private final String mCountryCode;
35        private final TextView mTextView;
36
37        public TextWatcherLoadAsyncTask(String countryCode, TextView textView) {
38            mCountryCode = countryCode;
39            mTextView = textView;
40        }
41
42        @Override
43        protected PhoneNumberFormattingTextWatcher doInBackground(Void... params) {
44            return new PhoneNumberFormattingTextWatcher(mCountryCode);
45        }
46
47        @Override
48        protected void onPostExecute(PhoneNumberFormattingTextWatcher watcher) {
49            if (watcher == null || isCancelled()) {
50                return; // May happen if we cancel the task.
51            }
52            // Setting a text changed listener is safe even after the view is detached.
53            mTextView.addTextChangedListener(watcher);
54
55            // Note changes the user made before onPostExecute() will not be formatted, but
56            // once they type the next letter we format the entire text, so it's not a big deal.
57            // (And loading PhoneNumberFormattingTextWatcher is usually fast enough.)
58            // We could use watcher.afterTextChanged(mTextView.getEditableText()) to force format
59            // the existing content here, but that could cause unwanted results.
60            // (e.g. the contact editor thinks the user changed the content, and would save
61            // when closed even when the user didn't make other changes.)
62        }
63    }
64
65    /**
66     * Delay-set {@link PhoneNumberFormattingTextWatcher} to a {@link TextView}.
67     */
68    public static final void setPhoneNumberFormattingTextWatcher(Context context,
69            TextView textView) {
70        new TextWatcherLoadAsyncTask(GeoUtil.getCurrentCountryIso(context), textView)
71                .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
72    }
73}

合一

Contacts_示例1
Contacts_示例2

王自如評價三星手機內(nèi)置應用時說“不同的產(chǎn)品經(jīng)理也許從來都沒有交流過,所以才會做出風格這么不統(tǒng)一的產(chǎn)品”院尔,這話放在Android M的Dialer與Contacts身上也是十分貼切啊蜻展,肉眼可辨的坑爹啊,強迫癥能忍么邀摆?

從功能上來講纵顾,你會發(fā)現(xiàn)與Dialer相比較,Contacts的存在感簡直弱爆了,它能做到的事情,Dialer全都能做击你。而且因為Contacts只能操作聯(lián)系人數(shù)據(jù)斤彼,幾乎讓人沒有想點它的興趣。

實際上蜡塌,在4.2版本中Contacts與Dialer就是同一個應用Contacts.apk,只不過分出了兩個應用入口來而已。當然蠕搜,雖然實際上是同一個應用,但在用戶感受上則是兩個收壕。

而國內(nèi)UI如MIUI還有錘子Rom都很明智地對這兩個應用進行了代碼與用戶感受上的“合一”妓灌,只不過小米是保留了兩個入口,但啟動的都是同一個應用蜜宪;而錘子是只提供Dialer應用虫埂,給你兩個應用的功能。華為最殘暴圃验,大手一揮把聯(lián)系不太大的Mms都合了掉伏,號稱“三合一”(不過,最新版本的EMUI又只有二合一了):

三合一

如何達到這一效果澳窑?不復用原生代碼的話斧散,自己刷刷刷開寫可以解決問題,但這實在是費力照捡。復用原生代碼的同時達到這一效果颅湘,主要就三點,一是前面提到過的makefile的修改栗精,刪除兩個makefile闯参,修改最后一個makefile讓它們編譯出一個應用來瞻鹏;二是修改manifest,善用alias讓應用能正常被使用鹿寨;最后則是應用重構想辦法讓它一個頂仨了新博。


推薦閱讀

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市脚草,隨后出現(xiàn)的幾起案子赫悄,更是在濱河造成了極大的恐慌,老刑警劉巖馏慨,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埂淮,死亡現(xiàn)場離奇詭異,居然都是意外死亡写隶,警方通過查閱死者的電腦和手機倔撞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慕趴,“玉大人痪蝇,你說我怎么就攤上這事∶岱浚” “怎么了躏啰?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耙册。 經(jīng)常有香客問我给僵,道長,這世上最難降的妖魔是什么详拙? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任想际,我火速辦了婚禮,結果婚禮上溪厘,老公的妹妹穿的比我還像新娘。我一直安慰自己牌柄,他們只是感情好畸悬,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著珊佣,像睡著了一般蹋宦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咒锻,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天冷冗,我揣著相機與錄音,去河邊找鬼惑艇。 笑死蒿辙,一個胖子當著我的面吹牛拇泛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播思灌,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼俺叭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泰偿?” 一聲冷哼從身側(cè)響起熄守,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耗跛,沒想到半個月后裕照,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡调塌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年晋南,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烟阐。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡搬俊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜒茄,到底是詐尸還是另有隱情唉擂,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布檀葛,位于F島的核電站玩祟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屿聋。R本人自食惡果不足惜空扎,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望润讥。 院中可真熱鬧转锈,春花似錦、人聲如沸楚殿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脆粥。三九已至砌溺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間变隔,已是汗流浹背规伐。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匣缘,地道東北人猖闪。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓鲜棠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親萧朝。 傳聞我的和親對象是個殘疾皇子岔留,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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

  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,432評論 0 17
  • 閑來無事寫了文章检柬,興起投了學校書刊的稿献联。年代變化的快,不知什么時候起何址,文章不看內(nèi)容甄選里逆,倒是愿意網(wǎng)絡拉票,敲鑼打鼓...
    又新閱讀 333評論 0 0
  • 童年的經(jīng)歷往往會對一個人的未來產(chǎn)生深遠地影響。 小時候偎血,大概是小學三年級開始寫日記诸衔,當時我特別喜歡寫,每天都寫颇玷,老...
    會飛的豬pengqing閱讀 535評論 2 3
  • 下雨的冬夜笨农,顯得特別的黝黑與陰沉,無邊的黑夜帖渠,仿佛要將大地上的一切一并吞噬谒亦。 加班回來,已是十點多空郊。洗了一個...
    一窗昏曉鎖流年閱讀 144評論 0 0