iOS7、8、9相册适配

相册适配


前言

由于在iOS8及以后苹果将原有的操作相册的ALAssetsLibrary framework替换为Photos framework,所以,如果在应用中使用到的相册需要支持iOS8以下的系统版本的话,就需要了解Photos framework以做不同的版本适配。

一、iOS8以下

1. 几个重要的实体概念

  • ALAsset(iOS8及以后使用PHAsset)
    一个ALAsset实例对象代表一个资源实体,比如一张图片、一个视频。共有三种类型:

    ALAssetTypePhoto // 图片
    ALAssetTypeVideo // 视频
    ALAssetTypeUnknown // 未知
    

通过这个实例,你可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息。

  • ALAssetsGroup(iOS8及以后使用PHAssetCollection)
    一个ALAssetsGroup实例对象代表一组资源的集合,也就是一个相册,可以是系统默认存在的相册(相机胶卷),也可以是开发者给用户创建的自定义相册(QQ)。
    通过它,你可以获取到这个相册的名称、封面缩略图等。

  • ALAssetsLibrary(iOS8及以后使用PHPhotoLibrary)
    一个ALAssetsLibrary实例对象对所有的相册资源进行索引。
    使用它,你可以获取指定类型的相册、单张图片等对象,也可以添加新的自定义相册或者图片到相薄。如果你想要知道有没有获取到系统相薄的访问权限,同样需要用到它。

2. 实体相关API

ALAssetsLibrary

在iOS7及以下系统中,如果你想要获取相册中的资源,或者对相册进行操作,那么你先要创建一个ALAssetsLibrary实例对象。

  • 获取指定类型的相册

     - (void)enumerateGroupsWithTypes:(ALAssetsGroupType)types usingBlock:(ALAssetsLibraryGroupsEnumerationResultsBlock)enumerationBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
    

注意
由于这里的遍历都是异步的操作,所以ALAssetsLibrary的实例对象需要被一个静态变量或者成员变量引用来保证不被销毁,回调的Block才能保证被成功调用。下面同此处。

  • 根据一个相册URL获取这个相册

     - (void)groupForURL:(NSURL *)groupURL resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
    
  • 创建一个相册

     - (void)addAssetsGroupAlbumWithName:(NSString *)name resultBlock:(ALAssetsLibraryGroupResultBlock)resultBlock failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
    
  • 相册授权状态

     + (ALAuthorizationStatus)authorizationStatus
    

ALAssetsGroup

iOS7及以下系统的中每个相册都是一个ALAssetsGroup实例,你可以使用这个实例获取这个相册的相关信息。

  • 获取相册信息

     - (id)valueForProperty:(NSString *)property  
    

参数property包含以下类型:
ALAssetsGroupPropertyName
ALAssetsGroupPropertyType
ALAssetsGroupPropertyPersistentID
ALAssetsGroupPropertyURL

  • 封面图

     - (CGImageRef)posterImage
    
  • 相册包含实体(照片、视频)的数量

     - (NSInteger)numberOfAssets
    
  • 过滤规则

     - (void)setAssetsFilter:(ALAssetsFilter *)filter
    

注意
参数中的filter是一个ALAssetsFilter实例,这个实例只有三种类型:

    + (ALAssetsFilter *)allPhotos; // 所有图片

    + (ALAssetsFilter *)allVideos; // 所有视频

    + (ALAssetsFilter *)allAssets; // 所有视频及图片

在使用- (NSInteger)numberOfAssets获取相册实体数量时,会依赖该过滤规则。比如一个相薄中存在5个视频和5张图片,如果指定allPhotos,则numberOfAssets返回值为5。同样使用- (void)enumerateAssetsUsingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock获取相册中的所有资源时,也只能获取到指定过滤规则下的资源。

  • 使用相应遍历规则获取相册中的所有ALAsset资源

     - (void)enumerateAssetsWithOptions:(NSEnumerationOptions)options usingBlock:(ALAssetsGroupEnumerationResultsBlock)enumerationBlock
    

3. 使用示例

  • 获取所有相册,并过滤相册中的视频

    + (void)getAssetsGroupsForIos8BelowSuccess:(void (^)(NSMutableArray *))success failure:(void (^)(NSError *error))failure {
    NSMutableArray *assetsGroups = [NSMutableArray array];
    [[self library] enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
        if (group != nil) {
            [group setAssetsFilter:[ALAssetsFilter allPhotos]];
            if (group.numberOfAssets > 0) {
                [assetsGroups addObject:group];
            }
        } else {
            if (success) {
                success(assetsGroups);
            }
        }
    } failureBlock:^(NSError *error) {
        if (failure) {
            failure(error);
        }
    }];
    

}

  • (ALAssetsLibrary *)library {
    static ALAssetsLibrary *library;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    library = [[ALAssetsLibrary alloc] init];
    });
    return library;
    }
  • 获取一个相册中的所有资源

    + (void)getTimeLineSectionModelsForIos8BelowWithGroup:(MRAlbumGroupModel *)group success:(void (^)(NSMutableArray *))success failure:(void (^)(NSError *))failure {
    NSMutableArray *sectionModels = [NSMutableArray array];
    [group.assetGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
        if(result) {
            [sectionModels addObject:result];
        }
    }];
    if (success != nil && sectionModels.count > 0) {
        success(sectionModels);
    }
    if (failure != nil && sectionModels.count == 0) {
        failure(nil);
    }
    

}
```

二、iOS8及以上

1. 几个重要的实体概念

与ALAssetsLibrary framework对应的几个实体

  • PHAsset
    一个PHAsset实例对象与ALAsset类似,代表一个资源实体,比如一张图片、一个视频。与ALAsset不同之处在于,多了一种实体类型,共四种类型:

    PHAssetMediaTypeUnknown = 0,
    PHAssetMediaTypeImage   = 1,
    PHAssetMediaTypeVideo   = 2,
    PHAssetMediaTypeAudio   = 3,
    

同时,还多了更具体的子类型PHAssetMediaSubtype:

```
PHAssetMediaSubtypeNone               = 0,

// Photo subtypes
PHAssetMediaSubtypePhotoPanorama      = (1UL << 0),
PHAssetMediaSubtypePhotoHDR           = (1UL << 1),
PHAssetMediaSubtypePhotoScreenshot PHOTOS_AVAILABLE_IOS_TVOS(9_0, 10_0) = (1UL << 2),
PHAssetMediaSubtypePhotoLive PHOTOS_AVAILABLE_IOS_TVOS(9_1, 10_0) = (1UL << 3),

// Video subtypes
PHAssetMediaSubtypeVideoStreamed      = (1UL << 16),
PHAssetMediaSubtypeVideoHighFrameRate = (1UL << 17),
PHAssetMediaSubtypeVideoTimelapse     = (1UL << 18),
```

通过这个实例,除了可以获取到这个资源的创建时间、资源类型、缩略图、二进制数据等信息,还可以获取到location等位置信息。

  • PHCollection
    PHCollection是个基类,它有两个子类。分别是PHAssetCollectionPHCollectionListPHAssetCollection代表 Photos 中的相册,PHCollectionList代表 Photos 中的文件夹。PHCollectionList里可嵌套PHAssetCollection,也可以嵌套自身类型,同时支持多重嵌套。
    一个PHAssetCollection实例对象与ALAssetsGroup类似,代表一组资源的集合,也就是一个相册。
    通过它,你可以获取到这个相册的名称、封面缩略图。
    以及相册类型PHAssetCollectionType

    PHAssetCollectionTypeAlbum      = 1, // 自定义相册,如QQ
    PHAssetCollectionTypeSmartAlbum = 2, // 相机胶卷、我的照片流、屏幕截图、全景照片等
    PHAssetCollectionTypeMoment     = 3, // 时刻
    

    相册子类型PHAssetCollectionSubtype

    // PHAssetCollectionTypeAlbum regular subtypes
    PHAssetCollectionSubtypeAlbumRegular         = 2,
    PHAssetCollectionSubtypeAlbumSyncedEvent     = 3,
    PHAssetCollectionSubtypeAlbumSyncedFaces     = 4,
    PHAssetCollectionSubtypeAlbumSyncedAlbum     = 5,
    PHAssetCollectionSubtypeAlbumImported        = 6,
        
    // PHAssetCollectionTypeAlbum shared subtypes
    PHAssetCollectionSubtypeAlbumMyPhotoStream   = 100,
    PHAssetCollectionSubtypeAlbumCloudShared     = 101,
        
    // PHAssetCollectionTypeSmartAlbum subtypes
    PHAssetCollectionSubtypeSmartAlbumGeneric    = 200,
    PHAssetCollectionSubtypeSmartAlbumPanoramas  = 201,
    PHAssetCollectionSubtypeSmartAlbumVideos     = 202,
    PHAssetCollectionSubtypeSmartAlbumFavorites  = 203,
    PHAssetCollectionSubtypeSmartAlbumTimelapses = 204,
    PHAssetCollectionSubtypeSmartAlbumAllHidden  = 205,
    PHAssetCollectionSubtypeSmartAlbumRecentlyAdded = 206,
    PHAssetCollectionSubtypeSmartAlbumBursts     = 207,
    PHAssetCollectionSubtypeSmartAlbumSlomoVideos = 208,
    PHAssetCollectionSubtypeSmartAlbumUserLibrary = 209,
    PHAssetCollectionSubtypeSmartAlbumSelfPortraits PHOTOS_AVAILABLE_IOS_TVOS(9_0, 10_0) = 210,
    PHAssetCollectionSubtypeSmartAlbumScreenshots PHOTOS_AVAILABLE_IOS_TVOS(9_0, 10_0) = 211,
        
    // Used for fetching, if you don't care about the exact subtype
    PHAssetCollectionSubtypeAny = NSIntegerMax
    
  • PHPhotoLibrary
    PHPhotoLibrary是个单例对象,与ALAssetsLibrary相比发生了较大变化,只能申请获取PHOtos权限以及授权状态获取等。
    使用这个单例你也可以注册相册改动的监听者,在相册发生变化(比如添加或导入了新图片)时,做一些刷新或其他处理。

新的实体

在iOS8及以上的相册资源获取中,都是使用fetch相关API的形式,过程类似Core Data。匹配资源的过程自然需要匹配规则,最终匹配结果也需要一个集合来记录。

  • PHFetchOptions
    PHFetchOptions的实例对象代表获取资源时的匹配规则。
    可以指定资源排序规则(NSArray<NSSortDescriptor *> *sortDescriptors;)、资源类型规则(NSPredicate *predicate)、最大数量(NSUInteger fetchLimit)等。

  • PHFetchResult
    PHFetchResult的实例对象代表相册资源的匹配结果集合。它包含零个或多个符合匹配规则的资源,这些资源可以是PHAssetCollection对象,也可以是PHAsset对象。

  • PHImageManager
    PHImageManager是一个单例对象,不需要你手动创建,这个单例对象可以让你获取到一个PHAsset资源的实际二进制数据——如一张图片数据。

2. 实体相关API

PHAssetCollection

在iOS8及以上系统中,获取相册不需要创建实例,直接使用PHAssetCollection的类方法进行操作即可。

  • 获取指定类型的相册

     // Fetch asset collections of a single type and subtype provided (use PHAssetCollectionSubtypeAny to match all subtypes)
     + (PHFetchResult<PHAssetCollection *> *)fetchAssetCollectionsWithType:(PHAssetCollectionType)type subtype:(PHAssetCollectionSubtype)subtype options:(nullable PHFetchOptions *)options;
    
  • 基本属性

    @property (nonatomic, strong, readonly, nullable) NSString *localizedTitle; // 相册标题
    @property (nonatomic, assign, readonly) PHAssetCollectionType assetCollectionType; // 相册类型
    @property (nonatomic, assign, readonly) PHAssetCollectionSubtype assetCollectionSubtype; // 子类型
    @property (nonatomic, assign, readonly) NSUInteger estimatedAssetCount; // 预估资源数量
    @property (nonatomic, strong, readonly, nullable) NSDate *startDate; // 开始日期
    @property (nonatomic, strong, readonly, nullable) NSDate *endDate; // 结束日期
    @property (nonatomic, strong, readonly, nullable) CLLocation *approximateLocation; // 位置信息
    @property (nonatomic, strong, readonly) NSArray<NSString *> *localizedLocationNames; // 位置名称
    

注意
estimatedAssetCount并不是一个准确的值,当PHAssetCollection对象不能马上计算出当前相册中的资源数量时,会返回NSNotFound,如果想要获取资源的具体数量,需要使用PHAsset来fetch所有资源,计算资源数量。

PHAsset

  • 获取某相册中的PHAsset资源

     + (PHFetchResult<PHAsset *> *)fetchAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
    
  • 获取某相册的封面资源

     + (nullable PHFetchResult<PHAsset *> *)fetchKeyAssetsInAssetCollection:(PHAssetCollection *)assetCollection options:(nullable PHFetchOptions *)options;
    

注意
这个API可以获取到系统默认的一些封面图资源,这个结果由零个或多个PHAsset组成,在相册是smartAlbum类型的情况下,可能会有零个结果的情况。

  • 获取中Photos中的所有PHAsset资源

     + (PHFetchResult<PHAsset *> *)fetchAssetsWithOptions:(nullable PHFetchOptions *)options;
    

注意
这里并不是获取某个相册中的PHAsset资源,而是手机中所有的PHAsset资源。
比如手机中有10个相册,每个相册中10个PHAsset资源,如果不指定PHFetchOptions,使用该方法会获取到所有的100个PHAsset资源。

  • 获取Photos中指定资源类型的所有PHAsset资源

     + (PHFetchResult<PHAsset *> *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(nullable PHFetchOptions *)options;
    

参数中的mediaType可选:

```
PHAssetMediaTypeUnknown = 0,
PHAssetMediaTypeImage   = 1,
PHAssetMediaTypeVideo   = 2,
PHAssetMediaTypeAudio   = 3,
```

PHImageManager

  • 获取PHAsset图片资源的图片数据

     - (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
    

注意
参数中的PHImageRequestOptions对象可以设置目标图片的一些属性,其中synchronous表示是否同步获取,默认是NO,也就是异步获取。

3. 使用示例

  • 获取指定类型的相册

    + (NSMutableArray *)getCollecionsWithSmartAlbumSubtype:(PHAssetCollectionSubtype)subtype {
    PHFetchOptions *userAlbumsOptions = [PHFetchOptions new];
    PHFetchResult *userAlbumsResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum
                                                                               subtype:PHAssetCollectionSubtypeAny
                                                                               options:userAlbumsOptions];
    PHFetchResult *userSmartAlbumsResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum
                                                                                    subtype:subtype
                                                                                    options:userAlbumsOptions];
    NSMutableArray *collections = [NSMutableArray array];
    void (^AlbumEnumerateObjectsUsingBlock)(PHAssetCollection *, NSUInteger idx, BOOL *) = ^(PHAssetCollection * _Nonnull collection, NSUInteger idx, BOOL * _Nonnull stop) {
        if (collection.estimatedAssetCount == 0) {
            return ;
        }
        NSUInteger numberOfAssets = 0;
        PHFetchResult *assetsResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
        numberOfAssets = [assetsResult countOfAssetsWithMediaType:PHAssetMediaTypeImage];
        if (numberOfAssets == 0) {
            return;
        }
        [collections addObject:collection];
    };
    [userSmartAlbumsResult enumerateObjectsUsingBlock:AlbumEnumerateObjectsUsingBlock];
    [userAlbumsResult enumerateObjectsUsingBlock:AlbumEnumerateObjectsUsingBlock];
    return collections;
    

}
```

  • 获取一个相册中的所有图片资源,按创建时间降序排序

    + (void)getTimeLineSectionModelsForIos8AboveWithGroup:(MRAlbumGroupModel *)group success:(void (^)(NSMutableArray *))success failure:(void (^)(NSError *))failure {
    PHFetchResult *assetsResult = [PHAsset fetchAssetsInAssetCollection:group.collection options:[self fetchOptions]];
    NSMutableArray *sectionModels = [NSMutableArray array];
    [assetsResult enumerateObjectsUsingBlock:^(PHAsset * _Nonnull asset, NSUInteger idx, BOOL * _Nonnull stop) {
        [sectionModels addObject:asset];
    }];
    if (success != nil && sectionModels.count > 0) {
        success(sectionModels);
    }
    if (failure != nil && sectionModels.count == 0) {
        failure(nil);
    }
    

}

  • (PHFetchOptions *)fetchOptions {
    PHFetchOptions *fetchOptions = [PHFetchOptions new];
    fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType = %@", @(PHAssetMediaTypeImage)];
    fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
    return fetchOptions;
    }
    
    

三、适配参考

我在不同系统的相册适配中采用适配器的方式进行的适配,仅供参考。

1. 适配器

相册适配主要是为了解决在iOS8以上的系统中,使用ALLibrary相关API可能存在一些不兼容的问题,导致资源获取不一致情况的出现。

在业务展现上并不期望因系统不同而给用户差别较大的体验,所以可以只针对相册资源数据获取的数据测适配即可,而无需改动UI。

这种场景可以在数据层对ALAssetsGroupPHAssetCollection以及ALAssetPHAsset做抽象,抽取适配器即可。

2. 简单示例

  • AssetGroupModel

    @interface AlbumGroupModel : NSObject
    
    @property (nonatomic, strong) UIImage *posterImage;
    @property (nonatomic, assign) NSUInteger numberOfAssets;
    @property (nonatomic, copy) NSString *assetsGroupPropertyName;
    
    @property (nonatomic, strong) PHAssetCollection *collection;
    @property (nonatomic, strong) ALAssetsGroup *assetGroup;
    
    @end
    @implementation AlbumGroupModel
    
  • (UIImage *)posterImage {
    if (_posterImage == nil) {
    if ([XSLAppInfoTool systemVersion] < 8.0) {
    _posterImage = [UIImage imageWithCGImage:self.assetGroup.posterImage];
    } else {
    PHFetchResult *groupResult = [PHAsset fetchAssetsInAssetCollection:self.collection options:self.class.fetchOptions];
    PHAsset *asset = groupResult.firstObject;
    PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
    requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;

          CGFloat scale = [UIScreen mainScreen].scale;
          CGFloat dimension = 80.0f;
          CGSize size = CGSizeMake(dimension * scale, dimension * scale);
          __block UIImage *resultImage = nil;
          [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFill options:requestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
              resultImage = result;
          }];
          _posterImage = resultImage;
      }
    

    }
    return _posterImage;
    }
    @end

    
    
  • AssetModel

    @interface AlbumAssetModel : NSObject
    
    @property (nonatomic, strong, nullable) PHAsset *assetPh;
    @property (nonatomic, strong, nullable) ALAsset *assetAl;
    
    - (void)thumbnail:(nullable void(^)(UIImage * _Nullable thumbnail))resultCallback;
    
    @end
    @implementation AlbumAssetModel
    
    - (void)thumbnail:(void (^)(UIImage *))resultCallback {
        if (self.assetPh) {
            PHImageManager *manager = [PHImageManager defaultManager];
            [manager requestImageForAsset:self.assetPh targetSize:CGSizeMake(100, 100) contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
                if (resultCallback) {
                    resultCallback(result);
                }
            }];
        } else if (self.assetAl) {
            if (resultCallback) {
                resultCallback([UIImage imageWithCGImage:[self.assetAl thumbnail]]);
            }
        }
    }
    
    @end
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,966评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,170评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,909评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,959评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,851评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,583评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,956评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,590评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,878评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,892评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,719评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,501评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,957评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,124评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,440评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,003评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,211评论 2 339

推荐阅读更多精彩内容