瞎拍(1)
柳绿花红,春草茵长
高楼林立之间,这一片绿到底争出了些春的颜色
柳绿花红,春草茵长
高楼林立之间,这一片绿到底争出了些春的颜色
“闪屏”又叫“启动画面”,追根溯源,移动端App上“闪屏”这个词似乎还是苹果“爸爸”拿过来在它那一套“HIG”里“重新定义”的。大概是为了解决应用冷启动加载时间长的问题,而采用的一个取巧的办法:先显示一个“占位图”以示程序加载中。有在这个占位图上显示LOGO的,也有干脆放个“菊花”转半天的,也有的放个应用首页的轮廓图。真可谓八仙过海,各显神通。
本人也不是iOS开发,个中细节也不甚了了,但我似乎记得在 Android 应用的开发中 Google 官方曾是不推荐用“闪屏”这种玩意的。也许我记忆混乱了,但至少相当长的一段时间 Google 的全家桶是没有“闪屏”的,直到15年某个版本全线加上了这个玩意。当时我就“震惊”了,Google你的节操呢。。。
你想,一个承载着企业品牌的LOGO的启动画面(“闪屏”),怎么也得至少显示个1秒吧,那你的应用真正被用户所看到得干等这一小段时间,不是本末倒置吗?
说起来,随着应用复杂度提升、代码越来越庞大,程序的冷启动随之变慢,那么用户似乎怎么着都得干等一段时间。。那怎么行(#‵′)凸,怎么能让“大爷们”看着黑/白屏干等呢?!
1 | WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); |
早在 Android M 预览版发布时就有人发现,通过WifiInfo.getMacAddress()
获取的MAC地址是一个“假”的固定值,其值为 “02:00:00:00:00:00”。对于这个,官方的说法当然不外乎“保护用户隐私数据”。
不知是有意还是一时不查,Google却忘了Java获取设备网络设备信息的API——NetworkInterface.getNetworkInterfaces()
——仍然可以间接地获取到MAC地址。
1 | Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); |
输出如下:
1 | interfaceName=dummy0, mac=e6:f9:44:3c:ee:da |
顾名思义,猜想wlan0
对应的mac地址应该就是我们要找的。
既然NetworkInterface
可以正常获取,那得好好看看它在 Android framework 中的实现源码:
1 | public byte[] getHardwareAddress() throws SocketException { |
原来MAC地址是直接从"/sys/class/net/" + name + "/address"
文件中读取的!
这个name
是什么呢?
上一篇说到Tabs+ViewPager+ListView是最常见的组合,这篇就议一议如何用RecyclerView
快速实现列表页面。
如一个简单的列表场景:TodoList。
分页加载现有Todo
现有数据基础上增、删、改
RecyclerView
的使用在此就不赘述了,本文主要讨论RecyclerView.Adapter
的实现
使用最简单的ArrayList
实现,如下:
1 | class ListAdapter extends RecyclerView.Adapter<TodoViewHolder> { |
这样的Adapter
一个显而易见的问题就是,如何做数据的去重。
addItem()
之前,遍历一次mData
,定位后再决定是插入还是更新现有数据,并调用notifyItemInserted(pos)
。对于少量数据来说这样做并不见得有什么问题,而且写得多了,都有自己封装好的诸如ArrayObjectAdapter
之类方便使用。
这样就够了吗?
答案肯定是不。Android Support Library 悄悄给我们提供了一个叫SortedList
的工具类,它默默的藏在support库的角落中,鲜为人知。
文档对它的定义:
- 是一个有序列表
- 数据变动会触发回调
SortedList.Callback
的方法,如onChanged()
想必Tabs+ViewPager+ListView 结合使用的场景在你的Android手机中的各大应用里并不少见,比如最为典型的网易新闻。
众所周知,用RecyclerView
可以非常简单的替代掉ListView
。可仅仅就为了将ListView换成RecyclerView,这换汤不换药的做法显然不足以让人心动。
如果我说,再用上RecycledViewPool
,可以使你的布局渲染速度、ViewPager滑动流畅度上升一个档次,你会心动并行动吗?
关于RecycledViewPool,官方文档是这样说的:
Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. This can be useful if you have multiple RecyclerViews with adapters that use the same view types, for example if you have several data sets with the same kinds of item views displayed by a ViewPager.
RecyclerView automatically creates a pool for itself if you don’t provide one.
简言之就是,你可以给RecyclerView设置一个ViewHolder的对象池,这个池称为RecycledViewPool
,这个对象池可以节省你创建ViewHolder的开销,更能避免GC
。即便你不给它设置,它也会自己创建一个。
如此说来,如果多个RecylerView间共用一个RecycledViewPool是不是能让你的UI更加的“顺滑”?
RecycledViewPool使用起来也是非常的简单:先从某个RecyclerView对象中获得它创建的RecycledViewPool对象,或者是自己实现一个RecycledViewPool对象,然后设置个接下来创建的每一个RecyclerView即可。
需要注意的是,如果你使用的LayoutManager是LinearLayoutManager或其子类(如GridLayoutManager),需要手动开启这个特性:layout.setRecycleChildrenOnDetach(true)
1 | RecyclerView view1 = new RecyclerView(context); |
ViewPager中使用原理同上,只是你得通过ViewPagerAdapter传递个下一个RecyclerView。
要先说明的是本文说的“渠道”单指在AndroidManifest.xml
用<meta-data>
定义的一个标识字符串(如友盟统计)。在代码或者通过其他文件定义的方式殊途同归。
说起 Android 多渠道打包,真是八仙过海各显神通:有手动一个个耐心打包的,有用Ant
或Maven
重复跑编译任务的,有用apktool解包后再修改重打包的,有在build.gradle定义一堆flavor
的,乃至有通过apk里META-INF/
下的空文件来定义渠道的。
上述方法各有优劣,在这里就不一一赘述了。
本文要介绍的是另一种方法:直接修改APK中的AndroidManifest.xml
。
上述种种,说白了都是围绕着如何修改AndroidManifest.xml
,如何重打包或是重编译。介绍的这个方法也不外如是,只是无需重打包重编译而已。
首先得知道一点,APK中的AndroidMainfest.xml
,解压出来用文本编辑器可是不能直接打开的,它是aapt
生成的一个二进制的xml格式(被称为AXML
),得用其他工具(如apktool)先解析出来。所以问题来了,如何直接修改这个 AXML 文件?
想要修改一个文件,你得先了解它的格式。AXML文件格式其实早已有人研究,如:《发布C语言的Android binary XML(AXML)解析代码》,
《AndroidManifest Ambiguity方案原理及代码》。
这里就直接引用结论了:string
在AXML中是存放在StringChunk
中的;string
都是UTF-16编码的;如果需要往AXML中新增string是比较麻烦的(牵一发而动全身…);为了4字节对齐string数据块末尾可能被填充数个0x00…
综上结论,可知,<meta-data>
定义的渠道值是UTF-16编码的string,并且可能被填充数个0x00。
那么为了方便后期修改,我们可以先编译的一个特殊的“占位渠道包”,这个包的渠道名是一个占位字符串,而这个字符串在AXML占的数据块长度能适应所有渠道名的长度。假设一个占位字符串长度16,那么它自然可以被个数小于16的任意字符串所替代,如占位字符串’abcdefghijklmnop’,渠道有’xxxx’,’abcdef789’…
通过这个特殊的渠道包,我们就能够生成所有渠道包。
近来迁移了一些项目到Android Studio,采用Gradle构建确实比原来的Ant方便许多。但是编译时下载依赖的网速又着实令人蛋疼不已。
如果能切换到国内的Maven镜像仓库,如开源中国的Maven库(已停止维护),或阿里云的Maven服务,又或者是换成自建的Maven私服,那想必是极好的。
一个简单的办法,修改项目根目录下的build.gradle,将jcenter()
或者mavenCentral()
替换掉即可:
1 | allprojects { |
但是架不住项目多,难不成每个都改一遍么?
自然是有省事的办法,将下面这段Copy到名为init.gradle
文件中,并保存到 USER_HOME/.gradle/
文件夹下即可。
Apktool(v2.0.0rc3) 解由最新的build-tools(v21.x) 所编译的Apk,如果其引用了Android Lollipop的Material主题,会出现资源引用偏移的情况。
如 values-v21
文件夹下的styles.xml
中的主题原来是:
<style name="AppBaseTheme" parent="@android:style/Theme.Material.Light">
解包后却变成
<style name="AppBaseTheme" parent="@android:style/Widget.Holo.DateTimePicker">
##原因
Apktool decode 某个Apk首先需要加载 package
为 android
(其package id一般为0x01[1]) 的资源映射表(Resource Table),这就是第一次运行Apktool会安装内置资源包resources.arsc
到xxx/apktool/framework/1.apk
的原因[2]。
可想而知,如果这个资源包没有及时升级,自然就会导致资源引用偏移甚至解包失败:
Exception in thread "main" brut.androlib.err.UndefinedResObject:resource spec: 0x01...
DialogFragment 在 Activity 中创建后通过 show(fm, tag)
,弹出 Dialog,如下面的代码所示:
1 | MyDialogFragment dialog = new MyDialogFragment(); // 一个自定义的 DialogFragment |
点击按钮可以回调 Activity 的 aListener
,但是,如果在对话框存在的情况下旋转屏幕,再怎么点按钮aLisenter
也不回调了?!
只要理解了Fragment 和 Activity 生命周期就会知道原因其实很简单:
Activity
将会被重新创建。onSaveInstanceState()
中保存 DialogFragment
的状态(FragmentManagerState
);onCreate()
中会根据savedInstanceState
所给予的FragmentManagerState
自动重新实例化DialogFragment
,并且 show()
出 dialog流程为:
旋转屏幕-->-Activity.onSaveInstanceState()-->-Activity.onCreate()-->- DialogFragment.show()--|
这个时候的DialogFragment所持有的aListener
引用当然也不复存在,再点按钮自然也不会收到回调(虽然这个 dialog 看起来跟旋转屏幕之前的没什么两样,但它们是两个实例)