安卓项目开发总结
一、网络请求
-
线程+Handler
- 使用线程池提高应用性能。
-
AsyncTask
-
xUtils
- 接口回调。封装网络请求,在子线程中执行,根据响应码判断结果,通过Handler调用接口的方法,完成对不同状态的不同操作。
-
使用GSON,将JSON字符串转换为JavaBean对象。
备注:HttpClient在Android 6.0中已被移除,但是HttpUrlConnection仍然可以使用。
二、数据缓存
-
普通数据的缓存
- 首先判断本地是否有缓存数据,如果有,先显示本地数据,如果没有直接访问网络获取最新数据并保存时间戳。
- 本地有缓存数据并显示完毕后,携带本地数据对应的时间戳访问服务器,判断当前数据是否为最新数据。
- 如果是最新数据,不再执行任何操作。
- 如果不是最新数据,请求服务器返回最新数据和对应的时间戳,缓存到本地文件或者数据库中,并进行显示。
- 图片的缓存
三、断点下载/上传
-
断点下载
- 通过设置range请求头,请求服务器要获取文件的字节范围,去下载指定数据段的文件。
- 通过DownloadTask执行下载任务,在下载过程中,每写一段文件数据就将已写文件数据大小保存至文件或数据库,以便下次开始下载的时候用于指定range的范围。
- 通过Handler去更新UI界面,如果有多个任务同时进行下载,分别为每一个DownloadTask设置DownloadListener。
- 为了避免ListView不同item显示错误的下载进度,使用ViewHolder对ListView进行优化处理。
-
断点上传
- 该功能需要服务器支持才可以进行。
四、业务逻辑
-
封装JavaBean,页面展示,友盟SDK,百度地图,支付集成等
-
数据存储(sp存储,文件存储,数据库和网络存储)
-
Service:开启线程,和服务器保持连接,接收服务器推送消息
-
BroadcaseReceiver:
- 注册常用广播事件,判断自己服务是否在运行,如果服务没有运行,开启服务,可以做到服务长时间在后台保持运行。
- 广播也可以作为四大组件间通信的工具
五、页面层
-
Activity
- 抽取BaseActivity,重写onCreate方法,依次执行initView,initData,initEvent,封装访问网络的操作,在回调结果里执行子类Activity的操作,需要子类提供的数据可以使用抽象方法让子类去实现或者暴露方法给子类,让子类去覆写该方法。
- 用集合记录已经打开/关闭的Activity,方便在任何Activity中执行退出应用的操作,退出后还需要调用killProcess(pid),杀死进程。
- 封装startAnActivity方法,在BaseActivity中使用一个静态变量记录当前应用的Activity是否在栈中的最顶层,如果在,直接开启Activity,如果不在,新建一个任务栈开启Activity。
-
Fragment
- show/hide:执行效率高,但是会占用内存
- replace:执行效率低,但是内存占用少
-
常用控件
- ListView:优化,局部刷新的重点是,找到要更新的那项的View,然后再根据业务逻辑更新数据即可。重写updateItem方法,更新界面后,对应的数据也要更新,否则下次再滚动到该View的时候可能又还原了。
private void updateItem(int index) {
int visiblePosition = listView.getFirstVisiblePosition();
if (index - visiblePosition >= 0) {
//得到要更新的item的view
View view = listView.getChildAt(index - visiblePosition);
// 更新界面(示例参考)
// TextView nameView = ViewLess.$(view, R.id.name);
// nameView.setText("update " + index);
// 更新列表数据(示例参考)
// list.get(index).setName("Update " + index);
}
}
- ViewPager:使用动画就使用JazzyViewPager
- LinearLayout
- RelativeLayout
- FrameLayout
- PercentLayout
- 自定义控件:可以拖动的GrideView
- 屏幕适配:dp和px的相互转换,使用百分比布局
- 动画
- 属性动画(会改变控件的真实位置,功能强大,代码复杂)
- 补间动画(不会改变控件的真实位置,包含4种)
- 位移动画
- 旋转动画
- 透明度
- 缩放动画
- 组合动画(以上4中动画可以组合为一个动画)
- 帧动画(图片的切换)
六、优化
- 布局优化
- include:布局重用
- merge:减少视图层级,例如你的主布局文件是垂直布局,引入了一个垂直布局的include,这是如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用
标签优化。 - ViewStub:需要时记载,使用该标签并不会影响UI初始化时的性能。当调用inflate()函数的时候,ViewStub被引用的资源替代,并且返回引用的view。
- 注:ViewStub目前有个缺陷就是还不支持
标签。 - 通过代码增加或删除View(addView/removeView)
- 代码优化
- 使用注解框架,ButterKnife,Dagger2,xUtils3,lint代码分析
- 内存/性能优化
- oom内存溢出,可能是由内存泄露导致,通过mat插件进行分析
- 电量优化
- WebView,HTML5,Android新特性
七、自定义控件
- 创建自定义控件
- 继承View/ViewGroup/已有控件(包括布局控件)
- 在3个构造方法中,从参数少的到参数多的,依次调用,可以实现传入一个上下文,就能返回一个带有默认效果的控件,这也是第三个参数的作用。
- 自定义控件执行的流程
- onMeasure:测量,RatioFrameLayout可以让图片按照比例显示的布局
- 两个参数:widthMeasureSpec/heightMeasureSpec
- 每个参数分别代表宽度的测量规格和高度的测量规格,值为一个32位的二进制数,前两位代表测量模式,后三十位代表测量值。
- 可以通过MeasureSpec.getMode获得测量模式
- 可以通过MeasureSpec.getSize获得测量值
- 该方法是在当前View的measure方法中调用,由于不能重写measure方法,只能重写onMeasure方法对控件进行测量
- onLayout:布局,自定义流水布局FlowLayout
- 该方法是通过当前View的layout方法中调用,同样,需要重写onLayout完成对子控件的布局
- 由于View没有子控件,所以该方法只针对ViewGroup
- onDraw:绘制,快速索引控件
- 在该方法中可以完成对控件的绘制,需要用到canvas和paint
- 不要在该方法中new对象,否则可能造成GC回收不及时导致内存溢出或者GC时导致界面卡顿
- onMeasure:测量,RatioFrameLayout可以让图片按照比例显示的布局
- 自定义控件的事件处理
- onTouchEvent(返回值)
- 调用invalidate对控件进行重绘
- 返回true,消费事件
- 返回false,执行父View的
- dispatchTouchEvent:是否分发
- 返回true,不分发,事件直接返回不处理
- 返回false,分发,
- 如果子View是ViewGroup,执行onInterceptTouchEvent判断是否对事件进行拦截
- 如果子View是View,执行onTouchEvent判断是否对事件进行处理
- onInterceptTouchEvent(ViewGroup才有)
- 返回true,对事件进行拦截,直接返回不处理
- 返回false,不进行拦截,调用子View的dispatchTouchEvent
- requestDisallowInterceptTouchEvent
- 子View告诉父容器,不要拦截自己的事件
- onTouchEvent(返回值)
- 通过回调,返回自定义控件的当前状态
- 定义控件的所有状态
- 定义回调接口和回调方法(参数)
- 设置成员变量(setXXXListener)
- 在适当的位置调用回调方法
- 外部在回调方法中执行自定义的任务
- 自定义控件的动画
- 帧动画
- 补间动画
- 属性动画(性能比补间动画低)
- 插值器(Interpolator)
- 估值器(TypeEvaluator)
- ValueAnimator(是属性动画的核心类)
- 参考文档:Android动画学习
- ViewDragHelper,滑动删除控件,nineoldandroid.jar(在低版本2.3上执行属性动画仍然不会改变控件的真实位置,所以低版本的Android系统不支持属性动画)
八、图片缓存
- 不能导致应用崩溃(OOM),需要进行压缩处理
- 使用xUtils中的BitmapDisplayConfig中的optimizeMaxSizeByView方法对图片进行压缩
- 不能导致卡顿,需要进行异步加载(使用Thread或者AsyncTask)
- 不能显示错位,因为快速滑动的时候对图片进行异步加载,由于ListView中item的复用可能会导致图片的错位显示,对ListView/GridView中的item设置tag,异步记载完成后对tag进行判断,如果tag相同才进行显示。
- 加载要快,对已加载的图片进行缓存
- 内存缓存(LruCache),LruCache的本质是使用LinkedHashMap集合,该集合会根据使用的情况,按照使用频率进行排序,当即将超出内存大小时,将使用频率最低的图片进行移除。
- 磁盘缓存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
九、Handler
- 为什么要有Handler?
- 主线程不能做耗时操作,需要放在子线程中执行
- 子线程不能更新UI,只有主线程才能更新UI,所以需要使用Handler对子线程和主线程之间进行通信
- 使用步骤
- new Handler(),该操作只能在主线程中执行
- 重写handleMessage方法
- 在子线程中发送消息sendMessage
- post可以延时发送消息
- 发送的消息可以使用Message.obtain()方式获取
- 消息队列MessageQueue
- Looper.prepare
- Looper.loop
- 所有的界面操作都依赖于Handler机制
- 子线程只需要调用Looper.prepare和Looper.loop,就可以在这两行代码之间使用WindowManager修改UI
- 主线程也不能修改子线程创建的UI
- Handler是线程间通信的工具