Flutter 總結(jié)《二》

Q1: Flutter是如何自定義View?

通過(guò)繼承CustomPainter,然后實(shí)現(xiàn)paint(Canvas canvas, Size size)方法拿到canvas,使用Canvas來(lái)繪制需要的View, 這個(gè)繪制方法和Android和iOS都基本類似

Q2: 在Flutter中如何使用Android和iOS的原生View?(什么是 platform view谢鹊?)

platform view 就是 AndroidViewUIKitView 的總稱脚仔,允許將 native view 嵌入到了 flutter widget 體系中轴脐,完成 Datr 代碼對(duì)native view 的控制。

1朗鸠、在Flutter中使用一個(gè)Widget包裹platform view便于使用
@override
Widget build(BuildContext context) {
     // 根據(jù)運(yùn)行平臺(tái)判斷執(zhí)行代碼
     if (defaultTargetPlatform == TargetPlatform.android) {
           return AndroidView(
                   // 在 native 中的唯一標(biāo)識(shí)符笼踩,需要與 native 側(cè)的值相同
                   viewType: "platform_text_view",
                   // 在創(chuàng)建 AndroidView 的同時(shí)穷娱,可以傳遞參數(shù)
                   creationParams: <String, dynamic>{"text": text},
                 // 用來(lái)編碼 creationParams 的形式讯屈,可選 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or           [BinaryCodec]
                 // 如果存在 creationParams蛋哭,則該值不能為null
                 creationParamsCodec: const StandardMessageCodec(),
           );
     }else if (defaultTargetPlatform == TargetPlatform.iOS) {
           return UiKitView(
               viewType: "platform_text_view",
               creationParams: <String, dynamic>{"text": text},
               creationParamsCodec: const StandardMessageCodec(),
           );
     }
     return Text("不支持的平臺(tái)");
}
2、 在原生Android端創(chuàng)建一個(gè)類繼承PlatformView,之后創(chuàng)建一個(gè)類繼承PlatformViewFactory,在create方法中返回繼承PlatformView類,代碼如下
class AndroidCustomeView(context: Context) : PlatformView {
    val contentView: TextView = TextView(context)
    override fun getView(): View {
        return contentView
    }
    override fun dispose() {}
}
class AndroidCustomeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val androidTextView = AndroidTextView(context)
        androidTextView.contentView.id = viewId
        val params = args?.let { args as Map<*, *> }
        val text = params?.get("text") as CharSequence?
        text?.let {
            androidTextView.contentView.text = it
        }
        return androidTextView
    }
}
3涮母、iOS中與Android中一樣

但是多一個(gè)步驟,在配置文件info.plist增加io.flutter.embedded_views_preview=true

小結(jié)一下

  • 我們了解了 AndroidViewController谆趾、_AndroidPlatformView 都做了什么。
  • AndroidView 的大小是由父節(jié)點(diǎn)的大小去定的所以上面使用 Expanded 包裹則可以生效叛本,如果不進(jìn)行包裹沪蓬,則大小為父控件大小,在 Column 中會(huì)出現(xiàn)問(wèn)題来候。當(dāng) Widget size 小于 View size跷叉,F(xiàn)lutter 會(huì)進(jìn)行裁剪。當(dāng) Widget size 大于 View size 時(shí)营搅,多出來(lái)的位置會(huì)被背景填充云挟。在 Android 側(cè),實(shí)現(xiàn)了 PlatformView 的 View 會(huì)被包裹在 FrameLayout 中剧防,可以對(duì) View 的繪制添加監(jiān)聽(tīng),打印出 View 的 parent辫樱;
  • platform view 是在 native 側(cè)渲染的峭拘,返回給 Flutter 側(cè)一個(gè) _textureId ,通過(guò)這個(gè) id Flutter 將 View 直接展示出來(lái)狮暑。這部分也說(shuō)明了為什么 platform view 在 Flutter 中的性能開(kāi)銷比較大鸡挠,整個(gè)過(guò)程數(shù)據(jù)需要從 GPU -> CPU -> GPU,這部分的代價(jià)是比較大的搬男。

如何開(kāi)發(fā)一個(gè) platform view

其實(shí) Flutter 官方維護(hù)了一些 plugin拣展,鏈接如下:
https://github.com/flutter/plugins

其中的 webview_fluttergoogle_maps_flutter 就是通過(guò) platform view缔逛,就是一個(gè)很好的 demo 备埃。

Q4: 動(dòng)態(tài)加載技術(shù)?

1、什么是動(dòng)態(tài)加載技術(shù)褐奴?

動(dòng)態(tài)加載技術(shù)就是使用類加載器加載相應(yīng)的apk按脚、dexjar(必須含有dex文件)敦冬,再通過(guò)反射獲得該apk辅搬、dexjar內(nèi)部的資源(class脖旱、圖片堪遂、color等等)進(jìn)而供宿主app使用介蛉。它的優(yōu)點(diǎn)可以讓應(yīng)用程序?qū)崿F(xiàn)插件化插拔式結(jié)構(gòu)

2溶褪、關(guān)于動(dòng)態(tài)加載使用的類加載器

使用動(dòng)態(tài)加載技術(shù)時(shí)币旧,一般需要用到這兩個(gè)類加載器:

  • PathClassLoader - 只能加載已經(jīng)安裝的apk,即/data/app目錄下的apk竿滨。
  • DexClassLoader - 能加載手機(jī)中未安裝的apk佳恬、jar、dex于游,只要能在找到對(duì)應(yīng)的路徑毁葱。
    這兩個(gè)加載器分別對(duì)應(yīng)使用的場(chǎng)景各不同,所以接下來(lái)贰剥,分別講解它們各自加載相同的插件apk的使用倾剿。
3、使用PathClassLoader加載已安裝的apk插件蚌成,獲取相應(yīng)的資源供宿主app使用

1. 首先我們需要知道一個(gè)manifest中的屬性:SharedUserId前痘。

SharedUserId

該屬性是用來(lái)干嘛的呢?簡(jiǎn)單的說(shuō)担忧,應(yīng)用從一開(kāi)始安裝在Android系統(tǒng)上時(shí)芹缔,系統(tǒng)都會(huì)給它分配一個(gè)linux user id,之后該應(yīng)用在今后都將運(yùn)行在獨(dú)立的一個(gè)進(jìn)程中瓶盛,其它應(yīng)用程序不能訪問(wèn)它的資源最欠,那么如果兩個(gè)應(yīng)用的sharedUserId相同,那么它們將共同運(yùn)行在相同的linux進(jìn)程中惩猫,從而便可以數(shù)據(jù)共享芝硬、資源訪問(wèn)了。所以我們?cè)谒拗鱝pp和插件app的manifest上都定義一個(gè)相同的sharedUserId轧房。

2拌阴、那么我們將插件apk安裝在手機(jī)上后,宿主app怎么知道手機(jī)內(nèi)該插件是否是我們應(yīng)用程序的插件呢奶镶?

我們之前是不是定義過(guò)插件apk也是使用相同的sharedUserId迟赃,那么,我就可以這樣思考了厂镇,是不是可以得到手機(jī)內(nèi)所有已安裝apk的sharedUserId呢捺氢,然后通過(guò)判斷sharedUserId是否和宿主app的相同,如果是剪撬,那么該app就是我們的插件app了摄乒。確實(shí)是這樣的思路的,那么有了思路最大的問(wèn)題就是怎么獲取一個(gè)應(yīng)用程序內(nèi)的sharedUserId了,我們可以通過(guò)PackageInfo.sharedUserId來(lái)獲取馍佑,請(qǐng)看代碼:

    /** 
     * 查找手機(jī)內(nèi)所有的插件 
     * @return 返回一個(gè)插件List 
     */  
    private List<PluginBean> findAllPlugin() {  
        List<PluginBean> plugins = new ArrayList<>();  
        PackageManager pm = getPackageManager();  
        //通過(guò)包管理器查找所有已安裝的apk文件  
        List<PackageInfo> packageInfos = pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);  
        for (PackageInfo info : packageInfos) {  
            //得到當(dāng)前apk的包名  
            String pkgName = info.packageName;  
            //得到當(dāng)前apk的sharedUserId  
            String shareUesrId = info.sharedUserId;  
            //判斷這個(gè)apk是否是我們應(yīng)用程序的插件  
            if (shareUesrId != null && shareUesrId.equals("com.sunzxyong.myapp") && !pkgName.equals(this.getPackageName())) {  
                String label = pm.getApplicationLabel(info.applicationInfo).toString();//得到插件apk的名稱  
                PluginBean bean = new PluginBean(label,pkgName);  
                plugins.add(bean);  
            }  
        }  
        return plugins;  
    }  

通過(guò)這段代碼斋否,我們就可以輕松的獲取手機(jī)內(nèi)存在的所有插件,其中PluginBean是定義的一個(gè)實(shí)體類而已拭荤,就不貼它的代碼了茵臭。

3、如果找到了插件舅世,就把可用的插件顯示出來(lái)了旦委,如果沒(méi)有找到,那么就可提示用戶先去下載插件什么的雏亚。

List<HashMap<String, String>> datas = new ArrayList<>();  
List<PluginBean> plugins = findAllPlugin();  
if (plugins != null && !plugins.isEmpty()) {  
    for (PluginBean bean : plugins) {  
        HashMap<String, String> map = new HashMap<>();  
        map.put("label", bean.getLabel());  
        datas.add(map);  
    }  
} else {  
    Toast.makeText(this, "沒(méi)有找到插件缨硝,請(qǐng)先下載!", Toast.LENGTH_SHORT).show();  
}  
showEnableAllPluginPopup(datas);  

4罢低、如果找到后查辩,那么我們選擇對(duì)應(yīng)的插件時(shí),在宿主app中就加載插件內(nèi)對(duì)應(yīng)的資源网持,這個(gè)才是PathClassLoader的重點(diǎn)宜岛。我們首先看看怎么實(shí)現(xiàn)的吧:

    /** 
     * 加載已安裝的apk 
     * @param packageName 應(yīng)用的包名 
     * @param pluginContext 插件app的上下文 
     * @return 對(duì)應(yīng)資源的id 
     */  
    private int dynamicLoadApk(String packageName, Context pluginContext) throws Exception {  
        //第一個(gè)參數(shù)為包含dex的apk或者jar的路徑,第二個(gè)參數(shù)為父加載器  
        PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath(),ClassLoader.getSystemClassLoader());  
//        Class<?> clazz = pathClassLoader.loadClass(packageName + ".R$mipmap");//通過(guò)使用自身的加載器反射出mipmap類進(jìn)而使用該類的功能  
        //參數(shù):1功舀、類的全名萍倡,2、是否初始化類辟汰,3列敲、加載時(shí)使用的類加載器  
        Class<?> clazz = Class.forName(packageName + ".R$mipmap", true, pathClassLoader);  
        //使用上述兩種方式都可以,這里我們得到R類中的內(nèi)部類mipmap莉擒,通過(guò)它得到對(duì)應(yīng)的圖片id酿炸,進(jìn)而給我們使用  
        Field field = clazz.getDeclaredField("one");  
        int resourceId = field.getInt(R.mipmap.class);  
        return resourceId;  
    }  

這個(gè)方法就是加載包名為packageName的插件瘫絮,然后獲得插件內(nèi)名為one.png的圖片的資源id涨冀,進(jìn)而供宿主app使用該圖片。現(xiàn)在我們一步一步來(lái)講解一下:

  • 首先就是new出一個(gè)PathClassLoader對(duì)象麦萤,它的構(gòu)造方法為:
public PathClassLoader(String dexPath, ClassLoader parent)  

中其中第一個(gè)參數(shù)是通過(guò)插件的上下文來(lái)獲取插件apk的路徑鹿鳖,其實(shí)獲取到的就是/data/app/apkthemeplugin.apk,那么插件的上下文怎么獲取呢壮莹?在宿主app中我們只有本app的上下文啊翅帜,答案就是為插件app創(chuàng)建一個(gè)上下文:

 Context pluginContext = createPackageContext(packageName, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE);

通過(guò)插件的包名來(lái)創(chuàng)建上下文,不過(guò)這種方法只適合獲取已安裝的app上下文命满±缘危或者不需要通過(guò)反射直接通過(guò)插件上下文getResource().getxxx(R..);也行,而這里用的是反射方法。第二個(gè)參數(shù)是父加載器歼疮,都是ClassLoader.getSystemClassLoader()杂抽。

  • 插件app的類加載器我們創(chuàng)建出來(lái)了,接下來(lái)就是通過(guò)反射獲取對(duì)應(yīng)類的資源了,這里我是獲取R類中的內(nèi)部類mipmap類韩脏,然后通過(guò)反射得到mipmap類中名為one的字段的值缩麸,



    然后通過(guò)

plugnContext.getResources().getDrawable(resouceId)  

就可以獲取對(duì)應(yīng)id的Drawable得到該圖片資源進(jìn)而宿主app的可用它設(shè)置背景等。
下面演示下該demo效果赡矢,在沒(méi)有插件情況下會(huì)提示請(qǐng)先下載插件杭朱,有插件時(shí)候就選擇對(duì)應(yīng)的插件而供宿主app使用,本demo是換背景的功能演示吹散,我來(lái)看宿主app中mipmap文件夾下并沒(méi)有one.png這張圖片弧械。

4、DexClassLoader加載未安裝的apk,提供資源供宿主app使用

關(guān)于動(dòng)態(tài)加載未安裝的apk埂伦,我先描述下思路:首先我們得到事先知道我們的插件apk存放在哪個(gè)目錄下异希,然后分別得到插件apk的信息(名稱、包名等)唁桩,然后顯示可用的插件,最后動(dòng)態(tài)加載apk獲得資源耸棒。
按照上面這個(gè)思路荒澡,我們需要解決幾個(gè)問(wèn)題:

  • 怎么得到未安裝的apk的信息
  • 怎么得到插件的context或者Resource,因?yàn)樗俏窗惭b的不可能通過(guò)createPackageContext(...);方法來(lái)構(gòu)建出一個(gè)context与殃,所以這時(shí)只有在Resource上下功夫单山。

現(xiàn)在我們就一一來(lái)解答這些問(wèn)題吧:
1、得到未安裝的apk信息可以通過(guò)mPackageManager.getPackageArchiveInfo()方法獲得幅疼,

public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) 

它的參數(shù)剛好是傳入一個(gè)FilePath米奸,然后返回apk文件的PackageInfo信息:

    /** 
     * 獲取未安裝apk的信息 
     * @param context 
     * @param archiveFilePath apk文件的path 
     * @return 
     */  
    private String[] getUninstallApkInfo(Context context, String archiveFilePath) {  
        String[] info = new String[2];  
        PackageManager pm = context.getPackageManager();  
        PackageInfo pkgInfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);  
        if (pkgInfo != null) {  
            ApplicationInfo appInfo = pkgInfo.applicationInfo;  
            String versionName = pkgInfo.versionName;//版本號(hào)  
            Drawable icon = pm.getApplicationIcon(appInfo);//圖標(biāo)  
            String appName = pm.getApplicationLabel(appInfo).toString();//app名稱  
            String pkgName = appInfo.packageName;//包名  
            info[0] = appName;  
            info[1] = pkgName;  
        }  
        return info;  
    }  

2、得到對(duì)應(yīng)未安裝apk的Resource對(duì)象爽篷,我們需要通過(guò)反射來(lái)獲得:

    /** 
     * @param apkName  
     * @return 得到對(duì)應(yīng)插件的Resource對(duì)象 
     */  
    private Resources getPluginResources(String apkName) {  
        try {  
            AssetManager assetManager = AssetManager.class.newInstance();  
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);//反射調(diào)用方法addAssetPath(String path)  
            //第二個(gè)參數(shù)是apk的路徑:Environment.getExternalStorageDirectory().getPath()+File.separator+"plugin"+File.separator+"apkplugin.apk"  
            addAssetPath.invoke(assetManager, apkDir+File.separator+apkName);//將未安裝的Apk文件的添加進(jìn)AssetManager中悴晰,第二個(gè)參數(shù)為apk文件的路徑帶apk名  
            Resources superRes = this.getResources();  
            Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),  
                    superRes.getConfiguration());  
            return mResources;  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  

通過(guò)得到AssetManager中的內(nèi)部的方法addAssetPath,將未安裝的apk路徑傳入從而添加進(jìn)assetManager中逐工,然后通過(guò)new Resource把a(bǔ)ssetManager傳入構(gòu)造方法中铡溪,進(jìn)而得到未安裝apk對(duì)應(yīng)的Resource對(duì)象。

好了泪喊!上面兩個(gè)問(wèn)題解決了棕硫,那么接下來(lái)就是加載未安裝的apk獲得它的內(nèi)部資源。

    /** 
     * 加載apk獲得內(nèi)部資源 
     * @param apkDir apk目錄 
     * @param apkName apk名字,帶.apk 
     * @throws Exception 
     */  
    private void dynamicLoadApk(String apkDir, String apkName, String apkPackageName) throws Exception {  
        File optimizedDirectoryFile = getDir("dex", Context.MODE_PRIVATE);//在應(yīng)用安裝目錄下創(chuàng)建一個(gè)名為app_dex文件夾目錄,如果已經(jīng)存在則不創(chuàng)建  
        Log.v("zxy", optimizedDirectoryFile.getPath().toString());// /data/data/com.example.dynamicloadapk/app_dex  
        //參數(shù):1袒啼、包含dex的apk文件或jar文件的路徑哈扮,2纬纪、apk、jar解壓縮生成dex存儲(chǔ)的目錄滑肉,3育八、本地library庫(kù)目錄,一般為null赦邻,4髓棋、父ClassLoader  
        DexClassLoader dexClassLoader = new DexClassLoader(apkDir+File.separator+apkName, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());  
        Class<?> clazz = dexClassLoader.loadClass(apkPackageName + ".R$mipmap");//通過(guò)使用apk自己的類加載器,反射出R類中相應(yīng)的內(nèi)部類進(jìn)而獲取我們需要的資源id  
        Field field = clazz.getDeclaredField("one");//得到名為one的這張圖片字段  
        int resId = field.getInt(R.id.class);//得到圖片id  
        Resources mResources = getPluginResources(apkName);//得到插件apk中的Resource  
        if (mResources != null) {  
            //通過(guò)插件apk中的Resource得到resId對(duì)應(yīng)的資源  
            findViewById(R.id.background).setBackgroundDrawable(mResources.getDrawable(resId));  
        }  
    }  

其中通過(guò)new DexClassLoader()來(lái)創(chuàng)建未安裝apk的類加載器惶洲,我們來(lái)看看它的參數(shù):

public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)  
  • dexPath - 就是apk文件的路徑
  • optimizedDirectory - apk解壓縮后的存放dex的目錄按声,值得注意的是,在4.1以后該目錄不允許在sd卡上恬吕,看官方文檔:
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:
File dexOutputDir = context.getDir("dex", 0);
Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection

所以我們用getDir()方法在應(yīng)用內(nèi)部創(chuàng)建一個(gè)dexOutputDir签则。

  • libraryPath - 本地的library,一般為null
  • parent - 父加載器
    接下來(lái)铐料,就是通過(guò)反射的方法渐裂,獲取出需要的資源。
    再看看拷貝了三個(gè)插件:
copyApkFile("apkthemeplugin-1.apk");  
copyApkFile("apkthemeplugin-2.apk");  
copyApkFile("apkthemeplugin-3.apk");  

可以看到只要一有插件下載钠惩,就能顯示出來(lái)并使用它柒凉。

當(dāng)然插件化開(kāi)發(fā)并不只是像只有這種換膚那么簡(jiǎn)單的用途,這只是個(gè)demo篓跛,學(xué)習(xí)這種插件化開(kāi)發(fā)思想的膝捞。由此可以聯(lián)想,這種插件化的開(kāi)發(fā)愧沟,是不是像QQ里的表情包啊蔬咬、背景皮膚啊,通過(guò)線上下載線下維護(hù)的方式沐寺,可以在線下載使用相應(yīng)的皮膚林艘,不使用時(shí)候就可以刪了,所以插件化開(kāi)發(fā)是插件與宿主app進(jìn)行解耦了混坞,即使在沒(méi)有插件情況下狐援,也不會(huì)對(duì)宿主app有任何影響,而有的話就供用戶選擇性使用了拔第。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咕村,一起剝皮案震驚了整個(gè)濱河市场钉,隨后出現(xiàn)的幾起案子蚊俺,更是在濱河造成了極大的恐慌,老刑警劉巖逛万,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泳猬,死亡現(xiàn)場(chǎng)離奇詭異批钠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)得封,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門埋心,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人忙上,你說(shuō)我怎么就攤上這事拷呆。” “怎么了疫粥?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵茬斧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我梗逮,道長(zhǎng)项秉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任慷彤,我火速辦了婚禮娄蔼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘底哗。我一直安慰自己岁诉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布跋选。 她就那樣靜靜地躺著唉侄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪野建。 梳的紋絲不亂的頭發(fā)上属划,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音候生,去河邊找鬼同眯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛唯鸭,可吹牛的內(nèi)容都是我干的须蜗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼目溉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼明肮!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起缭付,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柿估,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后陷猫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體秫舌,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡的妖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了足陨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫂粟。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖墨缘,靈堂內(nèi)的尸體忽然破棺而出星虹,到底是詐尸還是另有隱情,我是刑警寧澤镊讼,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布搁凸,位于F島的核電站,受9級(jí)特大地震影響狠毯,放射性物質(zhì)發(fā)生泄漏护糖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一嚼松、第九天 我趴在偏房一處隱蔽的房頂上張望嫡良。 院中可真熱鬧,春花似錦献酗、人聲如沸寝受。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)很澄。三九已至,卻和暖如春颜及,著一層夾襖步出監(jiān)牢的瞬間甩苛,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工俏站, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讯蒲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓肄扎,卻偏偏與公主長(zhǎng)得像墨林,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子犯祠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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