首页 文章 精选 留言 我的

精选列表

搜索[源代码漏洞],共10000篇文章
优秀的个人博客,低调大师

Android应用程序进程启动过程的源代码分析(2)

Step 5.ZygoteInit.runSelectLoopMode 这个函数定义在frameworks/base/core/java/com/android/internal/os/ZygoteInit.java文件中: publicclassZygoteInit{ ...... /** *Runsthezygoteprocess'sselectloop.Acceptsnewconnectionsas *theyhappen,andreadscommandsfromconnectionsonespawn-request's *worthatatime. * *@throwsMethodAndArgsCallerinachildprocesswhenamain()should *beexecuted. */ privatestaticvoidrunSelectLoopMode()throwsMethodAndArgsCaller{ ArrayList<FileDescriptor>fds=newArrayList(); ArrayList<ZygoteConnection>peers=newArrayList(); FileDescriptor[]fdArray=newFileDescriptor[4]; fds.add(sServerSocket.getFileDescriptor()); peers.add(null); intloopCount=GC_LOOP_COUNT; while(true){ intindex; /* *Callgc()beforeweblockinselect(). *It'sworkthathastobedoneanyway,andit'sbetter *toavoidmakingeverychilddoit.Itwillalso *madvise()anyfreememoryasaside-effect. * *Don'tcalliteverytime,becausewalkingtheentire *heapisalotofoverheadtofreeafewhundredbytes. */ if(loopCount<=0){ gc(); loopCount=GC_LOOP_COUNT; }else{ loopCount--; } try{ fdArray=fds.toArray(fdArray); index=selectReadable(fdArray); }catch(IOExceptionex){ thrownewRuntimeException("Errorinselect()",ex); } if(index<0){ thrownewRuntimeException("Errorinselect()"); }elseif(index==0){ ZygoteConnectionnewPeer=acceptCommandPeer(); peers.add(newPeer); fds.add(newPeer.getFileDesciptor()); }else{ booleandone; done=peers.get(index).runOnce(); if(done){ peers.remove(index); fds.remove(index); } } } } ...... } 当Step 4将数据通过Socket接口发送出去后,就会下面这个语句: done=peers.get(index).runOnce(); 这里从peers.get(index)得到的是一个ZygoteConnection对象,表示一个Socket连接,因此,接下来就是调用ZygoteConnection.runOnce函数进一步处理了。 Step 6.ZygoteConnection.runOnce 这个函数定义在frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java文件中: classZygoteConnection{ ...... booleanrunOnce()throwsZygoteInit.MethodAndArgsCaller{ Stringargs[]; ArgumentsparsedArgs=null; FileDescriptor[]descriptors; try{ args=readArgumentList(); descriptors=mSocket.getAncillaryFileDescriptors(); }catch(IOExceptionex){ ...... returntrue; } ...... /**thestderrofthemostrecentrequest,ifavail*/ PrintStreamnewStderr=null; if(descriptors!=null&&descriptors.length>=3){ newStderr=newPrintStream( newFileOutputStream(descriptors[2])); } intpid; try{ parsedArgs=newArguments(args); applyUidSecurityPolicy(parsedArgs,peer); applyDebuggerSecurityPolicy(parsedArgs); applyRlimitSecurityPolicy(parsedArgs,peer); applyCapabilitiesSecurityPolicy(parsedArgs,peer); int[][]rlimits=null; if(parsedArgs.rlimits!=null){ rlimits=parsedArgs.rlimits.toArray(intArray2d); } pid=Zygote.forkAndSpecialize(parsedArgs.uid,parsedArgs.gid, parsedArgs.gids,parsedArgs.debugFlags,rlimits); }catch(IllegalArgumentExceptionex){ ...... }catch(ZygoteSecurityExceptionex){ ...... } if(pid==0){ //inchild handleChildProc(parsedArgs,descriptors,newStderr); //shouldneverhappen returntrue; }else{/*pid!=0*/ //inparent...pidof<0meansfailure returnhandleParentProc(pid,descriptors,parsedArgs); } } ...... } 真正创建进程的地方就是在这里了: pid=Zygote.forkAndSpecialize(parsedArgs.uid,parsedArgs.gid, parsedArgs.gids,parsedArgs.debugFlags,rlimits); 有Linux开发经验的读者很容易看懂这个函数调用,这个函数会创建一个进程,而且有两个返回值,一个是在当前进程中返回的,一个是在新创建的进程中返回,即在当前进程的子进程中返回,在当前进程中的返回值就是新创建的子进程的pid值,而在子进程中的返回值是0。因为我们只关心创建的新进程的情况,因此,我们沿着子进程的执行路径继续看下去: if(pid==0){ //inchild handleChildProc(parsedArgs,descriptors,newStderr); //shouldneverhappen returntrue; }else{/*pid!=0*/ ...... } 这里就是调用handleChildProc函数了。 Step 7.ZygoteConnection.handleChildProc 这个函数定义在frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java文件中: classZygoteConnection{ ...... privatevoidhandleChildProc(ArgumentsparsedArgs, FileDescriptor[]descriptors,PrintStreamnewStderr) throwsZygoteInit.MethodAndArgsCaller{ ...... if(parsedArgs.runtimeInit){ RuntimeInit.zygoteInit(parsedArgs.remainingArgs); }else{ ...... } } ...... } 由于在前面的Step 3中,指定了"--runtime-init"参数,表示要为新创建的进程初始化运行时库,因此,这里的parseArgs.runtimeInit值为true,于是就继续执行RuntimeInit.zygoteInit进一步处理了。 Step 8.RuntimeInit.zygoteInit 这个函数定义在frameworks/base/core/java/com/android/internal/os/RuntimeInit.java文件中: publicclassRuntimeInit{ ...... publicstaticfinalvoidzygoteInit(String[]argv) throwsZygoteInit.MethodAndArgsCaller{ //TODO:Doingthishereworks,butitseemskindofarbitrary.Find //abetterplace.Thegoalistosetitupforapplications,butnot //toolslikeam. System.setOut(newAndroidPrintStream(Log.INFO,"System.out")); System.setErr(newAndroidPrintStream(Log.WARN,"System.err")); commonInit(); zygoteInitNative(); intcurArg=0; for(/*curArg*/;curArg<argv.length;curArg++){ Stringarg=argv[curArg]; if(arg.equals("--")){ curArg++; break; }elseif(!arg.startsWith("--")){ break; }elseif(arg.startsWith("--nice-name=")){ StringniceName=arg.substring(arg.indexOf('=')+1); Process.setArgV0(niceName); } } if(curArg==argv.length){ Slog.e(TAG,"MissingclassnameargumenttoRuntimeInit!"); //lettheprocessexit return; } //Remainingargumentsarepassedtothestartclass'sstaticmain StringstartClass=argv[curArg++]; String[]startArgs=newString[argv.length-curArg]; System.arraycopy(argv,curArg,startArgs,0,startArgs.length); invokeStaticMain(startClass,startArgs); } ...... } 这里有两个关键的函数调用,一个是zygoteInitNative函数调用,一个是invokeStaticMain函数调用,前者就是执行Binder驱动程序初始化的相关工作了,正是由于执行了这个工作,才使得进程中的Binder对象能够顺利地进行Binder进程间通信,而后一个函数调用,就是执行进程的入口函数,这里就是执行startClass类的main函数了,而这个startClass即是我们在Step 1中传进来的"android.app.ActivityThread"值,表示要执行android.app.ActivityThread类的main函数。 我们先来看一下zygoteInitNative函数的调用过程,然后再回到RuntimeInit.zygoteInit函数中来,看看它是如何调用android.app.ActivityThread类的main函数的。 step 9.RuntimeInit.zygoteInitNative 这个函数定义在frameworks/base/core/java/com/android/internal/os/RuntimeInit.java文件中: [java] view plain copy publicclassRuntimeInit{ ...... publicstaticfinalnativevoidzygoteInitNative(); ...... } 这里可以看出,函数zygoteInitNative是一个Native函数,实现在frameworks/base/core/jni/AndroidRuntime.cpp文件中: [cpp] view plain copy staticvoidcom_android_internal_os_RuntimeInit_zygoteInit(JNIEnv*env,jobjectclazz) { gCurRuntime->onZygoteInit(); } 这里它调用了全局变量gCurRuntime的onZygoteInit函数,这个全局变量的定义在frameworks/base/core/jni/AndroidRuntime.cpp文件开头的地方: [cpp] view plain copy staticAndroidRuntime*gCurRuntime=NULL; 这里可以看出,它的类型为AndroidRuntime,它是在AndroidRuntime类的构造函数中初始化的,AndroidRuntime类的构造函数也是定义在frameworks/base/core/jni/AndroidRuntime.cpp文件中: [cpp] view plain copy AndroidRuntime::AndroidRuntime() { ...... assert(gCurRuntime==NULL);//oneperprocess gCurRuntime=this; } 那么这个AndroidRuntime类的构造函数又是什么时候被调用的呢?AndroidRuntime类的声明在frameworks/base/include/android_runtime/AndroidRuntime.h文件中,如果我们打开这个文件会看到,它是一个虚拟类,也就是我们不能直接创建一个AndroidRuntime对象,只能用一个AndroidRuntime类的指针来指向它的某一个子类,这个子类就是AppRuntime了,它定义在frameworks/base/cmds/app_process/app_main.cpp文件中: [cpp] view plain copy intmain(intargc,constchar*constargv[]) { ...... AppRuntimeruntime; ...... } 而AppRuntime类继续了AndroidRuntime类,它也是定义在frameworks/base/cmds/app_process/app_main.cpp文件中: [cpp] view plain copy classAppRuntime:publicAndroidRuntime { ...... }; 因此,在前面的com_android_internal_os_RuntimeInit_zygoteInit函数,实际是执行了AppRuntime类的onZygoteInit函数。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android5.0 Recovery源代码分析与定制---recovery UI相关(二)

http://blog.csdn.net/morixinguan/article/details/72858346引用我的代码片 在上一篇文章中,我们大致的介绍了recovery的启动流程,那么,recovery升级或者做双清的时候,那些图形动画又是如何实现的呢?我们来看看代码: 以下这段代码位于recovery/screen_ui.cpp void ScreenRecoveryUI::Init() { gr_init(); gr_font_size(&char_width, &char_height); text_col = text_row = 0; text_rows = gr_fb_height() / char_height; if (text_rows > kMaxRows) text_rows = kMaxRows; text_top = 1; text_cols = gr_fb_width() / char_width; if (text_cols > kMaxCols - 1) text_cols = kMaxCols - 1; backgroundIcon[NONE] = NULL; LoadBitmapArray("icon_installing", &installing_frames, &installation); backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : NULL; backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; LoadBitmap("icon_error", &backgroundIcon[ERROR]); backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); LoadBitmap("stage_empty", &stageMarkerEmpty); LoadBitmap("stage_fill", &stageMarkerFill); LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); pthread_create(&progress_t, NULL, progress_thread, NULL); RecoveryUI::Init(); } 这段代码都做了哪些事情呢?这些recovery初始化图形显示最开始的部分, (1)调用了miniui中的gr_init初始化显示图形相关的步骤,因为recovery是基于framebuffer机制显示的。 (2)调用gr_font_size设置字体显示的大小,然后计算文本显示行列。 (3)接下来就是装载图片了,会调用到LoadBitmapArray和LoadBitmap这两个函数。其中,我们会看到这些函数里图片的名称: "icon_installing"、"icon_error"、"progress_empty"、"progress_fill"、 "stage_empty"、"stage_fill"、"installing_text"、"erasing_text"、 "no_command_text"、"error_text" 将上面的字符串与下面的图片一一对应: 那么这些分别是怎么显示的?其中erasing_text是用来显示做清除的时候显示的文字,放大后如下: 这上面有许许多多的语言版本,我们可以根据需要来选择,这些主要要看接下来初始化文字的代码逻辑。 其余的图片中,后缀带text的,也和这些是类似的,有出现错误显示的字体error_text,更新系统显示的字体installing_text,没有命令的时候显示的字体no_command_text。 除了文字显示,我们最关心的就是icon_installing这张图片了,在做系统更新的时候,这个机器人会转动。这不是动画吗?怎么只有一张图片呢?我们找到Android官方网站看看是为什么? 原因如下: Recovery UI imagesAndroid 5.xThe recovery user interface consists images. Ideally, users never interact with the UI: During a normal update,the phone boots into recovery, fills the installation progress bar, and boots back into the new system withoutinput from the user. In the event of a system update problem, the only user action that can be taken is tocall customer care.An image-only interface obviates the need for localization.However, as of Android 5.x the update can display a string of text (e.g. "Installing system update...")along with the image. For details, see Localized recovery text. efault images are available in different densities and are located inbootable/recovery/res$DENSITY/images (e.g., bootable/recovery/res-hdpi/images). To use a static image during installation, you need only provide the icon_installing.png image and set the number of frames in the animation to 0 (the error icon is not animated; it is always a static image). // 以上文档意思就是说,Android5.x以上的版本,机器人的动画是PNG图片和帧动画组成的, //我们可以使用recovery目录下的interlace-frames.py这个python脚本来进行合成, //具体的合成方法需要使用recovery代码中的一个python合成工具。 //我就可以把android原生态的动画给换了,因为这个机器人实在是丑。 源码如下: # Copyright (C) 2014 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Script to take a set of frames (PNG files) for a recovery animation and turn it into a single output image which contains the input frames interlaced by row. Run with the names of all the input frames on the command line, in order, followed by the name of the output file.""" import sys try: import Image import PngImagePlugin except ImportError: print "This script requires the Python Imaging Library to be installed." sys.exit(1) frames = [Image.open(fn).convert("RGB") for fn in sys.argv[1:-1]] assert len(frames) > 0, "Must have at least one input frame." sizes = set() for fr in frames: sizes.add(fr.size) assert len(sizes) == 1, "All input images must have the same size." w, h = sizes.pop() N = len(frames) out = Image.new("RGB", (w, h*N)) for j in range(h): for i in range(w): for fn, f in enumerate(frames): out.putpixel((i, j*N+fn), f.getpixel((i, j))) # When loading this image, the graphics library expects to find a text # chunk that specifies how many frames this animation represents. If # you post-process the output of this script with some kind of # optimizer tool (eg pngcrush or zopflipng) make sure that your # optimizer preserves this text chunk. meta = PngImagePlugin.PngInfo() meta.add_text("Frames", str(N)) out.save(sys.argv[-1], pnginfo=meta) 这也就是为什么,调用这张图片需要用到LoadBitmapArray这个函数的原因。 void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) { int result = res_create_multi_display_surface(filename, frames, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } }调完这个函数后会调用resources.cpp中的res_create_multi_display_surface函数用于显示,源码如下: int res_create_multi_display_surface(const char* name, int* frames, GRSurface*** pSurface) { GRSurface** surface = NULL; int result = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_uint_32 width, height; png_byte channels; int i; png_textp text; int num_text; unsigned char* p_row; unsigned int y; *pSurface = NULL; *frames = -1; result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); if (result < 0) return result; *frames = 1; if (png_get_text(png_ptr, info_ptr, &text, &num_text)) { for (i = 0; i < num_text; ++i) { if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) { *frames = atoi(text[i].text); break; } } printf(" found frames = %d\n", *frames); } if (height % *frames != 0) { printf("bad height (%d) for frame count (%d)\n", height, *frames); result = -9; goto exit; } surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*))); if (surface == NULL) { result = -8; goto exit; } for (i = 0; i < *frames; ++i) { surface[i] = init_display_surface(width, height / *frames); if (surface[i] == NULL) { result = -8; goto exit; } } #if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) png_set_bgr(png_ptr); #endif p_row = reinterpret_cast<unsigned char*>(malloc(width * 4)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, p_row, NULL); int frame = y % *frames; unsigned char* out_row = surface[frame]->data + (y / *frames) * surface[frame]->row_bytes; transform_rgb_to_draw(p_row, out_row, channels, width); } free(p_row); *pSurface = reinterpret_cast<GRSurface**>(surface); exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (result < 0) { if (surface) { for (i = 0; i < *frames; ++i) { if (surface[i]) free(surface[i]); } free(surface); } } return result; } 其余的和text无关图片,会用到LoadBitmap这个函数: void ScreenRecoveryUI::LoadBitmapArray(const char* filename, int* frames, gr_surface** surface) { int result = res_create_multi_display_surface(filename, frames, surface); if (result < 0) { LOGE("missing bitmap %s\n(Code %d)\n", filename, result); } }同样调用到以下函数: int res_create_multi_display_surface(const char* name, int* frames, GRSurface*** pSurface) { GRSurface** surface = NULL; int result = 0; png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_uint_32 width, height; png_byte channels; int i; png_textp text; int num_text; unsigned char* p_row; unsigned int y; *pSurface = NULL; *frames = -1; result = open_png(name, &png_ptr, &info_ptr, &width, &height, &channels); if (result < 0) return result; *frames = 1; if (png_get_text(png_ptr, info_ptr, &text, &num_text)) { for (i = 0; i < num_text; ++i) { if (text[i].key && strcmp(text[i].key, "Frames") == 0 && text[i].text) { *frames = atoi(text[i].text); break; } } printf(" found frames = %d\n", *frames); } if (height % *frames != 0) { printf("bad height (%d) for frame count (%d)\n", height, *frames); result = -9; goto exit; } surface = reinterpret_cast<GRSurface**>(malloc(*frames * sizeof(GRSurface*))); if (surface == NULL) { result = -8; goto exit; } for (i = 0; i < *frames; ++i) { surface[i] = init_display_surface(width, height / *frames); if (surface[i] == NULL) { result = -8; goto exit; } } #if defined(RECOVERY_ABGR) || defined(RECOVERY_BGRA) png_set_bgr(png_ptr); #endif p_row = reinterpret_cast<unsigned char*>(malloc(width * 4)); for (y = 0; y < height; ++y) { png_read_row(png_ptr, p_row, NULL); int frame = y % *frames; unsigned char* out_row = surface[frame]->data + (y / *frames) * surface[frame]->row_bytes; transform_rgb_to_draw(p_row, out_row, channels, width); } free(p_row); *pSurface = reinterpret_cast<GRSurface**>(surface); exit: png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (result < 0) { if (surface) { for (i = 0; i < *frames; ++i) { if (surface[i]) free(surface[i]); } free(surface); } } return result; } 关于图片我们大概都知道怎么来显示的了,所以,现在我们可以替换Android原生态中的图片,换成我们自己的图片, 当然,也不是什么图都可以的,在recovery中,所有的png图片必须是RGB且不带且不能带alhpa通道信息。 关于这一点,我们可以看open_png这个函数: static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr, png_uint_32* width, png_uint_32* height, png_byte* channels) { char resPath[256]; unsigned char header[8]; int result = 0; int color_type, bit_depth; size_t bytesRead; snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name); resPath[sizeof(resPath)-1] = '\0'; FILE* fp = fopen(resPath, "rb"); if (fp == NULL) { result = -1; goto exit; } bytesRead = fread(header, 1, sizeof(header), fp); if (bytesRead != sizeof(header)) { result = -2; goto exit; } if (png_sig_cmp(header, 0, sizeof(header))) { result = -3; goto exit; } *png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!*png_ptr) { result = -4; goto exit; } *info_ptr = png_create_info_struct(*png_ptr); if (!*info_ptr) { result = -5; goto exit; } if (setjmp(png_jmpbuf(*png_ptr))) { result = -6; goto exit; } png_init_io(*png_ptr, fp); png_set_sig_bytes(*png_ptr, sizeof(header)); png_read_info(*png_ptr, *info_ptr); png_get_IHDR(*png_ptr, *info_ptr, width, height, &bit_depth, &color_type, NULL, NULL, NULL); *channels = png_get_channels(*png_ptr, *info_ptr); if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) { // 8-bit RGB images: great, nothing to do. } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) { // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. png_set_expand_gray_1_2_4_to_8(*png_ptr); } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) { // paletted images: expand to 8-bit RGB. Note that we DON'T // currently expand the tRNS chunk (if any) to an alpha // channel, because minui doesn't support alpha channels in // general. png_set_palette_to_rgb(*png_ptr); *channels = 3; } else { fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth, *channels, color_type); result = -7; goto exit; } return result; exit: if (result < 0) { png_destroy_read_struct(png_ptr, info_ptr, NULL); } if (fp != NULL) { fclose(fp); } return result; }在代码中,我们可以看到如下: if (bit_depth == 8 && *channels == 3 && color_type == PNG_COLOR_TYPE_RGB) { // 8-bit RGB images: great, nothing to do. } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_GRAY) { // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray. png_set_expand_gray_1_2_4_to_8(*png_ptr); } else if (bit_depth <= 8 && *channels == 1 && color_type == PNG_COLOR_TYPE_PALETTE) { // paletted images: expand to 8-bit RGB. Note that we DON'T // currently expand the tRNS chunk (if any) to an alpha // channel, because minui doesn't support alpha channels in // general. png_set_palette_to_rgb(*png_ptr); *channels = 3; } else { fprintf(stderr, "minui doesn't support PNG depth %d channels %d color_type %d\n", bit_depth, *channels, color_type); result = -7; goto exit; } 以下参考一位网友给出的答案。 这个函数将图片文件的数据读取到内存,我在其中输出了一些调试信息,输出图片的 color_type, channels 等信息。查看LOG发现,android原生的图片 channels == 3,channels 即色彩通道个数,等于 3 的话,意味着只有 R,G,B 三个通道的信息,没有 ALPHA 通道信息!这段代码的逻辑是如果channels 不等于3, 则按channels = 1 来处理,即灰度图。美工给的图片是带 alpha通道信息的,即channels = 4,被当成灰度图像来处理了,怪不得显示的效果是灰度图像。我一直以为 png 图像就只有一种格式,都是带有 alpha通道的。。。使用图像处理工具(photoshop 或者 gimp),将美工给的图片去掉 alpha 通道信息,再替换recovery 的图片,编译,替换recovery.img ,reboot -r 。图片终于正常显示啦。 在下一节里,我们将继续分析UI显示....

优秀的个人博客,低调大师

使用 flex4 开发通用统计系统,发布源代码至github

1,关于flex4 很老的技术,我居然还再研究使用这个东西,自己想想都不敢相信。 技术存在总是有他的理由的。之所以想用flex4 开发一个通用统计系统,还是有几个有点的: a,组件比较全,比较全,基本上省去了自己开发 b,在局域网下一次获取大量json数据并解析展示到flex里面速度比html快,图标效果比较好 c,开发系统一个人就够了,浏览器兼容比较好,只要安装flash就行,尤其要做成产品给xp系统的使用那个IE兼容性痛苦啊 当然也有缺点,技术学习成本不低,属于不流行的没落的技术。 但这个不影响做项目,项目的技术选型要合理,维护成本低,综合考虑,而不是所有的全部用新技术,把自己玩死。 存在就有他的意义,不影响做项目,不影响挣钱。 2,系统设计原理 统计系统,设计的不是解决某一个统计问题,而是做一个统计的展示界面,把统计的sql都做xml配置起来。 系统展示界面展示效果: 其中系统的左侧菜单是menu.xml进行控制的 <?xml version="1.0" encoding="utf-8"?> <!-- 定义左侧菜单: --> <menus> <menu label="用户数据统计"> <menu label="用户注册统计" module="/assets/flex/DataGrid.swf" id="stat-demo"/> <menu label="用户登陆统计" module="/assets/flex/DataGrid.swf" id="stat-user-reg"/> </menu> <menu label="系统设置"> <menu label="菜单管理" module="/assets/flex/DataMenu.swf" /> <menu label="数据管理" module="/assets/flex/DataCRUD.swf" /> </menu> </menus> 通过配置不同的flash文件可以展示不同的配置显示。 通过扩展module可以扩展展示效果。 stat-user-reg.xml配置,显示查询数据 <?xml version="1.0" encoding="UTF-8"?> <stats> <!-- 统计数据xml 按照id进行查找。--> <stat id="stat-demo-1"> <!-- 展示数据 --> <list> <column field="id" text="id"/> <column field="name" text="注册姓名"/> <column field="sex" text="注册性别"/> <column field="city" text="注册城市"/> <column field="phone" text="注册电话"/> </list> <sql><![CDATA[ select id,`name`,sex,city,phone,address,qq,email from stat_demo_01 limit 10 ]]></sql> </stat> </stats> 显示效果如下: 目前已经完成数据显示。但没有数据查询和分页配置,继续开发。 3,服务端代码 服务端,读取xml中的sql语句和配置,拼接成json数据。 /** * User: freewebsys.com */ @Service("statBaseService") public class StatBaseServiceImpl implements StatBaseService { @Autowired private StatBaseDao statBaseDao; //读取xml文件返回Document。 private Document xmlReader(String confXml) { try { SAXReader reader = new SAXReader(); return reader.read(IOUtils.toInputStream(confXml)); } catch (DocumentException e) { e.printStackTrace(); return null; } } //主函数,负责循环处理节点。 public String findStat(String confXml) { Document document = xmlReader(confXml); System.out.println(document); List<DefaultElement> list = document.selectNodes("/stats/stat"); for (DefaultElement element : list) { mergeStatElement(element); } return document.asXML(); } private DefaultElement mergeStatElement(DefaultElement element) { System.out.println(element); //处理查询出所有列表显示的元素。 List<DefaultElement> columnList = element.selectNodes("list/column"); //找到查询sql。 Node sqlNode = element.selectSingleNode("sql"); String sql = sqlNode.getText(); //获取数据后将节点删除。在客户端不显示。 element.remove(sqlNode); System.out.println(sql); List<Map<String, Object>> list = statBaseDao.findStat(sql, null); JSONArray jsonArray = new JSONArray(); for (Map<String, Object> tmpData : list) { JSONObject jsonObject = new JSONObject(); for (DefaultElement column : columnList) { System.out.println(column); String columnName = column.attributeValue("field"); Object objValue = tmpData.get(columnName); jsonObject.put(columnName, objValue); //将一行数据组织成对象,放到数组里面。 } jsonArray.add(jsonObject); } DefaultElement elementData = new DefaultElement("data"); elementData.add(new DefaultCDATA(jsonArray.toJSONString())); //增加data字段,保存json数据。 element.add(elementData); return element; } //读取配置文件。 public String readConfFile(String configName) { String basePath = StatBaseServiceImpl.class.getResource("/") .toString().replace("file:", ""); try { return FileUtils.readFileToString(new File(basePath + configName), "utf-8"); } catch (IOException e) { e.printStackTrace(); return ""; } } } 通过读取xml配置文件,将数据返回 <?xml version="1.0" encoding="UTF-8"?> <stats> <!-- 统计数据xml 按照id进行查找。--> <stat id="stat-demo-1"> <!-- 展示数据 --> <list> <column field="id" text="id"/> <column field="name" text="注册姓名"/> <column field="sex" text="注册性别"/> <column field="city" text="注册城市"/> <column field="phone" text="注册电话"/> </list> <data><![CDATA[[{"city":"city_222","id":1,"name":"222","phone":"1222","sex":"1"}, {"city":"city_33","id":2,"name":"33","phone":"233","sex":"0"}, {"city":"city_33","id":3,"name":"33","phone":"333","sex":"1"}, {"city":"city_45","id":4,"name":"45","phone":"445","sex":"0"}, {"city":"city_6","id":5,"name":"6","phone":"56","sex":"1"}, {"city":"city_7","id":6,"name":"7","phone":"67","sex":"0"}, {"city":"city_8","id":7,"name":"8","phone":"78","sex":"1"}, {"city":"city_9","id":8,"name":"9","phone":"89","sex":"0"}, {"city":"city_9","id":9,"name":"9","phone":"99","sex":"1"}, {"city":"city_11","id":10,"name":"11","phone":"1011","sex":"0"}]]]></data></stat> </stats> 数据包括配置的xml和返回的数据。 4,客户端flex4代码 跟新xml返回的数据,动态生成AdvancedDataGridColumn,并添加到 AdvancedDataGrid里面。 <?xml version="1.0" encoding="utf-8"?> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%" initialize="initApp();"> <fx:Style source="./assets/main.css" /> <fx:Declarations> <mx:HTTPService id="moduleService" method="GET" resultFormat="e4x" result="resultHandler();"/> </fx:Declarations> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; mx|Text.headLabelStyle{fontSize:20;fontWeight:bold;} </fx:Style> <fx:Script> <![CDATA[ import mx.collections.*; import mx.controls.*; import mx.controls.advancedDataGridClasses.*; import mx.utils.*; import spark.components.*; private function initApp():void{ try { /* 删除问号前面的所有内容,包括问号。 */ var myPattern:RegExp = /.*\?/; var url:String = this.loaderInfo.url.toString(); //Alert.show(this.loaderInfo.height+","+this.loaderInfo.width); dataGridGroup.width = this.loaderInfo.width; //dataGridGroup.height = this.loaderInfo.height; url = url.replace(myPattern, ""); /* 创建一个形如name=value字符串数组 */ var params:Array = url.split("&"); /* 输出数组中的参数,找到moduleId,这个参数,其他参数类似。 */ var moduleId:String = ""; for (var key:String in params) { var value:String = String(params[key]); if(value.indexOf("moduleId=") >= 0){ moduleId = value.replace("moduleId=",""); break; } } //Alert.show(moduleId); //设置请求地址获取模块参数配置。 moduleService.url = "/stat/module/data.do?moduleId="+moduleId; moduleService.send(); } catch (e:Error) { trace(e); } /* 显示loaderInfo的一些有用的信息 */ trace("AS version: " + this.loaderInfo.actionScriptVersion); //trace("App height: " + this.loaderIno.height); //trace("App width: " + this.loaderInffo.width); trace("App bytes: " + this.loaderInfo.bytesTotal); } private function genWhere(dataXml:XML):HGroup{ var hgroup:HGroup = new HGroup(); //保证居中和左对齐。 hgroup.verticalAlign="middle" hgroup.horizontalAlign = "left"; //Alert.show(dataXml.where.column); for each(var objWhere:Object in dataXml.where.column){ var field:String = objWhere.@field; var text:String = objWhere.@text; var inputLabel:spark.components.Label = new spark.components.Label(); inputLabel.text = text; hgroup.addElement(inputLabel); var input:spark.components.TextInput = new spark.components.TextInput(); input.id = field; hgroup.addElement(input); var sp:Spacer = new Spacer(); sp.width = 10; hgroup.addElement(sp); } //如果存在where数据则增加查询按钮。 if(dataXml.where.column.length() > 0){ var search:spark.components.Button = new spark.components.Button(); search.label = "查询"; hgroup.addElement(search); } return hgroup; } //动态生成 AdvancedDataGrid 数据表单. private function genAdvancedDataGrid(dataXml:XML):AdvancedDataGrid{ //Alert.show(menuList.source.columns.column); //Alert.show(menuList.source.data); //格式化json数据。将数据转换成数组。放到DataGrid里面。 var tmpArray:Array = (JSON.parse(dataXml.data) as Array); //Alert.show(array.length()); var dataArray:ArrayCollection = new ArrayCollection(tmpArray); //#######################设置列表显示顶部标题. var columnArray:Array = new Array(); for each(var objColumn:Object in dataXml.list.column){ var dataField:String = objColumn.@field; var headerText:String = objColumn.@text; var width:String = objColumn.@width; //设置DataGridColun标题,和显示属性。 var adColumn:AdvancedDataGridColumn = new AdvancedDataGridColumn(dataField); adColumn.headerText = headerText; if(width){ adColumn.width = Number(width); } columnArray.push(adColumn); } //adg1.columns(); //adg1.columnCount(); //adg1.columns(columnArray); var adg:AdvancedDataGrid = new AdvancedDataGrid(); adg.id = "adg-"+id; adg.dataProvider = dataArray; adg.columns = columnArray; adg.rowCount = tmpArray.length + 1; return adg; } //请求数据成功之后处理动态生成表格。 private function resultHandler():void{ for each(var obj:Object in moduleService.lastResult.stat){ var dataXml:XML = obj as XML; //获得模板id,标题. var id:String = dataXml.@id; var title:String = dataXml.@title; //设置 var vgroup:VGroup = new VGroup(); //设置内边距15。 vgroup.paddingTop = 15; vgroup.paddingLeft = 15; vgroup.paddingRight = 15; vgroup.paddingBottom = 15; //设置标题。 var titleText:Text = new Text(); titleText.text = title; titleText.styleName = "headLabelStyle"; vgroup.addElement(titleText); var hgroup:HGroup = genWhere(dataXml); vgroup.addElement(hgroup); //生成高级表格。 var adg:AdvancedDataGrid = genAdvancedDataGrid(dataXml); vgroup.addElement(adg); dataGridGroup.addElement(vgroup); } } ]]> </fx:Script> <!-- 设置如果超过高度滚动. --> <s:VGroup id="dataGridGroup" height="100%"/> </mx:Module> 5,后续&代码 这只是最简单的应用,功能还需要完善。 代码已经放到github上面了。 https://github.com/freewebsys/flex4-stat 工程需要使用flahs builder 打开。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册