https://blog.csdn.net/fancylovejava/article/details/45787729

https://blog.csdn.net/dunqiangjiaodemogu/article/details/72956291

飞猪上的doraemon一直对过度绘制和布局深度有监控,不合理的布局和过深得过度绘制影响页面渲染速度。虽然发现了不少问题,多处可见如下图的红红的页面,但是一直很难推动解决,主要有两个原因。

  1. 让开发找到具体的位置需要从根布局一层层遍历分析下去,确实麻烦,所以不想改
  2. 修改后,会不会影响到其他控件的显示效果,光靠大脑想象难保万全,所以不敢改

新工具

感谢@睿牧提供的外部开源参考工具
于是doraemon里就多了一样新的工具,将当前页面的布局3D化,有点像xcode上的view ui hierarchy工具,如下图所示。新工具可以辅助分析页面布局的合理性和影响过度绘制的关键点:

  1. 在3D化的页面上将每个有名字的控件的名字(id)都写上了,便于直接看出是哪个控件(或者控件的爸爸)导致问题,以便快速定位到具体的控件;
  2. 在3D化的页面上通过拖拽和多点触摸放大来直观的看出每一个控件在整体布局里所处的位置,和对相关控件的影响,便于下结论能不能改;
  3. 在开发写布局文件时,经常用到layout嵌套layout,所以没有一个全局观,即不知当前正在写的布局在整体里的位置。在3D化的页面上,能够清晰的看出布局是否合理,是否有不合理的嵌套布局存在。不合理的布局导致过深得嵌套会引发crash

分析方法(这里以门票首页为例)

1. 打开过度绘制开关

2. 将门票首页布局3D化

按照上面的打开方式,然后进入门票首页,再点击“3D”Icon,可以看到如下图。可以看到所有控件的背景色都被涂上了标识过度绘制深度的颜色。

3. 找出影响过渡绘制的关键控件

从最外层布局向内看,导致背景色突变的控件是设置了背景色,如下图标记。其中5和6的背景色变化是因为加载了图片,这种情况可以不修改。我们主要看下1、2、3、4这4个地方。

1. 标记1位置

如下代码,在根布局里刷了一次全屏的白色。不合理性:标题栏本身也是白色,所在标题栏的像素区域内,根布局的全屏白色是多余的。

2. 标记2位置

整个页面的布局可以看成是上面一个标题栏,下面一个列表控件(listview),代码中为这个列表控件再一次刷了白色,如下代码所示:

3. 标记3位置

list的cell单元代码中再次刷了个白色底色,很显然这是多余的

4. 标记4位置

又一个list的cell单元这里也刷了个白色底色,很显然这也是多余的,前面的e6透明度更是多此一举。

4. 找到了痛点位置给出解决方案

  1. 去掉根布局的白色底色,保留listview的白色底色
  2. 去掉listview中的cell的白色底色

5. 初步优化前后对比

过度绘制数值由原先的4.04降低到2.63,提升53.6%。下图是初步优化前后颜色对比。

6. 布局合理性分析

如下黄色箭头指向的位置,4个图片控件(ImageView)并排放着,用了3层布局,对此表示质疑。

最后

3D布局工具结合过渡绘制开关可以有效地提升定位过度绘制问题,此外还容易发现多余的不合理的布局,以提升native的性能体验。

下面是源码,欢迎讨论共建。

  1. public class ScalpelFrameLayout extends FrameLayout {
  2. /**
  3. * 传入当前顶部的Activity
  4. */
  5. public static void attachActivityTo3dView(Activity activity) {
  6. if (activity == null) {
  7. return;
  8. }
  9. ScalpelFrameLayout layout;
  10. ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
  11. /**
  12. * 在ids.xml里定义一个id
  13. */
  14. if (decorView.findViewById(R.id.id_scalpel_frame_layout) == null) {
  15. layout = new ScalpelFrameLayout(activity);
  16. layout.setId(R.id.id_scalpel_frame_layout);
  17. View rootView = decorView.getChildAt(0);
  18. decorView.removeView(rootView);
  19. layout.addView(rootView);
  20. decorView.addView(layout);
  21. } else {
  22. layout = (ScalpelFrameLayout) decorView.findViewById(R.id.id_scalpel_frame_layout);
  23. }
  24. if (!layout.isLayerInteractionEnabled()) {
  25. layout.setLayerInteractionEnabled(true);
  26. } else {
  27. layout.setLayerInteractionEnabled(false);
  28. }
  29. }
  30. /**
  31. * 标记位:当前多点触摸滑动方向未确定
  32. */
  33. private final static int TRACKING_UNKNOWN = 0;
  34. /**
  35. * 标记位:当前多点触摸滑动方向是垂直方向
  36. */
  37. private final static int TRACKING_VERTICALLY = 1;
  38. /**
  39. * 标记位:当前多点触摸滑动方向是横向方向
  40. */
  41. private final static int TRACKING_HORIZONTALLY = -1;
  42. /**
  43. * 旋转的最大角度
  44. */
  45. private final static int ROTATION_MAX = 60;
  46. /**
  47. * 反方向旋转的最大角度
  48. */
  49. private final static int ROTATION_MIN = -ROTATION_MAX;
  50. /**
  51. * 默认X轴旋转角度
  52. */
  53. private final static int ROTATION_DEFAULT_X = -10;
  54. /**
  55. * 默认Y轴旋转角度
  56. */
  57. private final static int ROTATION_DEFAULT_Y = 15;
  58. /**
  59. * 默认缩放比例
  60. */
  61. private final static float ZOOM_DEFAULT = 0.6f;
  62. /**
  63. * 最小缩放比例
  64. */
  65. private final static float ZOOM_MIN = 0.33f;
  66. /**
  67. * 最大缩放比例
  68. */
  69. private final static float ZOOM_MAX = 2f;
  70. /**
  71. * 图层默认间距
  72. */
  73. private final static int SPACING_DEFAULT = 25;
  74. /**
  75. * 图层间最小距离
  76. */
  77. private final static int SPACING_MIN = 10;
  78. /**
  79. * 图层间最大距离
  80. */
  81. private final static int SPACING_MAX = 100;
  82. /**
  83. * 绘制id的文案的偏移量
  84. */
  85. private final static int TEXT_OFFSET_DP = 2;
  86. /**
  87. * 绘制id的文案的字体大小
  88. */
  89. private final static int TEXT_SIZE_DP = 10;
  90. /**
  91. * view缓存队列初始size
  92. */
  93. private final static int CHILD_COUNT_ESTIMATION = 25;
  94. /**
  95. * 是否绘制view的内容,如TextView上的文字和ImageView上的图片
  96. */
  97. private boolean mIsDrawingViews = true;
  98. /**
  99. * 是否绘制view的id
  100. */
  101. private boolean mIsDrawIds = true;
  102. /**
  103. * 打印debug log开关
  104. */
  105. private boolean mIsDebug = true;
  106. /**
  107. * view大小矩形
  108. */
  109. private Rect mViewBoundsRect = new Rect();
  110. /**
  111. * 绘制view边框和id
  112. */
  113. private Paint mViewBorderPaint = new Paint(ANTI_ALIAS_FLAG);
  114. private Camera mCamera = new Camera();
  115. private Matrix mMatrix = new Matrix();
  116. private int[] mLocation = new int[2];
  117. /**
  118. * 用来记录可见view
  119. * 可见view需要绘制
  120. */
  121. private BitSet mVisibilities = new BitSet(CHILD_COUNT_ESTIMATION);
  122. /**
  123. * 对id转字符串的缓存
  124. */
  125. private SparseArray<String> mIdNames = new SparseArray<String>();
  126. /**
  127. * 队列结构实现广度优先遍历
  128. */
  129. private ArrayDeque<LayeredView> mLayeredViewQueue = new ArrayDeque<LayeredView>();
  130. /**
  131. * 复用LayeredView
  132. */
  133. private Pool<LayeredView> mLayeredViewPool = new Pool<LayeredView>(
  134. CHILD_COUNT_ESTIMATION) {
  135. @Override
  136. protected LayeredView newObject() {
  137. return new LayeredView();
  138. }
  139. };
  140. /**
  141. * 屏幕像素密度
  142. */
  143. private float mDensity = 0f;
  144. /**
  145. * 对移动最小距离的合法性的判断
  146. */
  147. private float mSlop = 0f;
  148. /**
  149. * 绘制view id的偏移量
  150. */
  151. private float mTextOffset = 0f;
  152. /**
  153. * 绘制view id字体大小
  154. */
  155. private float mTextSize = 0f;
  156. /**
  157. * 3D视图功能是否开启
  158. */
  159. private boolean mIsLayerInteractionEnabled = false;
  160. /**
  161. * 第一个触摸点索引
  162. */
  163. private int mPointerOne = INVALID_POINTER_ID;
  164. /**
  165. * 第一个触摸点的坐标X
  166. */
  167. private float mLastOneX = 0f;
  168. /**
  169. * 第一个触摸点的坐标Y
  170. */
  171. private float mLastOneY = 0f;
  172. /**
  173. * 当有多点触摸时的第二个触摸点索引
  174. */
  175. private int mPointerTwo = INVALID_POINTER_ID;
  176. /**
  177. * 第二个触摸点的坐标X
  178. */
  179. private float mLastTwoX = 0f;
  180. /**
  181. * 第二个触摸点的坐标Y
  182. */
  183. private float mLastTwoY = 0f;
  184. /**
  185. * 当前多点触摸滑动方向
  186. */
  187. private int mMultiTouchTracking = TRACKING_UNKNOWN;
  188. /**
  189. * Y轴旋转角度
  190. */
  191. private float mRotationY = ROTATION_DEFAULT_Y;
  192. /**
  193. * X轴旋转角度
  194. */
  195. private float mRotationX = ROTATION_DEFAULT_X;
  196. /**
  197. * 缩放比例,默认是0.6
  198. */
  199. private float mZoom = ZOOM_DEFAULT;
  200. /**
  201. * 图层之间距离,默认是25单位
  202. */
  203. private float mSpacing = SPACING_DEFAULT;
  204. public ScalpelFrameLayout(Context context) {
  205. super(context, null, 0);
  206. mDensity = getResources().getDisplayMetrics().density;
  207. mSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
  208. mTextSize = TEXT_SIZE_DP * mDensity;
  209. mTextOffset = TEXT_OFFSET_DP * mDensity;
  210. mViewBorderPaint.setStyle(STROKE);
  211. mViewBorderPaint.setTextSize(mTextSize);
  212. if (Build.VERSION.SDK_INT >= JELLY_BEAN) {
  213. mViewBorderPaint.setTypeface(Typeface.create("sans-serif-condensed", NORMAL));
  214. }
  215. }
  216. /**
  217. * 设置是否让当前页面布局3D化
  218. * 使用该方法前先调用attachActivityTo3dView方法
  219. *
  220. * @param enabled
  221. */
  222. public void setLayerInteractionEnabled(boolean enabled) {
  223. if (mIsLayerInteractionEnabled != enabled) {
  224. mIsLayerInteractionEnabled = enabled;
  225. setWillNotDraw(!enabled);
  226. invalidate();
  227. }
  228. }
  229. /**
  230. * 当前页面布局是否已经3D化
  231. *
  232. * @return
  233. */
  234. public boolean isLayerInteractionEnabled() {
  235. return mIsLayerInteractionEnabled;
  236. }
  237. @Override
  238. public boolean onInterceptTouchEvent(MotionEvent ev) {
  239. return mIsLayerInteractionEnabled || super.onInterceptTouchEvent(ev);
  240. }
  241. @Override
  242. public boolean onTouchEvent(MotionEvent event) {
  243. if (!mIsLayerInteractionEnabled) {
  244. return super.onTouchEvent(event);
  245. }
  246. int action = event.getActionMasked();
  247. switch (action) {
  248. case MotionEvent.ACTION_DOWN:
  249. case MotionEvent.ACTION_POINTER_DOWN:
  250. int index = action == ACTION_DOWN ? 0 : event.getActionIndex();
  251. if (mPointerOne == INVALID_POINTER_ID) {
  252. mPointerOne = event.getPointerId(index);
  253. mLastOneX = event.getX(index);
  254. mLastOneY = event.getY(index);
  255. if (mIsDebug) {
  256. log("Got pointer 1! id: %s x: %s y: %s", mPointerOne, mLastOneY, mLastOneY);
  257. }
  258. } else if (mPointerTwo == INVALID_POINTER_ID) {
  259. mPointerTwo = event.getPointerId(index);
  260. mLastTwoX = event.getX(index);
  261. mLastTwoY = event.getY(index);
  262. if (mIsDebug) {
  263. log("Got pointer 2! id: %s x: %s y: %s", mPointerTwo, mLastTwoY, mLastTwoY);
  264. }
  265. } else {
  266. if (mIsDebug) {
  267. log("Ignoring additional pointer. id: %s", event.getPointerId(index));
  268. }
  269. }
  270. break;
  271. case MotionEvent.ACTION_MOVE:
  272. if (mPointerTwo == INVALID_POINTER_ID) {
  273. /**
  274. * 单触点滑动是控制3D布局的旋转角度
  275. */
  276. int i = 0;
  277. int count = event.getPointerCount();
  278. while (i < count) {
  279. if (mPointerOne == event.getPointerId(i)) {
  280. float eventX = event.getX(i);
  281. float eventY = event.getY(i);
  282. float dx = eventX - mLastOneX;
  283. float dy = eventY - mLastOneY;
  284. float drx = 90 * (dx / getWidth());
  285. float dry = 90 * (-dy / getHeight());
  286. /**
  287. * 屏幕上X的位移影响的是坐标系里Y轴的偏移角度,屏幕上Y的位移影响的是坐标系里X轴的偏移角度
  288. * 根据实际位移结合前面定义的旋转角度区间算出应该旋转的角度
  289. */
  290. mRotationY = Math.min(Math.max(mRotationY + drx, ROTATION_MIN),
  291. ROTATION_MAX);
  292. mRotationX = Math.min(Math.max(mRotationX + dry, ROTATION_MIN),
  293. ROTATION_MAX);
  294. if (mIsDebug) {
  295. log("Single pointer moved (%s, %s) affecting rotation (%s, %s).",
  296. dx, dy, drx, dry);
  297. }
  298. mLastOneX = eventX;
  299. mLastOneY = eventY;
  300. invalidate();
  301. }
  302. i++;
  303. }
  304. } else {
  305. /**
  306. * 多触点滑动是控制布局的缩放和图层间距
  307. */
  308. int pointerOneIndex = event.findPointerIndex(mPointerOne);
  309. int pointerTwoIndex = event.findPointerIndex(mPointerTwo);
  310. float xOne = event.getX(pointerOneIndex);
  311. float yOne = event.getY(pointerOneIndex);
  312. float xTwo = event.getX(pointerTwoIndex);
  313. float yTwo = event.getY(pointerTwoIndex);
  314. float dxOne = xOne - mLastOneX;
  315. float dyOne = yOne - mLastOneY;
  316. float dxTwo = xTwo - mLastTwoX;
  317. float dyTwo = yTwo - mLastTwoY;
  318. /**
  319. * 首先判断是垂直滑动还是横向滑动
  320. */
  321. if (mMultiTouchTracking == TRACKING_UNKNOWN) {
  322. float adx = Math.abs(dxOne) + Math.abs(dxTwo);
  323. float ady = Math.abs(dyOne) + Math.abs(dyTwo);
  324. if (adx > mSlop * 2 || ady > mSlop * 2) {
  325. if (adx > ady) {
  326. mMultiTouchTracking = TRACKING_HORIZONTALLY;
  327. } else {
  328. mMultiTouchTracking = TRACKING_VERTICALLY;
  329. }
  330. }
  331. }
  332. /**
  333. * 如果是垂直滑动调整缩放比
  334. * 如果是横向滑动调整层之间的距离
  335. */
  336. if (mMultiTouchTracking == TRACKING_VERTICALLY) {
  337. if (yOne >= yTwo) {
  338. mZoom += dyOne / getHeight() - dyTwo / getHeight();
  339. } else {
  340. mZoom += dyTwo / getHeight() - dyOne / getHeight();
  341. }
  342. /**
  343. * 算出调整后的缩放比例
  344. */
  345. mZoom = Math.min(Math.max(mZoom, ZOOM_MIN), ZOOM_MAX);
  346. invalidate();
  347. } else if (mMultiTouchTracking == TRACKING_HORIZONTALLY) {
  348. if (xOne >= xTwo) {
  349. mSpacing += (dxOne / getWidth() * SPACING_MAX)
  350. - (dxTwo / getWidth() * SPACING_MAX);
  351. } else {
  352. mSpacing += (dxTwo / getWidth() * SPACING_MAX)
  353. - (dxOne / getWidth() * SPACING_MAX);
  354. }
  355. /**
  356. * 算出调整后的图层间距
  357. */
  358. mSpacing = Math.min(Math.max(mSpacing, SPACING_MIN), SPACING_MAX);
  359. invalidate();
  360. }
  361. if (mMultiTouchTracking != TRACKING_UNKNOWN) {
  362. mLastOneX = xOne;
  363. mLastOneY = yOne;
  364. mLastTwoX = xTwo;
  365. mLastTwoY = yTwo;
  366. }
  367. }
  368. break;
  369. case MotionEvent.ACTION_CANCEL:
  370. case MotionEvent.ACTION_UP:
  371. case MotionEvent.ACTION_POINTER_UP:
  372. index = action != ACTION_POINTER_UP ? 0 : event.getActionIndex();
  373. int pointerId = event.getPointerId(index);
  374. if (mPointerOne == pointerId) {
  375. /**
  376. * 多触点状态切换到单触点状态
  377. * 即如果原先是调整缩放和图层间距的状态,放开一个手指后转为控制图层旋转状态
  378. */
  379. mPointerOne = mPointerTwo;
  380. mLastOneX = mLastTwoX;
  381. mLastOneY = mLastTwoY;
  382. if (mIsDebug) {
  383. log("Promoting pointer 2 (%s) to pointer 1.", mPointerTwo);
  384. }
  385. /**
  386. * reset多触点状态
  387. */
  388. mPointerTwo = INVALID_POINTER_ID;
  389. mMultiTouchTracking = TRACKING_UNKNOWN;
  390. } else if (mPointerTwo == pointerId) {
  391. if (mIsDebug) {
  392. log("Lost pointer 2 (%s).", mPointerTwo);
  393. }
  394. /**
  395. * reset多触点状态
  396. */
  397. mPointerTwo = INVALID_POINTER_ID;
  398. mMultiTouchTracking = TRACKING_UNKNOWN;
  399. }
  400. break;
  401. default:
  402. break;
  403. }
  404. return true;
  405. }
  406. @Override
  407. public void draw(Canvas canvas) {
  408. if (!mIsLayerInteractionEnabled) {
  409. super.draw(canvas);
  410. return;
  411. }
  412. getLocationInWindow(mLocation);
  413. /**
  414. * 页面左上角坐标
  415. */
  416. float x = mLocation[0];
  417. float y = mLocation[1];
  418. int saveCount = canvas.save();
  419. /**
  420. * 页面中心坐标
  421. */
  422. float cx = getWidth() / 2f;
  423. float cy = getHeight() / 2f;
  424. mCamera.save();
  425. /**
  426. * 先旋转
  427. */
  428. mCamera.rotate(mRotationX, mRotationY, 0F);
  429. mCamera.getMatrix(mMatrix);
  430. mCamera.restore();
  431. mMatrix.preTranslate(-cx, -cy);
  432. mMatrix.postTranslate(cx, cy);
  433. canvas.concat(mMatrix);
  434. /**
  435. * 再缩放
  436. */
  437. canvas.scale(mZoom, mZoom, cx, cy);
  438. if (!mLayeredViewQueue.isEmpty()) {
  439. throw new AssertionError("View queue is not empty.");
  440. }
  441. {
  442. int i = 0;
  443. int count = getChildCount();
  444. while (i < count) {
  445. LayeredView layeredView = mLayeredViewPool.obtain();
  446. layeredView.set(getChildAt(i), 0);
  447. mLayeredViewQueue.add(layeredView);
  448. i++;
  449. }
  450. }
  451. /**
  452. * 广度优先进行遍历
  453. */
  454. while (!mLayeredViewQueue.isEmpty()) {
  455. LayeredView layeredView = mLayeredViewQueue.removeFirst();
  456. View view = layeredView.mView;
  457. int layer = layeredView.mLayer;
  458. /**
  459. * 在draw期间尽量避免对象的反复创建
  460. * 回收LayeredView一会再复用
  461. */
  462. layeredView.clear();
  463. mLayeredViewPool.restore(layeredView);
  464. /**
  465. * 隐藏viewgroup内可见的view
  466. */
  467. if (view instanceof ViewGroup) {
  468. ViewGroup viewGroup = (ViewGroup) view;
  469. mVisibilities.clear();
  470. int i = 0;
  471. int count = viewGroup.getChildCount();
  472. while (i < count) {
  473. View child = viewGroup.getChildAt(i);
  474. /**
  475. * 将可见的view记录到mVisibilities中
  476. */
  477. if (child.getVisibility() == VISIBLE) {
  478. mVisibilities.set(i);
  479. child.setVisibility(INVISIBLE);
  480. }
  481. i++;
  482. }
  483. }
  484. int viewSaveCount = canvas.save();
  485. /**
  486. * 移动出图层的距离
  487. */
  488. float translateShowX = mRotationY / ROTATION_MAX;
  489. float translateShowY = mRotationX / ROTATION_MAX;
  490. float tx = layer * mSpacing * mDensity * translateShowX;
  491. float ty = layer * mSpacing * mDensity * translateShowY;
  492. canvas.translate(tx, -ty);
  493. /**
  494. * 画view的边框
  495. */
  496. view.getLocationInWindow(mLocation);
  497. canvas.translate(mLocation[0] - x, mLocation[1] - y);
  498. mViewBoundsRect.set(0, 0, view.getWidth(), view.getHeight());
  499. canvas.drawRect(mViewBoundsRect, mViewBorderPaint);
  500. /**
  501. * 画view的内容
  502. */
  503. if (mIsDrawingViews) {
  504. view.draw(canvas);
  505. }
  506. /**
  507. * 画view的id
  508. */
  509. if (mIsDrawIds) {
  510. int id = view.getId();
  511. if (id != NO_ID) {
  512. canvas.drawText(nameForId(id), mTextOffset, mTextSize, mViewBorderPaint);
  513. }
  514. }
  515. canvas.restoreToCount(viewSaveCount);
  516. /**
  517. * 把刚刚应该显示但又设置了不可见的view从队列里取出来,后面再绘制
  518. */
  519. if (view instanceof ViewGroup) {
  520. ViewGroup viewGroup = (ViewGroup) view;
  521. int i = 0;
  522. int count = viewGroup.getChildCount();
  523. while (i < count) {
  524. if (mVisibilities.get(i)) {
  525. View child = viewGroup.getChildAt(i);
  526. child.setVisibility(VISIBLE);
  527. LayeredView childLayeredView = mLayeredViewPool.obtain();
  528. childLayeredView.set(child, layer + 1);
  529. mLayeredViewQueue.add(childLayeredView);
  530. }
  531. i++;
  532. }
  533. }
  534. }
  535. canvas.restoreToCount(saveCount);
  536. }
  537. /**
  538. * 根据id值反算出在布局文件中定义的id名字
  539. *
  540. * @param id
  541. * @return
  542. */
  543. private String nameForId(int id) {
  544. String name = mIdNames.get(id);
  545. if (name == null) {
  546. try {
  547. name = getResources().getResourceEntryName(id);
  548. } catch (NotFoundException e) {
  549. name = String.format("0x%8x", id);
  550. }
  551. mIdNames.put(id, name);
  552. }
  553. return name;
  554. }
  555. private static void log(String message, Object... object) {
  556. TLog.i("Scalpel", String.format(message, object));
  557. }
  558. private static class LayeredView {
  559. private View mView = null;
  560. /**
  561. * mView所处的层级
  562. */
  563. private int mLayer = 0;
  564. void set(View view, int layer) {
  565. mView = view;
  566. mLayer = layer;
  567. }
  568. void clear() {
  569. mView = null;
  570. mLayer = -1;
  571. }
  572. }
  573. private static abstract class Pool<T> {
  574. private Deque<T> mPool;
  575. Pool(int initialSize) {
  576. mPool = new ArrayDeque<T>(initialSize);
  577. for (int i = 0; i < initialSize; i++) {
  578. mPool.addLast(newObject());
  579. }
  580. }
  581. T obtain() {
  582. return mPool.isEmpty() ? newObject() : mPool.removeLast();
  583. }
  584. void restore(T instance) {
  585. mPool.addLast(instance);
  586. }
  587. protected abstract T newObject();
  588. }
  589. }

安卓动态分析工具【Android】3D布局分析工具的更多相关文章

  1. 教你使用Android SDK布局优化工具layoutopt

    创建好看的Android布局是个不小的挑战,当你花了数小时调整好它们适应多种设备后,你通常不想再重新调整,但笨重的嵌套布局效率往往非常低下,幸运的是,在Android SDK中有一个工具可以帮助你优化 ...

  2. Android布局分析工具HierarchyView的使用方法

    本文是从这里看到的:http://www.2cto.com/kf/201404/296960.html 如果我们想宏观的看看自己的布局,Android SDK中有一个工具HierarchyView.b ...

  3. Android动态逆向分析工具ZjDroid--脱壳神器

    项目地址:https://github.com/BaiduSecurityLabs/ZjDroid 前提条件: 1.Root手机一部 2.须要通过Xposed installer( http://dl ...

  4. Android 静态代码分析工具

    简评: 作者在文中提到的三个静态代码分析工具不是互相替代的关系,各有各的侧重点,如果有需要完全可以同时使用. 静态代码分析是指无需运行被测代码,仅通过分析或检查源程序的语法.结构.过程.接口等来检查程 ...

  5. Android手机流量分析工具介绍

    一.20 Best Android Hacking Apps And Tools Of 2018 首先罗列常见的Android手机hacking的工具 #1The Android Network Ha ...

  6. Android优化—— 内存分析工具 MAT 的使用

    1 内存泄漏的排查方法 Dalvik Debug Monitor Server (DDMS) 是 ADT插件的一部分,其中有两项功能可用于内存检查 : ·    heap 查看堆的分配情况 ·     ...

  7. [Android Memory] 内存分析工具 MAT 的使用

    转载自: http://blog.csdn.net/aaa2832/article/details/19419679 1 内存泄漏的排查方法 Dalvik Debug Monitor Server ( ...

  8. 网站运维工具使用iis日志分析工具分析iis日志(iis日志的配置)

    我们只能通过各种系统日志来分析网站的运行状况,对于部署在IIS上的网站来说,IIS日志提供了最有价值的信息,我们可以通过它来分析网站的响应情况,来判断网站是否有性能问题,或者存在哪些需要改进的地方 对 ...

  9. 测试工具-慢sql日志分析工具pt-query-digest

    pt-query-digest分析来自慢速日志文件,常规日志文件和二进制日志文件的MySQL查询.它还可以分析来自tcpdump的查询和MySQL协议数据. 开启慢日志 set global slow ...

随机推荐

  1. Python DB operation

    mysql http://www.cnblogs.com/zhangzhu/archive/2013/07/04/3172486.html 1.连接到本机上的MYSQL.首先打开DOS窗口,然后进入目 ...

  2. CentOS安装VLC

    For EL7: rpm -Uvh https://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm rpm - ...

  3. 选择性搜索(SS)算法

    一.目标检测和目标识别 目标识别(object recognition)是要指明一张图像中包含哪类目标.输入是图像,输出是图像中的目标属于的类别(class probability).目标检测是识别出 ...

  4. spring事务源码分析结合mybatis源码(二)

    让我们继续上篇,分析下如果有第二个调用进入的过程. 代码部分主要是下面这个: if (isExistingTransaction(transaction)) { return handleExisti ...

  5. Struts2优缺点

    优点: (1)  实现了MVC模式,层次结构清晰,使程序员只需关注业务逻辑的实现. (2)  丰富的标签库,大大提高了开发的效率. (3) Struts2提供丰富的拦截器实现. (4) 通过配置文件, ...

  6. Ubuntu16.04 Liunx下同时安装Anaconda2与Anaconda3

    先根据Ubuntu预装的python2.7来安装Anaconda2,然后将Anaconda3作为其环境安装在envs文件夹下. 重要提示:有一些软件需要py2.7的环境,比如XX-Net, 最好是先安 ...

  7. Django-DRF-图书增删改查 !!!

      自己封装的 class MyResponse(): def __init__(self): self.status = 100 self.msg = None @property def get_ ...

  8. vector 的用法--------------自绘资源

    创建 命名 选择根元素 当然你也可以选择其他元素 属性介绍vector 元素 name:定义该矢量图形的名字.通过名字找到这个矢量图width,height:定义该矢量图形的固有宽高(必须的,矢量图内 ...

  9. NOIP基本算法

    NOIP基本算法 1.二分 poj 2018 Best Cow Fences ▪ http://poj.org/problem?id=2018 ▪ 题意:给定一个正整数数列

  10. matplotlib 无法显示中文和负号的解决办法

    matplotlib无法显示中文和负号解决办法