Launcher3 桌面加載流程分析(上)

省略一萬字前奏
如果大家沒有源碼购裙,
不介意的話廓推,可以參考https://github.com/Tic-pf/Launcher3-N-Folder 開發(fā)中

主入口Launcher

LauncherAppState

Launcher的onCreate里比較長劳淆,我們依次取代碼片段來分析聊倔,看oncrate方法的這一段,初始化LauncherAppState

public void onCreate() {
    ...
    LauncherAppState app = LauncherAppState.getInstance();
    ...
}
    

LauncherAppState是保存一些全局的匀们,核心的對(duì)象。主要有整個(gè)Launcher的工作臺(tái)workspace敢订,Launcher的控制器LauncherModel,應(yīng)用圖標(biāo)的緩存機(jī)制IconCache罢吃,設(shè)備的配置信息InvariantDeviceProfile等楚午。

構(gòu)造方法,首先初始化內(nèi)存的追蹤器TestingUtils尿招,記錄我們app的內(nèi)存信息醒叁,這個(gè)工具在我們開發(fā)其他app分析內(nèi)存信息時(shí)也是很有用的司浪。

private LauncherAppState() {
    ...

    if (TestingUtils.MEMORY_DUMP_ENABLED) {
        TestingUtils.startTrackingMemory(sContext);
    }

    mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
    mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
    mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);

    mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
    mModel = new LauncherModel(this, mIconCache, mAppFilter);

    LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

    // Register intent receivers
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_LOCALE_CHANGED);
    filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
    // For handling managed profiles
    filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
    filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
    filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE);
    filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE);

    sContext.registerReceiver(mModel, filter);
    ...
}         

LauncherAppsCompat里添加了一個(gè)應(yīng)用變化的回調(diào),由LauncherModel實(shí)現(xiàn)接口把沼,及時(shí)的響應(yīng)數(shù)據(jù)變化啊易。LauncherAppsCompat是獲取所有應(yīng)用,監(jiān)聽?wèi)?yīng)用變化的一個(gè)抽象饮睬,Android 5.0前后的版本獲取方式不一樣了租谈,這就是Launcher良好適配性的體現(xiàn)了。

        LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

Android 5.0以前的監(jiān)聽

private void registerForPackageIntents() {
    IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
    filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    filter.addDataScheme("package");
    mContext.registerReceiver(mPackageMonitor, filter);
    filter = new IntentFilter();
    filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
    filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
    mContext.registerReceiver(mPackageMonitor, filter);
}

Android5.0后的監(jiān)聽

import android.content.pm.LauncherApps;

protected LauncherApps mLauncherApps;

public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
    WrappedCallback wrappedCallback = new WrappedCallback(callback);
    synchronized (mCallbacks) {
        mCallbacks.put(callback, wrappedCallback);
    }
    mLauncherApps.registerCallback(wrappedCallback);
}

除了TestingUtils捆愁,應(yīng)用變化監(jiān)聽外割去,初始化兩個(gè)核心對(duì)象IconCache,LauncherModel昼丑。LauncherModel添加了設(shè)備變更呻逆,用戶信息變更的廣播,這是因?yàn)楫?dāng)用戶修改設(shè)備信息如語言菩帝,區(qū)域咖城,用戶信息等,LauncherModel會(huì)刷新數(shù)據(jù)呼奢,改變圖標(biāo)宜雀,標(biāo)題等信息。
LauncherAppState的初始化到這里基本上就完成了握础。

DeviceProfile辐董,InvariantDeviceProfile,Launcher的配置

我們繼續(xù)看禀综,下一步

LauncherAppState app = LauncherAppState.getInstance();

// Load configuration-specific DeviceProfile
mDeviceProfile = getResources().getConfiguration().orientation
        == Configuration.ORIENTATION_LANDSCAPE ?
        app.getInvariantDeviceProfile().landscapeProfile
        : app.getInvariantDeviceProfile().portraitProfile;

通過Configuration獲取橫屏配置landscapeProfile 或者portraitProfile豎屏配置

DeviceProfile

DeviceProfile的數(shù)據(jù)都是來自InvariantDeviceProfile简烘,封裝一些工具方法,我們直接看重點(diǎn)InvariantDeviceProfile

InvariantDeviceProfile

InvariantDeviceProfile的初始化是在LauncherAppState構(gòu)造里new出來的定枷,調(diào)用的是

InvariantDeviceProfile(Context context) {
    ...
    numRows = closestProfile.numRows;
    numColumns = closestProfile.numColumns;
    numHotseatIcons = closestProfile.numHotseatIcons;
    hotseatAllAppsRank = (int) (numHotseatIcons / 2);
    defaultLayoutId = closestProfile.defaultLayoutId;
    numFolderRows = closestProfile.numFolderRows;
    numFolderColumns = closestProfile.numFolderColumns;
    minAllAppsPredictionColumns = closestProfile.minAllAppsPredictionColumns;

    iconSize = interpolatedDeviceProfileOut.iconSize;
    iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
    iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
    hotseatIconSize = interpolatedDeviceProfileOut.hotseatIconSize;
    fillResIconDpi = getLauncherIconDensity(iconBitmapSize);

    // If the partner customization apk contains any grid overrides, apply them
    // Supported overrides: numRows, numColumns, iconSize
    applyPartnerDeviceProfileOverrides(context, dm);

    Point realSize = new Point();
    display.getRealSize(realSize);
    // The real size never changes. smallSide and largeSide will remain the
    // same in any orientation.
    int smallSide = Math.min(realSize.x, realSize.y);
    int largeSide = Math.max(realSize.x, realSize.y);

    landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
            largeSide, smallSide, true /* isLandscape */);
    portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
            smallSide, largeSide, false /* isLandscape */);
}

可以看到夸研,通過closestProfile和interpolatedDeviceProfileOut拿到了一系列配置項(xiàng),如桌面的行依鸥,列亥至,Hotseat(桌面底部固定的應(yīng)用欄)的個(gè)數(shù),Hotseat所有應(yīng)用的位置贱迟,布局id姐扮,文件夾的行列,圖標(biāo)的大小等等衣吠。之后再將這些信息new出我們的DeviceProfile對(duì)象茶敏。
那問題來了,closestProfile和interpolatedDeviceProfileOut是什么缚俏?惊搏?怎么計(jì)算出來的贮乳?

...
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);

Point smallestSize = new Point();
Point largestSize = new Point();
display.getCurrentSizeRange(smallestSize, largestSize);

// This guarantees that width < height
minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);

ArrayList<InvariantDeviceProfile> closestProfiles =
        findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles());
InvariantDeviceProfile interpolatedDeviceProfileOut =
        invDistWeightedInterpolate(minWidthDps,  minHeightDps, closestProfiles);
...

可以發(fā)現(xiàn),通過設(shè)備的寬高等信息恬惯,從各種分辨率的DeviceProfiles列表中找到最合適的配置信息向拆,查找的方法如下,根據(jù)設(shè)備的寬高跟列表里的的最小寬高差值的平方根從小到大排序酪耳,第一個(gè)就是我們期望的配置

/**
 * Returns the closest device profiles ordered by closeness to the specified width and height
 */
// Package private visibility for testing.
ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles(
        final float width, final float height, ArrayList<InvariantDeviceProfile> points) {

    // Sort the profiles by their closeness to the dimensions
    ArrayList<InvariantDeviceProfile> pointsByNearness = points;
    Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() {
        public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
            return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
                    dist(width, height, b.minWidthDps, b.minHeightDps));
        }
    });

    return pointsByNearness;
}

@Thunk float dist(float x0, float y0, float x1, float y1) {
    return (float) Math.hypot(x1 - x0, y1 - y0);
}

配置好的InvariantDeviceProfile列表信息如下浓恳,構(gòu)造參數(shù)依次是,
配置名稱碗暗,寬高颈将,行數(shù),列數(shù)言疗,文件夾行數(shù)列數(shù)晴圾,圖標(biāo)大小,圖標(biāo)文本大小
hotseat配置資源文件噪奄,hotseat圖標(biāo)大小死姚,默認(rèn)頁的資源文件
故,當(dāng)我們有新機(jī)型沒有適配梗醇,就可以在這里修改或新增配置

ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
    ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>();
    // width, height, #rows, #columns, #folder rows, #folder columns,
    // iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
            255, 300,     2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
            255, 400,     3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
            275, 420,     3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
            255, 450,     3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
            296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
            359, 567,     4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
            335, 567,     5, 4, 5, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_5x4_no_all_apps));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Large Phone",
            406, 694,     5, 5, 4, 4, 4, 64, 14.4f,  5, 56, R.xml.default_workspace_5x5));
    // The tablet profile is odd in that the landscape orientation
    // also includes the nav bar on the side
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 7",
            575, 904,     5, 6, 4, 5, 4, 72, 14.4f,  7, 60, R.xml.default_workspace_5x6));
    // Larger tablet profiles always have system bars on the top & bottom
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 10",
            727, 1207,    5, 6, 4, 5, 4, 76, 14.4f,  7, 76, R.xml.default_workspace_5x6));
    predefinedDeviceProfiles.add(new InvariantDeviceProfile("20-inch Tablet",
            1527, 2527,   7, 7, 6, 6, 4, 100, 20,  7, 72, R.xml.default_workspace_5x6));
    return predefinedDeviceProfiles;
}

Launcher的配置初始化到這里基本上就完成了。

還有一些其他的對(duì)象的初始化撒蟀,包括workspace狀態(tài)變化的動(dòng)畫加載控制LauncherStateTransitionAnimation, 應(yīng)用組件的管理器AppWidgetManagerCompat, 處理組件長按事件的ViewLauncherAppWidgetHost叙谨,因?yàn)樵贚auncher的初始化流程里不是特別需要,故后文有機(jī)會(huì)再做介紹保屯。

LauncherModel加載應(yīng)用信息

整個(gè)流程里比較復(fù)雜的就是LauncherModel加載應(yīng)用了, 在onCreate里

    mModel = app.setLauncher(this);
    

LauncherAppState里調(diào)用了LauncherModel的初始化方法 initialize,參數(shù)是Launcher

  LauncherModel setLauncher(Launcher launcher) {
        getLauncherProvider().setLauncherProviderChangeListener(launcher);
        mModel.initialize(launcher);
        mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
            new LauncherAccessibilityDelegate(launcher) : null;
        return mModel;
    }

LauncherModel的initialize參數(shù)是LauncherModel.Callbacks手负,Launcher里實(shí)現(xiàn)了LauncherModel.Callbacks的一系列接口用于綁定獲取到的應(yīng)用信息,文件夾信息姑尺,屏幕信息等等


 public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            // Disconnect any of the callbacks and drawables associated with ItemInfos on the
            // workspace to prevent leaking Launcher activities on orientation change.
            unbindItemInfosAndClearQueuedBindRunnables();
            mCallbacks = new WeakReference<Callbacks>(callbacks);
        }
    }

在加載數(shù)據(jù)之前先清除正在運(yùn)行的線程DeferredBindRunnables竟终,清除DeferredHandler里等待執(zhí)行的任務(wù),并且將所有ItemInfo unbind

/** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
void unbindWorkspaceItemsOnMainThread() {
    // Ensure that we don't use the same workspace items data structure on the main thread
    // by making a copy of workspace items first.
    final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
    synchronized (sBgLock) {
        tmpItems.addAll(sBgWorkspaceItems);
        tmpItems.addAll(sBgAppWidgets);
    }
    Runnable r = new Runnable() {
            @Override
            public void run() {
                for (ItemInfo item : tmpItems) {
                    item.unbind();
                }
            }
        };
    runOnMainThread(r);
}

接著我們才看到真正開始加載數(shù)據(jù)了切蟋,onCreate里的LauncherModel調(diào)用startLoader统捶,創(chuàng)建線程加載數(shù)據(jù)


        ...        
        if (!mRestoring) {
            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                // If the user leaves launcher, then we should just load items asynchronously when
                // they return.
                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
            } else {
                // We only load the page synchronously if the user rotates (or triggers a
                // configuration change) while launcher is in the foreground
                mModel.startLoader(mWorkspace.getRestorePage());
            }
        }
        ...
        

當(dāng)?shù)谝淮未蜷_時(shí),會(huì)調(diào)用 mModel.startLoader(PagedView.INVALID_RESTORE_PAGE)柄粹,停止舊的Loader任務(wù)喘鸟,stopLoaderLocked,然后new出一個(gè)LoaderTask(mApp.getContext(), loadFlags) Runnable驻右,通過Handler sworker post出去什黑,開始加載任務(wù)

public void startLoader(int synchronousBindPage) {
    startLoader(synchronousBindPage, LOADER_FLAG_NONE);
}

public void startLoader(int synchronousBindPage, int loadFlags) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        InstallShortcutReceiver.enableInstallQueue();
        synchronized (mLock) {
            // Clear any deferred bind-runnables from the synchronized load process
            // We must do this before any loading/binding is scheduled below.
            synchronized (mDeferredBindRunnables) {
                mDeferredBindRunnables.clear();
            }

            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                // If there is already one running, tell it to stop.
                stopLoaderLocked();
                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                } else {
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);
                }
            }
        }
    }


開始加載應(yīng)用信息的任務(wù)后,由于后續(xù)的篇幅比較長堪夭,請(qǐng)看下一篇文章的詳細(xì)介紹愕把。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拣凹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子恨豁,更是在濱河造成了極大的恐慌嚣镜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圣絮,死亡現(xiàn)場離奇詭異祈惶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)扮匠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門捧请,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棒搜,你說我怎么就攤上這事疹蛉。” “怎么了力麸?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵可款,是天一觀的道長。 經(jīng)常有香客問我克蚂,道長闺鲸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任埃叭,我火速辦了婚禮摸恍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赤屋。我一直安慰自己立镶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布类早。 她就那樣靜靜地躺著媚媒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涩僻。 梳的紋絲不亂的頭發(fā)上缭召,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音逆日,去河邊找鬼恼琼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛屏富,可吹牛的內(nèi)容都是我干的晴竞。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼狠半,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼噩死!你這毒婦竟也來了颤难?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤已维,失蹤者是張志新(化名)和其女友劉穎行嗤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垛耳,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栅屏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堂鲜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片栈雳。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缔莲,靈堂內(nèi)的尸體忽然破棺而出哥纫,到底是詐尸還是另有隱情,我是刑警寧澤痴奏,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布蛀骇,位于F島的核電站,受9級(jí)特大地震影響读拆,放射性物質(zhì)發(fā)生泄漏擅憔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一檐晕、第九天 我趴在偏房一處隱蔽的房頂上張望暑诸。 院中可真熱鬧,春花似錦棉姐、人聲如沸屠列。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至夏志,卻和暖如春乃坤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沟蔑。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工湿诊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘦材。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓厅须,卻偏偏與公主長得像,于是被迫代替她去往敵國和親食棕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子朗和,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,839評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理错沽,服務(wù)發(fā)現(xiàn),斷路器眶拉,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫千埃、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,066評(píng)論 4 62
  • 雖然時(shí)間很短歌也不多忆植,也就一個(gè)鐘左右吧放可。來看演唱會(huì)的都是年輕的男孩女孩們,他們都知道來這里都是想親耳聽到衛(wèi)蘭的歌聲...
    陳廣聯(lián)閱讀 357評(píng)論 0 0
  • 去巴黎痪枫,除了到耳熟能詳?shù)陌7茽栬F塔织堂,凱旋門,盧浮宮奶陈,凡爾賽宮易阳。還可以去哪里呢? 塞納河將巴黎一分為二吃粒。右岸代表著經(jīng)...
    惠茹姐姐閱讀 666評(píng)論 0 1