整理本人实际开发中遇到的一些问题以及解决办法和一些开发技巧,以后会不定时更新。
tip:利用“目录”可快速导航
问题来源:SwipeRefreshLayout源码:判断子View是否能向上滚动(或者是否滚动到顶部):
进入Android开发官网,假如要查看View API的变化,输入View,选择android.view.View
,如图:
进入View的API参考页面(文档页),如图:
从图中可以看到三个主要信息:
追溯API 变化就是通过上述第三条实现的。比如我们想看看API level 13和 API level 14之间有什么变化,将左侧API level设置为13,查看方法列表:
我们会发现一些API 是灰色的,当鼠标hover过方法名时,会显示出一个提示,如图:
这个提示告诉我们:View中的canScrollVertically(int direction)
方法是在API level 14以后才添加的,另外canScrollHorizontally(int direction)
也是API level 14以后才添加的方法。当我把API level 切换到14时,发现上述两个方法的颜色变为蓝色了,说明他们的确是在API level 14添加的:
使用这种方法的好处是不用下载每一个api 版本的源代码,也可以很方便的对比他们之间的变化。开发参考除了可以对比方法的变化以外,还可以对比内部类,接口等变化,当前选中的API level 为9,结果如图:
问题来源:使用RxJava时,出现莫名的卡顿,方法嵌套过深,或类关系过于复杂,难以定位问题。
进入sdk->tools文件夹。双击运行monitor.bat
打开device monitor:
左侧Device tab下是当前的设备名以及待调试应用的包名。在要测试某个操作(方法调用)之前,点击method profiling 按钮,弹出对话框:
输入采样间隔:
输入采样间隔,间隔越大,采集到某个方法调用栈的可能性就越小,可能漏掉某个调用栈,越小,采样精度越高(或者说覆盖率越高)但是采样间隔太小,会导致卡顿。所以需要输入一个合适的采样间隔。输入后点确认,然后在app上执行你的操作,执行完后点stop method profiling,会生成一个名为“ddms+时间戳+.trace”的文件,这个文件记录了方法的调用栈信息,这个文件是我们分析的重点:
视图中上一栏,展示了在method profile过程中并行执行的所有线程或进程。下一栏的表格展示了方法的调用信息,如方法名,所耗时间,cpu占用等。可参考:http://blog.csdn.net/androiddevelop/article/details/8223805
表格中相关列名的说明:
点击表格每一栏的名字可以进行排序,根据Name找到上述操作调用的方法,如fetchData:
Parents值得是fetchData的调用入口,而Children指的是fetchData方法中的子调用。
可以看出每个子调用或父调用的耗时情况,fetchData中的子调用fetchDataImpl耗时22.777ms,继续点击fetchDataImpl可以进入fetchDataImpl的调用栈,分析方式就同fetchData了,通过这种方式可以定位耗时操作的源头:
使用method profiling可以分析方法的调用栈,找出app的性能瓶颈。android device monitor的功能很强大,是一个工具的合集,还可以分析Heap Dump,Allocation,Network,查看Device的文件等。好的工具可以提升开发效率,科学使用工具可以事半功倍。另外推荐一款在线app 冷启动性能分析工具:Nimbledroid 可以分析app 冷启动的调用栈,通过分析app冷启动调用栈,可以找到影响app的启动速度的因素。另外,Nimbledroid上可以查找到许多app不同版本的冷启动调用栈信息,我们可以从中借鉴加速app启动的方式。
问题来源:Android Decelopers 官网API guide.
Service是无界面的,处于安全性等方面的考虑,应避免隐式启动:
启动Service时如没有特殊需求,采用显式启动,且不必为该服务声明 Intent 过滤器。当API>=21时bindService强制接受显式Intent,如果传入隐式Intent,会抛出异常。
问题来源:使用隐式Intent启动Activity时,如果没有匹配的Activity就会抛出异常。
警告:用户可能没有任何应用处理您发送到 startActivity() 的隐式 Intent。如果出现这种情况,则调用将会失败,且应用会崩溃。要验证 Activity 是否会接收 Intent,请对 Intent 对象调用 resolveActivity()。如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()。如果结果为空,则不应使用该 Intent。如有可能,您应禁用发出该 Intent 的功能。
代码片:
相关方法:
PackageManager 提供了一整套 query…() 方法来返回所有能够接受特定 Intent 的组件。此外,它还提供了一系列类似的 resolve…() 方法来确定响应 Intent 的最佳组件。例如,queryIntentActivities() 将返回能够执行那些作为参数传递的 Intent 的所有 Activity 列表,而 queryIntentServices() 则可返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。对于广播接收器,有一种类似的方法: queryBroadcastReceivers()。
使用隐式Intent启动Activity/Service/BroadcastReceiver时,必须要先判断是否有可处理该Intent的应用,否则会导致应用奔溃。
翻译过来就是移动设备使用的系统资源是有约束的,安卓设备的每个应用仅能拥有16MB的运行时内存(这个值应该是2.x的设备的值,已经很老了,许多设备可配置高于这个值),CDD根据不同设备的屏幕大小和密度,定了最小应用内存(设备实现时可以高于这个值)
Note that memory values specified below are considered minimum values and device
implementations MAY allocate more memory per application.
应用应该在这个最小内存值(上限)下进行优化,不要超过这个上限,当然,许多设备配置的上限要大些。比如CDD规定 Android 6.0下,屏幕大小为small/normal,像素密度为320dpi(xxhdpi)的设备最小应用内存为80MB,小米4可以实现为90MB(上限),那么应用在内存优化时,不应该超过90MB这个上限。
注:as little as 翻译为“仅仅”,一些地方翻译成了“最小”是错误的。
参考:
http://developer.android.com/intl/zh-cn/training/displaying-bitmaps/index.html
CDD Android N:
http://static.googleusercontent.com/media/source.android.com/zh-CN//compatibility/6.0/android-6.0-cdd.pdf
问题来了,如果你的应用需要120MB的内存,而设备的上限为100MB,该如何解决呢?
参考:
http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/
如何获得设备最大内存上限:
内存探测:
参考:
http://stackoverflow.com/questions/2630158/detect-application-heap-size-in-android
在加载大图时,往往会消耗大量的内存。
For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.
我们必须要知道和理解上述的内存限制,然后使用内存探测判断设备支持的最大应用内存,然后根据业务需求采取不同的方式,应对这一限制。其实微博,微信等,都是通过进程拆分解决这一问题的。微博有四个进程:主进程、推送进程、图片浏览进程以及插件进程,通过拆分,单个进程只占用少量的内存,当系统内存吃紧时,会首先杀死占用内存较大的进程,通过进程的分离,可以让解决内存上限问题,同时起到进程保活的作用。
问题来源:一次面试,被问到Activity有被销毁的风险,如配置改变,系统内存不足等,这种情况下,该如何及时地保存Activity中的数据,除了在onPause和onSaveInstanceState中保存,还有别的方式吗?
使用Fragment保存。
Code:
我们通常会认为,当Activity销毁时,其attach的Fragment也会销毁,然而当Fragment调用了setRetainInstance(true)后,其实例不会销毁。以下是setRetainInstance()方法的官方说明:
public void setRetainInstance (boolean retain)
Added in API level 11
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
1.onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
2.onCreate(Bundle) will not be called since the fragment is not being re-created.
3.onAttach(Activity) and onActivityCreated(Bundle) will still be called.
使用条件:该fragment不加入返回栈,即使用FragmentManager通过事务添加该fragment时,不调用addToBackStack()。
调用了setRetainInstance(true)后,Activity被销毁以及重新创建的过程中,fragment的实例将会被保存。fragment的onDestory()方法将不会被调用,fragment的onCreate(Bundle)方法也不会被调用。
如此,我们就可以用fragment来保存数据了。
除了在onPause和onSaveInstanceState中可以保存数据外,还可以使用不含任何UI元素的Fragment来保存数据。
Fragment与其关联的Activity的生命周期息息相关,具体可见FragmentActivity的实现,在FragmentActivity的每一个生命周期方法中都间接地调用了fragment的相应生命周期方法,所以一般情况下,Activity的某个生命周期方法被调用时,fragment的一个或多个生命周期方法也会被调用,但是当setRetainInstance(true)后,fragment的生命周期方法的调用会略有不同。