Android應(yīng)用更新-自動(dòng)檢測(cè)版本及自動(dòng)升級(jí)

步驟:

  • 1.檢測(cè)當(dāng)前版本的信息AndroidManifest.xml-->manifest-->[Android]

  • 2.從服務(wù)器獲取版本號(hào)(版本號(hào)存在于xml文件中)并與當(dāng)前檢測(cè)到的版本進(jìn)行匹配,如果不匹配,提示用戶進(jìn)行升級(jí),如果匹配則進(jìn)入程序主界面。(demo中假設(shè)需要更新)

  • 3.當(dāng)提示用戶進(jìn)行版本升級(jí)時(shí),如果用戶點(diǎn)擊了“更新”,系統(tǒng)將自動(dòng)從服務(wù)器上下載安裝包并進(jìn)行自動(dòng)升級(jí),如果點(diǎn)擊取消將進(jìn)入程序主界面。

效果圖如下:

更新

下載1
下載2
安裝

下面介紹一下代碼的實(shí)現(xiàn):

  • 1.獲取應(yīng)用的當(dāng)前版本號(hào),我是封裝了一個(gè)工具類來(lái)獲取
 // 獲取本版本號(hào),是否更新
        int vision = Tools.getVersion(this);

獲取當(dāng)前版本號(hào)工具類:


public class Tools {
    /**
     * 檢查是否存在SDCard
     *
     * @return
     */
    public static boolean hasSdcard() {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 2 * 獲取版本號(hào) 3 * @return 當(dāng)前應(yīng)用的版本號(hào) 4
     */
    public static int getVersion(Context context) {
        try {
            PackageManager manager = context.getPackageManager();
            PackageInfo info = manager.getPackageInfo(context.getPackageName(),
                    0);
            String version = info.versionName;
            int versioncode = info.versionCode;
            return versioncode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
   
}
  • 2.獲取服務(wù)器版本號(hào),是否要更新(此處就是簡(jiǎn)單的網(wǎng)絡(luò)請(qǐng)求拿到需要的數(shù)據(jù)即可,我是寫了固定值)
 // 獲取更新版本號(hào)
    private void getVersion(final int vision) {
//         {"data":{"content":"其他bug修復(fù)。","id":"2","api_key":"android",
//         // "version":"2.1"},"msg":"獲取成功","status":1}
        String data = "";
        //網(wǎng)絡(luò)請(qǐng)求獲取當(dāng)前版本號(hào)和下載鏈接
        //實(shí)際操作是從服務(wù)器獲取
        //demo寫死了

        String newversion = "2.1";//更新新的版本號(hào)
        String content = "\n" +
                "就不告訴你我們更新了什么-。-\n" +
                "\n" +
                "----------萬(wàn)能的分割線-----------\n" +
                "\n" +
                "(ㄒoㄒ) 被老板打了一頓,還是來(lái)告訴你吧:\n" +

                "1.下架商品誤買了?恩。。。我搞了點(diǎn)小動(dòng)作就不會(huì)出現(xiàn)了\n" +
                "2.側(cè)邊欄、彈框優(yōu)化 —— 這個(gè)你自己去探索吧,總得留點(diǎn)懸念嘛-。-\n";//更新內(nèi)容
        String url = "http://openbox.mobilem.360.cn/index/d/sid/3429345";//安裝包下載地址

        double newversioncode = Double
                .parseDouble(newversion);
        int cc = (int) (newversioncode);

        System.out.println(newversion + "v" + vision + ",,"
                + cc);
        if (cc != vision) {
            if (vision < cc) {
                System.out.println(newversion + "v"
                        + vision);
                // 版本號(hào)不同
                ShowDialog(vision, newversion, content, url);
            }
        }
    }
  • 3.接下來(lái)就是下載文件了
    (1) 顯示下載
    此處用的是自定義按鈕:
 /**
     * 升級(jí)系統(tǒng)
     *
     * @param content
     * @param url
     */
    private void ShowDialog(int vision, String newversion, String content,
                            final String url) {
        final MaterialDialog dialog = new MaterialDialog(this);
        dialog.content(content).btnText("取消", "更新").title("版本更新 ")
                .titleTextSize(15f).show();
        dialog.setCanceledOnTouchOutside(false);
        dialog.setOnBtnClickL(new OnBtnClickL() {// left btn click listener
            @Override
            public void onBtnClick() {
                dialog.dismiss();
            }
        }, new OnBtnClickL() {// right btn click listener

            @Override
            public void onBtnClick() {
                dialog.dismiss();
                // pBar = new ProgressDialog(MainActivity.this,
                // R.style.dialog);
                pBar = new CommonProgressDialog(MainActivity.this);
                pBar.setCanceledOnTouchOutside(false);
                pBar.setTitle("正在下載");
                pBar.setCustomTitle(LayoutInflater.from(
                        MainActivity.this).inflate(
                        R.layout.title_dialog, null));
                pBar.setMessage("正在下載");
                pBar.setIndeterminate(true);
                pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pBar.setCancelable(true);
                // downFile(URLData.DOWNLOAD_URL);
                final DownloadTask downloadTask = new DownloadTask(
                        MainActivity.this);
                downloadTask.execute(url);
                pBar.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        downloadTask.cancel(true);
                    }
                });
            }
        });
    }

原生的按鈕:

 new android.app.AlertDialog.Builder(this)
                .setTitle("版本更新")
                .setMessage(content)
                .setPositiveButton("更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        pBar = new CommonProgressDialog(MainActivity.this);
                        pBar.setCanceledOnTouchOutside(false);
                        pBar.setTitle("正在下載");
                        pBar.setCustomTitle(LayoutInflater.from(
                                MainActivity.this).inflate(
                                R.layout.title_dialog, null));
                        pBar.setMessage("正在下載");
                        pBar.setIndeterminate(true);
                        pBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                        pBar.setCancelable(true);
                        // downFile(URLData.DOWNLOAD_URL);
                        final DownloadTask downloadTask = new DownloadTask(
                                MainActivity.this);
                        downloadTask.execute(url);
                        pBar.setOnCancelListener(new DialogInterface.OnCancelListener() {
                            @Override
                            public void onCancel(DialogInterface dialog) {
                                downloadTask.cancel(true);
                            }
                        });
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                })
                .show();

(2)通過(guò)異步任務(wù)實(shí)現(xiàn)進(jìn)度++


    /**
     * 下載應(yīng)用
     *
     * @author Administrator
     */
    class DownloadTask extends AsyncTask<String, Integer, String> {

        private Context context;
        private PowerManager.WakeLock mWakeLock;

        public DownloadTask(Context context) {
            this.context = context;
        }

        @Override
        protected String doInBackground(String... sUrl) {
            InputStream input = null;
            OutputStream output = null;
            HttpURLConnection connection = null;
            File file = null;
            try {
                URL url = new URL(sUrl[0]);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
                // expect HTTP 200 OK, so we don't mistakenly save error
                // report
                // instead of the file
                if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    return "Server returned HTTP "
                            + connection.getResponseCode() + " "
                            + connection.getResponseMessage();
                }
                // this will be useful to display download percentage
                // might be -1: server did not report the length
                int fileLength = connection.getContentLength();
                if (Environment.getExternalStorageState().equals(
                        Environment.MEDIA_MOUNTED)) {
                    file = new File(Environment.getExternalStorageDirectory(),
                            DOWNLOAD_NAME);

                    if (!file.exists()) {
                        // 判斷父文件夾是否存在
                        if (!file.getParentFile().exists()) {
                            file.getParentFile().mkdirs();
                        }
                    }

                } else {
                    Toast.makeText(MainActivity.this, "sd卡未掛載",
                            Toast.LENGTH_LONG).show();
                }
                input = connection.getInputStream();
                output = new FileOutputStream(file);
                byte data[] = new byte[4096];
                long total = 0;
                int count;
                while ((count = input.read(data)) != -1) {
                    // allow canceling with back button
                    if (isCancelled()) {
                        input.close();
                        return null;
                    }
                    total += count;
                    // publishing the progress....
                    if (fileLength > 0) // only if total length is known
                        publishProgress((int) (total * 100 / fileLength));
                    output.write(data, 0, count);

                }
            } catch (Exception e) {
                System.out.println(e.toString());
                return e.toString();

            } finally {
                try {
                    if (output != null)
                        output.close();
                    if (input != null)
                        input.close();
                } catch (IOException ignored) {
                }
                if (connection != null)
                    connection.disconnect();
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // take CPU lock to prevent CPU from going off if the user
            // presses the power button during download
            PowerManager pm = (PowerManager) context
                    .getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                    getClass().getName());
            mWakeLock.acquire();
            pBar.show();
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            // if we get here, length is known, now set indeterminate to false
            pBar.setIndeterminate(false);
            pBar.setMax(100);
            pBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(String result) {
            mWakeLock.release();
            pBar.dismiss();
            if (result != null) {

//                // 申請(qǐng)多個(gè)權(quán)限。大神的界面
//                AndPermission.with(MainActivity.this)
//                        .requestCode(REQUEST_CODE_PERMISSION_OTHER)
//                        .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
//                        // rationale作用是:用戶拒絕一次權(quán)限,再次申請(qǐng)時(shí)先征求用戶同意,再打開授權(quán)對(duì)話框,避免用戶勾選不再提示。
//                        .rationale(new RationaleListener() {
//                                       @Override
//                                       public void showRequestPermissionRationale(int requestCode, Rationale rationale) {
//                                           // 這里的對(duì)話框可以自定義,只要調(diào)用rationale.resume()就可以繼續(xù)申請(qǐng)。
//                                           AndPermission.rationaleDialog(MainActivity.this, rationale).show();
//                                       }
//                                   }
//                        )
//                        .send();
                // 申請(qǐng)多個(gè)權(quán)限。
                AndPermission.with(MainActivity.this)
                        .requestCode(REQUEST_CODE_PERMISSION_SD)
                        .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                        // rationale作用是:用戶拒絕一次權(quán)限,再次申請(qǐng)時(shí)先征求用戶同意,再打開授權(quán)對(duì)話框,避免用戶勾選不再提示。
                        .rationale(rationaleListener
                        )
                        .send();


                Toast.makeText(context, "您未打開SD卡權(quán)限" + result, Toast.LENGTH_LONG).show();
            } else {
                // Toast.makeText(context, "File downloaded",
                // Toast.LENGTH_SHORT)
                // .show();
                update();
            }

        }
    }

此處下載apk文件,需要獲取SD的讀寫權(quán)限(用的是嚴(yán)大的權(quán)限庫(kù))
權(quán)限庫(kù)GitHub:https://github.com/yanzhenjie/AndPermission

  private static final int REQUEST_CODE_PERMISSION_SD = 101;

    private static final int REQUEST_CODE_SETTING = 300;
    private RationaleListener rationaleListener = new RationaleListener() {
        @Override
        public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
            // 這里使用自定義對(duì)話框,如果不想自定義,用AndPermission默認(rèn)對(duì)話框:
            // AndPermission.rationaleDialog(Context, Rationale).show();

            // 自定義對(duì)話框。
            AlertDialog.build(MainActivity.this)
                    .setTitle(R.string.title_dialog)
                    .setMessage(R.string.message_permission_rationale)
                    .setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            rationale.resume();
                        }
                    })

                    .setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            rationale.cancel();
                        }
                    })
                    .show();
        }
    };
    //----------------------------------SD權(quán)限----------------------------------//


    @PermissionYes(REQUEST_CODE_PERMISSION_SD)
    private void getMultiYes(List<String> grantedPermissions) {
        Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show();
    }

    @PermissionNo(REQUEST_CODE_PERMISSION_SD)
    private void getMultiNo(List<String> deniedPermissions) {
        Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show();

        // 用戶否勾選了不再提示并且拒絕了權(quán)限,那么提示用戶到設(shè)置中授權(quán)。
        if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) {
            AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING)
                    .setTitle(R.string.title_dialog)
                    .setMessage(R.string.message_permission_failed)
                    .setPositiveButton(R.string.btn_dialog_yes_permission)
                    .setNegativeButton(R.string.btn_dialog_no_permission, null)
                    .show();

            // 更多自定dialog,請(qǐng)看上面。
        }
    }

    //----------------------------------權(quán)限回調(diào)處理----------------------------------//

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
            grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        /**
         * 轉(zhuǎn)給AndPermission分析結(jié)果。
         *
         * @param object     要接受結(jié)果的Activity、Fragment。
         * @param requestCode  請(qǐng)求碼。
         * @param permissions  權(quán)限數(shù)組,一個(gè)或者多個(gè)。
         * @param grantResults 請(qǐng)求結(jié)果。
         */
        AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CODE_SETTING: {
                Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show();
                //設(shè)置成功,再次請(qǐng)求更新
                getVersion(Tools.getVersion(MainActivity.this));
                break;
            }
        }
    }

(3) 當(dāng)apk文件下載完畢時(shí),打開安裝

   private void update() {
        //安裝應(yīng)用
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(new File(Environment
                        .getExternalStorageDirectory(), DOWNLOAD_NAME)),
                "application/vnd.android.package-archive");
        startActivity(intent);
    }

Android 7.0 FileUriExposedException 的處理

發(fā)現(xiàn)問(wèn)題

前幾天把手機(jī)系統(tǒng)升級(jí)到基于 Android 7.0,后來(lái)在升級(jí)調(diào)試一個(gè)應(yīng)用時(shí)拋出如下異常信息:

android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com.skyrin.bingo/cache/app/app.apk exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)

at com.skyrin.bingo.update.AppUpdate.installApk(AppUpdate.java:295)

根據(jù)如上日志找到 AppUpdate 類下的 installApk 方法:

/**
 * 安裝apk
 */
public static void installApk(Context context,String apkPath) {
    if (TextUtils.isEmpty(apkPath)){
        Toast.makeText(context,"更新失敗!未找到安裝包", Toast.LENGTH_SHORT).show();
        return;
    }

    File apkFile = new File(apkPath
            + apkCacheName);

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setDataAndType(
            Uri.fromFile(apkFile),
            "application/vnd.android.package-archive");
    context.startActivity(intent); 
}

問(wèn)題出在啟動(dòng)安裝程序階段
由于沒(méi)升級(jí) 7.0 系統(tǒng)之前都沒(méi)有問(wèn)題,于是就在 Android 官網(wǎng)查看了一下 Android 7.0 新特性,終于發(fā)現(xiàn)其中 “在應(yīng)用間共享文件” 一欄明確指出了這個(gè)問(wèn)題

解決問(wèn)題

官方給出的解決方式是通過(guò) FileProvider 來(lái)為所共享的文件 Uri 添加臨時(shí)權(quán)限,詳細(xì)請(qǐng)看這里

  • 在 <application> 標(biāo)簽下添加 FileProvider 節(jié)點(diǎn)
<application
   ...>
   ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.skyrin.bingo.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
   ...
</application>

android:authority 屬性指定要用于 FileProvider 生成的 content URI 的 URI 權(quán)限,這里推薦使用 包名.fileprovider 以確保其唯一性。

<provider><meta-data> 子元素指向一個(gè) XML 文件,用于指定要共享的目錄。

  • res/xml 目錄下創(chuàng)建文件 file_paths.xml 內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-cache-path path="app/" name="apk"/>
</paths>

<external-cache-path> 表示應(yīng)用程序內(nèi)部存儲(chǔ)目錄下的 cache/ 目錄,完整路徑為 Android/data/com.xxx.xxx/cache/

path 屬性用于指定子目錄。

name 屬性告訴 FileProvider 為 Android/data/com.xxx.xxx/cache/app/ 創(chuàng)建一個(gè)名為 apk 的路徑字段。

想要通過(guò) FileProvider 為文件生成 content URI 只能在此處指定目錄,以上示例就表示我將要共享 Android/data/com.xxx.xxx/cache/app/ 這個(gè)目錄,除此之外還可以共享其它目錄,對(duì)應(yīng)的路徑如下:

標(biāo)簽 路徑
<files-path name="name" path="path" /> Context.getFilesDir()
<cache-path name="name" path="path" /> getCacheDir()
<external-path name="name" path="path" /> Environment.getExternalStorageDirectory()
<external-files-path name="name" path="path" /> Context.getExternalFilesDir()
<external-cache-path name="name" path="path" /> Context.getExternalCacheDir()
  • 完成以步驟后,我們修改出問(wèn)題的代碼如下:
/**
 * 安裝apk
 */
public static void installApk(Context context,String apkPath) {
    if (TextUtils.isEmpty(apkPath)){
        Toast.makeText(context,"更新失敗!未找到安裝包", Toast.LENGTH_SHORT).show();
        return;
    }

    File apkFile = new File(apkPath
            + apkCacheName);

    Intent intent = new Intent(Intent.ACTION_VIEW);
    //Android 7.0 系統(tǒng)共享文件需要通過(guò) FileProvider 添加臨時(shí)權(quán)限,否則系統(tǒng)會(huì)拋出 FileUriExposedException .
    if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri contentUri = FileProvider.getUriForFile(context,"com.skyrin.bingo.fileprovider",apkFile);
        intent.setDataAndType(contentUri,"application/vnd.android.package-archive");
    }else {
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(
                Uri.fromFile(apkFile),
                "application/vnd.android.package-archive");
    }
    context.startActivity(intent);
}
...
//調(diào)用,apkPath 入?yún)⒕褪?xml 中共享的路徑
String apkPath = context.getExternalCacheDir().getPath()+ File.separator+"app"+File.separator;
AppUpdate.installApk(context,apkPath );

結(jié)語(yǔ)

除了上面這個(gè)問(wèn)題,在 Android 7.0 之前開發(fā)的分享圖文、瀏覽編輯本地圖片、共享互傳文件等功能如果沒(méi)有使用 FileProvider 來(lái)生成 URI 的話,在 Android 7.0 上就必須做這種適配了,所以平時(shí)建議大家多關(guān)注 Android 新的 API ,盡早替換已被官方廢棄的 API ,實(shí)際上 FileProvider 在 API Level 22 已經(jīng)添加了。

源碼

此demo已經(jīng)上傳到GitHub,如有需要自行下載
GitHub:
https://github.com/huangshuyuan/UpdateDemo/

我的博客:
http://blog.csdn.net/Imshuyuan/article/details/62886741

我的簡(jiǎn)書:
http://www.lxweimin.com/p/2ab0459a9c3c

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,339評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評(píng)論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,201評(píng)論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,397評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,631評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評(píng)論 1 293
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,128評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,347評(píng)論 2 377

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,710評(píng)論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,818評(píng)論 18 139
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,489評(píng)論 0 17
  • 是這樣一個(gè)中午: 無(wú)風(fēng)、悶 太陽(yáng)躲在云彩里面 一群白鴿飛過(guò)頭頂 四周靜謐 我睜不開眼 思緒紊亂 情緒平穩(wěn) 我磨蹭到...
    鸉一閱讀 85評(píng)論 0 0
  • 2017.09.26號(hào),淅淅瀝瀝的小雨下了一天,天天越來(lái)越冷了。最近真的忙的不行了,所以每天去托輔接孩子的時(shí)候...
    愛(ài)孩子閱讀 248評(píng)論 0 0