金三银四那些事(二)
消息分发流程:
android事件分发的图例总的来说分为:
(dispatchTouchEvent)activity——>
(dispatchTouchEvent) viewgrouop——>
(onInterceptTouchEvent)viewgroup——>
(dispatchTouchEvent) view——>
(onTouchEvent)view——>
(onTouchEvent) viewgroup——>
(onTouchEvent) activity
正常的走法是这样的,如果dispatchTouchEvent返回false或true就直接当做消费了,事件不会继续往下传递了,只有返回super的时候才会正常往下传,onInterceptTouchEvent是返回true是拦截,其他返回值是不拦截的,拦截后,事件不会传递到子view,onTouchEvent返回true才会进行消费,如果返回false或super那么会将事件交给上一层了。
说说线程池怎么工作的:
corePoolSize: 核心线程数,能够同时执行的任务数量;
maximumPoolSize:除去缓冲队列中等待的任务,最大能容纳的任务数(其实是包括了核心线程池数量);
keepAliveTime:超出workQueue的等待任务的存活时间,就是指maximumPoolSize里面的等待任务的存活时间;
unit:时间单位;
workQueue:阻塞等待线程的队列,一般使用new LinkedBlockingQueue()这个,如果不指定容量,会一直往里边添加,没有限制,workQueue永远不会满;
threadFactory:创建线程的工厂,使用系统默认的类;
handler:当任务数超过maximumPoolSize时,对任务的处理策略,默认策略是拒绝添加;
当线程数小于corePoolSize时,每添加一个任务,则立即开启线程执
行;当corePoolSize满的时候,后面添加的任务将放入缓冲队列
workQueue等待;当workQueue也满的时候,看是否超过
maximumPoolSize线程数,如果超过,默认拒绝执行。
下面我们看个例子:假如corePoolSize=2,maximumPoolSize=3,
workQueue容量为8;最开始,执行的任务A,B,此时corePoolSize已
用完,再次执行任务C,则C将被放入缓冲队列workQueue中等待
着,如果后来又添加了7个任务,此时workQueue已满,则后面再来
的任务将会和maximumPoolSize比较,由于maximumPoolSize为3,
所以只能容纳1个了,因为有2个在corePoolSize中运行了,所以后面
来的任务默认都会被拒绝。
corePoolSize=cpu核数+1
maximumPoolSize=cpu核数*2+1
这里会涉及到自定义线程池,以及会用到线程池下载的问题,大致思路是,断点下载,咋们可以将一个文件,按照多少个线程去平均分配文件的大小,假如我们的线程个数是4个,文件大小是100M,那么此时分得的每个线程是25M,那么第一个线程的终点是25M,第二个线程的终点是50M,第三个线程的终点是75M,第四个线程的终点是100M,每一个线程的起点那又是多少呢。起点肯定是活的,因为起点咋们需要先从本地取一次,咋们每一个线程起点是接着上一次结束的位置
说说android中应用的启动流程:
当我们点击手机桌面上的图标的时候,App就由Launcher开始启动了。但是,你有没有思考过Launcher到底是一个什么东西?
Launcher本质上也是一个应用程序,和我们的App一样,也是继承自Activity
public final class Launcher extends Activity
implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
View.OnTouchListener {
}
Launcher实现了点击、长按等回调接口,来接收用户的输入。既然是普通的App,那么我们的开发经验在这里就仍然适用,比如,我们点击图标的时候,是怎么开启的应用呢?如果让你,你怎么做这个功能呢?捕捉图标点击事件,然后startActivity()发送对应的Intent请求呗!是的,Launcher也是这么做的,就是这么easy!
这里直接看Launcher点击事件:
/**
* Launches the intent referred by the clicked shortcut
*/
public void onClick(View v) {
...ignore some code...
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
// Open shortcut
final Intent intent = ((ShortcutInfo) tag).intent;
int[] pos = new int[2];
v.getLocationOnScreen(pos);
intent.setSourceBounds(new Rect(pos[0], pos[1],
pos[0] + v.getWidth(), pos[1] + v.getHeight()));
//开始开启Activity咯~
boolean success = startActivitySafely(v, intent, tag);
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
mWaitingForResume.setStayPressed(true);
}
} else if (tag instanceof FolderInfo) {
//如果点击的是图标文件夹,就打开文件夹
if (v instanceof FolderIcon) {
FolderIcon fi = (FolderIcon) v;
handleFolderClick(fi);
}
} else if (v == mAllAppsButton) {
...ignore some code...
}
}
从上面的代码我们可以看到,在桌面上点击快捷图标的时候,会调用
startActivitySafely(v, intent, tag);
那么从程序列表界面,点击图标的时候会发生什么呢?实际上,程序列表界面使用的是AppsCustomizePagedView对象,所以我在这个类里面找到了onClick(View v)。
com.android.launcher2.AppsCustomizePagedView.java
/**
* The Apps/Customize page that displays all the applications, widgets, and shortcuts.
*/
public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
View.OnClickListener, View.OnKeyListener, DragSource,
PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
LauncherTransitionable {
@Override
public void onClick(View v) {
...ignore some code...
if (v instanceof PagedViewIcon) {
mLauncher.updateWallpaperVisibility(true);
mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
} else if (v instanceof PagedViewWidget) {
...ignore some code..
}
}
}
可以看到,调用的是
mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
所以咱们现在又明白了一件事情:不管从哪里点击图标,调用的都是Launcher.startActivitySafely()。
下面我们就可以一步步的来看一下Launcher.startActivitySafely()到底做了什么事情。
boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
try {
success = startActivity(v, intent, tag);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
}
return success;
}
调用了startActivity(v, intent, tag)
boolean startActivity(View v, Intent intent, Object tag) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
boolean useLaunchAnimation = (v != null) &&
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
if (useLaunchAnimation) {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
startActivity(intent, opts.toBundle());
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(),
opts.toBundle());
}
} else {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
startActivity(intent);
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(), null);
}
}
return true;
} catch (SecurityException e) {
...
}
return false;
}
这里会调用Activity.startActivity(intent, opts.toBundle()),这个方法熟悉吗?这就是我们经常用到的Activity.startActivity(Intent)的重载函数。而且由于设置了
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
所以这个Activity会添加到一个新的Task栈中,而且,startActivity()调用的其实是startActivityForResult()这个方法。
@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);
}
}
所以我们现在明确了,Launcher中开启一个App,其实和我们在Activity中直接startActivity()基本一样,都是调用了Activity.startActivityForResult()。
binder机制的理解:
binder机制运行图每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的,Client端与Server端进程往往采用ioctl等方法跟内核空间的驱动进行交互。
binder通道运行图:
Binder的C/S架构
在Android开发中,我们大量使用到了系统Service,比如媒体播放、各种传感器以及WindowManagerService等等等等(太多了~)。那么Android是怎么管理这些服务,并且让用户跨进程调用这些服务呢?首先我们看看调用系统服务的过程。在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,然后通过这个引用向具体的服务端发送请求,服务端执行完成后就返回
Binder驱动实现原理
服务端跨进程的类都要继承Binder类。我们所持有的Binder引用(即服务端的类引用)并不是实际真实的远程Binder对象,我们的引用在Binder驱动里还要做一次映射。也就是说,设备驱动根据我们的引用对象找到对应的远程进程。客户端要调用远程对象函数时,只需把数据写入到Parcel,在调用所持有的Binder引用的transact()函数,transact函数执行过程中会把参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存,Binder驱动从Client的共享内存中读取数据,根据这些数据找到对应的远程进程的共享内存,把数据拷贝到远程进程的共享内存中,并通知远程进程执行onTransact()函数,这个函数也是属于Binder类。远程进程Binder对象执行完成后,将得到的写入自己的共享内存中,Binder驱动再将远程进程的共享内存数据拷贝到客户端的共享内存,并唤醒客户端线程。
Binder机制运用
好了,现在对Binder机制已经理解了,我们再看看Android是怎么运用Binder的。再现前面代码:
//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);
//布局参数layoutParams相关设置略...
View view=LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);
//添加view
wm.addView(view, layoutParams);
这段代码前面已经出现过。getSystemService(getApplication().WINDOW_SERVICE);函数内部原理就是向ServiceManager查询标识符为getApplication().WINDOW_SERVICE的远程对象的引用。即WindowManager对象的引用,这个引用的真正实现是WindowManager的某个代理。得到这个引用后,在调用addView时,真正的实现是在代理里面,代理把参数打包到Parcel对象中,然后调用transact函数(该函数继承自Binder),再触发Binder驱动的一系列调用过程。