分享

Android原理揭秘系列之framework本地方法注册

 jemeen 2012-02-21
Android原理揭秘系列之framework本地方法注册
  
    本文对Android framework层的API函数与其对应本地方法的映射、注册原理进行了介绍,通过本文,应该会对我们频繁调用的Android API的调用过程及实现原理有更加深入的认识。
  
    我们知道,Android平台是Java、C/C++等多种混合语言写成的,我们在使用Android SDK提供的framework层的API来进行应用开发时通常调用的是Java方法,而实际上,这些Java API很多时候只是一个入口,方法功能的真正实现是通过JNI调用到framework层的native本地方法来实现的。比如,我们在使用Canvas的drawBitmap方法来绘制图片时,drawBitmap的真正实现就是通过JNI调用C++的同名方法来实现的。
  
    下面,我们通过开机启动后本地方法的注册流程来具体的介绍Android API与对应本地方法究竟是如何关联起来的。
  
    Android在开机启动后,经过一系列的加载流程,将进入\frameworks\base\cmds\ app_process\ App_main.cpp的main()方法,在main()方法中,会调用\frameworks\base\core\jni\AndroidRuntime.cpp的start()方法。事实上,Android API与本地方法的注册关联主要就是在AndroidRuntime.cpp模块里完成的。
  
    在AndroidRuntime.cpp的start()方法中,会经过如下的调用流程,即:
  
    void AndroidRuntime::start() --->
  
    void AndroidRuntime::start(const char* className, const bool startSystemServer) --->
  
    int AndroidRuntime::startReg(JNIEnv* env)
  
    其中,startReg函数主要就是用来进行函数注册的。我们深入到该函数内部,可以看到如下的代码片段:
  
    view plainif (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
  
    env->PopLocalFrame(NULL);
  
    return -1;
  
    }
  
    好了,流程介绍到这里,我们已经定位到了Android API与本地方法的注册关联的关键之处,实际上,API与本地方法的注册关键就是在此处完成的。标记为红色的register_jni_procs及其参数gRegJNI是注册关联的两大要素,下面我们对其分别作具体分析。
  
    首先看gRegJNI的定义:
  
    view plainstatic const RegJNIRec gRegJNI[] = {
  
    REG_JNI(register_android_debug_JNITest),
  
    REG_JNI(register_com_android_internal_os_RuntimeInit),
  
    REG_JNI(register_android_os_SystemClock),
  
    REG_JNI(register_android_util_EventLog),
  
    REG_JNI(register_android_util_Log),
  
    REG_JNI(register_android_util_FloatMath),
  
    REG_JNI(register_android_text_format_Time),
  
    REG_JNI(register_android_pim_EventRecurrence),
  
    REG_JNI(register_android_content_AssetManager),
  
    REG_JNI(register_android_content_StringBlock),
  
    REG_JNI(register_android_content_XmlBlock),
  
    REG_JNI(register_android_emoji_EmojiFactory),
  
    REG_JNI(register_android_security_Md5MessageDigest),
  
    REG_JNI(register_android_text_AndroidCharacter),
  
    REG_JNI(register_android_text_AndroidBidi),
  
    REG_JNI(register_android_text_KeyCharacterMap),
  
    REG_JNI(register_android_os_Process),
  
    REG_JNI(register_android_os_Binder),
  
    REG_JNI(register_android_view_Display),
  
    REG_JNI(register_android_nio_utils),
  
    REG_JNI(register_android_graphics_PixelFormat),
  
    REG_JNI(register_android_graphics_Graphics),
  
    REG_JNI(register_android_view_Surface),
  
    REG_JNI(register_android_view_ViewRoot),
  
    REG_JNI(register_com_google_android_gles_jni_EGLImpl),
  
    REG_JNI(register_com_google_android_gles_jni_GLImpl),
  
    REG_JNI(register_android_opengl_jni_GLES10),
  
    REG_JNI(register_android_opengl_jni_GLES10Ext),
  
    REG_JNI(register_android_opengl_jni_GLES11),
  
    REG_JNI(register_android_opengl_jni_GLES11Ext),
  
    REG_JNI(register_android_opengl_jni_GLES20),
  
    REG_JNI(register_android_graphics_Bitmap),
  
    REG_JNI(register_android_graphics_BitmapFactory),
  
    REG_JNI(register_android_graphics_BitmapRegionDecoder),
  
    REG_JNI(register_android_graphics_Camera),
  
    REG_JNI(register_android_graphics_Canvas),
  
    REG_JNI(register_android_graphics_ColorFilter),
  
    REG_JNI(register_android_graphics_DrawFilter),
  
    REG_JNI(register_android_graphics_Interpolator),
  
    REG_JNI(register_android_graphics_LayerRasterizer),
  
    REG_JNI(register_android_graphics_MaskFilter),
  
    REG_JNI(register_android_graphics_Matrix),
  
    REG_JNI(register_android_graphics_Movie),
  
    REG_JNI(register_android_graphics_NinePatch),
  
    REG_JNI(register_android_graphics_Paint),
  
    REG_JNI(register_android_graphics_Path),
  
    REG_JNI(register_android_graphics_PathMeasure),
  
    REG_JNI(register_android_graphics_PathEffect),
  
    REG_JNI(register_android_graphics_Picture),
  
    REG_JNI(register_android_graphics_PorterDuff),
  
    REG_JNI(register_android_graphics_Rasterizer),
  
    REG_JNI(register_android_graphics_Region),
  
    REG_JNI(register_android_graphics_Shader),
  
    REG_JNI(register_android_graphics_Typeface),
  
    REG_JNI(register_android_graphics_Xfermode),
  
    REG_JNI(register_android_graphics_YuvImage),
  
    REG_JNI(register_com_android_internal_graphics_NativeUtils),
  
    REG_JNI(register_android_database_CursorWindow),
  
    REG_JNI(register_android_database_SQLiteCompiledSql),
  
    REG_JNI(register_android_database_SQLiteDatabase),
  
    REG_JNI(register_android_database_SQLiteDebug),
  
    REG_JNI(register_android_database_SQLiteProgram),
  
    REG_JNI(register_android_database_SQLiteQuery),
  
    REG_JNI(register_android_database_SQLiteStatement),
  
    REG_JNI(register_android_os_Debug),
  
    REG_JNI(register_android_os_FileObserver),
  
    REG_JNI(register_android_os_FileUtils),
  
    REG_JNI(register_android_os_MessageQueue),
  
    REG_JNI(register_android_os_ParcelFileDescriptor),
  
    REG_JNI(register_android_os_Power),
  
    REG_JNI(register_android_os_StatFs),
  
    REG_JNI(register_android_os_SystemProperties),
  
    REG_JNI(register_android_os_UEventObserver),
  
    REG_JNI(register_android_net_LocalSocketImpl),
  
    REG_JNI(register_android_net_NetworkUtils),
  
    REG_JNI(register_android_net_TrafficStats),
  
    REG_JNI(register_android_net_wifi_WifiManager),
  
    REG_JNI(register_android_nfc_NdefMessage),
  
    REG_JNI(register_android_nfc_NdefRecord),
  
    REG_JNI(register_android_os_MemoryFile),
  
    REG_JNI(register_com_android_internal_os_ZygoteInit),
  
    REG_JNI(register_android_hardware_Camera),
  
    REG_JNI(register_android_hardware_SensorManager),
  
    REG_JNI(register_android_media_AudioRecord),
  
    REG_JNI(register_android_media_AudioSystem),
  
    REG_JNI(register_android_media_AudioTrack),
  
    REG_JNI(register_android_media_JetPlayer),
  
    REG_JNI(register_android_media_ToneGenerator),
  
    REG_JNI(register_android_opengl_classes),
  
    REG_JNI(register_android_bluetooth_HeadsetBase),
  
    REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
  
    REG_JNI(register_android_bluetooth_BluetoothSocket),
  
    REG_JNI(register_android_bluetooth_ScoSocket),
  
    REG_JNI(register_android_server_BluetoothService),
  
    REG_JNI(register_android_server_BluetoothEventLoop),
  
    REG_JNI(register_android_server_BluetoothA2dpService),
  
    REG_JNI(register_android_server_Watchdog),
  
    REG_JNI(register_android_message_digest_sha1),
  
    REG_JNI(register_android_ddm_DdmHandleNativeHeap),
  
    REG_JNI(register_android_backup_BackupDataInput),
  
    REG_JNI(register_android_backup_BackupDataOutput),
  
    REG_JNI(register_android_backup_FileBackupHelperBase),
  
    REG_JNI(register_android_backup_BackupHelperDispatcher),
  
    REG_JNI(register_android_app_NativeActivity),
  
    REG_JNI(register_android_view_InputChannel),
  
    REG_JNI(register_android_view_InputQueue),
  
    REG_JNI(register_android_view_KeyEvent),
  
    REG_JNI(register_android_view_MotionEvent),
  
    REG_JNI(register_android_content_res_ObbScanner),
  
    REG_JNI(register_android_content_res_Configuration),
  
    };
  
    可以看到,gRegJNI是一个静态对象数组,这个对象数组实际上存储了Android framework中所有具有本地方法的java API模块的注册信息。为了理解这个RegJNIRec类型的静态数组,我们先看REG_JNI预编译宏和RegJNIRec类型的定义:
  
    view plain#ifdef NDEBUG
  
    #define REG_JNI(name) { name }
  
    struct RegJNIRec {
  
    int (*mProc)(JNIEnv*);
  
    };
  
    #else
  
    #define REG_JNI(name) { name, #name }
  
    struct RegJNIRec {
  
    int (*mProc)(JNIEnv*);
  
    const char* mName;
  
    };
  
    #endif
  
    显然,带参数的REG_JNII预编译宏实际上等价于一个“{name}”,而name是一个参数;而RegJNIRec类型实际上是一个包含函数指针的结构体类型。
  
    这样,在gRegJNI[]数组中,一个数组元素“REG_JNI(register_android_debug_JNITest),”也就等价于“{register_android_debug_JNITest },”,而这种数组定义方式的含义实际上就是把register_android_debug_JNITest这个函数指针,作为该数组元素的初始化参数,即该数组元素的mProc指针指向register_android_debug_JNITest。也就是说,gRegJNI[]数组的每个RegJNIRec结构体元素对应了一个framework子模块的本地方法注册信息,这个注册信息是通过RegJNIRec结构体元素的mProc指针来存储的。
  
    理解静态gRegJNI数组的含义非常重要,因为其直接定义了各个framework子模块本地方法的注册关联。
  
    下面我具体来看下gRegJNI[]数组中每个结构体元素的mProc指针存储的函数指针究竟是什么,这个函数指针指向的函数是如何工作的。我们在gRegJNI[]数组中选取大家比较熟悉的Canvas相关的数组元素REG_JNI( register_android_graphics_Canvas)为例,看看register_android_graphics_Canvas的实现,该函数的实现在frameworks\base\core\jni\android\graphics\ Canvas.cpp中:
  
    view plainint register_android_graphics_Canvas(JNIEnv* env) {
  
    int result;
  
    REG(env, "android/graphics/Canvas", gCanvasMethods);
  
    REG(env, "android/graphics/utils/BoundaryPatch", gBoundaryPatchMethods);
  
    return result;
  
    }
  
    其中REG是一个预编译宏,其定义是:
  
    view plain#define REG(env, name, array) \
  
    result = android::AndroidRuntime::registerNativeMethods(env, name, array, \
  
    SK_ARRAY_COUNT(array)); \
  
    if (result < 0) return result
  
    该宏最终会调用到\frameworks\base\core\jni \AndroidRuntime.cpp的registerNativeMethods 方法:
  
    view plain/*
  
    * Register native methods using JNI.
  
    */
  
    /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
  
    const char* className, const JNINativeMethod* gMethods, int numMethods)
  
    {
  
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
  
    }
  
    熟悉Java JNI的朋友对jniRegisterNativeMethods应该不会陌生,在这里完成了Java标准JNI的本地方法映射。
  
    好了,至此我们已经明白实际上函数register_android_graphics_Canvas的功能就是把数组gCanvasMethods存储的各个JNI映射元素通过标准Java JNI的jniRegisterNativeMethods方法来完成JNI映射。
  
    那么数组gCanvasMethods又是如何实现各个JNI映射的呢?
  
    view plainstatic JNINativeMethod gCanvasMethods[] = {
  
    {"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
  
    {"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
  
    {"initGL","()I", (void*) SkCanvasGlue::initGL},
  
    {"isOpaque","()Z", (void*) SkCanvasGlue::isOpaque},
  
    {"getWidth","()I", (void*) SkCanvasGlue::getWidth},
  
    {"getHeight","()I", (void*) SkCanvasGlue::getHeight},
  
    {"native_setBitmap","(II)V", (void*) SkCanvasGlue::setBitmap},
  
    {"nativeSetViewport", "(III)V", (void*) SkCanvasGlue::setViewport},
  
    {"save","()I", (void*) SkCanvasGlue::saveAll},
  
    {"save","(I)I", (void*) SkCanvasGlue::save},
  
    {"native_saveLayer","(ILandroid/graphics/RectF;II)I",
  
    (void*) SkCanvasGlue::saveLayer},
  
    {"native_saveLayer","(IFFFFII)I", (void*) SkCanvasGlue::saveLayer4F},
  
    {"native_saveLayerAlpha","(ILandroid/graphics/RectF;II)I",
  
    (void*) SkCanvasGlue::saveLayerAlpha},
  
    {"native_saveLayerAlpha","(IFFFFII)I",
  
    (void*) SkCanvasGlue::saveLayerAlpha4F},
  
    {"restore","()V", (void*) SkCanvasGlue::restore},
  
    {"getSaveCount","()I", (void*) SkCanvasGlue::getSaveCount},
  
    {"restoreToCount","(I)V", (void*) SkCanvasGlue::restoreToCount},
  
    {"translate","(FF)V", (void*) SkCanvasGlue::translate},
  
    {"scale","(FF)V", (void*) SkCanvasGlue::scale__FF},
  
    {"rotate","(F)V", (void*) SkCanvasGlue::rotate__F},
  
    {"skew","(FF)V", (void*) SkCanvasGlue::skew__FF},
  
    {"native_concat","(II)V", (void*) SkCanvasGlue::concat},
  
    {"native_setMatrix","(II)V", (void*) SkCanvasGlue::setMatrix},
  
    {"clipRect","(FFFF)Z", (void*) SkCanvasGlue::clipRect_FFFF},
  
    {"clipRect","(IIII)Z", (void*) SkCanvasGlue::clipRect_IIII},
  
    {"clipRect","(Landroid/graphics/RectF;)Z",
  
    (void*) SkCanvasGlue::clipRect_RectF},
  
    {"clipRect","(Landroid/graphics/Rect;)Z",
  
    (void*) SkCanvasGlue::clipRect_Rect},
  
    {"native_clipRect","(IFFFFI)Z", (void*) SkCanvasGlue::clipRect},
  
    {"native_clipPath","(III)Z", (void*) SkCanvasGlue::clipPath},
  
    {"native_clipRegion","(III)Z", (void*) SkCanvasGlue::clipRegion},
  
    {"nativeSetDrawFilter", "(II)V", (void*) SkCanvasGlue::setDrawFilter},
  
    {"native_getClipBounds","(ILandroid/graphics/Rect;)Z",
  
    (void*) SkCanvasGlue::getClipBounds},
  
    {"native_getCTM", "(II)V", (void*)SkCanvasGlue::getCTM},
  
    {"native_quickReject","(ILandroid/graphics/RectF;I)Z",
  
    (void*) SkCanvasGlue::quickReject__RectFI},
  
    {"native_quickReject","(III)Z", (void*) SkCanvasGlue::quickReject__PathI},
  
    {"native_quickReject","(IFFFFI)Z", (void*)SkCanvasGlue::quickReject__FFFFI},
  
    {"native_drawRGB","(IIII)V", (void*) SkCanvasGlue::drawRGB},
  
    {"native_drawARGB","(IIIII)V", (void*) SkCanvasGlue::drawARGB},
  
    {"native_drawColor","(II)V", (void*) SkCanvasGlue::drawColor__I},
  
    {"native_drawColor","(III)V", (void*) SkCanvasGlue::drawColor__II},
  
    {"native_drawPaint","(II)V", (void*) SkCanvasGlue::drawPaint},
  
    {"drawPoint", "(FFLandroid/graphics/Paint;)V",
  
    (void*) SkCanvasGlue::drawPoint},
  
    {"drawPoints", "([FIILandroid/graphics/Paint;)V",
  
    (void*) SkCanvasGlue::drawPoints},
  
    {"drawLines", "([FIILandroid/graphics/Paint;)V",
  
    (void*) SkCanvasGlue::drawLines},
  
    {"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
  
    {"native_drawRect","(ILandroid/graphics/RectF;I)V",
  
    (void*) SkCanvasGlue::drawRect__RectFPaint},
  
    {"native_drawRect","(IFFFFI)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
  
    {"native_drawOval","(ILandroid/graphics/RectF;I)V",
  
    (void*) SkCanvasGlue::drawOval},
  
    {"native_drawCircle","(IFFFI)V", (void*) SkCanvasGlue::drawCircle},
  
    {"native_drawArc","(ILandroid/graphics/RectF;FFZI)V",
  
    (void*) SkCanvasGlue::drawArc},
  
    {"native_drawRoundRect","(ILandroid/graphics/RectF;FFI)V",
  
    (void*) SkCanvasGlue::drawRoundRect},
  
    {"native_drawPath","(III)V", (void*) SkCanvasGlue::drawPath},
  
    {"native_drawBitmap","(IIFFIIII)V",
  
    (void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
  
    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/RectF;III)V",
  
    (void*) SkCanvasGlue::drawBitmapRF},
  
    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/Rect;III)V",
  
    (void*) SkCanvasGlue::drawBitmapRR},
  
    {"native_drawBitmap", "(I[IIIFFIIZI)V",
  
    (void*)SkCanvasGlue::drawBitmapArray},
  
    {"nativeDrawBitmapMatrix", "(IIII)V",
  
    (void*)SkCanvasGlue::drawBitmapMatrix},
  
    {"nativeDrawBitmapMesh", "(IIII[FI[III)V",
  
    (void*)SkCanvasGlue::drawBitmapMesh},
  
    {"nativeDrawVertices", "(III[FI[FI[II[SIII)V",
  
    (void*)SkCanvasGlue::drawVertices},
  
    {"native_drawText","(I[CIIFFI)V",
  
    (void*) SkCanvasGlue::drawText___CIIFFPaint},
  
    {"native_drawText","(ILjava/lang/String;IIFFI)V",
  
    (void*) SkCanvasGlue::drawText__StringIIFFPaint},
  
    {"drawText","(Ljava/lang/String;FFLandroid/graphics/Paint;)V",
  
    (void*) SkCanvasGlue::drawString},
  
    {"native_drawPosText","(I[CII[FI)V",
  
    (void*) SkCanvasGlue::drawPosText___CII_FPaint},
  
    {"native_drawPosText","(ILjava/lang/String;[FI)V",
  
    (void*) SkCanvasGlue::drawPosText__String_FPaint},
  
    {"native_drawTextOnPath","(I[CIIIFFI)V",
  
    (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
  
    {"native_drawTextOnPath","(ILjava/lang/String;IFFI)V",
  
    (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
  
    {"native_drawPicture", "(II)V", (void*) SkCanvasGlue::drawPicture},
  
    {"freeCaches", "()V", (void*) SkCanvasGlue::freeCaches}
  
    };
  
    到这里大家应该比较清楚了,正是gCanvasMethods[]数组完成我们比较熟悉的frameworks\base\graphics\java\android\graphics\Canvas.java中API函数与本地方法的关联。以gCanvasMethods[]数组的元素为例:
  
    {"native_drawBitmap","(IILandroid/graphics/Rect;Landroid/graphics/RectF;III)V",
  
    (void*) SkCanvasGlue::drawBitmapRF},
  
    在这里,native_drawBitmap方法是Canvas.java的private native void native_drawBitmap方法,该方法被Canvas.java的标准API drawBitmap方法直接调用,而native_drawBitmap方法通过这种JNI注册方式又具体调用SkCanvasGlue.cpp的drawBitmapRF方法。也就是说,我们在调用Canvas.java的标准API drawBitmap方法绘图时,正式通过这种JNI映射机制完成了从JAVA到C++实现的调用。
  
    至此,我们已经完成了\frameworks\base\core\jni\AndroidRuntime.cpp中,gRegJNI[]数组中一个数组元素:
  
    register_android_graphics_CanvasREG_JNI( register_android_graphics_Canvas)的全部分析,明白了函数指针register_android_graphics_Canvas是如何实现把Canvas.java 中的所有Java API相关的本地方法与对应具体的C++实现方法注册、映射起来的。gRegJNI[]数组中其它数组元素对应的函数指针的工作原理与之相同的,这里就不重复了。
  
    上面我们提到,AndroidRuntime.cpp中register_jni_procs及其参数gRegJNI是注册关联的两大要素,我们已经完成了gRegJNI[]数组的分析,明白了其数组元素的含义及实现机制。但是gRegJNI[]数组元素对应的函数指针只是一个指针,而gRegJNI[]数组只是存储了这些映指针,并没有具体调用这些函数指针对应的函数。那么系统又是如何利用gRegJNI[]数组存储的注册映射信息的,即如何调用这些函数指针的?这其实就是AndroidRuntime.cpp中register_jni_procs的任务了——
  
    上面已经介绍过,开机启动后,经过一系列加载流程,最终会运行到AndroidRuntime.cpp 的函数int AndroidRuntime::startReg(JNIEnv* env) 中,在该函数内部,可以看到如下的代码片段:
  
    view plainif (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
  
    env->PopLocalFrame(NULL);
  
    return -1;
  
    }
  
    下面我们就来具体分析函数是register_jni_procs如何实现的:
  
    view plainstatic int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
  
    {
  
    for (size_t i = 0; i < count; i++) {
  
    if (array[i].mProc(env) < 0) {
  
    #ifndef NDEBUG
  
    LOGD("----------!!! %s failed to load\n", array[i].mName);
  
    #endif
  
    return -1;
  
    }
  
    }
  
    return 0;
  
    }
  
    这里,数组形参array被传入的参数是gRegJNI[]数组,在该函数内部,会循环遍历gRegJNI[]数组的每一个元素,并调用每个RegJNIRec类型结构体元素的mProc函数指针指向的方法。上文已述,mProc函数指针实际上就是gRegJNI[]数组被初始化的函数指针,例如数组元素“REG_JNI(register_android_debug_JNITest),”这里mProc函数指针实际上就是register_android_debug_JNITest。
  
    也就是说,在AndroidRuntime::startReg(JNIEnv* env) 中,系统通过register_jni_procs方法的调用完成gRegJNI[]数组存储的注册映射信息函数指针的调用,将这些指针指向的farmwork Java API与本地函数的注册关联信息注册到了系统中。
  
    至此,我们完成了Android framework层的API函数与其对应本地方法的映射、注册原理的全部分析,归纳一下,有下面几点:
  
    1、 Android启动后,本地方法的注册关联是在调用AndroidRuntime::startReg过程中完成的。
  
    2、 AndroidRuntime的静态数组gRegJNI[]存储了各个framework子模块本地方法关联信息。数组gRegJNI[]的每一个元素存储了各个模块的注册相关的函数指针,这个函数指针的功能是把子模块中的众多本地函数关联添加进系统中。子模块中的众多本地函数也是通过子模块的具体的静态数组来存储的,这个子模块的静态数组被上述函数指针指向的函数作为参数来使用。
  
    3、 注册关联是具体通过AndroidRuntime::register_jni_procs方法把静态数组gRegJNI[]数组存储的各个framework子模块本地方法关联信息注册到系统中去的。
  
  达内总部3g培训:www.gsdtarena.com
  
  达内总部java培训:www.chinatarena.com

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多