如何接入騰訊Tinker熱更新,并使用Bugly進行托管

如何接入騰訊Tinker熱更新,并使用Bugly進行托管

【Tinker Github地址】: GitHub - Tencent/tinker: Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstall apk.

【Bugly 地址】: 騰訊Bugly - 一種愉悅的開發方式 _android anr_android anr分析_iOS崩潰日志分析平臺

【如何接入】

1.進入Bugly網站,登陸后新建產品

[圖片上傳失敗...(image-176945-1547370765018)]

2.新建后便會得到AppID,該id需要在后面初始化時候用到

[圖片上傳失敗...(image-7379f6-1547370765018)]

至次,申請工作已經完成,Bugly主要用來在后面上傳Patch補丁包以及監控異常等作用,接下來就需要在代碼中進行配置熱更新,我們通過在gradle中集成方式進行配置

【引入依賴】

1.在工程跟目錄的build.gradle中添加如下依賴

buildscript {
   
   repositories {
       google()
       jcenter()
   }
   dependencies {
       classpath 'com.android.tools.build:gradle:3.0.1'

       // NOTE: Do not place your application dependencies here; they belong
       // in the individual module build.gradle files

       // tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明確版本號,例如1.0.4
       classpath('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
       // tinkersupport插件(1.0.3以上無須再配置tinker插件)
       classpath "com.tencent.bugly:tinker-support:1.1.1"
   }
}

這里要注意,tinker版本和tinkersupport版本號是對應的,如1.9.1對應1.1.1,不可寫錯


其中,版本對應關系為:


tinker-support 1.1.2 對應 tinker 1.9.6
tinker-support 1.1.1 對應 tinker 1.9.1
tinker-support 1.0.9 對應 tinker 1.9.0
tinker-support 1.0.8 對應 tinker 1.7.11
tinker-support 1.0.7 對應 tinker 1.7.9
tinker-support 1.0.4 對應 tinker 1.7.7
tinker-support 1.0.3 對應 tinker 1.7.6
tinker-support 1.0.2 對應 tinker 1.7.5(需配置tinker插件的classpath)

2.在app下面的build.gradle中添加如下依賴:

// 依賴插件腳本
apply from: 'tinker-support.gradle'

defaultConfig {
       applicationId "com.thekey.buglyhotfix"
       minSdkVersion 15
       targetSdkVersion 26
       versionCode 2
       versionName "v2.0"
       testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
       // 開啟multidex
       multiDexEnabled true
   }
   
   // recommend
   dexOptions {
       jumboMode = true
   }
   
   // 構建類型
   buildTypes {
       release {
           minifyEnabled true
           signingConfig signingConfigs.release
           proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
       }
       debug {
           debuggable true
           minifyEnabled false
           signingConfig signingConfigs.debug
       }
   }
   
   // 簽名配置
   signingConfigs {
       release {
           try {
               storeFile file("./keystore/release.keystore")
               storePassword "android"
               keyAlias "android"
               keyPassword "android"
           } catch (ex) {
               throw new InvalidUserDataException(ex.toString())
           }
       }

       debug {
           storeFile file("./keystore/debug.keystore")
       }
   }
   
   dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:26.1.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

   implementation "com.android.support:multidex:1.0.1" // 多dex配置
   implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'// 遠程倉庫集成方式(推薦)
}

3.配置依賴插件版本tinker-support.gradle

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")

/**
* 此處填寫每次構建生成的基準包目錄
*/
def baseApkDir = "app-0111-14-10-58"
//def myTinkerId = "base-" + rootProject.ext.android.versionName // 用于生成基準包(不用修改)
def myTinkerId = "patch-" + "v2.0" + ".2.1" // 用于生成補丁包(每次生成補丁包都要修改一次,最好是 patch-${versionName}.x.x)
//這里配置是debug還是release
def variantName = "release"

/**
* 對于插件各參數的詳細解析請參考
*/
tinkerSupport {

   // 開啟tinker-support插件,默認值true
   enable = true

   // 是否啟用加固模式,默認為false.(tinker-spport 1.0.7起支持)
   isProtectedApp = false

   // 是否開啟反射Application模式
   enableProxyApplication = true

   // 是否支持新增非export的Activity(注意:設置為true才能修改AndroidManifest文件)
   supportHotplugComponent = true

   // 指定歸檔目錄,默認值當前module的子目錄tinker
   autoBackupApkDir = "${bakPath}"

   // 是否啟用覆蓋tinkerPatch配置功能,默認值false
   // 開啟后tinkerPatch配置不生效,即無需添加tinkerPatch
   overrideTinkerPatchConfiguration = true

   // 編譯補丁包時,必需指定基線版本的apk,默認值為空
   // 如果為空,則表示不是進行補丁包的編譯
   // @{link tinkerPatch.oldApk }

   //簡化配置
   def name = "${project.name}-${variantName}"

   baseApk = "${bakPath}/${baseApkDir}/${name}.apk"

   // 對應tinker插件applyMapping
   baseApkProguardMapping = "${bakPath}/${baseApkDir}/${name}-mapping.txt"

   // 對應tinker插件applyResourceMapping
   baseApkResourceMapping = "${bakPath}/${baseApkDir}/${name}-R.txt"

   // 構建基準包和補丁包都要指定不同的tinkerId,并且必須保證唯一性
   tinkerId = "${myTinkerId}"

   // 構建多渠道補丁時使用
   // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

}

/**
* 一般來說,我們無需對下面的參數做任何的修改
* 對于各參數的詳細介紹請參考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
   //oldApk ="${bakPath}/${appName}/app-release.apk"
   ignoreWarning = false
   useSign = true
   dex {
       dexMode = "jar"
       pattern = ["classes*.dex"]
       loader = []
   }
   lib {
       pattern = ["lib/*/*.so"]
   }

   res {
       pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
       ignoreChange = []
       largeModSize = 100
   }

   packageConfig {
   }
   sevenZip {
       zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
   }
   buildConfig {
       keepDexApply = false
       //tinkerId = "1.0.1-base"
       //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可選,設置mapping文件,建議保持舊apk的proguard混淆方式
       //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設置R.txt文件,通過舊apk文件保持ResId的分配
   }
}

其中, isProtectedApp參數表示是否開啟加固,如果不需要加固,則注釋掉該行或賦值為false,否則可能會無法熱更新

enableProxyApplication = false 的情況,這是Tinker推薦的接入方式,一定程度上會增加接入成本,對Application的改動較大,但具有更好的兼容性。

在該項目中,我們使用enableProxyApplication = true方式進行接入,該方式可以盡可能少的改變自己的Application

【代碼配置】

1.在自己的Application中進行如下初始化:

public class MyApplication extends Application{

    private static final String TAG = "MyApplication";

    private Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        //這里實現SDK初始化,appId為自己申請的appId
        //同時在調試時將第3個參數isDebug修改為true
        //是否提示用戶重啟,這里默認設置為false
        configTinker();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(mContext);
        // 安裝tinker
        // 此接口僅用于反射Application方式接入。
        Beta.installTinker();
    }

    /**
     * 初始化Tinker
     */
    private void configTinker(){
        //是否開啟熱更新能力,默認為true
        Beta.enableHotfix = true;
        //是否開啟自動下載補丁,默認為true
        Beta.canAutoDownloadPatch = true;
        //是否自動合成補丁,默認為true
        Beta.canAutoPatch = true;
        //是否提示用戶重啟,這里默認設置為false
        Beta.canNotifyUserRestart = true;
        //補丁回調接口
        Beta.betaPatchListener = new BetaPatchListener() {
            @Override
            public void onPatchReceived(String s) {
                Log.e(TAG, "補丁下載地址:" + s);
            }

            @Override
            public void onDownloadReceived(long l, long l1) {
                Log.e(TAG, String.format(Locale.getDefault(), "%s %d%%",
                        Beta.strNotificationDownloading,
                        (int) (l1 == 0 ? 0 : l * 100 / l1)));
            }

            @Override
            public void onDownloadSuccess(String s) {
                Log.e(TAG, "補丁下載成功");
                Toast.makeText(mContext, "補丁下載成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadFailure(String s) {
                Log.e(TAG, "補丁下載失敗");
                Toast.makeText(mContext, "補丁下載失敗", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onApplySuccess(String s) {
                Log.e(TAG, "補丁應用成功");
                Toast.makeText(mContext, "補丁應用成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onApplyFailure(String s) {
                Log.e(TAG, "補丁應用失敗");
                Toast.makeText(mContext, "補丁應用失敗", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPatchRollback() {

            }
        };

        //設置開發設備,默認為false,上傳補丁如果下發范圍指定為“開發設備”,需要調用此接口來標識開發設備
        Bugly.setIsDevelopmentDevice(mContext, false);
        // 多渠道需求塞入
        // String channel = WalleChannelReader.getChannel(getApplication());
        // Bugly.setAppChannel(getApplication(), channel);
        // 這里實現SDK初始化,appId替換成你的在平臺申請的appId
//        Bugly.init(mContext, "24662872d6", true);
    }

}

其中,configTinker()方法主要進行Bugly的相關配置,也可放在MainActivity或者其他地方進行初始化。

注意:tinker需要你開啟MultiDex,你需要在dependencies中進行配置compile "com.android.support:multidex:1.0.1"才可以使用MultiDex.install方法; SampleApplicationLike這個類是Application的代理類,以前所有在Application的實現必須要全部拷貝到這里,在onCreate方法調用SDK的初始化方法,在attachBaseContext中調用Beta.installTinker(this);。

2.自定義Application、ApplicationLike

public class SampleApplication extends TinkerApplication{

    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "com.thekey.buglyhotfix.SampleApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }

}

其中,第二個參數delegatedelegateClassName填寫自定義ApplicationLike的包名,ApplicationLike的實現如下:

public class SampleApplicationLike extends DefaultApplicationLike {

    private static final String TAG = "SampleApplicationLike";

    private Application mContext;

    public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplication();
        configTinker();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);
        // 安裝tinker
        Beta.installTinker(this);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
        getApplication().registerActivityLifecycleCallbacks(callbacks);
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        Beta.unInit();
    }

    /**
     * 初始化Tinker
     */
    private void configTinker(){
        //是否開啟熱更新能力,默認為true
        Beta.enableHotfix = true;
        //是否開啟自動下載補丁,默認為true
        Beta.canAutoDownloadPatch = true;
        //是否自動合成補丁,默認為true
        Beta.canAutoPatch = true;
        //是否提示用戶重啟,這里默認設置為false
        Beta.canNotifyUserRestart = true;
        //補丁回調接口
        Beta.betaPatchListener = new BetaPatchListener() {
            @Override
            public void onPatchReceived(String s) {
                Log.e(TAG, "補丁下載地址:" + s);
            }

            @Override
            public void onDownloadReceived(long l, long l1) {
                Log.e(TAG, String.format(Locale.getDefault(), "%s %d%%",
                        Beta.strNotificationDownloading,
                        (int) (l1 == 0 ? 0 : l * 100 / l1)));
            }

            @Override
            public void onDownloadSuccess(String s) {
                Log.e(TAG, "補丁下載成功");
                Toast.makeText(mContext, "補丁下載成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadFailure(String s) {
                Log.e(TAG, "補丁下載失敗");
                Toast.makeText(mContext, "補丁下載失敗", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onApplySuccess(String s) {
                Log.e(TAG, "補丁應用成功");
                Toast.makeText(mContext, "補丁應用成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onApplyFailure(String s) {
                Log.e(TAG, "補丁應用失敗");
                Toast.makeText(mContext, "補丁應用失敗", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPatchRollback() {

            }
        };

        //設置開發設備,默認為false,上傳補丁如果下發范圍指定為“開發設備”,需要調用此接口來標識開發設備
        Bugly.setIsDevelopmentDevice(mContext, false);
        // 多渠道需求塞入
        // String channel = WalleChannelReader.getChannel(getApplication());
        // Bugly.setAppChannel(getApplication(), channel);
        // 這里實現SDK初始化,appId替換成你的在平臺申請的appId
        Bugly.init(mContext, "24662872d6", true);
    }


}

【配置xml】

需要一下相關權限才可正常使用,需要注意的是,在android 6.0以上,需要動態申請某些權限,如果沒有相關讀寫等權限,可能會導致最后補丁應用失敗,相應權限如下:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

配置FileProvider,如果您想兼容Android N或者以上的設備,必須要在AndroidManifest.xml文件中配置FileProvider來訪問共享路徑的文件。

 <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

這里要注意一下,FileProvider類是在support-v4包中的,檢查你的工程是否引入該類庫。
在res目錄新建xml文件夾,創建provider_paths.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>
</paths>

注意:這里配置的兩個外部存儲路徑是升級SDK下載的文件可能存在的路徑,一定要按照上面格式配置,不然可能會出現錯誤。

注:1.3.1及以上版本,可以不用進行以上配置,aar已經在AndroidManifest配置了,并且包含了對應的資源文件。


【混淆配置】

為了避免混淆SDK,需要在Proguard混淆文件中增加以下配置:

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# tinker混淆規則
-dontwarn com.tencent.tinker.**
-keep class com.tencent.tinker.** { *; }

//如果你使用了support-v4包,你還需要配置以下混淆規則:
-keep class android.support.**{*;}

【如何打包】

1.編譯基準包
這里需要進行配置,主要配置tinkerid以及variantName


這里進行配置打什么類型的包
[圖片上傳失敗...(image-8d16f8-1547370765018)]


tinkerId最好是一個唯一標識,例如git版本號、versionName等等。 如果你要測試熱更新,你需要對基線版本進行聯網上報。

[圖片上傳失敗...(image-6f87fd-1547370765018)]

注意:這里強調一下,基線版本配置一個唯一的tinkerId,而這個基線版本能夠應用補丁的前提是集成過熱更新SDK,并啟動上報過聯網,這樣我們后臺會將這個tinkerId對應到一個目標版本,例如tinkerId = "bugly_1.0.0" 對應了一個目標版本是1.0.0,基于這個版本打的補丁包就能匹配到目標版本。

如:打基準包時可以命名為:"base- + "你的版本號"
打差異包時可以命名為: "patch-" + "你的版本號" + ".x.x"

一個基準包可以對應多個差異包,每次打差異包都要改變tinkerid,可以以后綴".1.0"等方式命名管理,這里需要注意的是,每次打基準包tinkerid也必須要改變,否則可能會導致不同的基準包會也會收到該更新

這里強調一下,基線版本配置一個唯一的tinkerId,而這個基線版本能夠應用補丁的前提是集成過熱更新SDK,并啟動上報過聯網,這樣我們后臺會將這個tinkerId對應到一個目標版本,例如tinkerId = "bugly_1.0.0" 對應了一個目標版本是1.0.0,基于這個版本打的補丁包就能匹配到目標版本。


2.執行assembleRelease打Release基準包
[圖片上傳失敗...(image-dabacc-1547370765018)]

打包后生成的基準包、混淆配置文件以及資源id文件都在build/outputs/bakApk路徑下
實際應用中,請注意保存線上發布版本的基準apk包、mapping文件、R.txt文件,如果線上版本有bug,就可以借助我們tinker-support插件進行補丁包的生成。

3.配置目錄
[圖片上傳失敗...(image-2d17f0-1547370765018)]

[圖片上傳失敗...(image-ccb5b7-1547370765018)]

這兩個配置必須一一對應

4.執行buildTinkerPatchRelease打對應的差異包

[圖片上傳失敗...(image-9e377b-1547370765018)]

這里需要注意,執行的是tinker-support下面的,而不是tinker

完成之后,差異包會在outputs/patch/release/路徑下的patch_signed_7zip.apk

[圖片上傳失敗...(image-ffe333-1547370765018)]

其中,差異包中包含如下文件,如果缺少YAPATCH.MF文件則說明打包過程或配置出現問題

[圖片上傳失敗...(image-31efc-1547370765018)]

該YAPATCH.MF文件中包含基準包和差異包相關對應信息:

[圖片上傳失敗...(image-15beff-1547370765018)]

5.獲取差異包之后,最好將后綴改為 .jar .zip .dex ,防止運營商劫持

【上傳補丁】

1.上傳補丁到Bugly

[圖片上傳失敗...(image-b19482-1547370765018)]

[圖片上傳失敗...(image-1adbff-1547370765018)]

點擊發布新補丁,上傳前面生成的patch包,平臺會自動為你匹配到目標版本,你可以選擇下發范圍(開發設備、全量設備、自定義),填寫完備注之后,點擊立即下發,這樣你就可以在客戶端當中收到我們的策略,SDK會自動幫你把補丁包下到本地。

進過測試,一般3分鐘左后才可能下載合成補丁

2.在加載補丁的過程中可以在log看到相關信息:

[圖片上傳失敗...(image-ef2ef4-1547370765018)]

[圖片上傳失敗...(image-25b421-1547370765018)]

【支持加固】

只需改變該配置即可:在tinker-support配置當中設置isProtectedApp = true,表示你要打加固的的apk。 是否使用加固模式,僅僅將變更的類合成補丁。注意,這種模式僅僅可以用于加固應用中。

需要集成升級SDK版本1.3.0以上版本才支持加固。
經過測試的加固產品:
騰訊樂固
愛加密
梆梆加固
360加固(SDK 1.3.1之后版本支持)

【API文檔】
1.安裝Tinker

Beta.installTinker();

此接口僅用于反射Application方式接入。

Beta.installTinker(this);

此接口僅用于改造Application方式接入,參數為ApplicationLike對象。

2.指定加載路徑

Beta.applyTinkerPatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");

3.清除補丁

Beta.cleanTinkerPatch();

清除補丁之后,就會回退基線版本狀態。

4.主動檢查更新

Beta.checkUpgrade();

用于主動檢查補丁策略的接口。

5.設置是否允許自動下載補丁

Beta.canAutoDownloadPatch = true;

默認為true,如果想選擇下載補丁的時機,設置為false即可。

6.設置是否允許自動合成補丁

Beta.canAutoPatch = true;

默認為true,如果想選擇合成補丁的時機,設置為false即可。

7.設置是否顯示彈窗提示用戶重啟

Beta.canNotifyUserRestart = false

默認為false,如果想彈窗提示用戶重啟,設置為true即可。

8.用戶主動下載補丁文件

 Beta.downloadPatch();

適用于Beta.canAutoDownloadPatch = false;的情況。 開發者自己選擇下載補丁的時機。

9.用戶主動合成補丁

Beta.applyDownloadedPatch();

適用于Beta.canAutoPatch = true;的情況。 開發者自己選擇合成補丁的時機。

10.相關回調

Beta.betaPatchListener = new BetaPatchListener() {
            @Override
            public void onPatchReceived(String patchFile) {
                Toast.makeText(getApplication(), "補丁下載地址" + patchFile, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadReceived(long savedLength, long totalLength) {
                Toast.makeText(getApplication(),
                        String.format(Locale.getDefault(), "%s %d%%",
                                Beta.strNotificationDownloading,
                                (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)),
                        Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadSuccess(String msg) {
                Toast.makeText(getApplication(), "補丁下載成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDownloadFailure(String msg) {
                Toast.makeText(getApplication(), "補丁下載失敗", Toast.LENGTH_SHORT).show();

            }

            @Override
            public void onApplySuccess(String msg) {
                Toast.makeText(getApplication(), "補丁應用成功", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onApplyFailure(String msg) {
                Toast.makeText(getApplication(), "補丁應用失敗", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPatchRollback() {
                Toast.makeText(getApplication(), "補丁回滾", Toast.LENGTH_SHORT).show();
            }
        };

更對相關API及說明,請參照熱更新API - Bugly 文檔

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

推薦閱讀更多精彩內容

  • 時間:2+10+15+60+15+5 感覺: 汗不是很多,時有抖動,但已無之前的那么厲害。 關鍵詞: 龜兔賽跑 平...
    倩倩打怪記閱讀 182評論 0 0
  • 一:《幸福的方法》第8.9.10章 讀后感 1."非??鞓返娜?quot;和"不快樂的人"唯一的區分兩種人的因素就是他們是否...
    雪兒讀書分享閱讀 479評論 0 2
  • 多年前說到理財,覺得非常高大上,似乎是成功人士的象征。銀行里的理財室也透露出一股濃濃的金錢的氣息。 到如今,各種賺...
    Hollyzzz閱讀 170評論 0 0