上一篇博客传送门:Android常见问题总结(八)
addFooterView与setAdapter顺序问题
最近博主遇到了一个 cannot be cast to android.widget.HeaderViewListAdapter 的崩溃,在网上查阅了相关资料,发现是ListView的addFooterView与setAdapter调用顺序不当导致的(addHeaderView同理)
结论:在Android 4.4之前,如果在调用setAdapter之后调用addFooterView,则会在调用removeFooterView的时候抛出异常
原因:
先贴上Android 4.2的listView源码:
http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/android/widget/ListView.java
再贴上Android 4.4的listView源码:
http://androidxref.com/4.4.4_r1/xref/frameworks/base/core/java/android/widget/ListView.java
我们先看下抛出异常的方法removeFooterView:
1 | /** |
如果我们addFooterView与setAdapter调用顺序不当,当我们调用removeFooterView时,在上面的源码中的第11行,会由于mAdapter强制转换为HeaderViewListAdapter类型而抛出异常
接下来我们来看下setAdapter方法:
1 | /** |
在setAdapter方法第19行我们可以看到,如果我们在setAdapter之前添加了headerView或者是footerView的话,则会把我们的adapter包装为HeaderViewListAdapter,因此在这种情况下当我们removeFooterView时,强制转换并不会抛出异常
接下来我们看一下4.2版本的addFooterView方法:
1 | public void addFooterView(View v, Object data, boolean isSelectable) { |
然后再对比下4.4版本的addFooterView方法:
1 | /** |
我们可以看到,为什么4.4及其以上版本就允许addFooterView在setAdapter以后调用,关键的地方在于第27行,发现如果此时mAdapter不为HeaderViewListAdapter,则会将其包装为HeaderViewListAdapter
因此,在4.4及其以上版本,我们并不需要关心setAdapter与addFooterView的调用顺序(headerView同理)
查看应用是否已安装
最近博主遇到一个需求,需要查看应用是否已安装
经过一番查找后,找到了如下方案:
1 | public static boolean isInstallApp(String appPackage) { |
原理是使用 android.content.pm.PackageManager#getInstalledPackages 方法,获取所有已安装的apk,然后从返回的列表中,查找是否包含特定包名来判断特定应用是否安装
然而,该方案在高版本的Android中,也许是因为系统权限的限制,我们并不能拿到所有的已安装应用
在没有权限的情况下,该方法只返回了部分系统的应用
因此再通过一番查到,博主找到了一个新的方案:
1 | public static boolean isInstallApp(String appPackage) { |
通过 android.content.pm.PackageManager#getPackageInfo(java.lang.String, int) 方法,我们可以获取特定的应用信息,如果用户并没有安装该应用,则会抛出 NameNotFoundException 异常
由于该方法没有系统权限的限制,所以我们可以放心使用
gradle 插件版本号与gradle 版本号对应关系
gradle 插件版本号:指build.gradle文件里,classpath ‘com.android.tools.build:gradle:3.1.2’,应用的插件的版本号
gradle 版本号:指“gradle-wrapper.properties”中,配置的gradle 的版本号
其中,gradle 插件的版本号和 gradle 的版本号是有关联的,关系如下:
插件版本号 | gradle版本号 |
---|---|
1.0.0 - 1.1.3 | 2.2.1 - 2.3 |
1.2.0 - 1.3.1 | 2.2.1 - 2.9 |
1.5.0 | 2.2.1 - 2.13 |
2.0.0 -2.1.2 | 2.10 - 2.13 |
2.1.3 - 2.2.3 | 2.14.1+ |
2.3.0+ | 3.3+ |
3.0.0+ | 4.1+ |
3.1.0+ | 4.4+ |
3.2.0 - 3.2.1 | 4.6+ |
3.3.0 - 3.3.2 | 4.10.1+ |
3.4.0 - 3.4.1 | 5.1.1+ |
3.5.0+ | 5.4.1 - 5.6.4 |
详细gradle插件版本与gradle版本更新日志如下:
https://developer.android.google.cn/studio/releases/gradle-plugin#updating-plugin
处理安装出现INSTALL_FAILED_TEST_ONLY错误
某次在本地debug模式编译打包apk后,通过adb install指令发现竟然无法正常安装,提示了INSTALL_FAILED_TEST_ONLY错误
在网上查询资料后发现,原来是Android Studio 3.0会在debug类型apk的manifest文件application标签里自动添加 android:testOnly=”true”属性
官方文档如下:
https://developer.android.google.cn/guide/topics/manifest/application-element#testOnly
解决方案有两个:
我们可以在项目中的gradle.properties全局配置中设置:
1 | android.injected.testOnly=false |
除此以外,我们还可以安装apk时,增加上-t的参数:
1 | adb install -t app-debug.apk |
Activity启动流程图总结
Activity启动流程图如下所示:
流程主要分为以下几步:
- 当前Activity把需要启动的目标Activity相关信息包装成Intent的形式,发送给ActivityManagerService(AMS)
- AMS检验信息合法后,保存信息,并通知当前Activity进入中止状态(onPaused),并通过Handler监控是否超时
- 当前Activity进入中止状态后,通知AMS
- AMS检查目标Activity在Manifest中设定启动的进程启动了没有,如果进程没有启动,则启动进程(也通过Handler计算超时),有则跳到第7步
- AMS通知zygote进程fork一个子进程,以ActivityThread的main函数作为进程执行入口
- 新进程初始化Handler消息循环机制,初始化Application,完成启动工作后,发送ApplicationThread给AMS(以后AMS就通过ApplicationThread找到这个进程)
- AMS通过进程启动后传回的ApplicationThread找到进程,发送命令启动目标Activity,同时开始Handler计算超时
- 目标进程接到IPC指令后,通过消息循环机制,在主线程中反射实例化Activity,并执行Activity的生命周期,完成后通知AMS
- AMS收到Activity启动的消息后,Activity启动流程结束