Android自定义View和Drawable总结

Android自定义View简述

Android自定义View方式有许多,本文对直接继承View的方式进行总结,建议深入阅读源码了解View是如何进行绘制的,可以从 ViewRootImpl#performTraversals() 方法开始,并且建议同时理解View的事件传递机制和动画机制,这样才能实现非常炫丽的View;我们主要讨论onMeasure()、onLayout()和onDraw()进行override的注意事项,不会讨论增加交互和动画等事宜。

自定义属性

1. 建立res/values/attrs.xml,demo如下:

1
2
3
4
5
6
7
8
9
<resources>
<declare-styleable name="PieChart">
<attr name="showText" format="boolean" />
<attr name="labelPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
</declare-styleable>
</resources>

2. 布局文件中使用自定义属性时,Gradle建议命名空间统一使用"http://schemas.android.com/apk/res-auto"
3. 官方建议通过TypedArray在程序中读取定义的属性,demo如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.PieChart,
0, 0);

try {
mShowText = a.getBoolean(R.styleable.PieChart_showText, false);
mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0);
} finally {
a.recycle();
}
}

Android 多线程使用总结

APK启动时会执行ActivityThread中main()方法,其中创建ActivityThread的线程作为主线程,由于该线程主要与UI交互相关,所以称为UI线程;UI线程中不能执行耗时操作,否则容易引起ANR,所以Google建议开启worker thread执行耗时操作,然后通过Handler机制通知主线程刷新UI。系统提供三种方法:

  1. Activity.runOnUiThread(Runnable)
  2. View.post(Runnable)
  3. View.postDelayed(Runnable,long)

使得worker thread中方便的刷新View,但上述方法不便于管理。针对这种场景,系统对Handler机制进行一定轻量级封装,方便worker thread通知主线程,我们主要分析以下几个类:

Android 动画总结

View Animation

1. Tween Animation

针对View的动画,其相关类在android.view.animation中,
由四种动画组成:平移动画、缩放动画、旋转动画和透明度动画,分别对应Animation四个子类:

名称 子类 标签
平移动画 <translate> TranslateAnimation
缩放动画 <scale> ScaleAnimation
旋转动画 <rotate> RotateAnimation
透明度动画 <alpha> AlphaAnimation

Android关于Touch事件的相关研究

Touch Mode

Android有许多Input Type,包括轨迹球、书写笔、声音等,我们主要探究触摸屏这种输入。针对其他输入,这里说明一点:有些手机的实体按键点击时会把该事件传递给当前屏幕中处于焦点状态View的onKey()回调方法,当然也可以在Activity中dispatchKeyEvent()进行拦截,或者通过onKeyDown()onKeyUp()进行监听。当前手机APP主要交互是通过触摸屏进行,根据KeyCode监听实体按键的情况不是很多了,而且像Back键、Menu键系统也提供了方便的回调方法,但Home键并没有,而且在onKey方法中也不回调,网上通常采用监听Home键产生的广播事件进行监听,本文不过多对此进行讨论。现在手机通常是虚拟软键盘输入,按键时通常不会回调onKeyDown方法,所以针对虚拟键盘最好不要使用onKeyDown等回调事件。针对EditText控件提供了OnEditorActionListener回调方法,用于处理输入完成后的后续工作。Demo如下:

1
2
3
4
5
6
7
8
9
10
private EditText mEditText;
...
mEditText.setImeOptions(EditorInfo.IME_ACTION_SEARCH); // 输入后搜索
mEditText.setSingleLine(true); // required
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return true;
}
});

想查看支持哪些输入类型,可以通过INPUT_SERVICE服务查看,其类型常量定义在InputDevice类。Demo如下:

1
2
3
4
5
6
7
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
int[] ids = inputManager.getInputDeviceIds();
for (int i : ids) {
...
inputManager.getInputDevice(i).getSources() == Inputdevice.XXX;
...
}

触摸操作相比其他交互,不需要高亮来提醒用户当前焦点处于何处,所以当用户点击触摸屏时会处于”Touch Mode”,通过isInTouchMode()判断是否处于Touch Mode。在Touc Mode下,只有isFocusableInTouchMode()==true的View会获得焦点(即isFocused()返回true),比如EditText;而可触摸的View(即isFocusable()返回true)点击时也不会获得焦点,仅仅执行其onClick回调,比如Button。如果Button要获得焦点(isFocused()返回true),必须保证:

  1. isFocusableInTouchMode() = true
  2. isFocusable() = true
  3. 调用requestFocus()

View的焦点变化可以通过OnFocusChangeListener进行监听,View失去焦点后如何传递焦点是由系统计算的,当然可以手动进行控制,详情参考(https://developer.android.com/guide/topics/ui/ui-events.html#HandlingFocus)。

Activity生命周期和启动模式探究

最近通过官网重新学习了Activity的相关知识,觉得收获挺大,遂总结一下。

Activity的生命周期分析

Tips:

  1. onCreate()在整个生命周期只出现一次,建议执行Activity的基本设置,例如:声明用户界面(在XML 布局文件中定义),定义成员变量,以及配置某些UI.
  2. onDestroy()建议进行终止在onCreate()期间创建的后台线程或其他如若未正确关闭可能导致内存泄露的长期运行资源.
  3. oncreate()内调用finish()时,系统会立刻调用onDestroy().
  4. onResume()建议初始化在onPause()期间释放的组件并且执行每当Activity进入“继续”状态时必须进行的任何其他初始化操作(比如开始动画和初始化只在Activity具有用户焦点时使用的组件)
  5. onStop()建议释放几乎所有用户不使用时不需要的资源,执行更大、占用更多CPU的关闭操作,比如向数据库写入信息
  6. onRestart()没有适用于一般应用群体的任何方法指导原则。onStart()方法作为onStop对应方法,可以初始化对应关闭的资源。比如:因为用户可能在回到它之前已离开应用很长时间,onStart() 方法是确认所需系统功能已启动的理想选择
  7. onPause()建议进行如下操作:
  • 停止动画或其他可能消耗 CPU 的进行之中的操作;
  • 提交未保存的更改,但仅当用户离开时希望永久性保存此类更改(比如电子邮件草稿);
  • 释放系统资源,比如广播接收器、传感器手柄(比如 GPS)或当您的Activity暂停且用户不需要它们时仍然可能影响电池寿命的任何其他资源

深入了解Handler以及相关问题

根据Google官方,Handler机制有两个主要作用:1.在未来某个时刻调度消息执行;2.在其他线程中执行相关操作。实际上,我们可以看成是framework层提供的一种线程之间通信方式。通常,我们与UI线程交互时会使用Handler实现,本文主要讨论其源码实现以及相关问题。

总体介绍

  • 事件驱动模型
  • Looper,Message和Hanlder关系图
  • Looper类
    用于建立消息循环并管理消息队列(MessageQueue),不停的从消息队列中抽取消息,分发执行。每个线程只能有一个Looper对象,并且Looper构造方法是私有的,只是创建一个MessageQueue对象并持有。根据Google官方文档,一个Thread实现Looper机制如下:

深入分析Android EventBus组件

这里要讲的EventBus特指greenrobot开源的项目(https://github.com/greenrobot/EventBus),是一个Android事件发布/订阅框架,通过解耦发布者和订阅者来简化Android事件传递。事件传递既可以用于Android四大组件间通信,也可以应用于异步线程和主线程间通信等。

Simple Demo

1. 定义事件

1
2
3
4
5
6
7
8
9
public class DemoEvent {
public String name;
public int grade;

public DemoEvent(String name, int grade) {
this.name = name;
this.grade = grade;
}
}

Android 文件存储路径探究

Android有两种文件存储空间:内部存储(internal storage)和外部存储(external storage),这主要是因为早期Android设备存储主要由内置的非易失性内存和可插拔的存储(比如micro SD卡)两部分组成,而随着现在设备提供商把存储都内置到手机中,已经没有外部存储一说了,但系统级别还是保持这两种存储空间。

Internal Storage

Internal storage总是可用的,默认只允许你的App访问,当用户删除App时,系统会自动把internal storage数据一并清除。App安装时默认安装在internal storage,但是可以在manifest文件中指定属性android:installLocation进行改变。从技术上来讲如果你在创建内部存储文件的时候将文件属性设置成可读,其他App能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。Internal storage大小相对比较宝贵,其大小由设备提供商根据官方推荐的最小值进行设定,可以从官方提供的compatibility document 7.6节查看,但并没有规定单个App最大可以使用多少。

APK签名原理解析

相关概念

  • Message Digest(数字摘要)
    将任意长度的消息变成固定长度的短消息,它类似于一个自变量是消息的函数,也就是Hash函数。数字摘要是采用单项Hash函数将需要加密的明文“摘要”成一串固定长度(128位)的密文这一串密文又称为数字指纹,它有固定的长度,而且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致。
  • Signature(数字签名)
    数字签名是非对称密钥加密技术与数字摘要技术的应用。将信息摘要用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的信息摘要,然后接收者用相同的Hash函数对收到的原文产生一个信息摘要,与解密的信息摘要做比对。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;不同则说明信息被修改过,因此数字签名能保证信息的完整性。并且由于只有发送者才有加密摘要的私钥,所以我们可以确定信息一定是发送者发送的(发送者的身份认证)。
  • Certification(数字证书)
    数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。最简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。数字证书还有一个重要的特征就是只在特定的时间段内有效。