NDK開發(fā)——快速入門

一 相關配置

Android Studio創(chuàng)建新項目時選擇最后一項Native C++汁展,這時候生成的項目配置就是默認的CMake開發(fā)的配置,如下圖所示:

但是如果原有項目不支持NDK開發(fā)适荣,要添加native代碼噪生,需要以下配置流程:

  1. 創(chuàng)建native代碼文件阵翎,在main目錄下創(chuàng)建cpp目錄逢并,右鍵點擊 cpp 目錄,然后依次選擇 New > C/C++ Source File,文件名取為demo郭卫,文件后綴為cpp砍聊,點擊OK,這時候就會生成一個demo.cpp的原生代碼文件贰军。目前這個文件為空玻蝌,后面我們會往里面添加C++代碼。

  2. 創(chuàng)建 CMake 構建腳本词疼。右鍵點擊 cpp 目錄俯树,然后依次選擇 New > File,輸入CMakeLists.txt作為文件名贰盗,然后點擊 OK许饿。我們需要編輯這個txt腳本:

    • cmake_minimum_required:cmake_minimum_required(VERSION 3.22.1),表示cmake的最小版本號,按照習慣舵盈,腳本的第一行一般都是配置這個屬性陋率。

    • project("demo")球化。配置項目的名稱,我們寫成demo即可翘贮,這行配置可以沒有赊窥。

    • add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [<source>...])爆惧,三個參數狸页,分別表示庫的 名字,庫的類型扯再,庫的路徑芍耘。我們配置如下:add_library(demo SHARED demo.cpp),其中第三個參數就是我們在第一步中創(chuàng)建的C++文件熄阻。這樣配置的結果斋竞,cmake就會在/app/build/intermediates/cmake/debug/obj/目錄下生成對應的libdemo.so

    • 這個命令以找到 NDK 庫并將其路徑存儲為一個變量秃殉“映酰可以使用此變量在構建腳本的其他部分引用 NDK 庫。以下配置會找到 Android 專有的日志支持庫钾军,并將其路徑存儲在 log-lib 中:

      find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib
              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )
      
    • 為了讓原生庫能夠調用 log 庫中的函數鳄袍,需要使用 CMake 構建腳本中的 target_link_libraries() 命令來關聯(lián)這些庫:

      # Links your native library against one or more other native libraries.
      target_link_libraries( # Specifies the target library.
                       demo
                       # Links the log library to the target library.
                       ${log-lib} ) 
      

最終的CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 3.22.1)

# Declares and names the project.

project("demo")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        demo
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        demo.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib
        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        demo
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})
  1. 指定ndk路徑。我們可以在項目根目錄的local.properties文件中指定ndk的路徑吏恭,比如這樣配置:

      ndk.dir=/Users/zhouzhihui/Library/Android/sdk/ndk/25.2.9519653
    

    在新的Android Studio中這樣配置會報一個警告:Please delete ndk.dir from local.properties and set android.ndkVersion to [25.2.9519653] in all native modules in the project拗小,那么我們就用新的配置方法,先刪除這一行配置樱哼,然后在app/build.gradle文件中添加ndkVersion的配置哀九,如下代碼:

        android {
            ndkVersion "25.2.9519653"
            compileSdk 33
            ......
    
  2. 將 Gradle 關聯(lián)到原生庫。將 externalNativeBuild塊添加到模塊級 build.gradle 文件中搅幅,并使用cmakendkBuild 塊對其進行配置阅束,這個配置和ndkVersion的配置是同一級別的:

    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }
    

    當然還可以指定其它參數,具體可以參考官網說明:關聯(lián)Gradle茄唐。

  3. 指定abi围俘。如果不指定abi,gradle 默認只生成arm64-v8a構架的so文件(我的小米手機是這樣琢融,應該是生成與設備相對應的so界牡,歡迎指正),我們可以如下指定abi:

      ndk {
            abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
      }
    

    這樣配置漾抬,就能生成四個架構下的so文件宿亡。

經過上面四個步驟,我們ndk的原生代碼開發(fā)配置基本完成了纳令,然后點擊Run app按鈕挽荠,運行項目克胳,最終apk中的lib目錄生成了四個so,如圖:

apk
因為我們的C++文件還沒有寫代碼圈匆,因此so都比較小漠另,只有2KB。

二 編寫C++代碼

由于native方法是在java層調用跃赚,因此對于一個so笆搓,我們就專門創(chuàng)建一個對應的java類。我們前面步驟新建了demo.cpp纬傲,那么我們就創(chuàng)建個對應的Demo.java類满败。我們在主package包目錄下創(chuàng)建一個新目錄demo,在該目錄下創(chuàng)建一個java類:Demo.java叹括,要使用native代碼算墨,首先將so加載到虛擬機中,如下代碼:

    static {
        System.loadLibrary("demo");
    }

然后在Demo.java中創(chuàng)建我們的第一個native方法汁雷,而且是一個static方法:

public static native String getStr();

這時候會報錯Cannot resolve corresponding JNI function Java_com_example_nativecpp_demo_Demo_getStr.净嘀,因為getStr方法還沒在demo.cpp中實現,然后我們將鼠標移到飆紅的代碼處侠讯,同時按住鍵盤的alt+enter鍵挖藏,就會出現修復的建議,如圖所示:

然后點擊enter鍵继低,在demo.cpp中就會出現以下代碼:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativecpp_demo_Demo_getStr(JNIEnv *env, jclass clazz) {
    // TODO: implement getStr()
}

這個自動生成的c++代碼函數就是getStr的native實現熬苍,我們修改下,最終實現如下:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativecpp_demo_Demo_getStr(JNIEnv *env, jclass /* this */) {
    std::string hello = "stringFromJNI";
    return env->NewStringUTF(hello.c_str());
}

按照上面的步驟袁翁,我們繼續(xù)添加兩個非static的native方法:

    public native int add(int a, int b);
    public native String concat(String a, String b);

對應的native實現如下:

extern "C" JNIEXPORT jint JNICALL
Java_com_example_nativecpp_demo_Demo_add(JNIEnv *env, jobject, jint a, jint b) {
    string sum = std::to_string(a + b);
    std::stringstream ss;
    ss << a << "+" << b << "==" << sum;
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", ss.str().c_str());
    return a + b;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativecpp_demo_Demo_concat(JNIEnv *env, jobject thiz, jstring a, jstring b) {
    //1柴底、直接使用GetStringUTFChars方法將傳遞過來的jstring轉為char*
    char *c1 = (char *) (env->GetStringUTFChars(a, JNI_FALSE));
    char *c2 = (char *) (env->GetStringUTFChars(b, JNI_FALSE));
    //2、再使用本地函數strcat 拼接兩個char*對象粱胜,然后NewStringUTF轉為jstring返回去
    char *res = strcat(c1, c2);
    return env->NewStringUTF(res);
}

不難發(fā)現柄驻,demo.cpp的方法名字很長,而且遵循一定的約束

  • 定義:通過 JNIEXPORT 和 JNICALL 兩個宏定義聲明焙压,在虛擬機加載 so 時發(fā)現上面兩個宏定義的函數時就會鏈接到對應的 native 方法
  • 規(guī)則:Java + 包名 + 類名 + 方法名鸿脓, 其中使用下劃線將每部分隔開,包名也使用下劃線隔開涯曲,如果名稱中本來就包含下劃線野哭,將使用下劃線加數字替換。
    明顯有以下缺點:
    1 必須遵循注冊規(guī)則
    2 名字過長
    3 運行時去找效率不高

下面我們介紹一下更加簡潔而且更加高效的native方法的注冊方式:動態(tài)注冊幻件。

三 動態(tài)注冊

我們在Demo.java中加上第四個native方法:

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

為了動態(tài)注冊該方法拨黔,首先,在demo.cpp中我們實現sortArray方法的功能绰沥,但不靜態(tài)注冊篱蝇,如下:

void sort(int &a, int &b) {
    a = a + b;
    b = a - b;
    a = a - b;
}

jintArray sortArray(JNIEnv *env, jobject thiz, jintArray arr) {
    jsize len = env->GetArrayLength(arr);
//    jint *body = env->GetIntArrayElements(arr, 0);
    jint *out = env->GetIntArrayElements(arr, NULL);
    for (int i = 0; i < len; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (out[j] > out[j + 1]) {
                sort(out[j], out[j + 1]);
//                jsize temp = out[j + 1];
//                out[j + 1] = out[j];
//                out[j] = temp;
            }
        }
    }

    jintArray arrSorted = env->NewIntArray(len);
    env->SetIntArrayRegion(arrSorted, 0, len, out);
    env->ReleaseIntArrayElements(arr, out, 0);
    env->DeleteLocalRef(arr);
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", "new arr");
    for (int i = 0; i < len; i++) {
        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%d", out[i]);
    }
    return arrSorted;
}

該方法就是接收一個java的整數數組贺待,然后使用冒泡從小到大排序,返回一個新的排過序的新的數組零截。這個方法不滿足前面討論過的靜態(tài)注冊的兩條約束條件麸塞,因此如果在java層調用該方法,就會報錯:
java.lang.UnsatisfiedLinkError: No implementation found for int[] com.example.nativecpp.demo.Demo.sortArray(int[]) (tried Java_com_example_nativecpp_demo_Demo_sortArray and Java_com_example_nativecpp_demo_Demo_sortArray___3I)涧衙,如圖:

哪工。我們需要注冊該方法,那么下面就來動態(tài)注冊該方法绍撞。

還記得我們在Demo.java中加載so的代碼static { System.loadLibrary("demo"); }嗎正勒?這個加載會觸發(fā)demo.cpp里的JNI_OnLoad方法得院,通常我們在 JNI_OnLoad 方法中完成動態(tài)注冊傻铣,直接上代碼吧:

jint RegisterNatives(JNIEnv *env) {
    jclass clazz = env->FindClass("com/example/nativecpp/demo/Demo");
    if (clazz == NULL) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,"con't find class: com/example/nativecpp/demo/Demo");
        return JNI_ERR;
    }
    JNINativeMethod method[] = {
            {"sortArray", "([I)[I", (void *) sortArray}
    };
    return env->RegisterNatives(clazz, method,sizeof(method) / sizeof(method[0]));
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "enter jni_onload");
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    jint result = RegisterNatives(env);
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "register natives result=%d", result);
    return JNI_VERSION_1_6;
}

動態(tài)注冊的邏輯在RegisterNatives方法中,主要分三步:

  1. 通過反射找到方法所在的java類:jclass clazz = env->FindClass("com/example/nativecpp/demo/Demo");
  2. 定義要注冊的方法:JNINativeMethod method[] = { {"sortArray", "([I)[I", (void *) sortArray} };,可以看到變量method是一個數組祥绞,數組的每個元素都對應一個要動態(tài)注冊的方法非洲,由于我們只有一個方法要動態(tài)注冊,因此method數組只有一個元素蜕径。元素的第一個參數是java層定義的函數名两踏,第三個參數是native層定義的函數名,兩個函數名沒有必然關系兜喻,可以不一樣梦染。要注意的是元素的第二個參數也就是函數簽名(如果不知道函數簽名也可以使用快捷鍵alt+enter自動修復),具體的函數簽名可以參考:Android深入理解JNI(二)類型轉換朴皆、方法簽名和JNIEnv
  3. 進行注冊:env->RegisterNatives(clazz, method,sizeof(method) / sizeof(method[0]));

四 最終的源代碼

所有的代碼如下:
Demo.java:

public class Demo {
    // Used to load the 'nativecpp' library on application startup.
    static {
        Log.i("zzh", "load 1");
        System.loadLibrary("demo");
        Log.i("zzh", "load 2");
    }

    /**
     * A native method that is implemented by the 'nativecpp' native library,
     * which is packaged with this application.
     */
    public static native String getStr();

    public native int add(int a, int b);

    public native String concat(String a, String b);

    // 動態(tài)注冊
    public native int[] sortArray(int[] arr);
}

demo.cpp

#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <android/log.h>

using namespace std;

#define LOG_TAG "zzh-cmake"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativecpp_demo_Demo_getStr(JNIEnv *env, jclass /* this */) {
    std::string hello = "stringFromJNI";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT jint JNICALL
Java_com_example_nativecpp_demo_Demo_add(JNIEnv *env, jobject, jint a, jint b) {
    string sum = std::to_string(a + b);
    std::stringstream ss;
    ss << a << "+" << b << "==" << sum;
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", ss.str().c_str());
    return a + b;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativecpp_demo_Demo_concat(JNIEnv *env, jobject thiz, jstring a, jstring b) {
    //1帕识、直接使用GetStringUTFChars方法將傳遞過來的jstring轉為char*
    char *c1 = (char *) (env->GetStringUTFChars(a, JNI_FALSE));
    char *c2 = (char *) (env->GetStringUTFChars(b, JNI_FALSE));
    //2、再使用本地函數strcat 拼接兩個char*對象遂铡,然后NewStringUTF轉為jstring返回去
    char *res = strcat(c1, c2);

    return env->NewStringUTF(res);
}

void sort(int &a, int &b) {
    a = a + b;
    b = a - b;
    a = a - b;
}

jintArray sortArray(JNIEnv *env, jobject thiz, jintArray arr) {
    jsize len = env->GetArrayLength(arr);
//    jint *body = env->GetIntArrayElements(arr, 0);
    jint *out = env->GetIntArrayElements(arr, NULL);
    for (int i = 0; i < len; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (out[j] > out[j + 1]) {
                sort(out[j], out[j + 1]);
//                jsize temp = out[j + 1];
//                out[j + 1] = out[j];
//                out[j] = temp;
            }
        }
    }

    jintArray arrSorted = env->NewIntArray(len);
    env->SetIntArrayRegion(arrSorted, 0, len, out);
    env->ReleaseIntArrayElements(arr, out, 0);
    env->DeleteLocalRef(arr);
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s", "new arr");
    for (int i = 0; i < len; i++) {
        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%d", out[i]);
    }
    return arrSorted;
}

jint RegisterNatives(JNIEnv *env) {
    jclass clazz = env->FindClass("com/example/nativecpp/demo/Demo");
    if (clazz == NULL) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,"con't find class: com/example/nativecpp/demo/Demo");
        return JNI_ERR;
    }
    JNINativeMethod method[] = {
            {"sortArray", "([I)[I", (void *) sortArray}
    };
    return env->RegisterNatives(clazz, method,sizeof(method) / sizeof(method[0]));
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "enter jni_onload");
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    jint result = RegisterNatives(env);
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "register natives result=%d", result);
    return JNI_VERSION_1_6;
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity:zzh";
    Demo mDemo;

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDemo = new Demo();
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(Demo.getStr());
        int[] arr = new int[]{10, 3, 2, 1, 9, 11, 8, 4};
        binding.sampleText.setOnClickListener(v -> {
            Log.i(TAG, "demo add 1+2=" + mDemo.add(1, 2));
            Log.i(TAG, "demo concat a concat b=" + mDemo.concat("a", "b"));
            int[] newArr = mDemo.sortArray(arr);
            Log.i(TAG, "demo after sort array=" + Arrays.toString(arr));
            Log.i(TAG, "demo after sort array2=" + Arrays.toString(newArr));
        });
    }
}

運行的日志:

2023-02-27 22:26:27.368 14524-14524 zzh                     com.example.nativecpp                I  load 1
2023-02-27 22:26:27.369 14524-14524 zzh-cmake               com.example.nativecpp                D  enter jni_onload
2023-02-27 22:26:27.369 14524-14524 zzh-cmake               com.example.nativecpp                D  register natives result=0
2023-02-27 22:26:27.369 14524-14524 zzh                     com.example.nativecpp                I  load 2
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  1+2==3
2023-02-27 22:26:31.715 14524-14524 MainActivity:zzh        com.example.nativecpp                I  demo add 1+2=3
2023-02-27 22:26:31.715 14524-14524 MainActivity:zzh        com.example.nativecpp                I  demo concat a concat b=ab
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  new arr
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  1
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  2
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  3
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  4
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  8
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  9
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  10
2023-02-27 22:26:31.715 14524-14524 zzh-cmake               com.example.nativecpp                D  11
2023-02-27 22:26:31.715 14524-14524 MainActivity:zzh        com.example.nativecpp                I  demo after sort array=[1, 2, 3, 4, 8, 9, 10, 11]
2023-02-27 22:26:31.715 14524-14524 MainActivity:zzh        com.example.nativecpp                I  demo after sort array2=[1, 2, 3, 4, 8, 9, 10, 11]

從日志中可以看出肮疗,native方法sortArray把java層傳入的int[]也給修改了。






參考:
ndk使用入門

JNI 靜態(tài)注冊和動態(tài)注冊

Android深入理解JNI(二)類型轉換扒接、方法簽名和JNIEnv

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末伪货,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子钾怔,更是在濱河造成了極大的恐慌碱呼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宗侦,死亡現場離奇詭異愚臀,居然都是意外死亡,警方通過查閱死者的電腦和手機凝垛,發(fā)現死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門懊悯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜓谋,“玉大人,你說我怎么就攤上這事炭分√一溃” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵捧毛,是天一觀的道長观堂。 經常有香客問我,道長呀忧,這世上最難降的妖魔是什么师痕? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮而账,結果婚禮上胰坟,老公的妹妹穿的比我還像新娘。我一直安慰自己泞辐,他們只是感情好笔横,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咐吼,像睡著了一般吹缔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锯茄,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天厢塘,我揣著相機與錄音,去河邊找鬼肌幽。 笑死晚碾,一個胖子當著我的面吹牛,可吹牛的內容都是我干的牍颈。 我是一名探鬼主播迄薄,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼煮岁!你這毒婦竟也來了讥蔽?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤画机,失蹤者是張志新(化名)和其女友劉穎冶伞,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體步氏,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡响禽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芋类。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡隆嗅,死狀恐怖,靈堂內的尸體忽然破棺而出侯繁,到底是詐尸還是另有隱情胖喳,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布贮竟,位于F島的核電站丽焊,受9級特大地震影響,放射性物質發(fā)生泄漏咕别。R本人自食惡果不足惜技健,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惰拱。 院中可真熱鬧雌贱,春花似錦、人聲如沸弓颈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翔冀。三九已至,卻和暖如春披泪,著一層夾襖步出監(jiān)牢的瞬間纤子,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工款票, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留控硼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓艾少,卻偏偏與公主長得像卡乾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缚够,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容