Android 中要禁用 APP?或四大組件

Android 中要禁用 APP四大組件会烙,可使用PackageManager提供的方法:

/**
 * Set the enabled setting for an application
 * This setting will override any enabled state which may have been set by the application in
 * its manifest.  It also overrides the enabled state set in the manifest for any of the
 * application's components.  It does not override any enabled state set by
 * {@link #setComponentEnabledSetting} for any of the application's components.
 *
 * @param packageName The package name of the application to enable
 * @param newState The new enabled state for the application.
 * @param flags Optional behavior flags.
 */
public abstract void setApplicationEnabledSetting(String packageName,
        @EnabledState int newState, @EnabledFlags int flags);

/**
 * Set the enabled setting for a package component (activity, receiver, service, provider).
 * This setting will override any enabled state which may have been set by the component in its
 * manifest.
 *
 * @param componentName The component to enable
 * @param newState The new enabled state for the component.
 * @param flags Optional behavior flags.
 */
public abstract void setComponentEnabledSetting(ComponentName componentName,
        @EnabledState int newState, @EnabledFlags int flags);

要想使用這兩個 API 得先獲取權(quán)限丘喻。

權(quán)限獲取

獲取權(quán)限又有幾種情況幾個步驟,如下:

1鲫剿、在 Manifest 里面聲明權(quán)限:
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
2掐暮、使用系統(tǒng)簽名

在申明上面的權(quán)限時,AS 會提示:

Permission is only granted to system apps less... (Ctrl+F1) 
Permissions with the protection level signature, privileged or signatureOrSystem are only granted to system apps. If an app is a regular non-system app, it will never be able to use these permissions.

這就是說愿卒,這個權(quán)限只有系統(tǒng) APP 可以獲取到缚去,這時就需要使用系統(tǒng)簽名來簽名 APP 了易结。

如何給 APP 系統(tǒng)簽名柜候,方法有多種搞动,比如:
1、APP 源碼放在系統(tǒng)源碼中編譯渣刷;
2鹦肿、APK 文件放在系統(tǒng)源碼中重簽名;
3辅柴、導(dǎo)出系統(tǒng)簽名文件箩溃,給 APK 簽名瞭吃;
4、使用系統(tǒng)簽名歪架,制作 keystore,放入 AS 使用牡拇。
相關(guān)方法穆律,網(wǎng)上文章很多,我也在公眾號推送過我自己的方法峦耘,有興趣可以去查閱剔蹋。

3、添加 sharedUserId

如果你還想在當(dāng)前 APP 中去操作其他 APP辅髓,在上面都配置好的前提下泣崩,還可能會出現(xiàn)類似如下的錯誤信息:

java.lang.SecurityException: Permission Denial: attempt to change component state from pid=27978, uid=10111, package uid=10087

這時洛口,就需要再給 APP 添加 sharedUserId,讓 APP 運(yùn)行在 system 進(jìn)程中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:sharedUserId="android.uid.system">

實際添加了 “android.uid.system” 后,上一步的 uses-permission 可以省略,因為 system 進(jìn)程默認(rèn)有這個權(quán)限。

添加好權(quán)限后,接下來就是使用了。

既然是 PackageManager 的方法硝皂,而且這兩個方法也不是靜態(tài)的贝或,那就要先獲取 PackageManager 的實例了咪奖。這兩個方法還是 abstract 的,那就說明 PackageManager 也是 abstract 的酱床,不能 new 羊赵,那如何獲取實例呢?

我們知道系統(tǒng)很多 Manager 都是單實例不能 new 的扇谣,PackageManager 也一樣昧捷,那是不是也是通過 getSystemService 獲取呢?并不是罐寨,注釋給出來了:

/**
 * Class for retrieving various kinds of information related to the application
 * packages that are currently installed on the device.
 *
 * You can find this class through {@link Context#getPackageManager}.
 */
public abstract class PackageManager {

知道如何獲取實例之后靡挥,我們就可以開始調(diào)用了,先看如何禁用 APP鸯绿。

禁用 APP

在開始操作前跋破,我們應(yīng)該先了解下這個方法及其參數(shù)定義和作用:

setApplicationEnabledSetting(String packageName,
        @EnabledState int newState, @EnabledFlags int flags)

1、packageName楞慈,要禁用的 APP 的包名幔烛。

2啃擦、newState囊蓝,就是要設(shè)置成的狀態(tài),它的取值通過注解@EnabledState進(jìn)行限制令蛉,有如下幾種:

/** @hide */
@IntDef(prefix = { "COMPONENT_ENABLED_STATE_" }, value = {
    //將APP或組件設(shè)置為manifest定義的狀態(tài)聚霜。
    COMPONENT_ENABLED_STATE_DEFAULT,
    //啟用APP或組件,忽略manifest的定義珠叔。
    COMPONENT_ENABLED_STATE_ENABLED,
    //禁用APP或組件蝎宇,忽略manifest的定義。
    COMPONENT_ENABLED_STATE_DISABLED,
    //以用戶身份禁用APP祷安,忽略manifest的定義姥芥。不能用于組件操作。
    COMPONENT_ENABLED_STATE_DISABLED_USER,
    //禁用APP直到用戶想用才出現(xiàn)汇鞭。也就是說凉唐,正常情況下庸追,用戶看不到(比如在Launcher上);但是特殊情況下台囱,用戶還是能看到并選擇到(比如輸入法APP)淡溯。不能用于組件操作。
    COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EnabledState {}

3簿训、flags咱娶,是 Optional behavior flags,它的取值由注解@EnabledFlags限制:

/** @hide */
@IntDef(flag = true, prefix = { "DONT_KILL_APP" }, value = {
    //僅對組件的操作起作用强品,用于指示禁用組件時膘侮,不 kill 組件所在的 APP。
    DONT_KILL_APP
})
@Retention(RetentionPolicy.SOURCE)
public @interface EnabledFlags {}

因此择懂,這里我們禁用 APP 時不需要關(guān)注第三個參數(shù)喻喳,置 0 即可,代碼如下:

PackageManager pm = context.getPackageManager();
pm.setApplicationEnabledSetting(
        pkgName,
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        0
);

如果要重新啟用APP困曙,只需按前面的限制修改第二個參數(shù)即可表伦。

PS:
這里禁用 APP 之后,APP 圖標(biāo)會從 Launcher 上消失慷丽,在Settings > APP List 中可看到 APP 變?yōu)?disable 狀態(tài)蹦哼,用戶無法使用。
如果只是想讓用戶無法使用要糊,不讓圖標(biāo)消失纲熏,可參考如下文章:
https://blog.csdn.net/qq_25815655/article/details/78355259

禁用組件

與禁用 APP,只需要 APP 包名不同锄俄,禁用組件時局劲,還需要知道組件的名字,也就是這兩個方法第一個參數(shù)不一樣的原因奶赠。

setComponentEnabledSetting(ComponentName componentName,
        @EnabledState int newState, @EnabledFlags int flags)

這里四大組件(activity, receiver, service, provider)都可以禁用鱼填,具體的參數(shù)說明就不再提了。

最后毅戈,禁用代碼如下:

PackageManager pm = context.getPackageManager();
ComponentName name = new ComponentName(pkg, clazz);
pm.setComponentEnabledSetting(name,
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);

補(bǔ)充

前面說了苹丸,PackageManager 是抽象類,它通過AIDL類 IPackageManager 與 PackageManagerService 通信苇经,上面兩個方法的具體實現(xiàn)赘理,也在 Service 中。

PackageManagerService 的路徑如下:

frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java

兩個方法的實現(xiàn)如下:

public void setApplicationEnabledSetting(String appPackageName,
    int newState, int flags, int userId, String callingPackage) {
    if (!sUserManager.exists(userId)) return;
    if (callingPackage == null) {
        callingPackage = Integer.toString(Binder.getCallingUid());
    }
    setEnabledSetting(appPackageName, null, newState, flags, userId, callingPackage);
}

@Override
public void setComponentEnabledSetting(ComponentName componentName,
    int newState, int flags, int userId) {
    if (!sUserManager.exists(userId)) return;
    setEnabledSetting(componentName.getPackageName(),
            componentName.getClassName(), newState, flags, userId, null);
}

我們可以看到它們最后都是調(diào)用了 setEnabledSetting 方法扇单,這個方法的實現(xiàn)代碼很多商模,這里我將它貼出來就不一一分析了,有興許可以自己研究下。

private void setEnabledSetting(final String packageName, String className, int newState,
        final int flags, int userId, String callingPackage) {
    if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT
          || newState == COMPONENT_ENABLED_STATE_ENABLED
          || newState == COMPONENT_ENABLED_STATE_DISABLED
          || newState == COMPONENT_ENABLED_STATE_DISABLED_USER
          || newState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
        throw new IllegalArgumentException("Invalid new component state: "
                + newState);
    }
    PackageSetting pkgSetting;
    final int callingUid = Binder.getCallingUid();
    final int permission;
    if (callingUid == Process.SYSTEM_UID) {
        permission = PackageManager.PERMISSION_GRANTED;
    } else {
        permission = mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
    }
    enforceCrossUserPermission(callingUid, userId,
            false /* requireFullPermission */, true /* checkShell */, "set enabled");
    final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
    boolean sendNow = false;
    boolean isApp = (className == null);
    final boolean isCallerInstantApp = (getInstantAppPackageName(callingUid) != null);
    String componentName = isApp ? packageName : className;
    int packageUid = -1;
    ArrayList<String> components;

    // reader
    synchronized (mPackages) {
        pkgSetting = mSettings.mPackages.get(packageName);
        if (pkgSetting == null) {
            if (!isCallerInstantApp) {
                if (className == null) {
                    throw new IllegalArgumentException("Unknown package: " + packageName);
                }
                throw new IllegalArgumentException(
                        "Unknown component: " + packageName + "/" + className);
            } else {
                // throw SecurityException to prevent leaking package information
                throw new SecurityException(
                        "Attempt to change component state; "
                        + "pid=" + Binder.getCallingPid()
                        + ", uid=" + callingUid
                        + (className == null
                                ? ", package=" + packageName
                                : ", component=" + packageName + "/" + className));
            }
        }
    }

    // Limit who can change which apps
    if (!UserHandle.isSameApp(callingUid, pkgSetting.appId)) {
        // Don't allow apps that don't have permission to modify other apps
        if (!allowedByPermission
                || filterAppAccessLPr(pkgSetting, callingUid, userId)) {
            throw new SecurityException(
                    "Attempt to change component state; "
                    + "pid=" + Binder.getCallingPid()
                    + ", uid=" + callingUid
                    + (className == null
                            ? ", package=" + packageName
                            : ", component=" + packageName + "/" + className));
        }
        // Don't allow changing protected packages.
        if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
            throw new SecurityException("Cannot disable a protected package: " + packageName);
        }
    }

    synchronized (mPackages) {
        if (callingUid == Process.SHELL_UID
                && (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
            // Shell can only change whole packages between ENABLED and DISABLED_USER states
            // unless it is a test package.
            int oldState = pkgSetting.getEnabled(userId);
            if (className == null
                    &&
                    (oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
                            || oldState == COMPONENT_ENABLED_STATE_DEFAULT
                            || oldState == COMPONENT_ENABLED_STATE_ENABLED)
                    &&
                    (newState == COMPONENT_ENABLED_STATE_DISABLED_USER
                            || newState == COMPONENT_ENABLED_STATE_DEFAULT
                            || newState == COMPONENT_ENABLED_STATE_ENABLED)) {
                // ok
            } else {
                throw new SecurityException(
                        "Shell cannot change component state for " + packageName + "/"
                                + className + " to " + newState);
            }
        }
    }
    if (className == null) {
        // We're dealing with an application/package level state change
        synchronized (mPackages) {
            if (pkgSetting.getEnabled(userId) == newState) {
                // Nothing to do
                return;
            }
        }
        // If we're enabling a system stub, there's a little more work to do.
        // Prior to enabling the package, we need to decompress the APK(s) to the
        // data partition and then replace the version on the system partition.
        final PackageParser.Package deletedPkg = pkgSetting.pkg;
        final boolean isSystemStub = deletedPkg.isStub
                && deletedPkg.isSystemApp();
        if (isSystemStub
                && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                        || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
            final File codePath = decompressPackage(deletedPkg);
            if (codePath == null) {
                Slog.e(TAG, "couldn't decompress pkg: " + pkgSetting.name);
                return;
            }
            // TODO remove direct parsing of the package object during internal cleanup
            // of scan package
            // We need to call parse directly here for no other reason than we need
            // the new package in order to disable the old one [we use the information
            // for some internal optimization to optionally create a new package setting
            // object on replace]. However, we can't get the package from the scan
            // because the scan modifies live structures and we need to remove the
            // old [system] package from the system before a scan can be attempted.
            // Once scan is indempotent we can remove this parse and use the package
            // object we scanned, prior to adding it to package settings.
            final PackageParser pp = new PackageParser();
            pp.setSeparateProcesses(mSeparateProcesses);
            pp.setDisplayMetrics(mMetrics);
            pp.setCallback(mPackageParserCallback);
            final PackageParser.Package tmpPkg;
            try {
                final int parseFlags = mDefParseFlags
                        | PackageParser.PARSE_MUST_BE_APK
                        | PackageParser.PARSE_IS_SYSTEM
                        | PackageParser.PARSE_IS_SYSTEM_DIR;
                tmpPkg = pp.parsePackage(codePath, parseFlags);
            } catch (PackageParserException e) {
                Slog.w(TAG, "Failed to parse compressed system package:" + pkgSetting.name, e);
                return;
            }
            synchronized (mInstallLock) {
                // Disable the stub and remove any package entries
                removePackageLI(deletedPkg, true);
                synchronized (mPackages) {
                    disableSystemPackageLPw(deletedPkg, tmpPkg);
                }
                final PackageParser.Package newPkg;
                try (PackageFreezer freezer =
                        freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
                    final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                            | PackageParser.PARSE_ENFORCE_CODE;
                    newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
                            0 /*currentTime*/, null /*user*/);
                    prepareAppDataAfterInstallLIF(newPkg);
                    synchronized (mPackages) {
                        try {
                            updateSharedLibrariesLPr(newPkg, null);
                        } catch (PackageManagerException e) {
                            Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                        }
                        updatePermissionsLPw(newPkg.packageName, newPkg,
                                UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
                        mSettings.writeLPr();
                    }
                } catch (PackageManagerException e) {
                    // Whoops! Something went wrong; try to roll back to the stub
                    Slog.w(TAG, "Failed to install compressed system package:"
                            + pkgSetting.name, e);
                    // Remove the failed install
                    removeCodePathLI(codePath);

                    // Install the system package
                    try (PackageFreezer freezer =
                            freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
                        synchronized (mPackages) {
                            // NOTE: The system package always needs to be enabled; even
                            // if it's for a compressed stub. If we don't, installing the
                            // system package fails during scan [scanning checks the disabled
                            // packages]. We will reverse this later, after we've "installed"
                            // the stub.
                            // This leaves us in a fragile state; the stub should never be
                            // enabled, so, cross your fingers and hope nothing goes wrong
                            // until we can disable the package later.
                            enableSystemPackageLPw(deletedPkg);
                        }
                        installPackageFromSystemLIF(new File(deletedPkg.codePath),
                                false /*isPrivileged*/, null /*allUserHandles*/,
                                null /*origUserHandles*/, null /*origPermissionsState*/,
                                true /*writeSettings*/);
                    } catch (PackageManagerException pme) {
                        Slog.w(TAG, "Failed to restore system package:"
                                + deletedPkg.packageName, pme);
                    } finally {
                        synchronized (mPackages) {
                            mSettings.disableSystemPackageLPw(
                                    deletedPkg.packageName, true /*replaced*/);
                            mSettings.writeLPr();
                        }
                    }
                    return;
                }
                clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
                        | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
                clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
                mDexManager.notifyPackageUpdated(newPkg.packageName,
                        newPkg.baseCodePath, newPkg.splitCodePaths);
            }
        }
        if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
            || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
            // Don't care about who enables an app.
            callingPackage = null;
        }
        synchronized (mPackages) {
            pkgSetting.setEnabled(newState, userId, callingPackage);
        }
    } else {
        synchronized (mPackages) {
            // We're dealing with a component level state change
            // First, verify that this is a valid class name.
            PackageParser.Package pkg = pkgSetting.pkg;
            if (pkg == null || !pkg.hasComponentClassName(className)) {
                if (pkg != null &&
                        pkg.applicationInfo.targetSdkVersion >=
                                Build.VERSION_CODES.JELLY_BEAN) {
                    throw new IllegalArgumentException("Component class " + className
                            + " does not exist in " + packageName);
                } else {
                    Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
                            + className + " does not exist in " + packageName);
                }
            }
            switch (newState) {
                case COMPONENT_ENABLED_STATE_ENABLED:
                    if (!pkgSetting.enableComponentLPw(className, userId)) {
                        return;
                    }
                    break;
                case COMPONENT_ENABLED_STATE_DISABLED:
                    if (!pkgSetting.disableComponentLPw(className, userId)) {
                        return;
                    }
                    break;
                case COMPONENT_ENABLED_STATE_DEFAULT:
                    if (!pkgSetting.restoreComponentLPw(className, userId)) {
                        return;
                    }
                    break;
                default:
                    Slog.e(TAG, "Invalid new component state: " + newState);
                    return;
            }
        }
    }
    synchronized (mPackages) {
        scheduleWritePackageRestrictionsLocked(userId);
        updateSequenceNumberLP(pkgSetting, new int[] { userId });
        final long callingId = Binder.clearCallingIdentity();
        try {
            updateInstantAppInstallerLocked(packageName);
        } finally {
            Binder.restoreCallingIdentity(callingId);
        }
        components = mPendingBroadcasts.get(userId, packageName);
        final boolean newPackage = components == null;
        if (newPackage) {
            components = new ArrayList<String>();
        }
        if (!components.contains(componentName)) {
            components.add(componentName);
        }
        if ((flags&PackageManager.DONT_KILL_APP) == 0) {
            sendNow = true;
            // Purge entry from pending broadcast list if another one exists already
            // since we are sending one right away.
            mPendingBroadcasts.remove(userId, packageName);
        } else {
            if (newPackage) {
                mPendingBroadcasts.put(userId, packageName, components);
            }
            if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
                // Schedule a message
                mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
            }
        }
    }

    long callingId = Binder.clearCallingIdentity();
    try {
        if (sendNow) {
            packageUid = UserHandle.getUid(userId, pkgSetting.appId);
            sendPackageChangedBroadcast(packageName,
                    (flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
        }
    } finally {
        Binder.restoreCallingIdentity(callingId);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末施流,一起剝皮案震驚了整個濱河市凉倚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫂沉,老刑警劉巖稽寒,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異趟章,居然都是意外死亡杏糙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蚓土,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宏侍,“玉大人,你說我怎么就攤上這事蜀漆×潞樱” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵确丢,是天一觀的道長绷耍。 經(jīng)常有香客問我,道長鲜侥,這世上最難降的妖魔是什么褂始? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮描函,結(jié)果婚禮上崎苗,老公的妹妹穿的比我還像新娘。我一直安慰自己舀寓,他們只是感情好胆数,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著互墓,像睡著了一般必尼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上轰豆,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天胰伍,我揣著相機(jī)與錄音齿诞,去河邊找鬼酸休。 笑死,一個胖子當(dāng)著我的面吹牛祷杈,可吹牛的內(nèi)容都是我干的斑司。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宿刮!你這毒婦竟也來了互站?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤僵缺,失蹤者是張志新(化名)和其女友劉穎胡桃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磕潮,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翠胰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了自脯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片之景。...
    茶點(diǎn)故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膏潮,靈堂內(nèi)的尸體忽然破棺而出锻狗,到底是詐尸還是另有隱情,我是刑警寧澤焕参,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布轻纪,位于F島的核電站,受9級特大地震影響叠纷,放射性物質(zhì)發(fā)生泄漏桐磁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一讲岁、第九天 我趴在偏房一處隱蔽的房頂上張望我擂。 院中可真熱鬧,春花似錦缓艳、人聲如沸校摩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衙吩。三九已至,卻和暖如春溪窒,著一層夾襖步出監(jiān)牢的瞬間坤塞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工澈蚌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摹芙,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓宛瞄,卻偏偏與公主長得像浮禾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評論 2 354