Android 硬件加速:概述

0x80 前言

Framework源码来自Android Platform API 25

0x81 说说invalidate与onDraw的关系

invalidate有多个重载方法,分别接收一个参数Rect、四个int类型的ltrb和无参数方法。

1
2
3
4
5
6
7
8
9
10
11
12
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}

public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}

其中前两个方法的作用是一致的,View类将Rect参数转换成了ltrb,并最终调用了invalidateInternal方法,并且invalidateCache参数为true,fullInvalidate为false,意为刷新缓存且非全局刷新,看Rect为参数的方法也能看出来,这种重绘绘制的是被标记为”dirty”的区域。

再看invalidate方法,它调用了invalidate(boolean invalidateCache),并入参为true:

1
2
3
4
5
6
7
public void invalidate() {
invalidate(true);
}

void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

可以发现,该方法最终也是调用了invalidateInterval,只不过重绘范围是整个View可见区域并设置了fullInvalidate为true。

而根据文档所说的,invalidate方法必须在UI线程调用,如果在非UI线程需使用postInvalidate方法,并且该方法最终会触发onDraw回调以让我们重绘自己的View。

0x82 invalidate可能导致的性能问题

假设我们需要实现View的拖拽,我们通常会在手指发生移动时更改绘制参数,然后通过invalidate方法(实际上是invalidateInterval内设定了相应的flag)通知Framework我的View中有”dirty”区域,需要重绘,这样当16ms检查到来时,就会调用我们的onDraw回调,我们便把View绘制出来。由于Android目前也是60fps渲染机制,因此每16ms都会开始一次新的绘制,这便要求我们的绘制工作需要在16ms内完成,负责就会出现跳帧丢帧的状况。

我们都知道CPU的资源通常非常宝贵,如果我们像以前开发那样使用CPU去绘制图形,一旦元素非常多就会占用过多的CPU资源,这时候不仅是绘制掉帧,还有可能拖慢整个机器的运行速度。

但是,GPU的定位就不太一样了,GPU内没有复杂的逻辑控制器,它的内部构造可以看作就是一个一个的图形处理单元堆叠,由于不需要逻辑判断,GPU便可以单纯的接受输入只做数据处理并输出,因此GPU还是并行计算的重要角色,他们不需要关心逻辑只需要正确的处理输入的数据。

于是在Android3.0之后,Google把使用GPU进行硬件加速的绘制方式正式提上了台面,使用硬件加速的方法其实也很简单。

Read More

基于Vue的Weex工程构建

0x81 工程初始化

早期我们通过编写Weex特有的.we文件来实现我们的需求,we文件的语法和vue.js的语法非常类似,都是<template></template>、<style></style>、<script></script>三大标签构成模板、样式和逻辑。
现如今,我们只需要使用最新的weex-toolkit即可以创建出基于webpack模块构建系统的工程,更让人欢喜的是,Weex在最新的SDK中已经引入了Vue2 Runtime,我们可以使用vue.js来开发我们的Weex应用。

使用weex init project命令创建的工程目录结构如下:

工程结构

其中.vscode是Visual Studio Code生成的配置文件夹,src是我们的源码目录,webpack.config.js是webpack(1.14.0)的配置文件,package.json是项目配置信息文件。

0x82 安装必要的包依赖

使用nodejs安装包依赖很简单,只需要在工程目录下使用npm工具执行cnpm i命令就可以了(cnpm是淘宝源的npm工具,由于npmjs在国外,由于国内特殊的状况速度很慢)。

我们看一下package.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"name": "weex",
"description": "A weex project.",
"version": "0.1.0",
"private": true,
"main": "index.js",
"keywords": [
"weex",
"vue"
],
"scripts": {
"build": "webpack",
"dev": "webpack --watch",
"serve": "node build/init.js && serve -p 8080",
"debug": "weex-devtool"
},
"dependencies": {
"axios": "^0.15.3",
"uuid": "^3.0.1",
"vue": "^2.1.8",
"vue-awesome-swiper": "^2.3.8",
"vue-router": "^2.1.1",
"vuex": "^2.1.1",
"vuex-router-sync": "^4.0.1",
"weex-vue-render": "^0.1.4"
},
"devDependencies": {
"babel-core": "^6.20.0",
"babel-loader": "^6.2.9",
"babel-preset-es2015": "^6.18.0",
"css-loader": "^0.26.1",
"ip": "^1.1.4",
"serve": "^1.4.0",
"vue-loader": "^10.0.2",
"vue-template-compiler": "^2.1.8",
"webpack": "^1.14.0",
"weex-devtool": "^0.2.64",
"weex-loader": "^0.4.1",
"weex-vue-loader": "^0.2.5"
}
}

和一般的包依赖配置一样,”script”下是npm命令脚本别名,”dependencies”和”devDependencies”分别是一般依赖和开发依赖。一般依赖主要是Vue和Weex,开发依赖主要是Babel翻译器和Webpack的Loader。

Read More

Android之startActivityForResult

0x80 前言

Framework源码来自Android Platform API 25

0x81 startActivity碎碎念

前面我记得我写了Context是如何调用startActivity的,其本质是一种代理机制,在这其中提到了非常重要的一个类——ContextImpl,实际上就是它进行了一系列判断(主要是该Intent不来自Activity的话FLAG_ACTIVITY_NEW_TASK的判断),并最终使用ActivityManagerNative通知到AMS打开了Activity。在这篇笔记里,我们就会发现,差别其实仅在过程,结果都是一样的,Activity都是由AMS管理的。

0x82 startActivity在Activity中的实现

1
2
3
4
5
6
7
8
9
10
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}

很明显,startActivity调用了Activity的startActvityForResult这个方法,并向其中传递了值为-1的requestCode,这个我们最常用的方法其实就是一个简单的封装,视为语法糖也无可厚非,我一我们重点来看startActvityForResult这个方法。

Read More

Android之startActivity

0x80 前言

Framework源码来自Android Platform API 25

0x81 startActivity的定义

是时候倒回来重新看一下startActivity这个方法了,我们从Activity进入会发现是在ContentWrapper中调用了mBase中的方法,而mBase继承自Context,贴一段在Context中的官方的源码:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Same as {@link #startActivity(Intent, Bundle)} with no options
* specified.
*
* @param intent The description of the activity to start.
*
* @throws ActivityNotFoundException &nbsp;
*`
* @see #startActivity(Intent, Bundle)
* @see PackageManager#resolveActivity
*/
public abstract void startActivity(@RequiresPermission Intent intent);

很明显,这个方法是定义在android.content.Context类中的一个抽象方法。我们接着来看Context的直属实现类ContextWrapper的对它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ContextWrapper(Context base) {
mBase = base;
}

/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context hasalready been set.
*
* @param base The new base context for thiswrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Basecontext already set");
}
mBase = base;
}

@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}

ContextWrapper中的startActivity也只是简单的调用了mBase的实现方法,而mBase是在Constructor或attachBaseContext方法中初始化的,并且attachBase不允许重复设值mBase,这是一种代理机制。那这个Context的真正实现者是谁?没错!是ContextImpl,是不是七大姑八大姨很多,但是这种代理的设计模式才有了Android比较出色的兼容性,ContextImpl的内容不在这里展开。当然你也会发现在Activity类中有一块重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Same as {@link #startActivit(Intent, Bundle)} with no options
* specified.
*
* @param intent The intent tostart.
*
* @throwsandroid.content.ActivityNotFoundEception
*
* @see #startActivity(Intent, Bundle)
* @see #startActivityForResult
*/
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}

这是因为除了Activity类之外的Context在调用startActivity的是非standard模式的,换句话说就是不会按常规的入栈方式,直接调用会抛异常。

0x82 startActivity的实现

继续追溯mBase,我们都知道,Activity这种持有文上下Context的类是不允许直接实例化的,而应该让Framework层创建并处理生命周期的问题,所以不深入Framework层我们就暂且看一下谁调用了attachBaseContext方法。

我们查一下ContextWrapper的实现,发现所有的Service实现类都实现了这个方法,关系链是Service : ContextWrapper : Context,还发现所有继承自Activity的类也实现了该方法,其实关系链是Activity : ContextThemeWrapper : ContextWrapper : Context,实际上ContextThemeWrapper也就是维护了一些主题资源和运行配置等相关的内容,所以我们先从Service下手:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ------------------ Internal API ------------------

/**
* @hide
*/
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}

是在Service被Framework层attach的时候传递的,而这个SDK中是internal API被标记为@hide的,这就只能去看AOSP的源码,并且各大厂商之间可能会有变动。再看看Activity:

1
2
3
4
5
6
7
8
9
10
11
// ------------------ Internal API ------------------

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
}

是一样的,都是在attach的时候设值,所以也指明了那个Context为什么叫mBase,方法为什么叫做attachBaseContext,至于这个Context是谁?没错!是ContextImpl,至于它到底是构造的时候传进去的,还是attach进去的,管它呢,看起来都可以(当然AOSP里其实有具体实现,感兴趣的可以看看,毕竟我只是想记录一下两种调用方式的不同)。因此我们可以认为这种方式是一种fallback的方式,Google希望你的新页面是正常进入任务栈且接受AMS管理的,它希望你从Activity中调用startActivity。

当然对于某些特殊需求,你可能完全在当前Affinity栈为空的情况下启动一个新的Actvity,这个时候你需要为你的Intent设置一个Context.FLAG_ACTIVITY_NEW_TASK来表示在必要的时候开启一个新的任务栈。

Read More

ContraintSet速记

0x81 ConstraintLayout

Google在之前的GoogleIO大会上宣布了全新的Android布局组件——ConstraintLayout,中文名约束布局,其相关概念有点类似iOS的布局约束,我们终于也可以更好的在布局编辑器里用鼠标拖拽进行布局了。

0x82 ConstraintSet

约束布局存在于support包中,向后兼容大部分版本,对于布局的描述和更改。Google为我们提供了一套API,今天我们就来看看ConstraintSet这个类。

Read More

RxJava-share操作符

0x80 前言

本部分源码参考RxJava 2.0.7

0x81 share 操作符是什么

share操作符是Observable的一个方法,它实际上就是调用了publish操作符和refCount操作符。
根据官方文档的解释,它返回了一个新的ObservableSource,这个新的事件源多目广播源事件,并且,只要存在一个Observer,这个ObservableSource就会被订阅并开始发送数据,并且当所有的订阅者都解除订阅时,它也会从源ObservableSource解除订阅。

0x82 ConnectableObservable

我们先来了解一个概念,ConnectableObservable(在RxJava 1.x中名字为ConnectedObservable)又叫作可连接的Observable,根据官方文档解释,这种Observable即使有再多的Observer订阅,它也不会发送事件,只有在调用了connect方法之后,这个Observable才开始发送事件。基于以上这种状况,我们通常认为这种Observable相比于普通的Observable是不活跃的,称为“冷”Observable。

Read More

Linux后台任务:Screen

0x81 Linux 中的后台任务

Linux作为互联网行业服务器上大量运行的软件,我们通常需要在其运行一些服务软件,也就是常说的daemon——守护进程,比如大部分服务伴随启动的sshd(ssh),httpd(apache)等,它们都是init或systemd的子进程,它们都是后台进程,一直监听着端口以向用户提供服务。

0x82 Linux 后台任务方式

Linux下将程序置于后台通常有三种方式:

  1. "&" 命令后置符
    这种方式将程序置于后台去运行,可以使用jobs命令查看当前login shell下有多少关联的后台任务,fgbg命令可以分别改变任务的运行方式。这种方式最简单,但是也有一个明显的坏处,那就是在你的shell退出后,这个后台任务也相应的退出了,因此只能用来做一些简单的短时的后台任务,比如编译一个小程序同时像监视CPU状况。

  2. "nohup" 配合"&"
    这种方式和命令后置符差不多,只是nohup这个命令可以让程序忽略掉用户退出登录时的SIG_HANG挂起信号,从而保证你退出登录程序也能运行。这种方式一定程度上让后台运行更可控,但是当我们通过ssh连接到远程服务器时,sshd其实给我们fork了一个子进程来运行bash shell,所以我们的后台任务是它的子程序,当我们断开连接这个后台任务还是会被杀死,这种状况我们就可以使用screen来开启一个“窗口”,在screen里执行后台任务,它是由screen管理器统一管理的。

  3. "screen" 命令
    Screen可以看作是窗口管理器的命令行界面版本。它提供了统一的管理多个会话的界面和相应的功能。

Read More

Android IPC:Messenger使用

0x81 Messenger原理

书接前文,Messenger作为一个比较简易的IPC实现,仍然是基于Binder的机制,Android上层的跨进程通信都是通过Binder完成的。

Messenger实现了Parcelable接口,因此可以在内存之间传递。它的构造方法需要一个Handler类型的target参数,这个参数是Messenger通过send方法发送Message时的消息处理者,也是消息发送send方法的真正发送者,也是Messenger的getBinder方法返回的实质对象(mTarget.asBinder();),也就是说其实是这个传入的Handler自己发送消息给自己处理,有点类似多线程通信的处理方式。

虽然Handler担起了消息处理者的角色,但Messager的跨进程实际上仍是通过AIDL完成的,而Handler作为Android最为关键的消息通信者,的确可以准确高效的完成消息通信这件事情。所以Messenger还有一个构造方法,通过传入一个Binder完成构建,而这个Messenger可以认为就是另一个进程中Messenger的代理。

0x82 Messenger 的用法

  1. 首先我们定义一个Messenger,然后让这个独立进程的Service返回一个Binder:
1
2
3
4
5
6
7
public Messenger messenger = new Messenger(new DealHandler());

@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
  1. 然后在另一端,我们创建一个ServiceConnection用来绑定远程服务(RemoteService):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Messenger sMessenger;

public Messenger getsMessenger() {
return sMessenger;
}

private ServiceConnection mServerConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
sMessenger = new Messenger(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {
sMessenger = null;
}
};

其中onServiceConnected回调中的IBinder就是在Service的onBind中返回的Binder,我们可以用这个Binder创建一个Messenger,这个Messenger中发的消息,Service中的Handler就可以收到并处理。

  1. 发送消息

发送过程也很简单,和用Handler进行线程通信的方法差不多:

1
2
3
4
5
6
7
8
9
Message message = Message.obtain();
message.replyTo = clientMessenger;
message.what = type;
message.setData(bundle);
try {
serviceMessage.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}

其中很关键的一句是message.replyTo = clientMessenger;,这一句在消息中带上了客户端的Messenger,用于服务端Service返回消息,放回消息的方法与发送无异。

Read More

Material Design绚丽动画:SharedElement

0x81 Transition Framework

如之前提到的,Transition Framework是Android Kitkat开始添加的动画框架,在Lolipop才得到完美的支持,所以支持源码里会对当前版本进行判断,采用不同的实现,API 19和21是两个分水岭。Transition其实就是动效转场的意思,Google官网既推出PropertyAnimator之后,为充分利用RenderThread并解放Android开发者,官方实现了一些专长动画,而这些动画是根据View状态来动态计算的,而Scene就保存了这些状态,动画实际上描述的就是从一种Scene到另一种Scene变化的过程。

0x82 SharedElementTransition

这种方式与之前记录的ContentTransition差不多,它不过是有一组共享的元素会被单独处理,具体的配置也有类似的属性,用法很简单:

1
2
3
4
startActivity(new Intent(context, ImageDetailActivity.class)
.putExtra("imageUrl", gankItemGirl.url), ActivityOptionsCompat
.makeSceneTransitionAnimation((Activity) context,
gankDayGirlHolder.iv_girl, context.getString(R.string.share_image)).toBundle());

其中,R.string.share_imagegankDayGirlHolder.iv_girl的transitionName,不需要任何其他的东西便可以开始一个共享元素的转场,你只需要保证目标Activity也有这样一个元素。

Read More

Android IPC:Messenger

0x81 Messenger 是什么

Messenger就是一个消息邮递员,它负责发送消息到队列并依次给它关联的Handler进行处理,它多用于跨进程通信,是Android IPC的一共工作实现方式。Messenger的源码比较简单,继承自Parcelable,构造时持有Handler并可将Handler转换成跨进程通信的Binder,它不会影响关联组件的生命周期,但是当相关组件(比如独立进程的Service)被销毁时通信也会中断。

0x82 如何在一个独立的进程中运行Activity或Service

我们都知道,Android系统上的App在启动时会通过zygote创建一个进程,Android Framework便会初始化一个Application,之后所运行的Acitivity或启动的Service都会运行在同一个进程下,共享PID。那我们如何指定某一个组件运行在独立的进程上呢,只需要在Manifest清单文件上指定process属性名就行:

1
2
3
<service
android:name=".service.UDeviceControllerService"
android:process=":controller" />

这样,UDeviceControllerService这个服务就会运行在package:controller这个进程。但是问题也接踵而来,由于跨进程的原因,原本很多偷懒或投机取巧的实现方式都会出现问题,包括一些系统调用也会变得不可靠。

Read More