「Android」adb调试源码(针对dumpsys SurfceFlinger、trace.txt获取)

时间:2023-02-17 20:30:07

首先对ADB作简单的阐述,接下来对adb shell dumpsys SurfaceFlinger服务的dump信息的查看、以及ANR问题如何获取trace文件并简单分析。

-×**************************************************************

目录:

一、ADB概述

二、ADB常用命令

(1)基本命令

(2)软件操作命令

(3)文件操作命令

(4)日志操作命令

三、dumpsys使用

四、dump SurfaceFlinger的打印信息分析

五、trace分析

-×*************************************************************

 一、ADB概述

adb(Android Debug Bridge),安卓平台调试桥,是连接Android手机与PC端的桥梁,通过adb可以管理、操作模拟器和设备,如安装软件、查看设备软硬件参数、系统升级、运行shell命令等。

手机启动USB调试模式,设备连接电脑。

注:我的手机一加6的USB调试模式打开方式如下:

(1)在手机设置的关于手机找到版本号,双击七次打开开发者模式;

(2)在开发者选项中打开USB调试选项;
(3)设备连接
 
二、ADB常用命令
(1)基本命令:
1、adb devices 查看设备连接情况
2、adb version 查看版本
3、adb kill-server 关闭adb服务
4、adb start-server 启动adb服务
5、adb 查看帮助
6、adb shell 进入adb sehll命令
7、adb shell top  查看手机当前进程占用手机内存情况 

8、adb shell kill -3 pid 杀掉进程

9、adb logcat -v process |grep 8607              //8607 是进程 PID

(2)软件操作命令:
  安装软件:
     adb install
    adb install <apk文件路径> :这个命令将指定的apk文件安装到设备上
   卸载软件:
     adb uninstall <软件名>
     adb uninstall -k <软件名> 如果加 -k 参数,为卸载软件但是保留配置和缓存文件
(3)文件操作命令:
  从电脑上发送文件到设备
   adb push <本地路径> <远程路径>

  用push命令可以把本机电脑上的文件或者文件夹复制到设备(手机)

  从设备上下载文件到电脑
   adb pull <远程路径> <本地路径>

  用pull命令可以把设备(手机)上的文件或者文件夹复制到本机电脑

(4)日志操作命令
  1. adb logcat 查看日志

日志等级(由上往下级别递增):

V verbase,级别最低,琐碎、不重要的日志信息

D debug,调试信息

I info,重要信息

W warning,警告信息

E error,错误信息

F fatal,严重错误信息

S slient,无记载

  1. adb logcat --help 查看帮助信息,获取该命令可配置的参数选项
  1. adb logcat -b <buffer> 加载一个可使用的日志缓冲区供查看

buffer选项可以填为:

radio 通信系统

system 系统组件

event event事件模块

main java层

kernel linux内核

  1. adb logcat [tag:level] 指定标签,指定级别过滤显示日志

例如:adb logcat Test:I

  1. adb logcat -c 清理已存在的日志
  1. adb logcat -g 打印日志缓冲区的大小
  1. adb logcat > home/mylog.txt 日志保存到电脑某路径
  1. adb logcat -d -f /sdcard/mylog.txt 保存到手机上指定位置(-d 日志显示在控制台)
  1. adb logcat -f /scard/log.txt 输出到手机指定位置
  1. adb logcat -s System.out 设置标签(某个字符串),过滤显示日志
  1. adb logcat -v <format> 设置日志输入格式控制输出字段

format选项可以填为:

brief 显示优先级/标记和原始进程的PID(默认)

process 只显示进程PID

tag 只显示优先级/标记

thread 只显示进程、线程、优先级/标记

raw

time

long

例如:adb logcat -v process

(5)adb shell命令
  1. adb shell pm list packages 查看2、adb shell pm list packages -f 查看包名对应的apk路径及名称
3、adb shell dumpsys  列出手机所有apk的详细信息

三、dumpsys使用

(1)adb shell     进入shell

(2)dumpsys -l   查看所有正在运行的服务名

    service list    查看这些服务名称调用了哪个服务

    下面列举了其中一些服务名:

服务名 类名 功能
activity ActivityManagerService AMS相关信息
package PackageManagerService PMS相关信息
window WindowManagerService WMS相关信息
input InputManagerService IMS相关信息
power PowerManagerService PMS相关信息
batterystats BatterystatsService 电池统计信息
battery BatteryService 电池信息
alarm AlarmManagerService 闹钟信息
dropbox DropboxManagerService 调试相关
procstats ProcessStatsService 进程统计
cpuinfo CpuBinder CPU
meminfo MemBinder 内存
gfxinfo GraphicsBinder 图像
dbinfo DbBinder 数据库
服务名 功能
SurfaceFlinger 图像相关
appops app使用情况
permission 权限
processinfo 进程服务
batteryproperties 电池相关
audio 查看声音信息
netstats 查看网络统计信息
diskstats 查看空间free状态
jobscheduler 查看任务计划
wifi wifi信息
diskstats 磁盘情况
usagestats 用户使用情况
devicestoragemonitor 设备信息

(3)dumpsys <service>    打印具体某一项服务(service就是前面表格中的服务名)

dumpsys cpuinfo //打印一段时间进程的CPU使用百分比排行榜
dumpsys meminfo -h //查看dump内存的帮助信息
dumpsys package <packagename> //查看指定包的信息
dumpsys SurfaceFLinger //查看SF服务

四、dump SurfaceFlinger的打印信息分析

  SurfaceFlinger的dump信息主要通过dumpAllLocked 函数来获取。

  一般包含:
1、layer的信息,layer一般对应于一个surface;
2、opengl的信息。一般是跟gpu比较相关的参数,opengl是标准的接口;
3、display。安卓支持三种类型的display,可以导出display当前的显示状态,也就是各个surface(layer)在各个display的显示属性;
4、surfaceflinger管理graphis buffer的信息。主要是layer申请的帧数据内存;
5、hwcomopser的如果实现dump接口也能知道hwcomposer的一些参数;
6、gralloc的内存分配信息。如果gralloc有实现dump接口的话;

(1)特殊宏的打开

Build configuration: [sf] [libui] [libgui]

(2)打印目前正在使用的Sync机制

Sync configuration: [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]

(3)打印Layer

Visible layers (count = )

  count的值来源于layersSortedByZ中layer的数量,接下来就进入各个layer的dump。

  例如:

  >  0xb3f92000指向当前layer对象的值,括号中是当前layer的名称,id是创建layer时产生的序列号:

+ Layer 0xb3f92000 (com.sec.android.app.launcher/com.android.launcher2.Launcher) id=

  >  区域信息,两段是两个Region的dump,每个region可能包含多个区域,所以这里count也可能不等于1,

前两行的值来源于activeTransparentRegion,表示的是这个layer里面透明区域的大小,
后两行值来源于visibleRegion,表示可见区域的大小:

Region transparentRegion (this=0xb3f92164, count=)
[ , , , ]
Region visibleRegion (this=0xb3f92008, count=)
[ , , , ]

  >  基本信息:

layerStack=   , z=    , pos=(,), size=(,), crop=(, ,,), isOpaque=,
invalidate=, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
client=0xb11160c0

    对应的dumpAllLock中的源码:

  result.appendFormat(
"layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), crop=(%4d,%4d,%4d,%4d), "
"isOpaque=%1d, invalidate=%1d, "
"alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n"
" client=%p\n",
s.layerStack, s.z, s.transform.tx(), s.transform.ty(), s.active.w, s.active.h,
s.active.crop.left, s.active.crop.top,
s.active.crop.right, s.active.crop.bottom,
isOpaque(s), contentDirty,
s.alpha, s.flags,
s.transform[][], s.transform[][],
s.transform[][], s.transform[][],
client.get());
  • layerStack表示这个layer是保存在哪个layerstack中(不同的display是有不同的layerstack的);

  • z表示Z轴坐标,z值越大,layer越靠上;

  • pos的值是layer左上角的位置,这个值比较特殊的是ImageWallpaper这个layer的pos值,因为ImageWallpaper的大小大于屏幕大小,所以ImageWallpaper的pos值在屏幕的外面(note4是pos=(-560,0)).

  • size自然是layer的大小;

  • crop代表裁剪区域,这点依然是对于壁纸很明显,因为壁纸layer大小大于屏幕,必须涉及到需要裁剪一部分显示在屏幕上,因此它的裁剪区域是crop=( 560, 0,2000,2560);
  • isOpaque代表是否是不透明的,只有完全不透明的layer这个值才是1,比如壁纸,像状态栏和launcher他们都是0,代表不是完全不透明;
  • invalidate表示这个layer的数据是失效的,这个值绝大多数情况下都是0.因为我们看到的一般都是绘制好的有效的数据.一种情况下这值特别频繁的多见为1,就是刚刚锁屏(解锁)时.因为突然锁屏,会导致绘制的内容和要显示的内容完全不同,导致layer的各种数据要重新计算,所以将layer置为失效;
  • alpha表示了这张layer的透明度,这个值跟isOpaque是有区别的.isOpaque表示了这个layer可以是透明的,也就是没有显示数据的地方,可以透明;而alpha表示透明度,也即是有数据的地方也可以因为透明度而收到影响产生透明的效果;
  • flag值含义丰富,它是众多flag或出来的结果,会a影星layer的状态;
  • 接下来的一组tr数据代表屏幕的旋转和缩放程度.大多数的layer实际上是不需要旋转和缩放的,因为他们定义的大小就是跟屏幕一致的,所以他们的这组数据是[1.00, 0.00][0.00, 1.00],实际上如果你使用这组数据来做矩阵变换的话,矩阵是不会发生变化的.需要旋转的比较典型的场景是照相机.横着拿相机时它的layer的变换矩阵是[-1.00, 0.00][-0.00, -1.00],也就是旋转180°. 这个值的来源是上层调用setMatrix函数设置的.
  • client含义比较简单,值的来源是创建layer时,对应的SurfaceSession中mNativeClient.这东西也是跟SurfaceSession一一对应的,也就是跟SurfaceFlinger连接时一一对应的.从这个值我们可以判断,client值相同的layer,必然来自同一个进程(因为他们是由同一个连接创建出来的).

(4)打印Displays信息

  首先会打印当前display的数量,数量基于mDisplays的大小,这个容器在SurfaceFlinger初始化时会生成数据,后面根据收到不同的消息在handleTransactionLocked函数中也会调整.
正常情况下是1,也就是只有一个display(Built-in Screen),当设备连接了HDMI或者使用了屏幕共享等功能时,会有额外的display加入。

Displays ( entries)  //这个是连接了HDMI后的数据
+ DisplayDevice: HDMI Screen
type=, hwcId=, layerStack=, (1920x1080), ANativeWindow=0xb4d94d08, orient= (type=), flips=, isSecure=,
secureVis=, powerMode=, activeConfig=, numLayers=
v:[,,,], f:[,,,], s:[,,,],transform:[[1.000,0.000,-0.000][0.000,1.000,-0.000][0.000,0.000,1.000]]
mAbandoned=
-BufferQueue mMaxAcquiredBufferCount=, mDequeueBufferCannotBlock=, default-size=[1920x1080], default-format=, transform-hint=,
FIFO()={}
[:0xb6418c80] state=FREE , 0xb43ed880 [1920x1080:, ]
[:0xb43cb300] state=FREE , 0xb640d970 [1920x1080:, ]
>[:0xb43cb280] state=ACQUIRED, 0xb43ed830 [1920x1080:, ]
+ DisplayDevice: Built-in Screen //DisplayDevice是设备的名字,这个可以调用接口设置,但是比较常见的值一般有:Built-in Screen,HDMI Screen,Virtual Screen,wfdservice等等
type=, hwcId=, layerStack=, (1080x1920), ANativeWindow=0xb4d94608, orient= (type=), flips=, isSecure=,
secureVis=, powerMode=, activeConfig=, numLayers=
v:[,,,], f:[,,,], s:[,,,],transform:[[1.000,0.000,-0.000][0.000,1.000,-0.000][0.000,0.000,1.000]]

五、trace分析

trace.txt生成:当APP(包括系统APP和用户APP)进程出现ANR、应用响应慢或WatchDog的监视没有得到回馈时,系统会dump此时的top进程,进程中Thread的运行状态就都dump到这个Trace文件中了。

ANR一般有三种类型:

  1、KeyDispatchTimeout(5 seconds) --主要类型  按键或触摸事件在特定时间内无响应

  2、BroadcastTimeout(10 seconds)        BroadcastReceiver在特定时间内无法处理完成

  3、ServiceTimeout(20 seconds) --小概率类型  Service在特定的时间内无法处理完成

---------------------------------------------------------------

1、adb shell 进入手机的/data/anr文件目录下面查看生成的trace.txt文件

  如果ls查看文件列表没有权限,可以先adb  root一下

2、adb pull /data/   ./     将该文件导出,然后分析

---------------------------------------------------------------

log打印了ANR的基本信息(adb shell top查看cg进程,adb logcat -v process |grep PID查看日志),

可以分析CPU使用率得知ANR的简单情况;如果CPU使用率很高,接近100%,可能是在进行大规模的计算更可能是陷入死循环;如果CUP使用率很低,说明主线程被阻塞了,并且当IOwait很高,可能是主线程在等待I/O操作的完成。

对于ANR只是分析Log很难知道问题所在,我们还需要通过Trace文件分析stack调用情况,在log中显示的pid在traces文件中与之对应,然后通过查看堆栈调用信息分析ANR的代码

(此处trace的分析参考 https://blog.csdn.net/qq_25804863/article/details/49111005 )

----- pid  at -- :: -----    // ANR出现的进程pid和时间
Cmd line: org.code:MessengerService // ANR出现的进程名(或者进程包名)
Build fingerprint: 'Homecare/qucii8976v3_64:6.0.1/pansen06141150:eng/test-keys' // 下面记录系统版本,内存等状态信息
ABI: 'arm64'
Build type: optimized
Zygote loaded classes= post zygote classes=
Intern table: strong; weak
JNI: CheckJNI is on; globals= (plus weak)
Libraries: /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so /system/lib64/libjavacrypto.so /system/lib64/libjnigraphics.so /system/lib64/libmedia_jni.so /system/lib64/libwebviewchromium_loader.so libjavacore.so ()
Heap: % free, 8MB/12MB; objects
Dumping cumulative Gc timings
Total number of allocations
Total bytes allocated 8MB
Total bytes freed 0B
Free memory 3MB
Free memory until GC 3MB
Free memory until OOME 183MB
Total memory 12MB
Max memory 192MB
Zygote space size 3MB
Total mutator paused time:
Total time waiting for GC to complete:
Total GC count:
Total GC time:
Total blocking GC count:
Total blocking GC time: suspend all histogram: Sum: 76us % C.I. .100us-28us Avg: .600us Max: 28us
DALVIK THREADS ():
// Signal Catcher是记录traces信息的线程
// Signal Catche(线程名)、(daemon)表示守护进程、prio(线程优先级,默认是5)、tid(线程唯一标识ID)、Runnable(线程当前状态)
"Signal Catcher" daemon prio= tid= Runnable
//线程组名称、suspendCount、debugSuspendCount、线程的Java对象地址、线程的Native对象地址
| group="system" sCount= dsCount= obj=0x12d8f0a0 self=0x5598ae55d0
//sysTid是线程号(主线程的线程号和进程号相同)
| sysTid= nice= cgrp=default sched=/ handle=0x7fb2350450
| state=R schedstat=( ) utm= stm= core= HZ=
| stack=0x7fb2254000-0x7fb2256000 stackSize=1013KB
| held mutexes= "mutator lock"(shared held)
native: # pc 0000000000489e28 /system/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, int, char const*, art::ArtMethod*, void*)+)
native: # pc 0000000000458fe8 /system/lib64/libart.so (art::Thread::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) const+)
native: # pc 0000000000465bc8 /system/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+)
native: # pc 0000000000466ae0 /system/lib64/libart.so (art::ThreadList::RunCheckpoint(art::Closure*)+)
native: # pc 000000000046719c /system/lib64/libart.so (art::ThreadList::Dump(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+)
native: # pc 0000000000467a84 /system/lib64/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+)
native: # pc /system/lib64/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+)
native: # pc 000000000043e604 /system/lib64/libart.so (art::SignalCatcher::HandleSigQuit()+)
native: # pc 000000000043f214 /system/lib64/libart.so (art::SignalCatcher::Run(void*)+)
native: # pc /system/lib64/libc.so (__pthread_start(void*)+)
native: # pc 000000000001c604 /system/lib64/libc.so (__start_thread+)
(no managed stack frames) //main(线程名)、prio(线程优先级,默认是5)、tid(线程唯一标识ID)、Sleeping(线程当前状态)
"main" prio= tid= Sleeping
| group="main" sCount= dsCount= obj=0x73132d10 self=0x5598a5f5e0
//sysTid是线程号(主线程的线程号和进程号相同)
| sysTid= nice= cgrp=default sched=/ handle=0x7fb6db6fe8
| state=S schedstat=( ) utm= stm= core= HZ=
| stack=0x7fefba3000-0x7fefba5000 stackSize=8MB
| held mutexes=
// java 堆栈调用信息(这里可查看导致ANR的代码调用流程)(分析ANR最重要的信息)
at java.lang.Thread.sleep!(Native method)
- sleeping on <0x0c60f3c7> (a java.lang.Object)
at java.lang.Thread.sleep(Thread.java:)
- locked <0x0c60f3c7> (a java.lang.Object) // 锁住对象0x0c60f3c7
at java.lang.Thread.sleep(Thread.java:)
at android.os.SystemClock.sleep(SystemClock.java:)
at org.code.ipc.MessengerService.onCreate(MessengerService.java:) //导致ANR的代码
at android.app.ActivityThread.handleCreateService(ActivityThread.java:)
at android.app.ActivityThread.access$(ActivityThread.java:)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:)
at android.os.Handler.dispatchMessage(Handler.java:)
at android.os.Looper.loop(Looper.java:)
at android.app.ActivityThread.main(ActivityThread.java:)
at java.lang.reflect.Method.invoke!(Native method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:)

Traces中显示的线程状态都是C代码定义的.我们可以通过查看线程状态对应的信息分析ANR问题

如: TimedWaiting对应的线程状态是TIMED_WAITING

kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout 执行了无超时参数的wait函数

kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep() 执行了带有超时参数的sleep函数

ZOMBIE                 线程死亡,终止运行
RUNNING/RUNNABLE           线程可运行或正在运行
TIMED_WAIT               执行了带有超时参数的wait、sleep或join函数
MONITOR                线程阻塞,等待获取对象锁
WAIT                   执行了无超时参数的wait函数
INITIALIZING              新建,正在初始化,为其分配资源
STARTING                  新建,正在启动
NATIVE                  正在执行JNI本地函数
VMWAIT                正在等待VM资源
SUSPENDED                线程暂停,通常是由于GC或debug被暂停

「Android」adb调试源码(针对dumpsys SurfceFlinger、trace.txt获取)的更多相关文章

  1. Android反编译调试源码

    Android反编译调试源码 1. 反编译得到源码 直接在windows 命令行下输入命令java -jar apktool_2.0.0.jar d -d 小米运动_1.4.641_1058.apk ...

  2. Android 自定义View及其在布局文件中的使用示例&lpar;三&rpar;&colon;结合Android 4&period;4&period;2&lowbar;r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

  3. 「Android」SurfaceFlinger分析

    本篇针对surfaceFlinger模块进行分析,目录如下: 1.SurfaceFlinger功能 1.1.BufferQueue原理(native/libs/gui模块) 1.2   layer显示 ...

  4. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  5. Android音乐播放器源码&lpar;歌词&period;均衡器&period;收藏&period;qq5&period;0菜单&period;通知&rpar;

    一款Android音乐播放器源码,基本功能都实现了 qq5.0菜单(歌词.均衡器.收藏.qq5.0菜单.通知) 只有向右滑动出现,菜单键和指定按钮都还没有添加. 源码下载:http://code.66 ...

  6. NuGet包调试源码的方法

    如果按照nuget官网给出的网址:https://docs.nuget.org/create/creating-and-publishing-a-symbol-package 那么你会发觉下载符号包的 ...

  7. Ubuntu 14&period;04 LTS 下 android 2&period;3&period;5 源码编译过程

    Ubuntu 14.04 LTS 下 android 2.3.5 源码编译过程   在新的Ubuntu 64位系统下去编译早期的安卓源码是会出现很多问题的,因为64位系统在安装完成后,很多32位的兼容 ...

  8. Java学习-025-类名或方法名应用之一 -- 调试源码

    上文讲述了如何获取类名和方法名,敬请参阅: Java学习-024-获取当前类名或方法名二三文 . 通常在应用开发中,调试或查看是哪个文件中的方法调用了当前文件的此方法,因而在实际的应用中需要获取相应的 ...

  9. android版猜拳游戏源码分享

    android版猜拳游戏源码分享安卓版猜拳游戏源码,该文件中带有安装测试包的,这个游戏源码比较简单的,现在有两个代码,一个自定义VIEW的,一个就是普通的imageView图片,游戏非常适合一些新手的 ...

随机推荐

  1. IOS开发网络数据---- AFNetworking的使用

    http网络库是集XML解析,Json解析,网络图片下载,plist解析,数据流请求操作,上传,下载,缓存等网络众多功能于一身的强大的类库.最新版本支持session,xctool单元测试.网络获取数 ...

  2. 压测如何观测jvm,就是使用jmx来实现jvm监控

    jps.jstack.jmap.jhat.jstat.hprof 基于jmx可以开发web版本,方便压测的时候观测jvm以及线程的信息 ================================ ...

  3. hdu 5294 Tricks Device(2015多校第一场第7题)最大流&plus;最短路

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5294   题意:给你n个墓室,m条路径,一个人在1号墓室(起点),另一个人在n号墓室(终点),起点的那 ...

  4. Thread &period;join 的用法一例

    在使用身份证读卡器时,要求 1. 身份证读到身份证 就 停止线程. 2. 关闭界面时会 自动停止调用读身份证的线程.这时候就需要用到 Thead.join 例子如下: Thread thread; p ...

  5. leecode 每日解题思路 64&Tab;Minimum Path Sum

    题目描述: 题目链接:64 Minimum Path Sum 问题是要求在一个全为正整数的 m X n 的矩阵中, 取一条从左上为起点, 走到右下为重点的路径, (前进方向只能向左或者向右),求一条所 ...

  6. 推荐IOS开发3个工具&colon;Homebrew、TestFight、Crashlytics-备

    1. Homebrew 什么是Homebrew? Homebrew is the easiest and most flexible way to install the UNIX tools App ...

  7. The account &&num;39&semi;&period;&period;&period;&&num;39&semi; is no team with ID &&num;39&semi;&period;&period;&period;&&num;39&semi;

    iOS升到9.2之后,有一个大坑,原先真机调试的开发者账号(未付费),连不了Xcode了,会弹出一个提示框提示你, The account '...' is no team with ID '...' ...

  8. WebServiceWSDLWeb

    WSDL 文档仅仅是一个简单的 XML 文档. 它包含一系列描述某个 web service 的定义. WSDL 文档是利用这些主要的元素来描述某个 web service 的: 元素 定义 < ...

  9. Spring &commat;AfterReturning 总是返回null

    在学习Spring Aop时,遇到一个问题,当 @Around(环绕通知)与 @AfterReturning(后置通知)共存 时,@AfterReturning 通过属性 returning = &q ...

  10. EasyUI datagrid easyui datagrid &plus;dialog 加载 可直接运行 七

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta ht ...