DownloadManager的使用和解析

DownloadManager的介紹

DownloadManger是android 2.3(API 9)開(kāi)始提供的系統(tǒng)服務(wù),用于處理長(zhǎng)時(shí)間的下載操作鸟妙。應(yīng)用場(chǎng)景是客戶端請(qǐng)求一個(gè)URL地址去下載一個(gè)目標(biāo)文件。DownloadManger可以構(gòu)建一個(gè)后臺(tái)下載服務(wù),在發(fā)生故障或連接更改草讶、重新啟動(dòng)系統(tǒng)等情況后,處理HTTP連接并重試下載炉菲。

如果APP通過(guò)DownloadManager請(qǐng)求下載堕战,那么應(yīng)用注冊(cè)ACTION_NOTIFICATION_CLICKED的廣播,以便在用戶單擊下載通知欄或者下載UI時(shí)拍霜,進(jìn)行適當(dāng)處理嘱丢。

需要注意使用DownloadManager時(shí),必須申請(qǐng)Manifest.permission.INTERNET權(quán)限祠饺。

獲取這個(gè)類的實(shí)例的方式有:Context.getSystemService(Class)越驻,參數(shù)為DownloadManager.class,或者道偷,Context.getSystemService(String)缀旁,其參數(shù)為Context.DOWNLOAD_SERVICE

主要的接口和類:

1、內(nèi)部類DownloadManager.Query勺鸦,這個(gè)類可以用于過(guò)濾DownloadManager的請(qǐng)求并巍。

2、內(nèi)部類DownloadManager.Request换途,這個(gè)類包含請(qǐng)求一個(gè)新下載連接的必要信息懊渡。

3嘶窄、公共方法enqueue,在隊(duì)列中插入一個(gè)新的下載距贷。當(dāng)連接正常
,并且DownloadManager準(zhǔn)備執(zhí)行這個(gè)請(qǐng)求時(shí)吻谋,開(kāi)始自動(dòng)下載忠蝗。返回結(jié)果是系統(tǒng)提供的唯一下載ID,這個(gè)ID可以用于與這個(gè)下載相關(guān)的回調(diào)漓拾。

4阁最、公共方法query,用于查詢下載信息骇两。

5速种、公共方法remove,用于刪除下載低千,如果下載中則取消下載配阵。同時(shí)會(huì)刪除下載文件和記錄。

DownloadManager的使用

1示血、在AndroidManifest中添加權(quán)限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

一個(gè)是網(wǎng)絡(luò)訪問(wèn)權(quán)限棋傍,一個(gè)是SDCARD寫權(quán)限。

2难审、初始化DownloadManager.Request瘫拣,調(diào)用enqueue方法開(kāi)始下載

DownloadManager mDownloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);

String apkUrl = “https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk”;
Uri resource = Uri.parse(apkUrl);

Request request = new Request(resource);
//下載的本地路徑,表示設(shè)置下載地址為SD卡的Download文件夾告喊,文件名為mobileqq_android.apk麸拄。
request.setDestinationInExternalPublicDir(“Download”, “mobileqq_android.apk”);

//start 一些非必要的設(shè)置
request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI);
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setVisibleInDownloadsUi(true);
request.setTitle(displayName);
//end 一些非必要的設(shè)置

mDownloadManager.enqueue(request);

DownloadManager.Request除了構(gòu)造函數(shù)的Uri必須外,其他設(shè)置都為可選設(shè)置黔姜。例如:

request.setMimeType(“application/cn.trinea.download.file”);

設(shè)置下載文件的mineType拢切。因?yàn)樵谙螺d管理UI中,點(diǎn)擊某個(gè)已下載完成文件地淀,以及失球,在下載完成后,點(diǎn)擊通知欄提示帮毁,都會(huì)根據(jù)mimeType去打開(kāi)文件实苞,所以我們可以利用這個(gè)屬性。比如設(shè)置了mimeType為application/cn.trinea.download.file烈疚,我們可以同時(shí)設(shè)置某個(gè)Activity的intent-filter為application/cn.trinea.download.file黔牵,用于響應(yīng)點(diǎn)擊的打開(kāi)文件。

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
 
    <category android:name="android.intent.category.DEFAULT" />
 
    <data android:mimeType="application/cn.trinea.download.file" />
</intent-filter>

3爷肝、下載進(jìn)度狀態(tài)的監(jiān)聽(tīng)及查詢

DownloadManager沒(méi)有提供相應(yīng)的回調(diào)接口猾浦,用于返回實(shí)時(shí)的下載進(jìn)度狀態(tài)陆错,但通過(guò)四大組件之一ContentProvider捌木,可以監(jiān)聽(tīng)到當(dāng)前下載項(xiàng)的進(jìn)度狀態(tài)變化肢础。

DownloadManager.getUriForDownloadedFile(id);

該方法會(huì)返回一個(gè)下載項(xiàng)的Uri,如content://downloads/my_downloads/125瓣俯,因此夹抗,我們通過(guò)ContentObserver監(jiān)聽(tīng)Uri.parse(“content://downloads/my_downloads”)(即Downloads.Impl.CONTENT_URI)绳慎,觀察這個(gè)Uri指向的數(shù)據(jù)庫(kù)項(xiàng)的變化,然后進(jìn)行下一步操作漠烧,如發(fā)送handler進(jìn)行更新UI杏愤。例子如下:

private Handler handler = new Handler(Looper.getMainLooper());
private static final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");
private DownloadContentObserver observer = new DownloadStatusObserver();

class DownloadContentObserver extends ContentObserver {
    public DownloadContentObserver() {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange) {
        updateView();
    }

}

@Override
protected void onResume() {
    super.onResume();
    getContentResolver().registerContentObserver(CONTENT_URI, true, observer);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    getContentResolver().unregisterContentObserver(observer);
}

public void updateView() {
    int[] bytesAndStatus = getBytesAndStatus(downloadId);
    int currentSize = bytesAndStatus[0];//當(dāng)前大小
    int totalSize = bytesAndStatus[1];//總大小
    int status = bytesAndStatus[2];//下載狀態(tài)
    Message.obtain(handler, 0, currentSize, totalSize, status).sendToTarget();
}

public int[] getBytesAndStatus(long downloadId) {
    int[] bytesAndStatus = new int[] { -1, -1, 0 };
    Query query = new Query().setFilterById(downloadId);
    Cursor c = null;
    try {
        c = mDownloadManager.query(query);
        if (c != null && c.moveToFirst()) {
            bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
            bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
            bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
        }
    } finally {
        if (c != null) {
            c.close();
        }
    }
    return bytesAndStatus;
}

上面的代碼主要調(diào)用queue()進(jìn)行查詢,參數(shù)屬性封裝在DownloadManager.Query()類中已脓。這個(gè)類主要包括以下接口:

  • setFilterById(long… ids)珊楼,根據(jù)下載id進(jìn)行過(guò)濾
  • setFilterByStatus(int flags),根據(jù)下載狀態(tài)進(jìn)行過(guò)濾
  • setOnlyIncludeVisibleInDownloadsUi(boolean value)度液,根據(jù)是否在Download UI中可見(jiàn)進(jìn)行過(guò)濾厕宗。
  • orderBy(String column, int direction),根據(jù)列進(jìn)行排序,不過(guò)目前僅支持
    DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP
    DownloadManager.COLUMN_TOTAL_SIZE_BYTES排序恨诱。

補(bǔ)充

如果界面上過(guò)多元素需要更新媳瞪,且網(wǎng)速較快不斷的執(zhí)行onChange會(huì)對(duì)頁(yè)面性能有一定影響,或者出現(xiàn)一些異常情況照宝,那么推薦ScheduledExecutorService定期查詢蛇受,如下:

//三秒定時(shí)刷新一次 
public static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3); 
Runnable command = new Runnable() { 
    @Override 
    public void run() { 
        updateView(); 
    } 
};
scheduledExecutorService.scheduleAtFixedRate(command, 0, 3, TimeUnit.SECONDS);

4、下載成功監(jiān)聽(tīng)

下載完成后厕鹃,下載管理服務(wù)會(huì)發(fā)出DownloadManager.ACTION_DOWNLOAD_COMPLETE這個(gè)廣播兢仰,并傳遞downloadId作為參數(shù)。通過(guò)接受廣播我們可以打開(kāi)對(duì)下載完成的內(nèi)容進(jìn)行操作剂碴。

private CompleteReceiver completeReceiver;

class CompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // get complete download id
        long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        // to do here
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //...
    
    completeReceiver = new CompleteReceiver();
    //register download success broadcast
    registerReceiver(completeReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}

@Override
protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(completeReceiver);
}

5把将、響應(yīng)通知欄的點(diǎn)擊

(1)下載中點(diǎn)擊

點(diǎn)擊下載中通知欄提示,系統(tǒng)會(huì)對(duì)下載的應(yīng)用單獨(dú)發(fā)送Action為DownloadManager.ACTION_NOTIFICATION_CLICKED廣播忆矛。intent.getData為content://downloads/all_downloads/29669察蹲,最后一位為downloadId。
如果同時(shí)下載多個(gè)應(yīng)用催训,intent會(huì)包含DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS這個(gè)key洽议,表示下載的downloadId數(shù)組。

(2)下載完成后點(diǎn)擊

下載完成后系統(tǒng)會(huì)調(diào)用下面代碼進(jìn)行處理漫拭,從中我們可以發(fā)現(xiàn)系統(tǒng)會(huì)調(diào)用View Action根據(jù)mimeType去查詢亚兄。所以可以利用上文第2條介紹的DownloadManager.Request的setMimeType函數(shù)。

private void openDownload(Context context, Cursor cursor) {
    String filename = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl._DATA));
    String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_MIME_TYPE));
    Uri path = Uri.parse(filename);
    // If there is no scheme, then it must be a file
    if (path.getScheme() == null) {
        path = Uri.fromFile(new File(filename));
    }
    Intent activityIntent = new Intent(Intent.ACTION_VIEW);
    mimetype = DownloadDrmHelper.getOriginalMimeType(context, filename, mimetype);
    activityIntent.setDataAndType(path, mimetype);
    activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    try {
        context.startActivity(activityIntent);
    } catch (ActivityNotFoundException ex) {
        Log.d(Constants.TAG, "no activity for " + mimetype, ex);
    }
}

DownloadManager的解析

DownloadManager開(kāi)始下載的入口enqueue方法采驻,這個(gè)方法的源碼如下:

public long enqueue(Request request) {
    ContentValues values = request.toContentValues(mPackageName);
    Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
    long id = Long.parseLong(downloadUri.getLastPathSegment());
    return id;
}

使用的ContentProvider方式审胚,將Request信息轉(zhuǎn)換為ContentValues類匈勋,然后調(diào)用ContentResolver進(jìn)行插入,底層會(huì)調(diào)用對(duì)應(yīng)的ContentProvider的insert方法膳叨。URI是Downloads.Impl.CONTENT_URI洽洁,即"content://downloads/my_downloads",找到對(duì)應(yīng)的Provider即系統(tǒng)提供的DownloadProvider菲嘴。

DownloadProvider類在系統(tǒng)源碼的src/com/android/providers/downloads的路徑下诡挂,找都其insert方法的實(shí)現(xiàn),可以發(fā)現(xiàn)最后部分的代碼:

public Uri insert(final Uri uri, final ContentValues values) {
    ...
    // Always start service to handle notifications and/or scanning
    final Context context = getContext();
    context.startService(new Intent(context, DownloadService.class));

    return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
}

即插入信息后临谱,會(huì)啟動(dòng)DownloadService開(kāi)始進(jìn)行下載。(從Android N (API 24) 開(kāi)始實(shí)現(xiàn)方式不同

DownloadService的入口是onStartCommand方法奴璃,其中用mUpdateHandler發(fā)送消息MSG_UPDATE悉默,mUpdateHandler處理消息的方式如下:

mUpdateHandler = new Handler(mUpdateThread.getLooper(), mUpdateCallback);

private Handler.Callback mUpdateCallback = new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        ...
        final boolean isActive;
        synchronized (mDownloads) {
            isActive = updateLocked();
        }
        ...
    }
};

private boolean updateLocked() {
    ...
     // Kick off download task if ready
     final boolean activeDownload = info.startDownloadIfReady(mExecutor);
    ...
}

public boolean startDownloadIfReady(ExecutorService executor) {
    synchronized (this) {
        final boolean isReady = isReadyToDownload();
        final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
        if (isReady && !isActive) {
            if (mStatus != Impl.STATUS_RUNNING) {
                mStatus = Impl.STATUS_RUNNING;
                ContentValues values = new ContentValues();
                values.put(Impl.COLUMN_STATUS, mStatus);
                mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
            }
            //啟動(dòng)DownloadThread開(kāi)始下載任務(wù)
            mTask = new DownloadThread(mContext, mSystemFacade, mNotifier, this);
            mSubmittedTask = executor.submit(mTask);
        }
        return isReady;
    }
}

從上面源碼可以看,DownloadService的onStartCommand方法苟穆,最終啟動(dòng)DownloadThread抄课,開(kāi)始下載的任務(wù)(網(wǎng)絡(luò)請(qǐng)求接口使用的是HttpURLConnection)。DownloadThread在下載過(guò)程中雳旅,會(huì)更新DownloadProvider跟磨。

綜上所述,DownloadManager的enqueue方法的流程是:

DownloadProvider插入信息 >> 啟動(dòng)DownloadService >> 開(kāi)始DownloadThread進(jìn)行下載

擴(kuò)展

1攒盈、DownloadManager出現(xiàn)崩潰

Fatal Exception: java.lang.IllegalArgumentException: Unknown URL content://downloads/my_downloads
    at android.content.ContentResolver.insert(ContentResolver.java:882)
    at android.app.DownloadManager.enqueue(DownloadManager.java:904)

原因:這一般是因?yàn)槭謩?dòng)禁用了下載器
(現(xiàn)象可以從打開(kāi)Google Play Store看到抵拘,會(huì)出現(xiàn)提示被禁用的彈窗)
(手動(dòng)禁用的方式可以是點(diǎn)擊在下載過(guò)程的通知欄信息,進(jìn)入設(shè)置頁(yè)面點(diǎn)擊“禁用”按鈕)

解決方法:

https://stackoverflow.com/questions/21551538/how-to-enable-android-download-manager

https://github.com/HanteIsHante/file/issues/25

可以在代碼中判斷下載管理器是否可用

static boolean downLoadMangerIsEnable(Context context) {
    int state = context.getApplicationContext().getPackageManager()
            .getApplicationEnabledSetting("com.android.providers.downloads");
        
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
        return !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
                state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
                || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
    } else {
        return !(state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED ||
                state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
    }
}

如果不可用型豁,則打開(kāi) 系統(tǒng)下載管理器 設(shè)置頁(yè)面 或者 打開(kāi)系統(tǒng)設(shè)置僵蛛,讓用戶設(shè)置

try {
     //Open the specific App Info page:
     Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
     intent.setData(Uri.parse("package:" + "com.android.providers.downloads"));
     startActivity(intent);

} catch ( ActivityNotFoundException e ) {
     e.printStackTrace();

     //Open the generic Apps page:
     Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
     startActivity(intent);
}       

2、DownloaderManager的斷點(diǎn)續(xù)傳是怎么觸發(fā)的迎变?

比如斷開(kāi)網(wǎng)絡(luò)充尉,然后恢復(fù)網(wǎng)絡(luò),DownloaderManger可以繼續(xù)下載衣形,這是怎么觸發(fā)的驼侠?

從源碼可以看到,實(shí)現(xiàn)方式是監(jiān)聽(tīng)網(wǎng)絡(luò)變化廣播谆吴,實(shí)現(xiàn)類是DownloadReceiver.java倒源。

//DownloadReceiver.java

public class DownloadReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, final Intent intent) {
        if (mSystemFacade == null) {
            mSystemFacade = new RealSystemFacade(context);
        }
        
        final String action = intent.getAction();
         
        //...   
         
        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
            final ConnectivityManager connManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            final NetworkInfo info = connManager.getActiveNetworkInfo();
            if (info != null && info.isConnected()) {
                startService(context);
            }
        } 
    }
        
    private void startService(Context context) {
        context.startService(new Intent(context, DownloadService.class));
    }
}

參考

https://developer.android.com/reference/android/app/DownloadManager

Android系統(tǒng)下載管理DownloadManager功能介紹及使用示例

Android系統(tǒng)下載管理DownloadManager

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纪铺,隨后出現(xiàn)的幾起案子相速,更是在濱河造成了極大的恐慌,老刑警劉巖鲜锚,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件突诬,死亡現(xiàn)場(chǎng)離奇詭異苫拍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)旺隙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門绒极,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蔬捷,你說(shuō)我怎么就攤上這事垄提。” “怎么了周拐?”我有些...
    開(kāi)封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵铡俐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我妥粟,道長(zhǎng)审丘,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任勾给,我火速辦了婚禮滩报,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘播急。我一直安慰自己脓钾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布桩警。 她就那樣靜靜地躺著可训,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捶枢。 梳的紋絲不亂的頭發(fā)上沉噩,一...
    開(kāi)封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音柱蟀,去河邊找鬼川蒙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛长已,可吹牛的內(nèi)容都是我干的畜眨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼术瓮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼康聂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起胞四,我...
    開(kāi)封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恬汁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后辜伟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氓侧,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脊另,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了约巷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片偎痛。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖独郎,靈堂內(nèi)的尸體忽然破棺而出踩麦,到底是詐尸還是另有隱情,我是刑警寧澤氓癌,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布谓谦,位于F島的核電站,受9級(jí)特大地震影響贪婉,放射性物質(zhì)發(fā)生泄漏茁计。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一谓松、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧践剂,春花似錦鬼譬、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至军洼,卻和暖如春巩螃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匕争。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工避乏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甘桑。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓拍皮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親跑杭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铆帽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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