本文首发于安全客

链接:https://www.anquanke.com/post/id/219094

0x1 前言

在Android平台上,程序员编写的Java代码最终将被编译成字节码在Android虚拟机上运行。自从Android进入大众的视野后,apktool,jadx等反编译工具也层出不穷,功能也越来越强大,由Java编译成的字节码在这些反编译工具面前变得不堪一击,这相当于一个人裸奔在茫茫人海,身体的各个部位被众人一览无余。一种事物的出现,也会有与之对立的事物出现。有反编译工具的出现,当然也会有反反编译工具的出现,这种技术一般我们加固技术。APP经过加固,就相当于给那个裸奔的人穿了衣服,“衣服”在一定程度上保护了APP,使APP没那么容易被反编译。当然,有加固技术的出现,也会有反加固技术的出现,即本文要分析的脱壳技术。

Android经过多个版本的更迭,它无论在外观还是内在都有许多改变,早期的Android使用的是dalvik虚拟机,Android4.4开始加入ART虚拟机,但不默认启用。从Android5.0开始,ART取代dalvik,成为默认虚拟机。由于dalvik和ART运行机制的不同,在它们内部脱壳原理也不太相同,本文分析的是ART下的脱壳方案:FART。它的整体思路是通过主动调用的方式来实现脱壳,项目地址:https://github.com/hanbinglengyue/FART 。FART的代码是通过修改少量Android源码文件而成的,经过修改的Android源码编译成系统镜像,刷入手机,这样的手机启动后,就成为一台可以用于脱壳的脱壳机。

0x2 流程分析

FART的入口在frameworks\base\core\java\android\app\ActivityThread.java的performLaunchActivity函数中,即APP的Activity启动的时候执行fartthread

  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2. Log.e("ActivityThread","go into performLaunchActivity");
  3. ActivityInfo aInfo = r.activityInfo;
  4. if (r.packageInfo == null) {
  5. r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
  6. Context.CONTEXT_INCLUDE_CODE);
  7. }
  8. ......
  9. //开启fart线程
  10. fartthread();
  11. ......
  12. }

fartthread函数开启一个线程,休眠一分钟后调用fart函数

  1. public static void fartthread() {
  2. new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. try {
  6. Log.e("ActivityThread", "start sleep,wait for fartthread start......");
  7. Thread.sleep(1 * 60 * 1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. Log.e("ActivityThread", "sleep over and start fartthread");
  12. fart();
  13. Log.e("ActivityThread", "fart run over");
  14. }
  15. }).start();
  16. }

fart函数中,获取Classloader,反射获取一些类。反射调用dalvik.system.DexPathList的dexElements字段得到dalvik.system.DexPathList$Element类对象数组,Element类存储着dex的路径等信息。接下来通过遍历dexElements,得到每一个Element对象中的DexFile对象,再获取DexFile对象中的mCookie字段值,调用DexFile类中的String[] getClassNameList(Object cookie)函数并传入获取到mCookie,以得到dex文件中所有的类名。随后,遍历dex中的所有类名,传入loadClassAndInvoke函数。

  1. public static void fart() {
  2. ClassLoader appClassloader = getClassloader();
  3. List<Object> dexFilesArray = new ArrayList<Object>();
  4. Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
  5. Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
  6. Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
  7. Field dexFile_fileField = null;
  8. try {
  9. dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. Class DexFileClazz = null;
  14. try {
  15. DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. Method getClassNameList_method = null;
  20. Method defineClass_method = null;
  21. Method dumpDexFile_method = null;
  22. Method dumpMethodCode_method = null;
  23. for (Method field : DexFileClazz.getDeclaredMethods()) {
  24. if (field.getName().equals("getClassNameList")) {
  25. getClassNameList_method = field;
  26. getClassNameList_method.setAccessible(true);
  27. }
  28. if (field.getName().equals("defineClassNative")) {
  29. defineClass_method = field;
  30. defineClass_method.setAccessible(true);
  31. }
  32. if (field.getName().equals("dumpMethodCode")) {
  33. dumpMethodCode_method = field;
  34. dumpMethodCode_method.setAccessible(true);
  35. }
  36. }
  37. Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
  38. for (int j = 0; j < ElementsArray.length; j++) {
  39. Object element = ElementsArray[j];
  40. Object dexfile = null;
  41. try {
  42. dexfile = (Object) dexFile_fileField.get(element);
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. if (dexfile == null) {
  47. continue;
  48. }
  49. if (dexfile != null) {
  50. dexFilesArray.add(dexfile);
  51. Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
  52. if (mcookie == null) {
  53. continue;
  54. }
  55. String[] classnames = null;
  56. try {
  57. classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. continue;
  61. } catch (Error e) {
  62. e.printStackTrace();
  63. continue;
  64. }
  65. if (classnames != null) {
  66. for (String eachclassname : classnames) {
  67. loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);
  68. }
  69. }
  70. }
  71. }
  72. return;
  73. }

loadClassAndInvoke除了传入上面提到的类名,还传入ClassLoader对象和dumpMethodCode函数的Method对象,看上面的代码可以知道,dumpMethodCode函数来自DexFile,原本的DexFile类没有这个函数,是FART加上去的。dumpMethodCode究竟做了什么我们待会再来看,先把loadClassAndInvoke函数看完。loadClassAndInvoke工作也很简单,根据传入的类名来加载类,再从加载的类获取它的所有的构造函数和函数,然后调用dumpMethodCode,传入Constructor对象或者Method对象

  1. public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {
  2. Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);
  3. Class resultclass = null;
  4. try {
  5. resultclass = appClassloader.loadClass(eachclassname);
  6. } catch (Exception e) {
  7. e.printStackTrace();
  8. return;
  9. } catch (Error e) {
  10. e.printStackTrace();
  11. return;
  12. }
  13. if (resultclass != null) {
  14. try {
  15. Constructor<?> cons[] = resultclass.getDeclaredConstructors();
  16. for (Constructor<?> constructor : cons) {
  17. if (dumpMethodCode_method != null) {
  18. try {
  19. dumpMethodCode_method.invoke(null, constructor);
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. continue;
  23. } catch (Error e) {
  24. e.printStackTrace();
  25. continue;
  26. }
  27. } else {
  28. Log.e("ActivityThread", "dumpMethodCode_method is null ");
  29. }
  30. }
  31. } catch (Exception e) {
  32. e.printStackTrace();
  33. } catch (Error e) {
  34. e.printStackTrace();
  35. }
  36. try {
  37. Method[] methods = resultclass.getDeclaredMethods();
  38. if (methods != null) {
  39. for (Method m : methods) {
  40. if (dumpMethodCode_method != null) {
  41. try {
  42. dumpMethodCode_method.invoke(null, m);
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. continue;
  46. } catch (Error e) {
  47. e.printStackTrace();
  48. continue;
  49. }
  50. } else {
  51. Log.e("ActivityThread", "dumpMethodCode_method is null ");
  52. }
  53. }
  54. }
  55. } catch (Exception e) {
  56. e.printStackTrace();
  57. } catch (Error e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. }

上面提到dumpMethodCode函数在DexFile类中,DexFile的完整路径为:libcore\dalvik\src\main\java\dalvik\system\DexFile.java,它是这么定义的:

  1. private static native void dumpMethodCode(Object m);

可见,它是一个native方法,它的实际代码在:art\runtime\native\dalvik_system_DexFile.cc,代码为:

  1. static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method) {
  2. ScopedFastNativeObjectAccess soa(env);
  3. if(method!=nullptr)
  4. {
  5. ArtMethod* artmethod = ArtMethod::FromReflectedMethod(soa, method);
  6. myfartInvoke(artmethod);
  7. }
  8. return;
  9. }

DexFile_dumpMethodCode函数中,method是loadClassAndInvoke函数传过来的java.lang.reflect.Method对象,传进来的Java层Method对象传入FromReflectedMethod函数得到ArtMethod结构指针,再将ArtMethod结构指针传入myfartInvoke函数。

myfartInvoke实际代码在art/runtime/art_method.cc文件里

  1. extern "C" void myfartInvoke(ArtMethod * artmethod)
  2. SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  3. JValue *result = nullptr;
  4. Thread *self = nullptr;
  5. uint32_t temp = 6;
  6. uint32_t *args = &temp;
  7. uint32_t args_size = 6;
  8. artmethod->Invoke(self, args, args_size, result, "fart");
  9. }

在myfartInvoke函数中,值得关注的是self被设置为空指针,并传入ArtMethod的Invoke函数。

Invoke函数也是在art/runtime/art_method.cc文件里,在Invoke函数开头,它对self参数做了个判断,如果self为空,说明Invoke函数是被FART所调用的,反之则是系统本身的调用。self为空的时候,调用dumpArtMethod函数,并立即返回

  1. void ArtMethod::Invoke(Thread * self, uint32_t * args,
  2. uint32_t args_size, JValue * result,
  3. const char *shorty) {
  4. if (self == nullptr) {
  5. dumpArtMethod(this);
  6. return;
  7. }
  8. ......
  9. }

dumpArtMethod函数这里就到了dump dex的代码了。

  1. extern "C" void dumpArtMethod(ArtMethod * artmethod)
  2. SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  3. char *dexfilepath = (char *) malloc(sizeof(char) * 2000);
  4. if (dexfilepath == nullptr) {
  5. LOG(INFO) <<
  6. "ArtMethod::dumpArtMethodinvoked,methodname:"
  7. << PrettyMethod(artmethod).
  8. c_str() << "malloc 2000 byte failed";
  9. return;
  10. }
  11. int fcmdline = -1;
  12. char szCmdline[64] = { 0 };
  13. char szProcName[256] = { 0 };
  14. int procid = getpid();
  15. sprintf(szCmdline, "/proc/%d/cmdline", procid);
  16. fcmdline = open(szCmdline, O_RDONLY, 0644);
  17. if (fcmdline > 0) {
  18. read(fcmdline, szProcName, 256);
  19. close(fcmdline);
  20. }
  21. if (szProcName[0]) {
  22. const DexFile *dex_file = artmethod->GetDexFile();
  23. const char *methodname =
  24. PrettyMethod(artmethod).c_str();
  25. const uint8_t *begin_ = dex_file->Begin();
  26. size_t size_ = dex_file->Size();
  27. memset(dexfilepath, 0, 2000);
  28. int size_int_ = (int) size_;
  29. memset(dexfilepath, 0, 2000);
  30. sprintf(dexfilepath, "%s", "/sdcard/fart");
  31. mkdir(dexfilepath, 0777);
  32. memset(dexfilepath, 0, 2000);
  33. sprintf(dexfilepath, "/sdcard/fart/%s",
  34. szProcName);
  35. mkdir(dexfilepath, 0777);
  36. memset(dexfilepath, 0, 2000);
  37. sprintf(dexfilepath,
  38. "/sdcard/fart/%s/%d_dexfile.dex",
  39. szProcName, size_int_);
  40. int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
  41. if (dexfilefp > 0) {
  42. close(dexfilefp);
  43. dexfilefp = 0;
  44. } else {
  45. dexfilefp =
  46. open(dexfilepath, O_CREAT | O_RDWR,
  47. 0666);
  48. if (dexfilefp > 0) {
  49. write(dexfilefp, (void *) begin_,
  50. size_);
  51. fsync(dexfilefp);
  52. close(dexfilefp);
  53. }
  54. }
  55. //下半部分开始
  56. const DexFile::CodeItem * code_item =
  57. artmethod->GetCodeItem(); // (1)
  58. if (LIKELY(code_item != nullptr)) {
  59. int code_item_len = 0;
  60. uint8_t *item = (uint8_t *) code_item;
  61. if (code_item->tries_size_ > 0) { // (2)
  62. const uint8_t *handler_data = (const uint8_t *) (DexFile::GetTryItems(*code_item,code_item->tries_size_));
  63. uint8_t *tail = codeitem_end(&handler_data);
  64. code_item_len = (int)(tail - item);
  65. } else {
  66. code_item_len =
  67. 16 +
  68. code_item->
  69. insns_size_in_code_units_ * 2;
  70. }
  71. memset(dexfilepath, 0, 2000);
  72. int size_int = (int) dex_file->Size(); // Length of data
  73. uint32_t method_idx =
  74. artmethod->get_method_idx();
  75. sprintf(dexfilepath,
  76. "/sdcard/fart/%s/%d_%ld.bin",
  77. szProcName, size_int, gettidv1());
  78. int fp2 =
  79. open(dexfilepath,
  80. O_CREAT | O_APPEND | O_RDWR,
  81. 0666);
  82. if (fp2 > 0) {
  83. lseek(fp2, 0, SEEK_END);
  84. memset(dexfilepath, 0, 2000);
  85. int offset = (int) (item - begin_);
  86. sprintf(dexfilepath,
  87. "{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",
  88. methodname, method_idx,
  89. offset, code_item_len);
  90. int contentlength = 0;
  91. while (dexfilepath[contentlength]
  92. != 0)
  93. contentlength++;
  94. write(fp2, (void *) dexfilepath,
  95. contentlength);
  96. long outlen = 0;
  97. char *base64result =
  98. base64_encode((char *) item,
  99. (long)
  100. code_item_len,
  101. &outlen);
  102. write(fp2, base64result, outlen);
  103. write(fp2, "};", 2);
  104. fsync(fp2);
  105. close(fp2);
  106. if (base64result != nullptr) {
  107. free(base64result);
  108. base64result = nullptr;
  109. }
  110. }
  111. }
  112. }
  113. if (dexfilepath != nullptr) {
  114. free(dexfilepath);
  115. dexfilepath = nullptr;
  116. }
  117. }

dumpArtMethod函数开始先通过/proc/<pid>/cmdline虚拟文件读取进程pid对应的进程名,根据得到的进程名在sdcard下创建目录,所以在脱壳之前要给APP写入外部存储的权限。之后通过ArtMethod的GetDexFile函数得到DexFile指针,即ArtMethod所在的dex的指针,再从DexFile的Begin函数和Size函数得到dex文件在内存中起始的地址和dex文件的大小,接着用write函数把内存中的dex写到文件名以_dexfile.dex的文件中。

但该函数还没完,dumpArtMethod函数的下半部分,对函数的CodeItem进行dump。可能有些人就有疑问了,函数的上半部分不是把dex给dump了吗,为什么还需要取函数的CodeItem进行dump呢?对于某些壳,dumpArtMethod的上半部分已经能对dex进行整体dump,但是对于部分抽取壳,dex即使被dump下来,函数体还是以nop填充,即空函数体,FART还把函数的CodeItem给dump下来是让用户手动来修复这些dump下来的空函数。

我们来看dumpArtMethod函数的下半部分,这里将会涉及dex文件的结构,如果不了解请结合文档来看。注释(1)处,从ArtMethod中得到一个CodeItem。注释(2)处,根据CodeItem的tries_size_,即try_item的数量来计算CodeItem的大小:

(1)如果tries_size_不为0,说明这个CodeItem有try_item,那么去把CodeItem的结尾地址给算出来

  1. const uint8_t *handler_data = (const uint8_t *) (DexFile::GetTryItems(*code_item,code_item->tries_size_));
  2. uint8_t *tail = codeitem_end(&handler_data);
  3. code_item_len = (int)(tail - item);

codeitem_end函数怎么算出CodeItem的结束地址呢?

GetTryItems第二参数传入tries_size_,即跳过所有的try_item,得到encoded_catch_handler_list的地址,然后传入codeitem_end函数

  1. uint8_t *codeitem_end(const uint8_t ** pData) {
  2. uint32_t num_of_list = DecodeUnsignedLeb128(pData);
  3. for (; num_of_list > 0; num_of_list--) {
  4. int32_t num_of_handlers =
  5. DecodeSignedLeb128(pData);
  6. int num = num_of_handlers;
  7. if (num_of_handlers <= 0) {
  8. num = -num_of_handlers;
  9. }
  10. for (; num > 0; num--) {
  11. DecodeUnsignedLeb128(pData);
  12. DecodeUnsignedLeb128(pData);
  13. }
  14. if (num_of_handlers <= 0) {
  15. DecodeUnsignedLeb128(pData);
  16. }
  17. }
  18. return (uint8_t *) (*pData);
  19. }

codeitem_end函数的开头读取encoded_catch_handler_list结构中包含多少个encoded_catch_handler结构,如果不为0,遍历所有encoded_catch_handler结构,读取encoded_catch_handler结构中有多少encoded_type_addr_pair结构,有的话全部跳过,即跳过了整个encoded_catch_handler_list结构。最后函数返回的pData即为CodeItem的结尾地址。

得到了CodeItem结尾地址,用CodeItem结尾的地址减去CodeItem的起始地址得到CodeItem的真实大小。

(2)如果tries_size_为0,那么就没有try_item,直接就能把CodeItem的大小计算出来:

  1. code_item_len = 16 + code_item->insns_size_in_code_units_ * 2;

CodeItem的大小计算出来之后,接下来可以看到,有几个变量以格式化的方式打印到dexfilepath

  1. sprintf(dexfilepath,
  2. "{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",
  3. methodname,
  4. method_idx,
  5. offset,
  6. code_item_len
  7. );
  • name 函数的名称
  • method_idx 来源FART新增的函数:uint32_t get_method_idx(){ return dex_method_index_; },函数返回dex_method_index_,dex_method_index_是函数在method_ids中的索引
  • offset 是该函数的CodeItem相对于dex文件开始的偏移
  • code_item_len CodeItem的长度

数据组装好之后,写入到以.bin为后缀的文件中:

  1. write(fp2, (void *) dexfilepath,
  2. contentlength);
  3. long outlen = 0;
  4. char *base64result =
  5. base64_encode((char *) item,
  6. (long)
  7. code_item_len,
  8. &outlen);
  9. write(fp2, base64result, outlen);
  10. write(fp2, "};", 2);

对于上面的dexfilepath,它们是明文字符,直接写入即可。而对于CodeItem中的bytecode这种非明文字符,直接写入不太好看,所以FART选择对它们进行base64编码后再写入。

分析到这里好像已经结束了,从主动调用,到dex整体dump,再到函数CodeItem的dump,都已经分析了。但是FART中确实还有一部分逻辑是没有分析的。如果你使用过FART来脱过壳,会发现它dump下来的dex中还有以_execute.dex结尾的dex文件。这种dex是怎么生成的呢?

这一部分的代码也是在art\runtime\art_method.cc文件中

  1. extern "C" void dumpDexFileByExecute(ArtMethod * artmethod)
  2. SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  3. char *dexfilepath = (char *) malloc(sizeof(char) * 2000);
  4. if (dexfilepath == nullptr) {
  5. LOG(INFO) <<
  6. "ArtMethod::dumpDexFileByExecute,methodname:"
  7. << PrettyMethod(artmethod).
  8. c_str() << "malloc 2000 byte failed";
  9. return;
  10. }
  11. int fcmdline = -1;
  12. char szCmdline[64] = { 0 };
  13. char szProcName[256] = { 0 };
  14. int procid = getpid();
  15. sprintf(szCmdline, "/proc/%d/cmdline", procid);
  16. fcmdline = open(szCmdline, O_RDONLY, 0644);
  17. if (fcmdline > 0) {
  18. read(fcmdline, szProcName, 256);
  19. close(fcmdline);
  20. }
  21. if (szProcName[0]) {
  22. const DexFile *dex_file = artmethod->GetDexFile();
  23. const uint8_t *begin_ = dex_file->Begin(); // Start of data.
  24. size_t size_ = dex_file->Size(); // Length of data.
  25. memset(dexfilepath, 0, 2000);
  26. int size_int_ = (int) size_;
  27. memset(dexfilepath, 0, 2000);
  28. sprintf(dexfilepath, "%s", "/sdcard/fart");
  29. mkdir(dexfilepath, 0777);
  30. memset(dexfilepath, 0, 2000);
  31. sprintf(dexfilepath, "/sdcard/fart/%s",
  32. szProcName);
  33. mkdir(dexfilepath, 0777);
  34. memset(dexfilepath, 0, 2000);
  35. sprintf(dexfilepath,
  36. "/sdcard/fart/%s/%d_dexfile_execute.dex",
  37. szProcName, size_int_);
  38. int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
  39. if (dexfilefp > 0) {
  40. close(dexfilefp);
  41. dexfilefp = 0;
  42. } else {
  43. dexfilefp =
  44. open(dexfilepath, O_CREAT | O_RDWR,
  45. 0666);
  46. if (dexfilefp > 0) {
  47. write(dexfilefp, (void *) begin_,
  48. size_);
  49. fsync(dexfilefp);
  50. close(dexfilefp);
  51. }
  52. }
  53. }
  54. if (dexfilepath != nullptr) {
  55. free(dexfilepath);
  56. dexfilepath = nullptr;
  57. }
  58. }

可以看到,dumpDexFileByExecute函数有点像dumpArtMethod函数的上半部分,即对dex文件的整体dump。那么,dumpDexFileByExecute在哪里被调用呢?

通过搜索,在art\runtime\interpreter\interpreter.cc文件的开始,看到了FART在art命名空间下定义了一个dumpDexFileByExecute函数

  1. namespace art {
  2. extern "C" void dumpDexFileByExecute(ArtMethod* artmethod);
  3. namespace interpreter {
  4. ......
  5. }
  6. }

同时在文件其中找到了对dumpDexFileByExecute函数的调用:

  1. static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,
  2. ShadowFrame& shadow_frame, JValue result_register) {
  3. if(strstr(PrettyMethod(shadow_frame.GetMethod()).c_str(),"<clinit>")!=nullptr)
  4. {
  5. dumpDexFileByExecute(shadow_frame.GetMethod());
  6. }
  7. ......
  8. }

在Execute函数中,通过判断函数名称中是否为<clinit>决定要不要调用dumpDexFileByExecute,即判断传入的是否为静态代码块,对于加了壳的App来说静态代码块是肯定存在的。如果Execute传入的是静态代码块则调用dumpDexFileByExecute函数,并传入一个ArtMethod指针。

dumpDexFileByExecute中对dex进行了整体dump,可以把它看作是dumpArtMethod方式的互补,有时dumpArtMethod中得不到想得到的dex,用dumpDexFileByExecute或许能得到惊喜。

0x3 结语

非常感谢FART作者能够开源FART,这使得人们对抗ART环境下App壳得到了良好的思路。FART脱壳机理论上来讲能脱大多数壳,但是仍有例外,需要自行摸索。

0x4 参考

Android FART脱壳机流程分析的更多相关文章

  1. Gradle之Android Gradle Plugin 主要流程分析(二)

    [Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要流程分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin ...

  2. Android 4.4KitKat AudioRecord 流程分析

    Android是架构分为三层: 底层      Linux Kernel 中间层  主要由C++实现 (Android 60%源码都是C++实现) 应用层  主要由JAVA开发的应用程序 应用程序执行 ...

  3. Cocos2d-x3.3RC0的Android编译Activity启动流程分析

    本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2 ...

  4. Android恢复出厂设置流程分析【Android源码解析十】

    最近看恢复出厂的一个问题,以前也查过这方面的流程,所以这里整理一些AP+framework层的流程: 在setting-->备份与重置--->恢复出厂设置--->重置手机---> ...

  5. Openstack之Nova创建虚机流程分析

    前言        Openstack作为一个虚拟机管理平台,核心功能自然是虚拟机的生命周期的管理,而负责虚机管理的模块就是Nova. 本文就是openstack中Nova模块的分析,所以本文重点是以 ...

  6. Android 9.0 关机流程分析

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...

  7. 【转载】Android 中 View 绘制流程分析

    创建Window 在Activity的attach方法中通过调用PolicyManager.makeNewWindo创建Window,将一个View add到WindowManager时,Window ...

  8. Android View 绘制刷新流程分析

    Android中对View的更新有很多种方式,使用时要区分不同的应用场合.1.不使用多线程和双缓冲      这种情况最简单,一般只是希望在View发生改变时对UI进行重绘.你只需显式地调用View对 ...

  9. Android 4.4KitKat AudioTrack 流程分析

    Android Audio 系统的主要内容: AudioManager:这个主要是用来管理Audio系统的,需要考虑整个系统上声音的策略问题,例如来电话铃声,短信铃声等,主要是策略上的问题. Audi ...

随机推荐

  1. C语言环境总结

    1.虚拟内存下C语言环境 2.main函数调用 3.栈调用 每次函数调用,在栈中分配一个栈帧,寄存器通过持有该栈帧的基地址,并上下偏移,访问形参和本地变量, C语言形参为一个字大小,所以通常传指针,这 ...

  2. rgw的rgw_thread_pool_size配置调整

    前言 在比对rgw的不同前端的区别的时候,官方说civetweb是通过线程池来控制连接的,beast是后面加入了流控相关的,这块一直也没有调整过相关的参数,然后通过ab压测了一下,还是有很明显的区别的 ...

  3. HotSpot类模型之InstanceKlass

    上一篇 HotSpot源码分析之类模型 介绍了类模型的基础类Klass的重要属性及方法,这一篇介绍一下InstanceKlass及InstanceKlass的子类. 1.InstanceKlass类 ...

  4. 基础网络路由命令(tracert、route print 、netstat )

    网络知识有限,平时自己积累,捣鼓自己电脑使用,如是一样菜鸟,请勿自行在服务器端使用. 快捷键Ctrl+C  结束跟踪 快捷键    ↑      可以查询上次输入的命令 window+R组合键,输入C ...

  5. webug第一关:很简单的一个注入

    第一关:很简单的一个注入 上单引号报错 存在注入,用order  by猜列的个数 union select 出现显示位 查数据库版本,用户和当前数据库名 查表名和列名 最后,激动人心的拿flag

  6. OxyPlot组件的基本使用

    在制作上位机的时候,很多时候需要使用到监控绘图界面,使用来绘制曲线的组件有很多,GDI+.char.OxyPlot等等,这篇文章用来介绍OxyPlot组件的基本应用,在本文中主要是利用随心数生成函数结 ...

  7. 教你调节Boom 3D的3D音效强度,让音乐更带感

    Boom 3D的专业3D环绕技术,让用户能全身心地沉浸在立体音效中.无论是聆听音乐,还是观赏电影,立体音效都能为人们带来更加真实的听觉感触. 那么,Boom 3D的3D环绕功能到底能给用户带来怎样的体 ...

  8. ThreadLocal以及强软弱虚引用

    1.ThreadLocal ThreadLocal即线程本地,可以实现每个线程存入取出TreadLocal值互不影响.因为TheadLocal底层是用了一个Map结构存放数据,而这个Map是从当前这个 ...

  9. leetcode 108 和leetcode 109 II

    //感想:没啥上篇写完了 //思路:对于这道题109来说,就是数组变成了链表,其他没有变,我觉得非常不解,因为我想到的依旧是找中点,用快慢指针来找, 找到以后将链表分成两半,继续递归的去找,我就觉得这 ...

  10. JSX中写 switch case 进行判断

    场景:根据后端返回的数据进行多条件渲染,三元表达式已不能满足条件. 代码: <span> {(() => { switch (record.generalRuleInfos[0]?. ...