吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4443|回复: 4
收起左侧

[Android 分享] Android 自定义布局 性能问题 初探

  [复制链接]
王旭东 发表于 2015-1-7 12:28
Android 自定义布局 性能问题 初探                 

大家在写android 代码的时候,基本上都使用过如下几种布局 RelativeLayout,LinearLayout, FrameLayout
但是很多时候 这几种布局 也无法满足我们的使用。于是我们会考虑用自定义布局,使用自定义布局会有几个优点
比如可以减少view的使用啊,让ui显示的更加有效率啊,以及实现一些原生控件无法实现的效果。

我们首先去github上 下载一个开源项目 https://github.com/lucasr/android-layout-samples
注意这个项目是基于android studio 结构的。你如果用Eclipse来导入是导入不成功的。

最近github上很多开源项目都开始支持android studio了。所以还是建议大家拥抱下谷歌的新ide。

然后这个项目的作者是http://lucasr.org/about/ 就是国外一个很牛逼的android 工程师,我们就以他
的开源项目以及博客 来感受一下 自定义布局的性能。

这个项目运行起来以后实际上就是仿照的twitter的一些效果。图片库用的是picasso。有兴趣的同学可以
去http://square.github.io/picasso/  这个地方看一下这个图片库。
然后我们来看第一个自定义ui  TweetCompositeView

1 /* 2 * Copyright (C) 2014 Lucas Rocha 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 *     http://www.apache.org/licenses/LICENSE-2.0 9 *10 * Unless required by applicable law or agreed to in writing, software11 * distributed under the License is distributed on an "AS IS" BASIS,12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13 * See the License for the specific language governing permissions and14 * limitations under the License.15  */16 17 package org.lucasr.layoutsamples.widget;18 19 import android.content.Context;20 import android.text.TextUtils;21 import android.util.AttributeSet;22 import android.view.LayoutInflater;23 import android.view.View;24 import android.widget.ImageView;25 import android.widget.RelativeLayout;26 import android.widget.TextView;27 28 import org.lucasr.layoutsamples.adapter.Tweet;29 import org.lucasr.layoutsamples.adapter.TweetPresenter;30 import org.lucasr.layoutsamples.app.R;31 import org.lucasr.layoutsamples.util.ImageUtils;32 33 import java.util.EnumMap;34 import java.util.EnumSet;35 36 public class TweetCompositeView extends RelativeLayout implements TweetPresenter {37     private final ImageView mProfileImage;38     private final TextView mAuthorText;39     private final TextView mMessageText;40     private final ImageView mPostImage;41     private final EnumMap<Action, ImageView> mActionIcons;42 43     public TweetCompositeView(Context context, AttributeSet attrs) {44         this(context, attrs, 0);45     }46 47     public TweetCompositeView(Context context, AttributeSet attrs, int defStyleAttr) {48         super(context, attrs, defStyleAttr);49 50         LayoutInflater.from(context).inflate(R.layout.tweet_composite_view, this, true);51         mProfileImage = (ImageView) findViewById(R.id.profile_image);52         mAuthorText = (TextView) findViewById(R.id.author_text);53         mMessageText = (TextView) findViewById(R.id.message_text);54         mPostImage = (ImageView) findViewById(R.id.post_image);55 56         mActionIcons = new EnumMap(Action.class);57         for (Action action : Action.values()) {58             final ImageView icon;59             switch (action) {60                 case REPLY:61                     icon = (ImageView) findViewById(R.id.reply_action);62                     break;63 64                 case RETWEET:65                     icon = (ImageView) findViewById(R.id.retweet_action);66                     break;67 68                 case FAVOURITE:69                     icon = (ImageView) findViewById(R.id.favourite_action);70                     break;71 72                 default:73                     throw new IllegalArgumentException("Unrecognized tweet action");74             }75 76             mActionIcons.put(action, icon);77         }78     }79 80     @Override81     public boolean shouldDelayChildPressedState() {82         return false;83     }84 85     @Override86     public void update(Tweet tweet, EnumSet<UpdateFlags> flags) {87         mAuthorText.setText(tweet.getAuthorName());88         mMessageText.setText(tweet.getMessage());89 90         final Context context = getContext();91         ImageUtils.loadImage(context, mProfileImage, tweet.getProfileImageUrl(), flags);92 93         final boolean hasPostImage = !TextUtils.isEmpty(tweet.getPostImageUrl());94         mPostImage.setVisibility(hasPostImage ? View.VISIBLE : View.GONE);95         if (hasPostImage) {96             ImageUtils.loadImage(context, mPostImage, tweet.getPostImageUrl(), flags);97         }98     }99 }


我们可以看一下这个自定义ui。实际上这个自定义ui非常简单,我们工作中也经常这样使用自定义ui。
他一般就是这么使用的:
1 继承一个layout。当然这个layout可以是相对布局 也可以是流布局
2 在构造函数里 inflate 我们的布局文件 同时初始化我们的自定义布局的子元素
3 增加一些对应的方法 来更新我们的元素 比如说 update 这个方法 就是来做这个工作的。

然后我们来看一下这个布局对应的布局文件


1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- 3   ~ Copyright (C) 2014 Lucas Rocha 4   ~ 5   ~ Licensed under the Apache License, Version 2.0 (the "License"); 6   ~ you may not use this file except in compliance with the License. 7   ~ You may obtain a copy of the License at 8   ~ 9   ~     http://www.apache.org/licenses/LICENSE-2.010   ~11   ~ Unless required by applicable law or agreed to in writing, software12   ~ distributed under the License is distributed on an "AS IS" BASIS,13   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14   ~ See the License for the specific language governing permissions and15   ~ limitations under the License.16   -->17 18 <merge xmlns:android="http://schemas.android.com/apk/res/android"19     android:layout_width="match_parent"20     android:layout_height="match_parent">21 22     <ImageView23         android:id="@+id/profile_image"24         android:layout_width="@dimen/tweet_profile_image_size"25         android:layout_height="@dimen/tweet_profile_image_size"26         android:layout_marginRight="@dimen/tweet_content_margin"27         android:scaleType="centerCrop"/>28 29     <TextView30         android:id="@+id/author_text"31         android:layout_width="fill_parent"32         android:layout_height="wrap_content"33         android:layout_toRightOf="@id/profile_image"34         android:layout_alignTop="@id/profile_image"35         android:textColor="@color/tweet_author_text_color"36         android:textSize="@dimen/tweet_author_text_size"37         android:singleLine="true"/>38 39     <TextView40         android:id="@+id/message_text"41         android:layout_width="fill_parent"42         android:layout_height="wrap_content"43         android:layout_below="@id/author_text"44         android:layout_alignLeft="@id/author_text"45         android:textColor="@color/tweet_message_text_color"46         android:textSize="@dimen/tweet_message_text_size"/>47 48     <ImageView49         android:id="@+id/post_image"50         android:layout_width="fill_parent"51         android:layout_height="@dimen/tweet_post_image_height"52         android:layout_below="@id/message_text"53         android:layout_alignLeft="@id/message_text"54         android:layout_marginTop="@dimen/tweet_content_margin"55         android:scaleType="centerCrop"/>56 57     <LinearLayout android:layout_width="fill_parent"58         android:layout_height="wrap_content"59         android:layout_below="@id/post_image"60         android:layout_alignLeft="@id/message_text"61         android:layout_marginTop="@dimen/tweet_content_margin"62         android:orientation="horizontal">63 64         <ImageView65             android:id="@+id/reply_action"66             android:layout_width="0dp"67             android:layout_height="@dimen/tweet_icon_image_size"68             android:layout_weight="1"69             android:src="@drawable/tweet_reply"70             android:scaleType="fitStart"/>71 72         <ImageView73             android:id="@+id/retweet_action"74             android:layout_width="0dp"75             android:layout_height="@dimen/tweet_icon_image_size"76             android:layout_weight="1"77             android:src="@drawable/tweet_retweet"78             android:scaleType="fitStart"/>79 80         <ImageView81             android:id="@+id/favourite_action"82             android:layout_width="0dp"83             android:layout_height="@dimen/tweet_icon_image_size"84             android:layout_weight="1"85             android:src="@drawable/tweet_favourite"86             android:scaleType="fitStart"/>87 88     </LinearLayout>89 90 </merge>


我们可以来看一下这个布局 其中包含了 LinearLayout 这个布局。 我们知道在android里面 linearlayout和relativelayout 是非常复杂的ui
这种viewgroup 会不断的检测子view的大小和布局位置。 所以实际上效率是有损失的。所以我们如果想更近一步的 优化我们的ui效率
我们要尽量避免使用这种高级的viewgroup
比如我们可以来看看这个view

  1 /*  2 * Copyright (C) 2014 Lucas Rocha  3 *  4 * Licensed under the Apache License, Version 2.0 (the "License");  5 * you may not use this file except in compliance with the License.  6 * You may obtain a copy of the License at  7 *  8 *     http://www.apache.org/licenses/LICENSE-2.0  9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15  */ 16 17 package org.lucasr.layoutsamples.widget; 18 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.util.AttributeSet; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.widget.ImageView; 26 import android.widget.TextView; 27 28 import org.lucasr.layoutsamples.adapter.Tweet; 29 import org.lucasr.layoutsamples.adapter.TweetPresenter; 30 import org.lucasr.layoutsamples.app.R; 31 import org.lucasr.layoutsamples.util.ImageUtils; 32 33 import java.util.EnumMap; 34 import java.util.EnumSet; 35 36 public class TweetLayoutView extends ViewGroup implements TweetPresenter { 37     private final ImageView mProfileImage; 38     private final TextView mAuthorText; 39     private final TextView mMessageText; 40     private final ImageView mPostImage; 41     private final EnumMap<Action, View> mActionIcons; 42 43     public TweetLayoutView(Context context, AttributeSet attrs) { 44         this(context, attrs, 0); 45     } 46 47     public TweetLayoutView(Context context, AttributeSet attrs, int defStyleAttr) { 48         super(context, attrs, defStyleAttr); 49 50         LayoutInflater.from(context).inflate(R.layout.tweet_layout_view, this, true); 51         mProfileImage = (ImageView) findViewById(R.id.profile_image); 52         mAuthorText = (TextView) findViewById(R.id.author_text); 53         mMessageText = (TextView) findViewById(R.id.message_text); 54         mPostImage = (ImageView) findViewById(R.id.post_image); 55 56         mActionIcons = new EnumMap(Action.class); 57         for (Action action : Action.values()) { 58             final int viewId; 59             switch (action) { 60                 case REPLY: 61                     viewId = R.id.reply_action; 62                     break; 63 64                 case RETWEET: 65                     viewId = R.id.retweet_action; 66                     break; 67 68                 case FAVOURITE: 69                     viewId = R.id.favourite_action; 70                     break; 71 72                 default: 73                     throw new IllegalArgumentException("Unrecognized tweet action"); 74             } 75 76             mActionIcons.put(action, findViewById(viewId)); 77         } 78     } 79 80     private void layoutView(View view, int left, int top, int width, int height) { 81         MarginLayoutParams margins = (MarginLayoutParams) view.getLayoutParams(); 82         final int leftWithMargins = left + margins.leftMargin; 83         final int topWithMargins = top + margins.topMargin; 84 85         view.layout(leftWithMargins, topWithMargins, 86                     leftWithMargins + width, topWithMargins + height); 87     } 88 89     private int getWidthWithMargins(View child) { 90         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 91         return child.getWidth() + lp.leftMargin + lp.rightMargin; 92     } 93 94     private int getHeightWithMargins(View child) { 95         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 96         return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 97     } 98 99     private int getMeasuredWidthWithMargins(View child) {100         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();101         return child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;102     }103 104     private int getMeasuredHeightWithMargins(View child) {105         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();106         return child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;107     }108 109     @Override110     public boolean shouldDelayChildPressedState() {111         return false;112     }113 114     @Override115     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {116         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);117 118         int widthUsed = 0;119         int heightUsed = 0;120 121         measureChildWithMargins(mProfileImage,122                                 widthMeasureSpec, widthUsed,123                                 heightMeasureSpec, heightUsed);124         widthUsed += getMeasuredWidthWithMargins(mProfileImage);125 126         measureChildWithMargins(mAuthorText,127                                 widthMeasureSpec, widthUsed,128                                 heightMeasureSpec, heightUsed);129         heightUsed += getMeasuredHeightWithMargins(mAuthorText);130 131         measureChildWithMargins(mMessageText,132                                 widthMeasureSpec, widthUsed,133                                 heightMeasureSpec, heightUsed);134         heightUsed += getMeasuredHeightWithMargins(mMessageText);135 136         if (mPostImage.getVisibility() != View.GONE) {137             measureChildWithMargins(mPostImage,138                                     widthMeasureSpec, widthUsed,139                                     heightMeasureSpec, heightUsed);140             heightUsed += getMeasuredHeightWithMargins(mPostImage);141         }142 143         int maxIconHeight = 0;144         for (Action action : Action.values()) {145             final View iconView = mActionIcons.get(action);146             measureChildWithMargins(iconView,147                                     widthMeasureSpec, widthUsed,148                                     heightMeasureSpec, heightUsed);149 150             final int height = getMeasuredHeightWithMargins(iconView);151             if (height > maxIconHeight) {152                 maxIconHeight = height;153             }154 155             widthUsed += getMeasuredWidthWithMargins(iconView);156         }157         heightUsed += maxIconHeight;158 159         int heightSize = heightUsed + getPaddingTop() + getPaddingBottom();160         setMeasuredDimension(widthSize, heightSize);161     }162 163     @Override164     protected void onLayout(boolean changed, int l, int t, int r, int b) {165         final int paddingLeft = getPaddingLeft();166         final int paddingTop = getPaddingTop();167 168         int currentTop = paddingTop;169 170         layoutView(mProfileImage, paddingLeft, currentTop,171                    mProfileImage.getMeasuredWidth(),172                    mProfileImage.getMeasuredHeight());173 174         final int contentLeft = getWidthWithMargins(mProfileImage) + paddingLeft;175         final int contentWidth = r - l - contentLeft - getPaddingRight();176 177         layoutView(mAuthorText, contentLeft, currentTop,178                    contentWidth, mAuthorText.getMeasuredHeight());179         currentTop += getHeightWithMargins(mAuthorText);180 181         layoutView(mMessageText, contentLeft, currentTop,182                 contentWidth, mMessageText.getMeasuredHeight());183         currentTop += getHeightWithMargins(mMessageText);184 185         if (mPostImage.getVisibility() != View.GONE) {186             layoutView(mPostImage, contentLeft, currentTop,187                        contentWidth, mPostImage.getMeasuredHeight());188 189             currentTop += getHeightWithMargins(mPostImage);190         }191 192         final int iconsWidth = contentWidth / mActionIcons.size();193         int iconsLeft = contentLeft;194 195         for (Action action : Action.values()) {196             final View icon = mActionIcons.get(action);197 198             layoutView(icon, iconsLeft, currentTop,199                        iconsWidth, icon.getMeasuredHeight());200             iconsLeft += iconsWidth;201         }202     }203 204     @Override205     public LayoutParams generateLayoutParams(AttributeSet attrs) {206         return new MarginLayoutParams(getContext(), attrs);207     }208 209     @Override210     protected LayoutParams generateDefaultLayoutParams() {211         return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);212     }213 214     @Override215     public void update(Tweet tweet, EnumSet<UpdateFlags> flags) {216         mAuthorText.setText(tweet.getAuthorName());217         mMessageText.setText(tweet.getMessage());218 219         final Context context = getContext();220         ImageUtils.loadImage(context, mProfileImage, tweet.getProfileImageUrl(), flags);221 222         final boolean hasPostImage = !TextUtils.isEmpty(tweet.getPostImageUrl());223         mPostImage.setVisibility(hasPostImage ? View.VISIBLE : View.GONE);224         if (hasPostImage) {225             ImageUtils.loadImage(context, mPostImage, tweet.getPostImageUrl(), flags);226         }227     }228 }

然后看看他的布局文件
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2014 Lucas Rocha
  ~
  ~ 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.
  -->

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/profile_image"
        android:layout_width="@dimen/tweet_profile_image_size"
        android:layout_height="@dimen/tweet_profile_image_size"
        android:layout_marginRight="@dimen/tweet_content_margin"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/author_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/tweet_author_text_color"
        android:textSize="@dimen/tweet_author_text_size"
        android:singleLine="true"/>

    <TextView
        android:id="@+id/message_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/tweet_content_margin"
        android:textColor="@color/tweet_message_text_color"
        android:textSize="@dimen/tweet_message_text_size"/>

    <ImageView
        android:id="@+id/post_image"
        android:layout_width="fill_parent"
        android:layout_height="@dimen/tweet_post_image_height"
        android:layout_marginBottom="@dimen/tweet_content_margin"
        android:scaleType="centerCrop"/>

    <ImageView
        android:id="@+id/reply_action"
        android:layout_width="@dimen/tweet_icon_image_size"
        android:layout_height="@dimen/tweet_icon_image_size"
        android:src="@drawable/tweet_reply"
        android:scaleType="fitStart"/>

    <ImageView
        android:id="@+id/retweet_action"
        android:layout_width="@dimen/tweet_icon_image_size"
        android:layout_height="@dimen/tweet_icon_image_size"
        android:src="@drawable/tweet_retweet"
        android:scaleType="fitStart"/>

    <ImageView
        android:id="@+id/favourite_action"
        android:layout_width="@dimen/tweet_icon_image_size"
        android:layout_height="@dimen/tweet_icon_image_size"
        android:src="@drawable/tweet_favourite"
        android:scaleType="fitStart"/>

</merge>





看一下我们就会发现,TweetLayoutView 是通过 onMeasure onlayout 自己来决定子布局的大小和位置的
完全跟linearlayout和relativelayout 没有任何关系。这样性能上就有极大的提高。

当然我们不可能自己实现 所有的layout对吧,不然的话 我们就都去谷歌了。。。。哈哈。
但是可以有选择的把你app里 ui最复杂的地方 选择性的优化他。提高 ui渲染的效率。

最后我们看一下前面这个TweetLayoutView  这个布局实际上还不是最优解。
因为里面有很多系统自带的imageview 和textview。

我们可以打开一下设置--开发者选项-显示布局边界 这个功能
这个功能可以把你当前app的 布局边界全部标示出来
我们可以打开android 版的gmail 随便点击个列表。
可以看一下他们listview里的每个item 布局边界都是在外面。里面没有任何布局边界。
所以可以得知gmail的listview里的 item 是自己重写的一整个view 里面没有使用
任何系统自带的textview 或者是imageview 之类的。
这样就是ui终极进化了。。。。。。

当然这么做 工作量很多,而且很多地方需要考虑。比如你自己画文本是简单了,效率是提高了
但是textview 的文本截断呢?你能做么?imageview里的图片缩放呢?你能做么?

所以我们在自定义布局的时候 除了考虑ui实现的效率,我们还需要着重考虑实现的难度,和技术上的风险。
个人感觉只需要修改你app最卡顿的地方的布局 即可。尤其是listview viewpager里面的item
这一般在低端手机上 确实会出现卡帧的现象。其他地方看情况修改。



发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Heart 发表于 2015-1-7 12:54
好的,顶楼主
查水表的叔叔 发表于 2015-1-7 13:08
GA゛木子 发表于 2015-1-7 13:54 来自手机
覲年lt 发表于 2015-1-11 08:34
布局 是什么
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 13:34

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表