Long Luo's Life Notes

每一天都是奇迹

By Long Luo

周星驰的电影《功夫》里面借火云邪神之口说出了一句至理名言:“天下武功,唯快不破”。

在移动互联网时代,同样如此,留给一个公司的窗口往往只有很短的时间,如何把握住这个时机,迅速开发出产品,成为至关重要的一环。相对传统互联网时代的PC产品,用户对移动端产品的容忍度更低。而一款移动应用在推出的时候可能只是接近完成的状态,这就需要通过快速的迭代开发来更新产品,不断完善产品来留住用户。同时,通过更新产品也能唤醒一些沉默用户,让一些原本下载了应用但使用次数非常少的用户给该应用多一次机会。

所以快速迭代成为移动互联网时代的一个重要生存法则。

1. 为什么需要性能优化?

上面说到,在时间窗口期内开发出产品是极端重要的,但是虽然基本功能我们实现了,但是开发出来的产品代码运行的效率怎么样呢?我们的App用户给用户的体验如何呢?

  • 我们的App在低端机上经常ANR、闪退、卡顿等
  • 我们的App在其他分辨率上显示惨不忍睹?
  • 我们的App在不同网络的情况下如何处理的

我们的App体验如此之差,导致大量的用户流失。这些迫使我们认识到性能优化是非常重要,某种程度上甚至超过了新功能的开发。

也验证了一句话:“别人有的我们也有,而且比他们的要好要快。

做Android开发已经3年了,期间也开发了不少App了,最开始写App的时候,只追求迅速完成所需要的功能,后来随着对相关知识的熟悉,再回头看之前写的代码,也知道有哪些改进和优化的手段了。性能优化,一方面需要自身能力的提高,另外一方面,也需要有意识去学习优化技术,并应用于自身项目的开发中。

2. 性能优化技术

我们的Android App开发除了NDK之外,使用的都是Java语言,而Java语言是一种基于虚拟机JVM运行的语言,所以相比C/C++语言来说,效率是比较低的。Java需要占用大量内存来换取执行速度,而不定期的GC机制,直接导致Android界面的卡顿现象。

相比Apple的iOS,Android要面对无数不同的硬件组合,分辨率,驱动等,导致App质量参差不齐。

性能优化技术,简而言之,就是提高我们程序的性能,让我们的应用更快,更少使用CPU资源,更少使用内存。

3. 性能优化笔记提纲

性能优化是一个非常大的课题,在这里目前准备写8篇笔记来记录,提纲如下:

  1. 如何书写优秀代码?
  2. 程序性能测试
  3. App内存优化
  4. 图片缓存技术(ImageCache及Image SD卡缓存技术)
  5. 数据库优化
  6. 网络优化
  7. App UI优化
  8. 性能调优工具

Long Luo Version0.1 Created at PM14:10 April 12th, 2014 @Shenzhen, China. Long Luo Version0.1 Completed at PM15:25 April 12th, 2014 @Shenzhen, China.

By Long Luo

从小到大,虽然玩过的游戏不少,但是从写程序开始,目前为此仅仅写过2个游戏。其一是2011年在MTK平台下写的贪食蛇,其二是2010年在嵌入式开发板上写过一个迷宫的游戏。第一个代码量大概有3000行左右,第二个有2000行左右。

这2个游戏都很简单,而且网上有很多现成的例子可供参考,因此难度也比较低。

这2天把拖延了好久的《Android应用开发揭秘》的游戏引擎的那一章看完了,收获还是很大,在此写一篇读书笔记。

关于Game Engine,我能想到的几个问题:

  1. 游戏引擎是什么?
  2. Game Engine是为了解决什么问题?
  3. Game Engine的架构是什么?
  4. 如何设计一款游戏引擎?
  5. 游戏引擎包含哪些模块?

下面就来探讨几个问题:

一、Game Engine是什么?

游戏产业在全球来看是一个很大的产业,一款游戏大作包含了非常多的元素。游戏涉及到剧情、人物、任务、关卡、地图、画质、美术、音乐、网络等多种元素。开发一款游戏实际上需要耗费非常多的资源,据说North Star的《GTA V》耗资几亿美元。正因为如此,在开发项目过程中,尽可能复用之前项目成功的东西就非常重要。

一款游戏中,Game Engine直接控制着剧情、关卡、美工、音乐、操作等内容,将游戏的所有元素捆绑在一起。

一般来说,一款Game Engine需要包含以下模块:

  1. 基本框架(渲染、逻辑、物理 等等各部分如何组装)
  2. 资源管理
  3. 渲染
  4. 基本逻辑(网游还要解决逻辑的同步问题)
  5. 物理(有时候和逻辑合并)

———–分割线,以下是重要但较为独立的部分————- 6. UI 7. 音乐音效 8. 网络 9. 脚本(有些类型的游戏引擎需要脚本和逻辑的关联性非常强,有些脚本则比较独立)

二、Game Engine为了解决什么问题?

Game Engine实际上有效的减少开发者编写程序时的冗余劳动,同时增强游戏的可移植性

Engine就是游戏的框架,我们需要往框架中填充内容就可以形成一个游戏。

引擎,就是一系列的工具和生产链,像Unreal 3,Unity这样的成熟引擎,用起来非常方便,就是因为它的关卡/场景编辑器十分宜用,支持多种脚本语言。这类引擎运用恰当的话,理论上能将关卡调试和物件流水线的大部分工作从程序员那里完全移出。

三、Game Engine的架构

游戏 = 引擎(程序) + 资源(图像、声音、动画等)

目前的Game Engine的架构都是Model-View-Controller架构,逻辑和显示分开,由一个逻辑控制流来协调Client的请求和Server的行动。

  1. View: 负责界面回执
  2. Controller:处理工作流程的创建和种植,用户输入,各种事件的处理
  3. Model: 模型、逻辑,程序的功能实现

消息循环->更新数据->绘制各节点 这是绘制的基本结构基本不会有大的改变。

各种引擎的变种很大部分是在游戏逻辑上的封装。脚本也好,直接写代码也好。比如较为古老的数据与函数分离,以C语言为代表。大行其道的类结构。以c++为代表。以及现在光环日耀的CBSE,基于组件的架构

阅读全文 »

By Long Luo

最近一个月在驾校学车,前2次去练车,总是不得要领,连曲线行驶开不好,被教练狂K。经历了当学渣的痛苦之后,痛定思痛,立志成为学霸

一、 面临问题:

我分析遇到的问题之后,得出我面临的几个问题:

  1. 方向盘打法不规范;
  2. 离合器踩的不到位,车速偏快;
  3. 不清楚方向盘转的度数和车轮转的度数关系;
  4. 不记得当前车轮方向。

清楚自己的问题就好办了,那就各个击破。

昨天去实际操作了半个小时,果然大有进展。

二、 转向理论:

我学习新东西的时候,往往会学习下理论,就是搞清楚*为什么的问题,目的是为了做到理论和实践相结合,才能真正掌握一个东西。

首先目前的小汽车都是前置发动机前轮驱动的,之前我一直认为小汽车也是后驱的,这纠正了我的一个错误认识,简而言之,就是前轮带着汽车往前跑。

汽车前轮是转向轮,只有前轮才能改变方向,后轮是不能转变方向的,请看下面一张动态图:

后轮无法左右移动

汽车转弯时车轮状态:

前置前驱

我们在倒车和转弯各项操作的时候,就必须弄明白汽车转弯的机制和轨迹是什么?

转弯轨迹

上面这张图很直观的展示了汽车在转弯的时候车辆的旋转中心和前后轮的轨迹。

了解了理论知识,那么在倒车时应该注意些什么呢?

倒车注意事项
阅读全文 »

翻译 By Long Luo

原文链接:Introducing Smoothie

smoothie

Pattrn UI 中的很大一部分是在滑动时从云端获取图片列表。所以我花了相当长的一段时间去调试,以获得滑动时的体验尽可能的流畅。在过去的几周里,我一直在试图解耦代码,完成了一个很小的库:Smoothie

Smoothie 提供了一个简单的 API 来异步加载 ListView/GridView 的项目,以和 UI 线程分离。它做了所有你所期望做的事情,加载项目变得可见,取消要求回收的 View 对应的项目等。但它所完成的还不止这些。

Smoothie手势识别:在Fling手势时,它会必须发起加载项目请求;在滑动列表时,当你的手指按下时,将会启用增量加载项目请求。此外,它支持当前屏幕外项目预加载功能,当你滑动时,可以减少加载占位符类型的项目数。说穿来,Smoothie使用了一个支持可以阻塞队列动态优先执行的的线程池。在屏幕上滑动时,屏幕外即将可见的项目加载请求将动态的获取更高的优先级。

那么,怎么使用它呢?很简单:

  1. 首先在你的布局文件中增加一个 AsyncListView 或者 AsyncGridView ,只需要增加一个额外传递的方法到响应的父类中。

  2. 然后实现一个和你的应用程序加载和显示项目逻辑一致的 ItemLoader 。你将需要重写下面四种方法: getItemParams() , loadItem() , loadItemFromMemory() 以及 displayItem() 。

  3. 最后在 ItemLoader 中建一个 ItemManager ,同时把它和目标 AsyncListView 或者 AsyncGridView 关联起来。

在你的 ListView/GridView 控件需要异步加载时,考虑下把Smoothie作为你的轻量骨架。您可以轻松地连接您自己的图像加载/缓存框架在里面。例如,一个典型的示例应用, Android-BitmapCache 实现了 ItemLoader 使用一个简单的淡入淡出的动画来显示图像。

除了在代码中的 API 文档,还可以看看 App 示例,一边更好地了解如何使用该库。请记住, API 是​不是最终版本哦。目前反响是非常好的!

Enjoy it:-)

文章修改历史

  • Long Luo at AM11:30 ~ 12:42 Feb. 15th, 2014 @Shenzhen, China.
  • 修改图片链接 2024.03.03 in Shenzhen.

翻译By Long Luo

原文链接:Performance Tips for Android’s ListView

译者注:
1. 由于这是技术文章,所以有些词句使用原文,表达更准确。
2. 由于水平有效,有些地方可能翻译的不够准确,如有不当之处,敬请批评指正;
3. inflation这个词一直找不到特别好的中文翻译。

ListView如何运作的?

ListView是设计应用于对可扩展性和高性能要求的地方。实际上,这就意味着ListView有以下2个要求:

  1. 尽可能少的创建View;
  2. 只是绘制和布局在屏幕上可见的子View。

理解第一点很简单:通过布局xml文件在创建View并显示是很昂贵耗时耗资源的操作。尽管布局文件已经编译打包成了二进制形式以便于更高效的语法解析,但是创建View仍然需要通过一个特殊的XML树,并实例化所有需要响应的View。

ListView通过回收一些不可见的Views,通常在Android源码中称为“ScrapView(废弃的View)”来解决这个问题。这及意味着开发者只需要简单的更新每行的内容而不需要针对每个单独的行的布局来创建View。

为了实现第二点,在我们滑动屏幕时,ListView通过使用View回收器来增加低于或者高于当当前窗口的Views,并当前活动的Views移动到一个可回收池中。这样的话,ListView只需要在内存中保持足够多的Views去填充分配空间中的布局和一些额外的可回收Views,即使当你的Adapter有上百个items的适合。它会使用不同的方法去填充行之间的空间,从顶部或者底部等等,具体取决于窗口是如何变化的。

下面这个图很直观的展示了当你按下ListView的时候发生了什么:

ListView

通过上述介绍,相比我们已经熟悉了ListView的这种机制,让我们继续前往技巧部分。正如上述介绍的,在滑动时,ListView通过动态的创建和回收很多View,实现了尽可能地让Adapter的getView()轻量。所有的技巧都是通过多种方法让getView()更快。

View的回收

ListView每次需要在屏幕上显示新的一行的时候,会从其Adapter中调用getView()的方法。众所周知,getView()方法有3个参数:行的位置, convertView以及父ViewGroup。

参数convertView说穿来就是之前讲述的ScrapView。当ListView要求更新一行的布局时,convertView是一个非空值。因此,当convertView值非空时,你仅仅需要更新内容即可,而不需要重新一个新行的布局。getView()在Adapter中一般是如下的形式:

1
2
3
4
5
6
7
8
9
10
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.your_layout, null);
}

TextView text = (TextView) convertView.findViewById(R.id.text);
text.setText("Position " + position);

return convertView;
}

View Holder如何写的模板

Android很常见的一个操作就是在布局文件中找到一个内部的View。通常是使用一个findViewById()的View方法来实现的。这个findViewById()方法在View树中,根据一个View ID,会递归的被调用来找到其子树。虽然在静态UI布局中使用findViewById()是完全正常的。但是,在滑动时,ListView调用其Adapter中的getView()是非常频繁的。findViewById()可能会影响ListView滑动时的性能,尤其是你的行布局是很复杂的时候。

寻找一个充气布局内的内部观点是在Android上最常用的操作之一。这通常是通过一个名为findViewById(查看方法完成)。此方法将递归经过视图树寻找一个孩子用给定的ID码。静态的UI布局使用findViewById()是完全正常,但正如你所看到的,ListView中滚动时调用适配器的getView()非常频繁。 findViewById()可能perceivably击中ListViews,尤其是滚动的性能,如果你行的布局是不平凡的。

View Holder的模式就是减少在Adapter中getView()方法中调用findViewById()次数。实际上,View Holder是一个轻量级的内部类,用于直接引用到所有内部views。在创建View之后,你可以在每行的View存储为一个标签。通过这种方法,只需要在初次创建布局的时候调用findViewById()。下面是一个使用上述方法的View Holder模板的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if (convertView == null) {
convertView = mInflater.inflate(R.layout.your_layout, null);

holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);

convertView.setTag(holder);
} else {
holder = convertView.getTag();
}

holder.text.setText("Position " + position);

return convertView;
}

private static class ViewHolder {
public TextView text;
}
阅读全文 »
0%