Dalvik的指令执行是解释器+JIT的方式,解释器就是虚拟机来对Javac编译出来的字节码,做译码、执行,而不是转化成CPU的指令集,由CPU来做译码,执行。可想而知,解释器的效率是相对较低的,所以出现了JIT(Just In Time),JIT是将执行次数较多的函数,做即时编译,在运行时刻,编译成本地目标代码,JIT可以看成是解释器的一个补充优化。再之后又出现了Art虚拟机的AOT(Ahead Of Time)模式,做静态编译,在Apk安装的时候就会做字节码的编译,从而效率直逼静态语言。

  Java所有的方法都是类方法,因此Dalvik的字节码执行就两种,一是类的Method,包括静态和非静态,两者的差距也就是有没有this参数,二就是类的初始化代码,就是类加载的时候,成员变量的初始化以及显式的类初始化块代码。

  其中类的初始化代码在dalvik/vm/oo/Class.cpp的dvmInitClass:

C++代码
  1. bool dvmInitClass(ClassObject* clazz)    
  2. {    
  3.     ...    
  4.     dvmLockObject(self, (Object*) clazz);    
  5.     ...    
  6.     android_atomic_release_store(CLASS_INITIALIZING,    
  7.                                  (int32_t*)(void*)&clazz->status);    
  8.     dvmUnlockObject(self, (Object*) clazz);    
  9.     ...    
  10.     initSFields(clazz);    
  11.     
  12.     /* Execute any static initialization code.  
  13.      */    
  14.     method = dvmFindDirectMethodByDescriptor(clazz, "<clinit>""()V");    
  15.     if (method == NULL) {    
  16.         LOGVV("No <clinit> found for %s", clazz->descriptor);    
  17.     } else {    
  18.         LOGVV("Invoking %s.<clinit>", clazz->descriptor);    
  19.         JValue unused;    
  20.         dvmCallMethod(self, method, NULL, &unused);    
  21.     }    
  22.     ...    
  23. }   

  从代码可见,类初始化的主要代码逻辑包括:

  类对象加锁,所以类的加载是单线程的

  初始化static成员(initSFields)

  调用<cinit>,静态初始化块

  类的初始化块代码在<cinit>的成员函数里。可见Dalvik的字节码解释,本质上还是类成员函数的解释执行。

  虚拟机以Method作为解释器的执行单元,其入口就统一为dvmCallMethod,该函数的定义在dalvik/vm/interp/Stack.cpp里。

C++代码
  1. void dvmCallMethod(Thread* self, const Method* method, Object* obj,    
  2.     JValue* pResult, ...)    
  3. {    
  4.     va_list args;    
  5.     va_start(args, pResult);    
  6.     dvmCallMethodV(self, method, obj, false, pResult, args);    
  7.     va_end(args);    
  8. }    
  9.     
  10. void dvmCallMethodV(Thread* self, const Method* method, Object* obj,    
  11.     bool fromJni, JValue* pResult, va_list args)    
  12. {    
  13.    ...    
  14.     if (dvmIsNativeMethod(method)) {    
  15.         TRACE_METHOD_ENTER(self, method);    
  16.         /*  
  17.          * Because we leave no space for local variables, "curFrame" points  
  18.          * directly at the method arguments.  
  19.          */    
  20.         (*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,    
  21.                               method, self);    
  22.         TRACE_METHOD_EXIT(self, method);    
  23.     } else {    
  24.         dvmInterpret(self, method, pResult);    
  25.     }    
  26.    …    
  27. }

  Java的Method有native函数和非native函数,native的函数的代码段是在so里,是本地指令集而非虚拟机的字节码。

  虚拟机以Method作为解释器的执行单元,其入口就统一为dvmCallMethod,该函数的定义在dalvik/vm/interp/Stack.cpp里。

C++代码
  1. void dvmCallMethod(Thread* self, const Method* method, Object* obj,    
  2.     JValue* pResult, ...)    
  3. {    
  4.     va_list args;    
  5.     va_start(args, pResult);    
  6.     dvmCallMethodV(self, method, obj, false, pResult, args);    
  7.     va_end(args);    
  8. }    
  9.     
  10. void dvmCallMethodV(Thread* self, const Method* method, Object* obj,    
  11.     bool fromJni, JValue* pResult, va_list args)    
  12. {    
  13.    ...    
  14.         if (dvmIsNativeMethod(method)) {    
  15.         TRACE_METHOD_ENTER(self, method);    
  16.         /*  
  17.          * Because we leave no space for local variables, "curFrame" points  
  18.          * directly at the method arguments.  
  19.          */    
  20.         (*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,    
  21.                               method, self);    
  22.         TRACE_METHOD_EXIT(self, method);    
  23.     } else {    
  24.         dvmInterpret(self, method, pResult);    
  25.  }    
  26.    …    
  27. }

  如果method是个native的函数,那么就直接调用nativeFunc这个函数指针,否则就调用dvmInterpret代码,dvmInterpret就是解释器的入口。

  如果把Dalvik函数执行的调用栈画出来,我们会更清楚整个流程。

Java代码
  1. public class HelloWorld {    
  2.     
  3.     public int foo(int i, int j){    
  4.         int k = i + j;    
  5.         return k;    
  6.     }    
  7.     
  8.     public static void main(String[] args) {    
  9.         System.out.print(new HelloWorld().foo(12));    
  10.     }    
  11. }    

深入理解Dalvik虚拟机- 解释器的运行机制

  Dalvik虚拟机有两个栈,一个Java栈,一个是VM的native栈,vm的栈是OS的函数调用栈,Java的栈则是由VM管理的栈,每次在dvmCallMethod的时候,在Method执行之前,会调用dvmPushInterpFrame(java→java)或者dvmPushJNIFrame(java→native),JNI的Frame比InterpFrame少了局部变量的栈空间,native函数的局部变量是在vm的native栈里,由OS负责压栈出栈。DvmCallMethod结束的时候会调用dvmPopFrame做Java Stack的出栈。

  所以Java Method的执行就是dvmInterpret函数对这个Method的字节码做解析,函数的实参与局部变量都在Java的Stack里获取。SaveBlock是StackSaveArea数据结构,里面包含了当前函数对应的栈信息,包括返回地址等。而Native  Method的执行就是Method的nativeFunc的执行,实参和局部变量都是在VM的native stack里。

  Method的nativeFunc是native函数的入口,dalvik虚拟机上的java 的函数hook技术,都是通过改变Method的属性,SET_METHOD_FLAG(method, ACC_NATIVE),伪装成native函数,再设置nativeFunc作为钩子函数,从而实现hook功能。很显然,hook了的method不再具有多态性。

  nativeFunc的默认函数是dvmResolveNativeMethod(vm/Native.cpp)

C++代码
  1. void dvmResolveNativeMethod(const u4* args, JValue* pResult,    
  2.     const Method* method, Thread* self)    
  3. {    
  4.     ClassObject* clazz = method->clazz;    
  5.     
  6.     /*  
  7.      * If this is a static method, it could be called before the class  
  8.      * has been initialized.  
  9.      */    
  10.     if (dvmIsStaticMethod(method)) {    
  11.         if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {    
  12.             assert(dvmCheckException(dvmThreadSelf()));    
  13.             return;    
  14.         }    
  15.     } else {    
  16.         assert(dvmIsClassInitialized(clazz) ||    
  17.                dvmIsClassInitializing(clazz));    
  18.     }    
  19.     
  20.     /* start with our internal-native methods */    
  21.     DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);    
  22.     if (infunc != NULL) {    
  23.         /* resolution always gets the same answer, so no race here */    
  24.         IF_LOGVV() {    
  25.             char* desc = dexProtoCopyMethodDescriptor(&method->prototype);    
  26.             LOGVV("+++ resolved native %s.%s %s, invoking",    
  27.                 clazz->descriptor, method->name, desc);    
  28.             free(desc);    
  29.         }    
  30.         if (dvmIsSynchronizedMethod(method)) {    
  31.             ALOGE("ERROR: internal-native can't be declared 'synchronized'");    
  32.             ALOGE("Failing on %s.%s", method->clazz->descriptor, method->name);    
  33.             dvmAbort();     // harsh, but this is VM-internal problem    
  34.         }    
  35.         DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;    
  36.         dvmSetNativeFunc((Method*) method, dfunc, NULL);    
  37.         dfunc(args, pResult, method, self);    
  38.         return;    
  39.     }    
  40.     
  41.     /* now scan any DLLs we have loaded for JNI signatures */    
  42.     void* func = lookupSharedLibMethod(method);    
  43.     if (func != NULL) {    
  44.         /* found it, point it at the JNI bridge and then call it */    
  45.         dvmUseJNIBridge((Method*) method, func);    
  46.         (*method->nativeFunc)(args, pResult, method, self);    
  47.         return;    
  48.     }    
  49.     
  50.     IF_ALOGW() {    
  51.         char* desc = dexProtoCopyMethodDescriptor(&method->prototype);    
  52.         ALOGW("No implementation found for native %s.%s:%s",    
  53.             clazz->descriptor, method->name, desc);    
  54.         free(desc);    
  55.     }    
  56.     
  57.     dvmThrowUnsatisfiedLinkError("Native method not found", method);    
  58. }

  dvmResolveNativeMethod首先会调用dvmLookupInternalNativeMethod查询这个函数是否预置的函数,主要是查下面的函数集:

C++代码
  1. static DalvikNativeClass gDvmNativeMethodSet[] = {    
  2.     { "Ljava/lang/Object;",               dvm_java_lang_Object, 0 },    
  3.     { "Ljava/lang/Class;",                dvm_java_lang_Class, 0 },    
  4.     { "Ljava/lang/Double;",               dvm_java_lang_Double, 0 },    
  5.     { "Ljava/lang/Float;",                dvm_java_lang_Float, 0 },    
  6.     { "Ljava/lang/Math;",                 dvm_java_lang_Math, 0 },    
  7.     { "Ljava/lang/Runtime;",              dvm_java_lang_Runtime, 0 },    
  8.     { "Ljava/lang/String;",               dvm_java_lang_String, 0 },    
  9.     { "Ljava/lang/System;",               dvm_java_lang_System, 0 },    
  10.     { "Ljava/lang/Throwable;",            dvm_java_lang_Throwable, 0 },    
  11.     { "Ljava/lang/VMClassLoader;",        dvm_java_lang_VMClassLoader, 0 },    
  12.     { "Ljava/lang/VMThread;",             dvm_java_lang_VMThread, 0 },    
  13.     { "Ljava/lang/reflect/AccessibleObject;",    
  14.             dvm_java_lang_reflect_AccessibleObject, 0 },    
  15.     { "Ljava/lang/reflect/Array;",        dvm_java_lang_reflect_Array, 0 },    
  16.     { "Ljava/lang/reflect/Constructor;",    
  17.             dvm_java_lang_reflect_Constructor, 0 },    
  18.     { "Ljava/lang/reflect/Field;",        dvm_java_lang_reflect_Field, 0 },    
  19.     { "Ljava/lang/reflect/Method;",       dvm_java_lang_reflect_Method, 0 },    
  20.     { "Ljava/lang/reflect/Proxy;",        dvm_java_lang_reflect_Proxy, 0 },    
  21.     { "Ljava/util/concurrent/atomic/AtomicLong;",    
  22.             dvm_java_util_concurrent_atomic_AtomicLong, 0 },    
  23.     { "Ldalvik/bytecode/OpcodeInfo;",     dvm_dalvik_bytecode_OpcodeInfo, 0 },    
  24.     { "Ldalvik/system/VMDebug;",          dvm_dalvik_system_VMDebug, 0 },    
  25.     { "Ldalvik/system/DexFile;",          dvm_dalvik_system_DexFile, 0 },    
  26.     { "Ldalvik/system/VMRuntime;",        dvm_dalvik_system_VMRuntime, 0 },    
  27.     { "Ldalvik/system/Zygote;",           dvm_dalvik_system_Zygote, 0 },    
  28.     { "Ldalvik/system/VMStack;",          dvm_dalvik_system_VMStack, 0 },    
  29.     { "Lorg/apache/harmony/dalvik/ddmc/DdmServer;",    
  30.             dvm_org_apache_harmony_dalvik_ddmc_DdmServer, 0 },    
  31.     { "Lorg/apache/harmony/dalvik/ddmc/DdmVmInternal;",    
  32.             dvm_org_apache_harmony_dalvik_ddmc_DdmVmInternal, 0 },    
  33.     { "Lorg/apache/harmony/dalvik/NativeTestTarget;",    
  34.             dvm_org_apache_harmony_dalvik_NativeTestTarget, 0 },    
  35.     { "Lsun/misc/Unsafe;",                dvm_sun_misc_Unsafe, 0 },    
  36.     { NULL, NULL, 0 },    
  37. };

  不是内置的话,就会加载so库,查询对应的native函数,查询的规则就是我们熟知的了,com.xx.Helloworld.foobar对应com_xx_Helloworld_foobar。要注意的是,这个函数并不是nativeFunc,接下来的dvmUseJNIBridge调用里,dvmCallJNIMethod会作为nativeFunc,这个函数主要需要将之前提到的java stack frame里的ins实参,转译成jni的函数调用参数。xposed/dexposed就会自己设置自己的nativeFun自己接管native函数的执行。

  dvmInterpret是解释器的代码入口,代码位置在interp/Interp.cpp

C++代码
  1. void dvmInterpret(Thread* self, const Method* method, JValue* pResult)    
  2. {    
  3.     InterpSaveState interpSaveState;    
  4.     ExecutionSubModes savedSubModes;    
  5.     . . .     
  6.     interpSaveState = self->interpSave;    
  7.     self->interpSave.prev = &interpSaveState;     
  8.     . . .     
  9.     
  10.     self->interpSave.method = method;    
  11.     self->interpSave.curFrame = (u4*) self->interpSave.curFrame;    
  12.     self->interpSave.pc = method->insns;    
  13.     . . .    
  14.     typedef void (*Interpreter)(Thread*);    
  15.     Interpreter stdInterp;    
  16.     if (gDvm.executionMode == kExecutionModeInterpFast)    
  17.         stdInterp = dvmMterpStd;    
  18. #if defined(WITH_JIT)    
  19.     else if (gDvm.executionMode == kExecutionModeJit ||    
  20.              gDvm.executionMode == kExecutionModeNcgO0 ||    
  21.              gDvm.executionMode == kExecutionModeNcgO1)    
  22.         stdInterp = dvmMterpStd;    
  23. #endif    
  24.     else    
  25.         stdInterp = dvmInterpretPortable;    
  26.     
  27.     // Call the interpreter    
  28.     (*stdInterp)(self);    
  29.     *pResult = self->interpSave.retval;    
  30.     
  31.     /* Restore interpreter state from previous activation */    
  32.     self->interpSave = interpSaveState;    
  33. #if defined(WITH_JIT)    
  34.     dvmJitCalleeRestore(calleeSave);    
  35. #endif    
  36.     if (savedSubModes != kSubModeNormal) {    
  37.         dvmEnableSubMode(self, savedSubModes);    
  38.     }    
  39. }

  Thread的一个很重要的field就是interpSave,是InterpSaveState类型的,里面包含了当前函数,pc,当前栈帧等重要的变量,dvmInterpret一开始调用的时候就会初始化。

  Dalvik解释器有两个,一个是dvmInterpretPortable,一个是 dvmMterpStd。两者的区别在于,前者是从c++实现,后者是汇编实现。

  dvmInterpretPortable是在vm/mterp/out/InterpC-portable.cpp中定义

C++代码
  1. void dvmInterpretPortable(Thread* self)    
  2. {    
  3.     . . .    
  4.     DvmDex* methodClassDex;     // curMethod->clazz->pDvmDex    
  5.     JValue retval;    
  6.     
  7.     /* core state */    
  8.     const Method* curMethod;    // method we're interpreting    
  9.     const u2* pc;               // program counter    
  10.     u4* fp;                     // frame pointer    
  11.     u2 inst;                    // current instruction    
  12.     /* instruction decoding */    
  13.     u4 ref;                     // 16 or 32-bit quantity fetched directly    
  14.     u2 vsrc1, vsrc2, vdst;      // usually used for register indexes    
  15.     /* method call setup */    
  16.     const Method* methodToCall;    
  17.     bool methodCallRange;    
  18.     
  19.     /* static computed goto table */    
  20.     DEFINE_GOTO_TABLE(handlerTable);    
  21.     /* copy state in */    
  22.     curMethod = self->interpSave.method;    
  23.     pc = self->interpSave.pc;    
  24.     fp = self->interpSave.curFrame;    
  25.     retval = self->interpSave.retval;       
  26.     
  27.     methodClassDex = curMethod->clazz->pDvmDex;    
  28.     
  29.     . . .     
  30.        
  31.     FINISH(0);                  /* fetch and execute first instruction */    
  32. /*--- start of opcodes ---*/    
  33.     
  34. /* File: c/OP_NOP.cpp */    
  35. HANDLE_OPCODE(OP_NOP)    
  36.     FINISH(1);    
  37. OP_END    
  38.     
  39. /* File: c/OP_MOVE.cpp */    
  40. HANDLE_OPCODE(OP_MOVE /*vA, vB*/)    
  41.     vdst = INST_A(inst);    
  42.     vsrc1 = INST_B(inst);    
  43.     ILOGV("|move%s v%d,v%d %s(v%d=0x%08x)",    
  44.         (INST_INST(inst) == OP_MOVE) ? "" : "-object", vdst, vsrc1,    
  45.         kSpacing, vdst, GET_REGISTER(vsrc1));    
  46.     SET_REGISTER(vdst, GET_REGISTER(vsrc1));    
  47.     FINISH(1);    
  48. OP_END    
  49. …..    
  50. }

  解释器的指令执行是通过跳转表来实现,DEFINE_GOTO_TABLE(handlerTable)定义了指令Op的goto表。

  FINISH(0),则表示从第一条指令开始执行,

C++代码
  1. # define FINISH(_offset) {                                                  \    
  2.         ADJUST_PC(_offset);                                                 \    
  3.         inst = FETCH(0);                                                    \    
  4.         if (self->interpBreak.ctl.subMode) {                                \    
  5.             dvmCheckBefore(pc, fp, self);                                   \    
  6.         }                                                                   \    
  7.         goto *handlerTable[INST_INST(inst)];                                \    
  8.     }    
  9.     
  10. #define FETCH(_offset)     (pc[(_offset)])  

  FETCH(0)获得当前要执行的指令,通过查跳转表handlerTable来跳转到这条指令的执行点,就是函数后面的HANDLE_OPCODE的定义。

  后者是针对不同平台做过优化的解释器。

  dvmMterpStd会做汇编级的优化,dvmMterpStdRun的入口就是针对不同的平台指令集,有对应的解释器代码,比如armv7 neon对应的代码就在mterp/out/InterpAsm-armv7-a-neon.S。

C++代码
  1. dvmMterpStdRun:    
  2. #define MTERP_ENTRY1 \    
  3.     .save {r4-r10,fp,lr}; \    
  4.     stmfd   sp!, {r4-r10,fp,lr}         @ save 9 regs    
  5. #define MTERP_ENTRY2 \    
  6.     .pad    #4; \    
  7.     sub     sp, sp, #4                  @ align 64    
  8.     
  9.     .fnstart    
  10.     MTERP_ENTRY1    
  11.     MTERP_ENTRY2    
  12.     
  13.     /* save stack pointer, add magic word for debuggerd */    
  14.     str     sp, [r0, #offThread_bailPtr]  @ save SP for eventual return    
  15.     
  16.     /* set up "named" registers, figure out entry point */    
  17.     mov     rSELF, r0                   @ set rSELF    
  18.     LOAD_PC_FP_FROM_SELF()              @ load rPC and rFP from "thread"    
  19.     ldr     rIBASE, [rSELF, #offThread_curHandlerTable] @ set rIBASE    
  20.     . . .    
  21.     /* start executing the instruction at rPC */    
  22.     FETCH_INST()                        @ load rINST from rPC    
  23.     GET_INST_OPCODE(ip)                 @ extract opcode from rINST    
  24.     GOTO_OPCODE(ip)                     @ jump to next instruction    
  25.     . . .    
  26.     
  27. #define rPC     r4    
  28. #define rFP     r5    
  29. #define rSELF   r6    
  30. #define rINST   r7    
  31. #define rIBASE  r8    

  非jit的情况下,先是FETCH_INST把pc的指令加载到rINST寄存器,之后GET_INST_OPCODE获得操作码 and     _reg, rINST, #255,是把rINST的低16位给ip寄存器,GOTO_OPCODE跳转到对应的地址。

  #define GOTO_OPCODE(_reg)       add     pc, rIBASE, _reg, lsl #6

  rIBASE 指向的curHandlerTable是跳转表的首地址,GOTO_OPCODE(ip)就将pc的地址指向该指令对应的操作码所在的跳转表地址。

C++代码
  1. static Thread* allocThread(int interpStackSize)    
  2. #ifndef DVM_NO_ASM_INTERP    
  3.     thread->mainHandlerTable = dvmAsmInstructionStart;    
  4.     thread->altHandlerTable = dvmAsmAltInstructionStart;    
  5.     thread->interpBreak.ctl.curHandlerTable = thread->mainHandlerTable;    
  6. #endif    

  可见dvmAsmInstructionStart就是跳转表的入口,定义在dvmMterpStdRun里,

  你可以在这里找到所有的Java字节码的指令对应的解释器代码。

  比如new操作符对应的代码如下,先加载Thread.interpSave.methodClassDex,这是一个DvmDex指针,随后加载 DvmDex的pResClasses来查找类是否加载过,如果没加载过,那么跳转到 LOP_NEW_INSTANCE_resolve去加载类,如果加载过,就是类的初始化以及AllocObject的处理。LOP_NEW_INSTANCE_resolve就是调用clazz的dvmResolveClass加载。

C++代码
  1. /* ------------------------------ */    
  2.     .balign 64    
  3. .L_OP_NEW_INSTANCE: /* 0x22 */    
  4. /* File: armv5te/OP_NEW_INSTANCE.S */    
  5.     /*  
  6.      * Create a new instance of a class.  
  7.      */    
  8.     /* new-instance vAA, class@BBBB */    
  9.     ldr     r3, [rSELF, #offThread_methodClassDex]    @ r3<- pDvmDex    
  10.     FETCH(r1, 1)                        @ r1<- BBBB    
  11.     ldr     r3, [r3, #offDvmDex_pResClasses]    @ r3<- pDvmDex->pResClasses    
  12.     ldr     r0, [r3, r1, lsl #2]        @ r0<- resolved class    
  13. #if defined(WITH_JIT)    
  14.     add     r10, r3, r1, lsl #2         @ r10<- &resolved_class    
  15. #endif    
  16.     EXPORT_PC()                         @ req'd for init, resolve, alloc    
  17.     cmp     r0, #0                      @ already resolved?    
  18.     beq     .LOP_NEW_INSTANCE_resolve         @ no, resolve it now    
  19. .LOP_NEW_INSTANCE_resolved:   @ r0=class    
  20.     ldrb    r1, [r0, #offClassObject_status]    @ r1<- ClassStatus enum    
  21.     cmp     r1, #CLASS_INITIALIZED      @ has class been initialized?    
  22.     bne     .LOP_NEW_INSTANCE_needinit        @ no, init class now    
  23. .LOP_NEW_INSTANCE_initialized: @ r0=class    
  24.     mov     r1, #ALLOC_DONT_TRACK       @ flags for alloc call    
  25.     bl      dvmAllocObject              @ r0<- new object    
  26.     b       .LOP_NEW_INSTANCE_finish          @ continue    
  27.     
  28.     
  29. .LOP_NEW_INSTANCE_needinit:    
  30.     mov     r9, r0                      @ save r0    
  31.     bl      dvmInitClass                @ initialize class    
  32.     cmp     r0, #0                      @ check boolean result    
  33.     mov     r0, r9                      @ restore r0    
  34.     bne     .LOP_NEW_INSTANCE_initialized     @ success, continue    
  35.     b       common_exceptionThrown      @ failed, deal with init exception    
  36.     
  37.     /*  
  38.      * Resolution required.  This is the least-likely path.  
  39.      *  
  40.      *  r1 holds BBBB  
  41.      */    
  42. .LOP_NEW_INSTANCE_resolve:    
  43.     ldr     r3, [rSELF, #offThread_method] @ r3<- self->method    
  44.     mov     r2, #0                      @ r2<- false    
  45.     ldr     r0, [r3, #offMethod_clazz]  @ r0<- method->clazz    
  46.     bl      dvmResolveClass             @ r0<- resolved ClassObject ptr    
  47.     cmp     r0, #0                      @ got null?    
  48.     bne     .LOP_NEW_INSTANCE_resolved        @ no, continue    
  49.     b       common_exceptionThrown      @ yes, handle exception  
本文发布:Android开发网
本文地址:http://www.jizhuomi.com/android/environment/555.html
2016年4月7日
发布:鸡啄米 分类:Android开发环境 浏览: 评论:0