Launcher3 中 IconCache 緩存邏輯

概述

我們先看下IconCache的初始化過程锣披,接著看下IconCache核心數據結構丰刊、算法,最后介紹與之關聯(lián)的幾個類岸梨。

Launcher.java

public class Launcher extends StatefulActivity<LauncherState> implements ... {
    ...
    public static final String TAG = "Launcher";
    private LauncherModel mModel;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        LauncherAppState app = LauncherAppState.getInstance(this);
        mOldConfig = new Configuration(getResources().getConfiguration());
        mModel = app.getModel();
        ...
       }
   }
  • 這個類是Launcher的主入口元暴,即 MainActivity篷扩。onCreate()做了許多界面和管理器的初始化。

  • 這里我們關注是初始化了 LauncherAppStateLauncherModel

LauncherAppState.java

public class LauncherAppState {
    // 注釋1
    // We do not need any synchronization for this variable as its only written on UI thread.
    public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
            new MainThreadInitializedObject<>(LauncherAppState::new);

    private final Context mContext;
    private final LauncherModel mModel;
    private final IconProvider mIconProvider;
    private final IconCache mIconCache;
    private final DatabaseWidgetPreviewLoader mWidgetCache;
    private final InvariantDeviceProfile mInvariantDeviceProfile;
    private final RunnableList mOnTerminateCallback = new RunnableList();

    public static LauncherAppState getInstance(final Context context) {
        return INSTANCE.get(context);
    }

    public LauncherAppState(Context context) {
        // 注釋2
        this(context, LauncherFiles.APP_ICONS_DB);
        ...
    }

   public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
       ...
       // 注釋3
       mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
       iconCacheFileName, mIconProvider);
       ...
   }
}
  • 注釋1:LauncherAppState 是單例茉盏,且限定在主線程上初始化

  • 注釋2 注釋3 傳入數據庫名字 app_icon.db鉴未,進而初始化 IconCache

  • mModel 即數據管理器,用于維護啟動器的內存狀態(tài)鸠姨。預計靜態(tài)中應該只有一個LauncherModel對象铜秆。還提供用于更新 Launcher 的數據庫狀態(tài)的 API。

  • mIconCache應用程序icon和title的緩存享怀,圖標可以由任何線程創(chuàng)建羽峰。

  • mWidgetCache 存儲widget預覽信息的數據庫

LoaderTask.java

是有數據管理類LauncherModel來調用的趟咆,其核心是Run方法添瓷。

主要分為四大步驟,并開啟事務機制來管理

  1. 加載與綁定桌面內容

    1. loadWorkspace

    2. sanitizeData

    3. bindWorkspace

    4. sendFirstScreenActiveInstallsBroadcast

  2. 加載和綁定所有的應用圖標和信息

    1. loadAllApps

    2. bindAllApps

    3. update icon cache 對應圖標緩存邏輯類 LauncherActivityCachingLogic

    4. save shortcuts in icon cache

    這一步實際是在第一步的值纱,對應的圖標緩存邏輯類 ShortcutCachingLogic

  3. 加載和綁定所有DeepShortcuts

    1. loadDeepShortcuts

    2. bindDeepShortcuts

    3. save deep shortcuts in icon cache 對應的圖標緩存邏輯類 ShortcutCachingLogic

  4. 加載和綁定所有的Widgets

    1. load widgets

    2. bindWidgets

    3. save widgets in icon cache 對應的圖標緩存邏輯類 ComponentWithIconCachingLogic

IconCacheUpdateHandler.java

IconCacheUpdateHandler掃描到所有應用后鳞贷,會開啟一個線程 SerializedIconUpdateTask進行更新圖標操作,把圖標緩存到內存和數據庫里虐唠。

調用流程

  • 在上面LoaderTask過程中更新圖標用的是IconCacheUpdateHandler.updateIcons()搀愧,

  • 這是個工具類,處理更新圖標緩存, 處理業(yè)務與IconCache的連接

  • 內部類 SerializedIconUpdateTask 序列化圖標更新任務疆偿,即將這些圖標信息存儲或者更新到數據庫中

舉例說明過程

updateIcons

public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic,
        OnUpdateCallback onUpdateCallback) {
    // Filter the list per user
    HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>();
    int count = apps.size();
    for (int i = 0; i < count; i++) {
        T app = apps.get(i);
        UserHandle userHandle = cachingLogic.getUser(app);
        HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);
        if (componentMap == null) {
            componentMap = new HashMap<>();
            userComponentMap.put(userHandle, componentMap);
        }
        componentMap.put(cachingLogic.getComponent(app), app);
    }

    for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) {
        updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
    }

    // From now on, clear every valid item from the global valid map.
    mFilterMode = MODE_CLEAR_VALID_ITEMS;
}
  • 這里有兩個Map咱筛,按照用戶維度來分組組件

    • 按照用戶維度來分組組件 HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap;

    • 按照組件不同分組 HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);

updateIconsPerUser


/**
 * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
 * the DB and are updated.
 * @return The set of packages for which icons have updated.
 */
@SuppressWarnings("unchecked")
private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap,
        CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) {
    Set<String> ignorePackages = mPackagesToIgnore.get(user);
    if (ignorePackages == null) {
        ignorePackages = Collections.emptySet();
    }
    long userSerial = mIconCache.getSerialNumberForUser(user);
    Log.d(TAG, "updateIconsPerUser: userSerial = " + userSerial + " ,componentMap =" + componentMap.size());

    Stack<T> appsToUpdate = new Stack<>();
    try (Cursor c = mIconCache.mIconDb.query(
            new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
                    IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
                    IconDB.COLUMN_SYSTEM_STATE},
            IconDB.COLUMN_USER + " = ? ",
            new String[]{Long.toString(userSerial)})) {

        final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
        final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
        final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
        final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
        final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);

        Log.d(TAG, "updateIconsPerUser: 111");
        while (c.moveToNext()) {
            Log.d(TAG, "updateIconsPerUser: 222");
            ...
        }
    } catch (SQLiteException e) {
        Log.d(TAG, "Error reading icon cache", e);
        // Continue updating whatever we have read so far
    }

    // Insert remaining apps.
    if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
        Stack<T> appsToAdd = new Stack<>();
        appsToAdd.addAll(componentMap.values());
        Log.d(TAG, "SerializedIconUpdateTask appsToAdd = " + appsToAdd.size() + ", appsToUpdate = "+ appsToUpdate.size());
        new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic,
                onUpdateCallback).scheduleNext();
    }
}

  • 為什么要刪除操作杆故?setIgnorePackages

SerializedIconUpdateTask.run()

private class SerializedIconUpdateTask<T> implements Runnable {
    ....
    @Override
    public void run() {
       ...
       if (!mAppsToAdd.isEmpty()) {
            T app = mAppsToAdd.pop();
            PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
            // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
            // app should have package info, this is not guaranteed by the api
            if (info != null) {
                mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
                        mUserSerial, false /*replace existing*/);
            }

            if (!mAppsToAdd.isEmpty()) {
                scheduleNext();
            }
        }
    }

    public void scheduleNext() {
        mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
                SystemClock.uptimeMillis() + 1);
    }
}

IconCache.java

核心思想:針對每類圖標提供通用的HashMap內存緩存 + 數據庫緩存迅箩,同時通過CachingLogic多種實現圖標差異性。


// 加載 shortcut 圖標
private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
        boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) {
    BitmapInfo bitmapInfo;
    if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
        bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(),
                () -> si, mShortcutCachingLogic, false, false).bitmap;
    } else {
        // If caching is disabled, load the full icon
        bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si);
    }
    if (bitmapInfo.isNullOrLowRes()) {
        bitmapInfo = getDefaultIcon(si.getUserHandle());
    }

    if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
        return;
    }
    info.bitmap = bitmapInfo;
    if (useBadged) {
        BitmapInfo badgeInfo = getShortcutInfoBadge(si);
        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
            info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo);
        }
    }
}

/**
 * 加載 Widget 圖標
 */
public synchronized String getTitleNoCache(ComponentWithLabel info) {
    CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
            mComponentWithLabelCachingLogic, false /* usePackageIcon */,
            true /* useLowResIcon */);
    return Utilities.trim(entry.title);
}

BaseIconCache.java

1.首先看下構造方法
public abstract class BaseIconCache {
    ....
    private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
    private final Map<ComponentKey, CacheEntry> mCache;
    ...
    public BaseIconCache(Context context, String dbFileName, Looper bgLooper,
        int iconDpi, int iconPixelSize, boolean inMemoryCache) {
        ...
        if (inMemoryCache) {
            mCache = new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
        } else {
            // Use a dummy cache
            mCache = new AbstractMap<ComponentKey, CacheEntry>() {
                @Override
                public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
                    return Collections.emptySet();
                }

                @Override
                public CacheEntry put(ComponentKey key, CacheEntry value) {
                    return value;
                }
            };
        }
        ...
    }

}

// 緩存key, 組成: 組件名 和 用戶UserHandle
public class ComponentKey {

    public final ComponentName componentName;
    public final UserHandle user;

    private final int mHashCode;

    public ComponentKey(ComponentName componentName, UserHandle user) {
        if (componentName == null || user == null) {
            throw new NullPointerException();
        }
        this.componentName = componentName;
        this.user = user;
        mHashCode = Arrays.hashCode(new Object[] {componentName, user});
    }
    ...
}

// 緩存Value处铛,組成:圖標 + title + contentDesc
public static class CacheEntry {

    @NonNull
    public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
    public CharSequence title = "";
    public CharSequence contentDescription = "";
}

  • 緩存數據結構:Map<ComponentKey, CacheEntry>
  • 緩存集合初始大小為50
  • ComponentKey:緩存key, 組成: 組件名(pkg+cls) 和 用戶UserHandle
  • CacheEntry:緩存Value饲趋,組成:圖標 + title + contentDesc
  • 注意這里有一段代碼是虛內存,使用技巧值得學習
 // Use a dummy cache
            mCache = new AbstractMap<ComponentKey, CacheEntry>() {
                @Override
                public Set<Entry<ComponentKey, CacheEntry>> entrySet() {
                    return Collections.emptySet();
                }

                @Override
                public CacheEntry put(ComponentKey key, CacheEntry value) {
                    return value;
                }
            };
2.繼續(xù)看另一個重要方法 cacheLocked()
/**
 * @param  componentName  組件名
 * @param  user 用戶
 * @param  infoProvider 組件信息提供者
 * @param  cachingLogic  對應的緩存邏輯處理類
 * @param  usePackageIcon 是否使用pkg的icon
 * @param  useLowResIcon  是否使用默認的空圖標
 */
protected <T> CacheEntry cacheLocked(
        @NonNull ComponentName componentName, @NonNull UserHandle user,
        @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
        boolean usePackageIcon, boolean useLowResIcon) {
    assertWorkerThread();
    // 1.生成緩存key
    ComponentKey cacheKey = new ComponentKey(componentName, user);
    // 2.嘗試根據key,從緩存中取
    CacheEntry entry = mCache.get(cacheKey);
    // 3.尚未緩存 或者 緩存了但是緩存的是空的默認圖標撤蟆,此時去緩存
    if (entry == null || (entry.bitmap.isLowRes() && !useLowResIcon)) {
        entry = new CacheEntry();
        //4.如果對應的緩存邏輯控制類 允許添加到內存緩存中奕塑,即存入mCache,但此時value未賦值
        if (cachingLogic.addToMemCache()) { 
            mCache.put(cacheKey, entry);
        }

        // Check the DB first.
        T object = null;
        boolean providerFetchedOnce = false;

        // 4.首先查看數據庫是否存在
        // 如果數據存在家肯,取出來賦值給entry
        // 如果數據庫不存在龄砰,加載默認空圖標、pkg圖標讨衣、或者 cachingLogic.loadIcon
        if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
            object = infoProvider.get();
            providerFetchedOnce = true;

            if (object != null) { // 4.1如果信息提供者不為空寝贡,直接去對應的緩存控制邏輯取圖標
                entry.bitmap = cachingLogic.loadIcon(mContext, object);
            } else { // 4.2如果提供者是空的扒披,返回默認的或者使用pkg的圖標
                if (usePackageIcon) {
                    CacheEntry packageEntry = getEntryForPackageLocked(
                            componentName.getPackageName(), user, false);
                    if (packageEntry != null) {
                        if (DEBUG) Log.d(TAG, "using package default icon for " +
                                componentName.toShortString());
                        entry.bitmap = packageEntry.bitmap;
                        entry.title = packageEntry.title;
                        entry.contentDescription = packageEntry.contentDescription;
                    }
                }
                // 如果pkg依然為空,使用默認的空白圖標
                if (entry.bitmap == null) {
                    if (DEBUG) Log.d(TAG, "using default icon for " +
                            componentName.toShortString());
                    entry.bitmap = getDefaultIcon(user);
                }
            }
        }

        // 5.檢查并對entry的title和desc繼續(xù)賦值
        if (TextUtils.isEmpty(entry.title)) {
            if (object == null && !providerFetchedOnce) {
                object = infoProvider.get();
                providerFetchedOnce = true;
            }
            if (object != null) {
                entry.title = cachingLogic.getLabel(object);
                entry.contentDescription = mPackageManager.getUserBadgedLabel(
                        cachingLogic.getDescription(object, entry.title), user);
            }
        }
    }
    return entry; // 返回緩存的Value,及CacheEntry
}

補充說明兩點

  • getEntryFromDB 從數據庫中查詢目標 Entry

  • getEntryForPackageLocked 與上面這個方法類似圃泡,唯一多的邏輯是當從packagemanger查詢到應用圖標會存入到數據庫

3.方法addIconToDBAndMemCache
/**
* 在數據庫和內存緩存中添加一個條目碟案。 
* @param replaceExisting 如果為真,它會重新創(chuàng)建位圖颇蜡,即使它已經存在于內存中价说。
* 這在以前的位圖是使用舊數據創(chuàng)建時很有用。
*/
public synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
        PackageInfo info, long userSerial, boolean replaceExisting) {
    UserHandle user = cachingLogic.getUser(object);
    ComponentName componentName = cachingLogic.getComponent(object);

    final ComponentKey key = new ComponentKey(componentName, user);
    CacheEntry entry = null;
    if (!replaceExisting) {
        entry = mCache.get(key);
        // We can't reuse the entry if the high-res icon is not present.
        if (entry == null || entry.bitmap.isNullOrLowRes()) {
            entry = null;
        }
    }
    // 新加載圖標
    if (entry == null) {
        entry = new CacheEntry();
        entry.bitmap = cachingLogic.loadIcon(mContext, object);
    }

    // 無法從 cachingLogic 加載圖標风秤,這意味著已加載替代圖標(例如后備圖標鳖目、默認圖標)。
    // 所以我們放在這里缤弦,因為緩存空條目沒有意義领迈。
    if (entry.bitmap.isNullOrLowRes()) return;
    entry.title = cachingLogic.getLabel(object);
    entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
    // 是否需要添加到內存中
    if (cachingLogic.addToMemCache()) mCache.put(key, entry);

    ContentValues values = newContentValues(entry.bitmap, entry.title.toString(),
            componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
    // 添加到數據庫
    addIconToDB(values, componentName, info, userSerial,
            cachingLogic.getLastUpdatedTime(object, info));
}

IconDB

  • 類路徑:com.android.launcher3.icons.cache.BaseIconCache.IconDB
  • db數據庫名:app_icons.db
  • table表名:icons
某個手機數據庫表示例

CachingLogic.java 系列

  • LauncherActivityCachingLogic 用于allApp的圖標緩存

  • ShortcutCachingLogic 用于shortcut的圖標緩存

  • ComponentWithIconCachingLogic 用于widget的圖標緩存

  • 其中 loadIcon 是可以定制圖標樣式的

public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {

    private static final String TAG = "ShortcutCachingLogic";

    // 根據shortcutInfo獲取組件
    @Override
    public ComponentName getComponent(ShortcutInfo info) {
        return ShortcutKey.fromInfo(info).componentName;
    }

   ...

    @NonNull
    @Override
    public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
        try (LauncherIcons li = LauncherIcons.obtain(context)) {
            Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
                    context, info, LauncherAppState.getIDP(context).fillResIconDpi);
            if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
            return new BitmapInfo(li.createScaledBitmapWithoutShadow(
                    unbadgedDrawable, 0), Themes.getColorAccent(context));
        }
    }

    @Override
    public boolean addToMemCache() {
        return false;// 表示不緩存到內存中
    }

    /**
     * Similar to {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} with additional
     * Launcher specific checks
     */
    public static Drawable getIcon(Context context, ShortcutInfo shortcutInfo, int density) {
        if (GO_DISABLE_WIDGETS) { // 開關控制是否允許有shortcut
            return null;
        }
        try {// 從LauncherApps中查詢圖標
            return context.getSystemService(LauncherApps.class)
                    .getShortcutIconDrawable(shortcutInfo, density);
        } catch (SecurityException | IllegalStateException e) {
            Log.e(TAG, "Failed to get shortcut icon", e);
            return null;
        }
    }
}

WidgetsModel.java

// True is the widget support is disabled.
public static final boolean GO_DISABLE_WIDGETS = true;
  • 當打開GO_DISABLE_WIDGETS = false ,會開啟widget,同時在optionsview上會顯示菜單, 如下圖
OptionsPopupView.java
public static WidgetsFullSheet openWidgets(Launcher launcher) {
    if (launcher.getPackageManager().isSafeMode()) {
        Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
        return null;
    } else {
        return WidgetsFullSheet.show(launcher, true /* animated */);
    }
}
  • 異常情況默認圖標兜底 makeDefaultIcon
    com.android.launcher3.icons.BaseIconFactory#getFullResDefaultActivityIcon
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末碍沐,一起剝皮案震驚了整個濱河市狸捅,隨后出現的幾起案子,更是在濱河造成了極大的恐慌累提,老刑警劉巖尘喝,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異斋陪,居然都是意外死亡朽褪,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門无虚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缔赠,“玉大人,你說我怎么就攤上這事友题∴脱撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵咆爽,是天一觀的道長梁棠。 經常有香客問我,道長斗埂,這世上最難降的妖魔是什么符糊? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮呛凶,結果婚禮上男娄,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好模闲,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布建瘫。 她就那樣靜靜地躺著,像睡著了一般尸折。 火紅的嫁衣襯著肌膚如雪啰脚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天实夹,我揣著相機與錄音橄浓,去河邊找鬼。 笑死亮航,一個胖子當著我的面吹牛荸实,可吹牛的內容都是我干的。 我是一名探鬼主播缴淋,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼准给,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了重抖?” 一聲冷哼從身側響起露氮,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仇哆,沒想到半個月后沦辙,有當地人在樹林里發(fā)現了一具尸體夫植,經...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡讹剔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了详民。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片延欠。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沈跨,靈堂內的尸體忽然破棺而出由捎,到底是詐尸還是另有隱情,我是刑警寧澤饿凛,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布狞玛,位于F島的核電站,受9級特大地震影響涧窒,放射性物質發(fā)生泄漏心肪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一纠吴、第九天 我趴在偏房一處隱蔽的房頂上張望硬鞍。 院中可真熱鬧,春花似錦、人聲如沸固该。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伐坏。三九已至怔匣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桦沉,已是汗流浹背劫狠。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留永部,地道東北人独泞。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像苔埋,于是被迫代替她去往敵國和親懦砂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容