随着最近几年移动市场蓬勃发展,引来大批人员投入到Android、iOS的开发前线,与此同时全国各大培训机构每个月都培养出成千上万名号称拥有2到3年工做经验的开发者。固然,这都已经不是什么秘密了,从目前来看,中国IT行业的主力军基本上都走过培训的道路。java
但问题是,这号称2~3年工做经验者,使招聘单位错误的认为:2~3年开发经验和刚刚结束的培训经历,基本上划等号。这就致使了企业大幅度提升用人标准,形成了为什么现在移动开发市场依旧火热,可是工做却很差找的现状。react
最悲惨的例子恐怕就是前几年IOS如日中天,可时间就过了一年开发人员就出现了井喷的状况,大量IOS开发者找不到工做。android
总的来讲:工做机会的确是不少,可是企业把用人要求都大大提升了。如何在万千人群中脱颖而出,走上人生巅峰,迎娶白富美,没有亮点,是万万不行滴。。。ios
接下来我就一块儿学习Android UI优化吧编程
你们在开发应用的时候或多或少都遇到过可感知的界面卡顿现象,尤为是在布局层次嵌套太多,存在没必要要的绘制,或者onDraw方法中执行了过多耗时操做、动画执行的次数过多等状况下,很容易形成此类状况。现在APP设计都要求界面美观、拥有更多的动画、图片等时尚元素从而打造良好的用户体验。可是大量复杂的渲染工做极可能形成Android系统压力过大,没法及时完成渲染工做。那么多久执行一次渲染,才能让界面流畅运行呢?性能优化
如上图所示,Android系统每隔16ms就会发送一个VSYNC信号(VSYNC:vertical synchronization 垂直同步,帧同步),触发对UI进行渲染,若是每次渲染都成功,这样就可以达到流畅的画面所须要的正常帧率:60fps。一旦这时候系统正在作大于16ms的耗时操做,系统就会没法响应VSYNC信号,执行渲染工做,致使发生丢帧现象。微信
你们在察觉到APP卡顿的时候,能够看看logcat控制台,会有
drop frames
相似的警告
本引用来自: [Android UI性能优化实战 识别绘制中的性能问题](http://blog.csdn.net/lmj623565791/article/details/45556391)网络
例如上图所示:若是你的某个操做花费时间是24ms,系统在获得VSYNC信号的时候就没法进行正常渲染,只能等待下一个VSYNC信号(第二个16ms)才能执行渲染工做。那么用户在32ms内看到的会是同一帧画面。(我就是感受google给的图给错了,明明是 32ms,怎么给标了一个34ms,难道是有其余寓意我没有理解上去???)微信开发
用户容易在UI执行动画、ListView、RecyclerView滑动的时候感知到界面的卡顿与不流畅现象。因此开发者必定要注意在设计布局时不要嵌套太多层,多使用 include
方法引入布局。同时不要让动画执行次数太多,致使CPU或者GPU负载太重。app
看到这里同窗可能会疑问:为何是16ms渲染一次,和60fps有什么关系呢?下面让咱们看一下原理:
16ms意味着着1000/60hz,至关于60fps。
那么只要解释为何是60fps,这个问题就迎刃而解:
这是由于人眼和大脑之间的写做没法感知超过60fps的画面更新,12fps大概相似手动快速翻动书籍的帧率,这是明显能够感知到不够顺滑的。
24fps使得人眼感知的是连续的线性运动,这实际上是归功于运动模糊效果,24fps是电影胶圈一般使用的帧率,由于这个帧率已经足够支撑大部分电影画面须要表达的内容,同时可以最大的减小费用支出。
可是低于30fps是
没法顺畅表现绚丽的画面内容的,此时就须要用到60fps来达到想要的效果,固然超过60fps是没有必要的
本引用来源:Google 发布 Android 性能优化典范 - 开源中国社区
过渡绘制是指屏幕上某个像素在同一帧的时间内绘制了屡次。在多层次的UI结构里面,若是不可见的UI也在作绘制操做,这就会致使某些像素区域被绘制了屡次,这就是很大程度上浪费了CPU和GPU资源。最最多见的过分绘制,就是设置了无用的背景颜色!!!
对于Overdraw这个问题仍是很容易发现的,咱们能够经过如下步骤打开显示GPU过分绘制(Show GPU Overrdraw)选项
设置 -> 开发者选项 -> 调试GPU过分绘制 -> 显示GPU过分绘制
打开之后以后,你会发现屏幕上有各类颜色,此时你能够切换到须要检测的程序与界面,对于各个色块的含义,请看下图:
蓝色,淡绿,淡红,深红表明了4种不一样程度的Overdraw状况,
蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色仍是能够接受的(若整个窗口是蓝色的,能够摆脱一层)。
绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是能够接受的但你应该尝试优化、减小它们。
淡红: 意味着overdraw 3倍。像素绘制了四次,小范围能够接受。
深红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。
咱们的目标就是尽可能减小红色Overdraw,看到更多的蓝色区域。
经过Hierarchy Viewer去检测渲染效率,去除没必要要的嵌套
经过Show GPU Overdraw去检测Overdraw,最终能够经过移除没必要要的背景。
(因为公司项目还处于保密阶段,因此摘取了Android UI性能优化实战 识别绘制中的性能问题的部分示例)
下面看一个简单的展现ListView的例子:
?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:background="@android:color/white" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="@dimen/narrow_space" android:textSize="@dimen/large_text_size" android:layout_marginBottom="@dimen/wide_space" android:text="@string/header_text"/> <ListView android:id="@+id/id_listview_chats" android:layout_width="match_parent" android:background="@android:color/white" android:layout_height="wrap_content" android:divider="@android:color/transparent" android:dividerHeight="@dimen/divider_height"/> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:paddingBottom="@dimen/chat_padding_bottom"> <ImageView android:id="@+id/id_chat_icon" android:layout_width="@dimen/avatar_dimen" android:layout_height="@dimen/avatar_dimen" android:src="@drawable/joanna" android:layout_margin="@dimen/avatar_layout_margin" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/darker_gray" android:orientation="vertical"> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/white" android:textColor="#78A" android:orientation="horizontal"> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:padding="@dimen/narrow_space" android:text="@string/hello_world" android:gravity="bottom" android:id="@+id/id_chat_name" /> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:textStyle="italic" android:text="@string/hello_world" android:padding="@dimen/narrow_space" android:id="@+id/id_chat_date" /> </RelativeLayout> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/narrow_space" android:background="@android:color/white" android:text="@string/hello_world" android:id="@+id/id_chat_msg" /> </LinearLayout> </LinearLayout>
package com.zhy.performance_01_render; import android.os.Bundle; import android.os.PersistableBundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; /** * Created by zhy on 15/4/29. */ public class OverDrawActivity01 extends AppCompatActivity { private ListView mListView; private LayoutInflater mInflater ; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_overdraw_01); mInflater = LayoutInflater.from(this); mListView = (ListView) findViewById(R.id.id_listview_chats); mListView.setAdapter(new ArrayAdapter<Droid>(this, -1, Droid.generateDatas()) { @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null ; if(convertView == null) { convertView = mInflater.inflate(R.layout.chat_item,parent,false); holder = new ViewHolder(); holder.icon = (ImageView) convertView.findViewById(R.id.id_chat_icon); holder.name = (TextView) convertView.findViewById(R.id.id_chat_name); holder.date = (TextView) convertView.findViewById(R.id.id_chat_date); holder.msg = (TextView) convertView.findViewById(R.id.id_chat_msg); convertView.setTag(holder); }else { holder = (ViewHolder) convertView.getTag(); } Droid droid = getItem(position); holder.icon.setBackgroundColor(0x44ff0000); holder.icon.setImageResource(droid.imageId); holder.date.setText(droid.date); holder.msg.setText(droid.msg); holder.name.setText(droid.name); return convertView; } class ViewHolder { ImageView icon; TextView name; TextView date; TextView msg; } }); } }
实体的代码
package com.zhy.performance_01_render; import java.util.ArrayList; import java.util.List; public class Droid { public String name; public int imageId; public String date; public String msg; public Droid(String msg, String date, int imageId, String name) { this.msg = msg; this.date = date; this.imageId = imageId; this.name = name; } public static List<Droid> generateDatas() { List<Droid> datas = new ArrayList<Droid>(); datas.add(new Droid("Lorem ipsum dolor sit amet, orci nullam cra", "3分钟前", -1, "alex")); datas.add(new Droid("Omnis aptent magnis suspendisse ipsum, semper egestas", "12分钟前", R.drawable.joanna, "john")); datas.add(new Droid("eu nibh, rhoncus wisi posuere lacus, ad erat egestas", "17分钟前", -1, "7heaven")); datas.add(new Droid("eu nibh, rhoncus wisi posuere lacus, ad erat egestas", "33分钟前", R.drawable.shailen, "Lseven")); return datas; } }
如今的效果是:
注意,咱们的需求是总体是Activity是个白色的背景。
开启Show GPU Overdraw之后:
对比上面的参照图,能够发现一个简单的ListView展现Item,居然不少地方被过分绘制了4X 。 那么,其实主要缘由是因为该布局文件中存在不少没必要要的背景,仔细看上述的布局文件,那么开始移除吧。
* 没必要要的Background 1
咱们主布局的文件已是background为white了,那么能够移除ListView的白色背景
没必要要的Background 2
Item布局中的LinearLayout的android:background=”@android:color/darker_gray”
没必要要的Background 3
Item布局中的RelativeLayout的android:background=”@android:color/white”
没必要要的Background 4
Item布局中id为id_msg的TextView的android:background=”@android:color/white”
这四个没必要要的背景也比较好找,那么移除后的效果是:
对比以前的是否是好多了~~~接下来还存在一些没必要要的背景,你还能找到吗?
* 没必要要的Background 5
这个背景比较难发现,主要须要看Adapter的getView的代码,上述代码你会发现,首先为每一个icon设置了背景色(主要是当没有icon图的时候去显示),而后又设置了一个头像。那么就形成了overdraw,有头像的彻底不必去绘制背景,全部修改代码:
Droid droid = getItem(position); if(droid.imageId ==-1) { holder.icon.setBackgroundColor(0x4400ff00); holder.icon.setImageResource(android.R.color.transparent); }else { holder.icon.setImageResource(droid.imageId); holder.icon.setBackgroundResource(android.R.color.transparent); }
ok,还有最后一个,这个也是很是容易被忽略的。
* 没必要要的Background 6
记得咱们以前说,咱们的这个Activity要求背景色是白色,咱们的确在layout中去设置了背景色白色,那么这里注意下,咱们的Activity的布局最终会添加在DecorView
中,这个View会中的背景是否是就没有必要了,因此咱们但愿调用mDecor.setWindowBackground(drawable)
;,那么能够在Activity
调用getWindow().setBackgroundDrawable(null);
setContentView(R.layout.activity_overdraw_01); getWindow().setBackgroundDrawable(null);
ok,一个简单的listview显示item,咱们一共找出了6个没必要要的背景,如今再看最后的Show GPU Overdraw 与最初的比较。
ok,对比参照图,基本已经达到了最优的状态。
相信你们使用的最多的布局标签就是 <include>
了。 <include>
的用途就是将布局中的公共部分提取出来以供其余Layout使用,从而实现布局的优化。这种布局的编写方式大大便利了开发,我的感受这种思想和React Native中的面向组件编程思想有着殊途同归之妙,都是将特定功能抽取成为一个独立的组件,只要控制其中传入的参数就能够满局不一样的需求。例如:咱们在编辑Android界面的时候经常须要添加标题栏,若是在不使用<include>
的状况下,只能在每个须要显示标题栏的xml文件中编写重复的代码,费时费力。可是只要咱们将这个须要屡次被使用的标题栏布局抽取成一个独立的xml文件,而后在须要的地方使用<include>
标签引入便可。
下面以在一个布局main.xml中用include引入另外一个布局foot.xml为例。main.mxl代码以下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/simple_list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="80dp" /> <include android:id="@+id/my_foot_ly" layout="@layout/foot" /> </RelativeLayout>
其中include引入的foot.xml为公用的页面底部,代码以下:
?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/my_foot_parent_id"> <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_above="@+id/title_tv"/> <TextView android:id="@+id/title_tv" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_alignParentBottom="true" android:text="@string/app_name" /> </RelativeLayout>
<include>
使用起来很简单,只须要指定一个layout属性为须要包含的布局文件便可。固然还能够根据需求指定 android:id
、 android:height
、android:width
属性来覆盖被引入根节点属性。
注意
在使用<include>
标签最多见的问题就是 findViewById
查找不到<include>
进来地控件的跟布局,而这个问题出现的前提就是在include的时候设置了id
。当设置id后,原有的foot.xml跟布局Id已经被替换为在 <include>
中指定的id
了,因此在 findViewById
查找原有id的时候就会报空指针异常。
View titleView = findViewById(R.id.my_foot_parent_id) ; // 此时id已经被覆盖 titleView 为空,找不到。此时空指针
View titleView = findViewById(R.id.my_foot_ly) ; //重写指定id便可
<include>
标签简单的说就是至关与将layout
指定的布局总体引入到main.xml中。因此咱们就和操做直接在main.xml中的布局是同样的只不过有一个上面提到的更布局id
被覆盖的问题。
ViewStub
标签同include
同样能够用来引入一个外部布局。不一样的是,ViewStub
引入的布局默认是不会显示也不会占用位置的,从而在解析的layout
的时候能够节省cpu、内存等硬件资源。
ViewStub经常用来引入那些默认不显示,只在特定状况下才出现的布局,例如:进度条,网络链接失败显示的提示布局等。
下面以在一个布局main.xml中加入网络错误时的提示页面network_error.xml为例。main.mxl代码以下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > …… <ViewStub android:id="@+id/network_error_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout="@layout/network_error" /> </RelativeLayout>
其中network_error.xml为只有在网络错误时才须要显示的布局,默认不会被解析,示例代码以下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/network_setting" android:layout_width="@dimen/dp_160" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="@string/network_setting" /> <Button android:id="@+id/network_refresh" android:layout_width="@dimen/dp_160" android:layout_height="wrap_content" android:layout_below="@+id/network_setting" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/dp_10" android:text="@string/network_refresh" /> </RelativeLayout>
在代码中经过(ViewStub)findViewById(id)找到ViewStub,经过stub.inflate()展开ViewStub,而后获得子View,以下:
private View networkErrorView; private void showNetError() { if (networkErrorView != null) { networkErrorView.setVisibility(View.VISIBLE); }else{ ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout); if(stub !=null){ networkErrorView = stub.inflate(); // 效果和上面是同样的 // stub.setVisibility(View.VISIBLE); // ViewStub被展开后的布局所替换 // networkErrorView = findViewById(R.id.network_error_layout); // 获取展开后的布局 } } } private void showNormal() { if (networkErrorView != null) { networkErrorView.setVisibility(View.GONE); } }
在上面showNetError()中展开了ViewStub,同时咱们对networkErrorView进行了保存,这样下次不用继续inflate。
注意这里我对ViewStub的实例进行了一个非空判断,这是由于ViewStub在XML中定义的id只在一开始有效,一旦ViewStub中指定的布局加载以后,这个id也就失败了,那么此时findViewById()获得的值也会是空
viewstub标签大部分属性同include标签相似。
注意:
根据需求咱们有时候须要将View的可讲性设置为GONE,在inflate时,这个View以及他的字View仍是会被解析的。因此使用<ViewStub>
就能避免解析其中的指定的布局文件。从而加快布局的解析时间,节省cpu内存等硬件资源。同时ViewStub所加载的布局是不可使用<merge>
标签的
在使用了include后可能会致使布局嵌套太多,致使视图节点太多,减慢了解析速度。
merge标签可用于两种典型状况:
1. 布局顶接点是FrameLayout
而且不须要设置background
或者padding
等属性,可以使用merge
代替,由于Activity
内容视图的parent view
就是一个FrameLayout
,因此能够用merge
消除只能一个。
2. 某布局做为子布局被其余布局include时,使用merge看成该布局的顶节点,这样在被引入时,顶结点会自动被忽略,而其本身点所有合并到主布局中。
以【4.2.1 标签 】中的代码示例为例,使用用hierarchy viewer查看main.xml布局以下图:
能够发现多了一层不必的RelativeLayout,将foot.xml中RelativeLayout改成merge,以下:
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_above="@+id/text"/> <TextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="@dimen/dp_40" android:layout_alignParentBottom="true" android:text="@string/app_name" /> </merge>
运行后再次用hierarchy viewer查看main.xml布局以下图:
这样就不会有多余的RelativeLayout节点了。