03-JNI语法和实例

native方法为static

  • 在java中声明的JNI的native方法静态方法和非静态,对于底层的C/C++代码来说是有区别的:
    1,JNI函数的参数也由三部分组成:首先是JNIEnv*,是一个指向JNI运行环境的指针;2,第二个参数随本地方法是静态还是非静态而有所不同一一非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用;
    3,其余的参数对应通常Java方法的参数,参数类型需要根据一定规则进行映射。

  • native方法static和非static,在c/c++中回调java方法的区别
    1,static:JNI回调静态方法: 不需要对象实例,只需要拿到class即可

void jniCallUnStaticMethod()
{
   getJNIEnv(&env);
   clazz = env->FindClass("com.test.Test");
    jmethodID method = env->GetStaticMethodID(clazz, "staticTestMethod", "(I)V");
   env->CallStaticVoidMethod(clazz, method, channel);
}

2,非static:JNI回调非静态方法: 需要class及其一个实例,可通过如下方式注册一个实例。

void jniCallUnStaticMethod()
{
   getJNIEnv(&env);
   clazz = env->FindClass("com.test.Test");
   jmethodID method = env->GetMethodID(clazz, "unStaticTestMethod", "(I)V");
   env->CallVoidMethod(initedObj, method, channel);
}

传递数据

  • 传递int
public native int add(int x , int y); 

JNIEXPORT jint JNICALL Java_com_huachao_jnipassdata_JNI_add
  (JNIEnv *env, jobject obj, jint x, jint y){
    return x+y;
} 
  • 传递字符串
    ①, Java字符串转为c字符串
#include <jni.h>

//因为下面用到的NULL在stdlib库中,所以导入该库

#include <stdlib.h> 

/**

 * 把一个jstring转换成一个c语言的char* 类型.

 */

char* _JString2CStr(JNIEnv* env, jstring jstr) {

  char* rtn = NULL;

  jclass clsstring = (*env)->FindClass(env, "java/lang/String");

  jstring strencode = (*env)->NewStringUTF(env,"GB2312");

  jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");

  jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");

  jsize alen = (*env)->GetArrayLength(env, barr);

  jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);

  if(alen > 0) {

 rtn = (char*)malloc(alen+1); //"\0"

 memcpy(rtn, ba, alen);

 rtn[alen]=0;

  }

  (*env)->ReleaseByteArrayElements(env, barr, ba,0);

  return rtn;

}

②, 与①函数结合,将字符串的每一个字符+1,实现简单加密

//对java字符串进行处理

public native String sayHelloInC(String c);
 
JNIEXPORT jstring JNICALL Java_com_huachao_jnipassdata_JNI_sayHelloInC
  (JNIEnv *env, jobject obj, jstring jstr){
 //调用①函数将java字符串转为c语言字符串

 char* cstr = _JString2CStr(env , jstr);
 //调用strlen获取cstr字符串的长度
 int length = strlen(cstr);
 int i;
 for (i= 0;  i< length; i++) {
 *(cstr+i)+=1;
 }
 return (*env)->NewStringUTF(env , cstr);
}

  • 传递java的int数组

public native int[] arrElementIncarse(int[] arr);  

JNIEXPORT jintArray JNICALL Java_com_huachao_jnipassdata_JNI_arrElementIncarse

  (JNIEnv *env, jobject obj, jintArray jArray){

 //获取java数组的长度

 jsize length = (*env)->GetArrayLength(env,jArray);

 LOGI("size=%d",length);

 /*

  * 将java数组转为c语言指针

  * 参数:(JNIEnv*, jintArray, jboolean*)

  * 第三个参数 : &iscopy

  */

 jboolean iscopy = 1;

 jint* arrayPointer = (*env)->GetIntArrayElements(env,jArray,NULL);

 int i;

 for (i = 0; i<length; i++) {

 *(arrayPointer+i) += 10;

 //pintf("--%d" , *(arrayPointer+i));

 LOGI("arr[%d]=%d",i,*(arrayPointer+i));

 }

 /*

  *亲眼看见别人写代码时,不需要下面这个调用,直接返回jArray就是已经被修改过的数组

  *但是我亲自尝试直接返回jArray,还是原来的数组,值并没有改变,需要调用下面的函数给jArray数组赋值

  */

 //给需要返回的数组赋值

 (*env)->SetIntArrayRegion(env , jArray , 0,length , arrayPointer);

 return jArray;

}
  

注意:上面(*env)->SetIntArrayRegion(env , jArray , 0,length , arrayPointer);

JNI中使用LogCat输出

1,在Android.mk中添加(一定要添加在include $(CLEAR_VARS)之后)


#增加log函数对应的log库liblog.so 
LOCAL_LDLIBS += -llog 

实质就是加载F:\as\plugin\android-ndk-r9d\platforms\android-14\arch-arm\usr\lib下的liblog.so库,例如:如果想加载libEGL.so,那么就在Android.mk中添加LOCAL_LDLIBS += -lEGL
2,在.c文件中添加`

include <android/log.h>`

3,在.c文件中添加宏

#define TAG "MY

JNILog
" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型

#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型

#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 

4,调用上面的宏打印日志(与printf用法相似)

LOGI("size=%d",length); 
或
LOGI("arr[%d]=%d",i,*(arrayPointer+i)); 

注意:JNI开发中,如果在eclipse的.c文件中报错,可能并不是你代码的错误,而是插件的问题,解决:将项目clean一下,或者将项目close然后open。

C回调JAVA(利用放射)

  • JNI.java

package com.hhc.ccalljava;
import android.util.Log;
public class JNI {
 static{
 System.loadLibrary("callback");
 }
 public native void callbackvoidmethod();
 public native void callbackintparammethod();
 public native void callbackstringparammethod();
 //c调用带两个int参数的方法
    public int add(int x , int y) {
 return x+y;
 }
    //c调用java空的方法
    public void helloFromJava() {
 System.out.println("hello from java");
 }
    //c调java带字符串的方法
    public void printString(String s) {
 Log.i("hhc", s);
 }
}

  • 获取方法签名

1,cd 到:项目根目录\bin\classes目录下,本例中如下:
F:\as\Eclipse\personal\JNI\04-C回调Java\bin\classes>
2,使用javap命令:javap -s com.hhc.ccalljava.JNI

Compiled from "JNI.java"

public class com.hhc.ccalljava.JNI {

  public com.hhc.ccalljava.JNI();

    descriptor: ()V

  public native void callbackvoidmethod();

    descriptor: ()V

  public int add(int, int);

    descriptor: (II)I

  public void helloFromJava();

    descriptor: ()V

  public void printString(java.lang.String);

    descriptor: (Ljava/lang/String;)V

}

  • callback.c

#include <jni.h>
#include <android/log.h>
#define TAG "myJNILog" // 这个是自定义的LOG的标识
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackvoidmethod
  (JNIEnv *env, jobject obj){
    //1,获取字节码对象
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI");
    //2,获取Method方法
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    /*
     * 参数3:方法名称
     * 参数4:方法签名(因为java中有方法重载)
             *  使用javap查看
     */
    jmethodID methodID = (*env)->GetMethodID(env , clazz , "helloFromJava" , "()V");
    //3,通过字节码创建java对象,如果native方法和要回调的java方法在同一个类里面,可以直接用JNI传过来的java对象调用创建的方法
    //此处的obj就是这个java对象,所以不需要创建
    //4,通过对象调用方法
    //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    //obj 其实就是我们要调用的方法的那个对象的实例
    (*env)->CallVoidMethod(env , obj , methodID);
}
JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackintparammethod
  (JNIEnv *env, jobject obj){
    LOGI("running");
    //1,获取字节码对象
    jclass claz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI");
    //2,获取Method方法
    jmethodID methodID = (*env)->GetMethodID(env , claz ,"add", "(II)I");
    //jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
    int result = (*env)->CallIntMethod(env , obj , methodID ,9,6);

    LOGI("result=%d" , result);
}
JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackstringparammethod
  (JNIEnv *env, jobject obj){
    jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI");
    jmethodID methodID = (*env)->GetMethodID(env ,clazz,"printString","(Ljava/lang/String;)V");
    //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    jstring str = (*env)->NewStringUTF(env,"weiwei,caonima");
    (*env)->CallVoidMethod(env ,obj,methodID,str);
}

  • MainActivity中调用
public class MainActivity extends Activity {
    private JNI jni;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jni = new JNI();
    }
    public void cCallJava(View v) {
        jni.callbackvoidmethod();
    }
    
    public void cCallJavaInt(View v) {
        jni.callbackintparammethod();
    }
    
    public void cCallJavaString(View v) {
        jni.callbackstringparammethod();
    }  
}  

注意:CallVoidMethodCallIntMethod这里的void和int都是指返回值类型

  • C回调显示Toast
    JNI.java
public class JNI {
    static{
        System.loadLibrary("callback");
    }
    public native void callbackShowToast(); 
}

callback.c

/**
 * AllocObject(env,clazz);这相当于新建一个Activity,但Activity是不能新建的,如果新建的话,Context上下文是空,
 * 但是显示Toast,要传Context,这个时候会报NullPointerException
 * 解决方案一:native方法和显示Toast的方法都定义在Activity中,这样传过来的jobject就是Activity实例了,就不需要新建
 * 解决方案二:在JNI的构造方法中传入上下文,showToast方法放入JNI.java中  
 */
JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackShowToast
  (JNIEnv *env, jobject obj){
    //1,获取字节码对象
    jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/MainActivity");
    //2,获取方法methodID
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID methodID = (*env)->GetMethodID(env,clazz,"showToast","(Ljava/lang/String;)V");

    //此时的obj是JNI.java这个类的实例,而被调的函数在MainActivity中,所以不能直接用obj调用
    //3,通过字节码创建java对象
    //jobject     (*AllocObject)(JNIEnv*, jclass);
    jobject instance = (*env)->AllocObject(env,clazz);
    //4,通过对象调用方法
    jstring *jstr = (*env)->NewStringUTF(env,"this is C");
    (*env)->CallVoidMethod(env,instance,methodID,jstr);
}  

MainActivity.java

public class MainActivity extends Activity {
    private JNI jni;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jni = new JNI();
    }
    public void cCallJavaShowToast(View v) {
        jni.callbackShowToast();
    }
    public void showToast(String s) {
        Toast.makeText(getApplicationContext(), "from c="+s, Toast.LENGTH_LONG).show();
    }
}  

注意:上面方式会抛NullPointerException

  • 解决和优化

1,解决方案一:native方法和显示Toast的方法都定义在Activity中
2,解决方案二:JNI.java的构造函数传入Context上下文,showToast方法放入JNI.java中。

JNI.java

public class JNI {
    static{
        System.loadLibrary("callback");
    }
    private Context mContext;
    public JNI(Context mContext) {
        super();
        this.mContext = mContext;
    }
          public native void callbackShowToast();
    public void showToast(String s) {
        Toast.makeText(mContext, "from c="+s, Toast.LENGTH_LONG).show();
    }
}     

callback.c

JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackShowToast
  (JNIEnv *env, jobject obj){
    //1,获取字节码对象
    jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI");
    //2,获取方法methodID
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID methodID = (*env)->GetMethodID(env,clazz,"showToast","(Ljava/lang/String;)V");

    //此时的obj是JNI.java这个类的实例,而被调的函数在MainActivity中,所以不能直接用obj调用
    //3,通过对象调用方法
    jstring *jstr = (*env)->NewStringUTF(env,"this is C");
    (*env)->CallVoidMethod(env,obj,methodID,jstr);
}  

MainActivity.java调用

public class MainActivity extends Activity {
    private JNI jni;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jni = new JNI(this);
    }
    public void cCallJavaShowToast(View v) {
        jni.callbackShowToast();
    }  
} 

jstring转char*

  • 方式一:env提供的函数
//字符串逆置
char *strrev(char *s){
    char *p = s;
    char *q = s;
    char temp;

    while(*q != '\0')
        q++;

    q--;//移动'\0'之前的字符位置

    while(p < q){
        temp = *p;
        *p = *q;
        *q = temp;
        p++;
        q--;
    }

    return s;
} 
 
JNIEXPORT jstring JNICALL Java_com_hhc_ccalljava_JNI_jstringtocchar
  (JNIEnv *env, jobject obj, jstring jstr){
    char* cstr = NULL;
    jsize len = 0;
    jstring new_str;
    //字符串长度
    len = (*env)->GetStringLength(env,jstr);
    //申请内存,大小等于len+1
    int char_len = sizeof(char);
    cstr = (char *)malloc((len+1)*char_len);//需要结束符
    //把java字符串转为c字符串
    //void        (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*);
    (*env)->GetStringUTFRegion(env,jstr,0,len,cstr);
    //字符串逆置
//    LOGI("before---%s" , cstr);
    cstr = strrev(cstr,len);
//    LOGI("hou---%s" , cstr);
    //转为jstr
    new_str = (*env)->NewStringUTF(env,cstr);
    //释放申请的内存空间
    free(cstr);
    return new_str;
}  

注意:
1,实际测试,在genymotion android 6.0的模拟器返回的字符串尾部会多一个tochar,而用下面的方式二不会,应该是编码的问题,因为下面的方式可以指定编码方式
2,上面的问题经过反复尝试将(*env)->GetStringUTFRegion(env, jstr, 0, len, cstr); 替换成char* cs = (*env)->GetStringUTFChars(env,jstr,0);问题解决,但是会有警告。

  • 方式二:自定义函数实现
//详解jstring转char*的函数
char* _JString2CStr(JNIEnv* env, jstring jstr) {
     char* rtn = NULL;
     //获取String的字符串
     jclass clsstring = (*env)->FindClass(env, "java/lang/String");
     //"GB2312"的jstring类型
     jstring strencode = (*env)->NewStringUTF(env,"GB2312");
     //就是调用java的String类的:byte[] bs = .getBytes("GB23122");方法
     jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
     jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
     //byte数组的长度
     jsize alen = (*env)->GetArrayLength(env, barr);
     //转为jbyte*,就是c语言的char*, 指向数组的首地址
     jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
     if(alen > 0) {
        //c的字符串实质就是字符数组,C的字符数组必须要有结束符,所以需要加1
        //申请堆内存
        rtn = (char*)malloc(alen+1); //"\0"
        //内存拷贝,将ba拷贝到rtn,长度为alen
        memcpy(rtn, ba, alen);
        //结束符为0
        rtn[alen]=0;
     }
     //释放不再使用的变量内存
     (*env)->ReleaseByteArrayElements(env, barr, ba,0);
     return rtn;
}  

JNI开发中编码问题

1,JNI返回字符串乱码或报错
2,在.c代码中打印中文字符串乱码
解决方式:.c文件的编码方式改为UTF-8编码


声明:java中所有引用类型(非基本数据类型),在jni底层都是对应的void*

typedef void*           jobject;

C++开发JNI

  • C的预处理命令
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif  
  • #开头的就是C/C++的预处理命令
  • 在编译之前,先会走预编译阶段,预编译阶段的作用就是把include进来的头文件copy到源文件中,define这些宏定义,用真实的值替换一下
  • c++开发JNI时,env不再是结构体JNINativeInterface的二级指针
  • _JNIEnv、JNIEnv:_JNIEnv是C++的结构体,C++结构体跟C的区别:C++结构体可以定义函数
  • env 是JNIEnv的一级指针,也就是结构体_JNIEnv的一级指针,env->来调用
  • _JNIEnv的函数,实际上调用的就是结构体JNINativeInterface的同名函数指针,在调用时第一个参数env已经传过去了。
  • C++的函数要先声明在使用,可以把javah生成的头文件include进来作为函数的声明
    native方法
static{
    System.loadLibrary("jnicpp");
}  

public native String hellofromcpp();  

jnicpp.cpp

#include <jni.h>
#include "com_huachao_jnicpp_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_huachao_jnicpp_MainActivity_hellofromcpp
  (JNIEnv *env, jobject obj){
    return env->NewStringUTF("1234haha");
}  

Fork子进程

MainActivity.java中调用

public class MainActivity extends Activity {
    static{
        System.loadLibrary("cfork");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public native void cfork();  
    public void cforksub(View v){
        cfork();
    }
}  

cfork.c

#include <jni.h>
#include <android/log.h>
#define TAG "MYJNILog" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)

JNIEXPORT void JNICALL Java_com_huachao_forksub_MainActivity_cfork
  (JNIEnv *env, jobject obj){
    //fork成功的分叉出一个子进程,会返回当前进程的id,但是只能在主进程中fork成功
    //在子进程中运行fork,会返回0,但是不能再分叉出新进程
    //所以这个方法会被调用两次,第二次是在子进程中调用
    //fork的返回值三种可能 >0 ==0 <0
    int pid = fork();
    if(pid<0){
        LOGD("PID<0 ,pid=%d" , pid);
    }else if(pid==0){
        LOGD("PID==0");
        //进入子进程
        while(1){
            LOGI("sub process is running");
            sleep(2);
        }
    }else{
        LOGD("PID>0 ,pid=%d" , pid);
    }
}  
Paste_Image.png

注意:在手机上,无论你做什么操作,退出应用、杀死进程、退出后台进程、等等...(只要不关机),会发现fork的c进程一直在打印。
利用此特性,我们可以实现杀不死的进程。

AM命令

  • am命令 :在adb shell里可以通过am命令进行一些操作 如启动activity Service 启动浏览器等等
  • am命令的源码在Am.java中, 在adb shell里执行am命令实际上就是启动一个线程执Am.java的main方法,am命令后面带的参数都会当作运行时的参数传递到main函数中
  • am命令可以用start子命令,并且带指定的参数
  • 常见参数: -a: action -d data -t 表示传入的类型 -n 指定的组件名字
  • 举例: 在adb shell中通过am命令打开网页
  • am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com
  • 通过am命令打开activity
  • am start --user 0 -n com.itheima.cppJNI/com.itheima.cppJNI.MainActivity
    (如果是Android API 16以上,上面的--user可以省略)
  • adb shell 中做一些操作 ,例如打开activity

execlp

  • execlp c语言中执行系统命令的函数
  • execlp() 会从PATH环境变量所指的目录中查找符合参数file的文件找到后就执行该文件, 第二个参数开始就是执行这个文件的 args[0],args[1] 最后一个参数用(char*)NULL结束
  • android开发中 execlp函数对应android的path路径为
  • system/bin/目录
  • 调用格式:
execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
execlp("am", "am", "start", "--user","0", "-n" , "com.itheima.cforktest/com.itheima.cforktest.MainActivity",(char *) NULL);

监听APP被卸载后打开浏览器跳到指定网址实例

JNIEXPORT void JNICALL Java_com_huachao_forksub_MainActivity_cfork
  (JNIEnv *env, jobject obj){
    int pid = fork();
    int ppid = 0;//父进程的id
    FILE *file = NULL;
    if(pid<0){
        LOGD("PID<0 ,pid=%d" , pid);
    }else if(pid==0){
        LOGD("PID==0");
        //进入子进程
        while(1){
            //拿到父进程的编号
            ppid = getppid();
            //如果父进程为1,说明父进程被杀死了
            if(ppid == 1){
                /**
                 * c语言打开流
                 * 参数一:路径,应用的安装文件存放路径,来检测应用是否被卸载
                 */
                file = fopen("/data/data/com.huachao.forksub" , "r");
                if(file == NULL){
                    /*
                     * 打开网页
                     * 实际开发中,将网址换成我们自己的网址
                     */
                    execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
                }else{
                    //启动Activity
                    execlp("am", "am", "start", "--user","0", "-n" , "com.huachao.forksub/com.huachao.forksub.MainActivity",(char *) NULL);
                }
                //break,父进程死了,子进程执行完操作也要退出
                break;
            }
            LOGI("sub process is running");
            sleep(2);
        }
    }else{
        LOGD("PID>0 ,pid=%d" , pid);
    }
}  

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容