android技术分析
Android AIDL使用详解
1.什么是aidl:aidl是 Android Interface definition language的缩写,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口
icp:interprocess communication :内部进程通信
2.既然aidl可以定义并实现进程通信,那么我们怎么使用它呢?文档/android-sdk/docs/guide/developing/tools/aidl.html中对步骤作了详细描述:
--1.Create your .aidl file - This file defines an interface (YourInterface.aidl) that defines the methods and fields available to a client.
创建你的aidl文件,我在后面给出了一个例子,它的aidl文件定义如下:写法跟java代码类似,但是这里有一点值得注意的就是它可以引用其它aidl文件中定义的接口,但是不能够引用你的java类文件中定义的接口
- package com.cao.android.demos.binder.aidl;
- import com.cao.android.demos.binder.aidl.AIDLActivity;
- interface AIDLService {
- void registerTestCall(AIDLActivity cb);
- void invokCallBack();
- }
--2.Add the .aidl file to your makefile - (the ADT Plugin for Eclipse manages this for you). Android includes the compiler, called AIDL, in the tools/ directory.
编译你的aidl文件,这个只要是在eclipse中开发,你的adt插件会像资源文件一样把aidl文件编译成java代码生成在gen文件夹下,不用手动去编译:编译生成AIDLService.java如我例子中代码
--3.Implement your interface methods - The AIDL compiler creates an interface in the Java programming language from your AIDL interface. This interface has an inner abstract class named Stub that inherits the interface (and implements a few additional methods necessary for the IPC call). You must create a class that extends YourInterface.Stub and implements the methods you declared in your .aidl file.
实现你定义aidl接口中的内部抽象类 Stub,public static abstract class Stub extends android.os.Binder implements com.cao.android.demos.binder.aidl.AIDLService
Stub类继承了Binder,并继承我们在aidl文件中定义的接口,我们需要实现接口方法,下面是我在例子中实现的Stub类:
- private final AIDLService.Stub mBinder = new AIDLService.Stub() {
- @Override
- public void invokCallBack() throws RemoteException {
- Log("AIDLService.invokCallBack");
- Rect1 rect = new Rect1();
- rect.bottom=-1;
- rect.left=-1;
- rect.right=1;
- rect.top=1;
- callback.performAction(rect);
- }
- @Override
- public void registerTestCall(AIDLActivity cb) throws RemoteException {
- Log("AIDLService.registerTestCall");
- callback = cb;
- }
- };
Stub翻译成中文是存根的意思,注意Stub对象是在被调用端进程,也就是服务端进程,至此,服务端aidl服务端得编码完成了。
--4.Expose your interface to clients - If you're writing a service, you should extend Service and override Service.onBind(Intent) to return an instance of your class that implements your interface.
第四步告诉你怎么在客户端如何调用服务端得aidl描述的接口对象,doc只告诉我们需要实现Service.onBind(Intent)方法,该方法会返回一个IBinder对象到客户端,绑定服务时不是需要一个 ServiceConnection对象么,在没有了解aidl用法前一直不知道它是什么作用,其实他就是用来在客户端绑定service时接收 service返回的IBinder对象的:
- AIDLService mService;
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- Log("connect service");
- mService = AIDLService.Stub.asInterface(service);
- try {
- mService.registerTestCall(mCallback);
- } catch (RemoteException e) {
- }
- }
- public void onServiceDisconnected(ComponentName className) {
- Log("disconnect service");
- mService = null;
- }
- };
mService就是AIDLService对象,具体可以看我后面提供的示例代码,需要注意在客户端需要存一个服务端实现了的aidl接口描述文件,但是客户端只是使用该aidl接口,不需要实现它的Stub类,获取服务端得aidl对象后mService = AIDLService.Stub.asInterface(service);,就可以在客户端使用它了,对mService对象方法的调用不是在客户端执行,而是在服务端执行。
4.aidl中使用java类,需要实现Parcelable接口,并且在定义类相同包下面对类进行声明:
上面我定义了Rect1类
之后你就可以在aidl接口中对该类进行使用了
package com.cao.android.demos.binder.aidl;
import com.cao.android.demos.binder.aidl.Rect1;
interface AIDLActivity {
void performAction(in Rect1 rect);
}
注意in/out的说明,我这里使用了in表示输入参数,out没有试过,为什么使用in/out暂时没有做深入研究。
5.aidl使用完整示例,为了清除说明aidl使用,我这里写了一个例子,例子参考了博客:
http://blog.csdn.net/saintswordsman/archive/2010/01/04/5130947.aspx
作出说明
例子实现了一个 AIDLTestActivity,AIDLTestActivity通过bindservice绑定一个服务AIDLTestService,通过并获取AIDLTestActivity的一个aidl对象AIDLService,该对象提供两个方法,一个是registerTestCall注册一个 aidl对象,通过该方法,AIDLTestActivity把本身实现的一个aidl对象AIDLActivity传到 AIDLTestService,在AIDLTestService通过操作AIDLActivity这个aidl远端对象代理,使 AIDLTestActivity弹出一个toast,完整例子见我上传的资源:
http://download.csdn.net/source/3284820
文章仓促而成,有什么疑问欢迎大家一起讨论。
原文:http://blog.csdn.net/stonecao/article/details/6425019
posted @ 2012-02-08 17:49 小小博客小小员 阅读(20) 评论(0) 编辑
Android AIDL——实现机制浅析
1.基于前面写的aidl使用,这段时间准备研究ActivityManager框架,对aidl进行了更深入的研究,因为android框架大量使用了进程通信机制,所以,在研究android framework前认真研究一下AIDL的实现机制十分有必要的
2.前面讲了aidl是 Android Interface definition language的缩写,它是一种进程通信接口的描述,通过sdk解释器对器进行编译,会把它编译成java代码在gen目录下,类路径与aidl文件的类路径相同。
3.aidl接口
package com.cao.android.demos.binder.aidl;
import com.cao.android.demos.binder.aidl.AIDLActivity;
interface AIDLService {
void registerTestCall(AIDLActivity cb);
void invokCallBack();
}
它编译后生成的java文件如下
AIDLService.java详细描述了aidl接口的实现,看上面图示,AIDLActivity.aidl编译成了一个接口AIDLActivity,一个存根类Stub,一个代理类Proxy
public interface AIDLService extends android.os.IInterface//与AIDLActivity.aidl中定义的接口对应的java接口实现
public static abstract class Stub extends android.os.Binder implements com.cao.android.demos.binder.aidl.AIDLService
//继承android.os.Binder,在onTransact完成对通信数据的接收,通过不同通信参数code调用AIDLService接口方法,并回写调用返回结果AIDLService接口方法需要在
//服务端实现
private static class Proxy implements com.cao.android.demos.binder.aidl.AIDLService
//实现AIDLService接口方法,但是方法只是执行代理远程调用操作,具体方法操作在远端的Stub存根类中实现
总的来说,AIDLActivity.aidl编译会生成一个AIDLActivity接口,一个stub存根抽像类,一个proxy代理类,这个实现其实根axis的wsdl文件编译生成思路是一致的,
stub存根抽像类需要在服务端实现,proxy代理类被客户端使用,通过stub,proxy的封装,屏蔽了进程通信的细节,对使用者来说就只是一个AIDLActivity接口的调用
4.根据以上思路使用aidl再看一下AIDLService调用实现代码
--1.在服务端实现AIDLService.Stub抽象类,在服务端onBind方法中返回该实现类
--2.客户端绑定service时在ServiceConnection.onServiceConnected获取onBind返回的IBinder对象
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log("connect service");
mService = AIDLService.Stub.asInterface(service);
try {
mService.registerTestCall(mCallback);
} catch (RemoteException e) {
}
}
注意mConnection在bindservice作为调用参数:bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
--3.AIDLService.Stub.asInterface(service);
public static com.cao.android.demos.binder.aidl.AIDLService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);
//如果bindService绑定的是同一进程的service,返回的是服务端Stub对象本省,那么在客户端是直接操作Stub对象,并不进行进程通信了
if (((iin!=null)&&(iin instanceof com.cao.android.demos.binder.aidl.AIDLService))) {
return ((com.cao.android.demos.binder.aidl.AIDLService)iin);
}
//bindService绑定的不是同一进程的service,返回的是代理对象,obj==android.os.BinderProxy对象,被包装成一个AIDLService.Stub.Proxy代理对象
//不过AIDLService.Stub.Proxy进程间通信通过android.os.BinderProxy实现
return new com.cao.android.demos.binder.aidl.AIDLService.Stub.Proxy(obj);
}
--4.调用AIDLService接口方法,如果是同一进程,AIDLService就是service的Stub对象,等同直接调用Stub对象实现的AIDLService接口方法
如果是一个proxy对象,那就是在进程间调用了,我们看一个客户端调用的例子:
public void onClick(View v) {
Log("AIDLTestActivity.btnCallBack");
try {
mService.invokCallBack();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
--mService.invokCallBack()等同调用Proxy.invokCallBack,这个时候是进程间调用,我们看代理方法的实现
public void invokCallBack() throws android.os.RemoteException
{
//构造一个Parcel对象,该对象可在进程间传输
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
//DESCRIPTOR = "com.cao.android.demos.binder.aidl.AIDLService",描述了调用哪个Stub对象
_data.writeInterfaceToken(DESCRIPTOR);
//Stub.TRANSACTION_invokCallBack 标识调用Stub中哪个接口方法,mRemote在是构造Proxy对象的参数obj,也就是public void onServiceConnected(ComponentName className, IBinder service)
//中的service参数,它是一个BinderProxy对象,负责传输进程间数据。
mRemote.transact(Stub.TRANSACTION_invokCallBack, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
--5.BinderProxy.transact 该方法本地化实现
public native boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
//对应实现的本地化代码 /frameworks/base/core/jni/android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj,
jobject replyObj, jint flags)
//具体进程通信在c代码中如何实现,以后再深入研究。
--6.服务端进程数据接收
--调用堆栈
##AIDLService.Stub.onTransact
##AIDLService.Stub(Binder).execTransact
##NativeStart.run
--AIDLService.Stub.onTransact
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_registerTestCall:
{
data.enforceInterface(DESCRIPTOR);
com.cao.android.demos.binder.aidl.AIDLActivity _arg0;
_arg0 = com.cao.android.demos.binder.aidl.AIDLActivity.Stub.asInterface(data.readStrongBinder());
this.registerTestCall(_arg0);
reply.writeNoException();
return true;
}
//TRANSACTION_invokCallBack由前面客户端调用的时候transact方法参数决定,code==TRANSACTION_invokCallBack,执行
//invokCallBack方法,方法由继承Stud的服务端存根类实现。
case TRANSACTION_invokCallBack:
{
data.enforceInterface(DESCRIPTOR);
this.invokCallBack();
reply.writeNoException();
return true;
}
5.里面设置本地C代码的调用,我没有深入研究,随着后面我对android框架的深入,我会发blog进一步说民底层C代码是如何实现进程通信的,关于AIDL进程通信,暂时研究到这里。
原文:http://blog.csdn.net/stonecao/article/details/6579333
posted @ 2012-02-08 16:39 小小博客小小员 阅读(97) 评论(0) 编辑
android 布局长度单位深入研究
要想使自己的布局在不同设备达到精准空置,理清理顺android布局长度单位之间关系很有必要,否则你也许会经常挠头为什么显示出来的布局不是自己定义的效果呢,有些东西,虽然基础,但是弄个透彻也需要花些功夫,废话不多说,下面开始。
1.先了解一下android有支持哪些长度单位:
px: pixels(像素). 不同设备显示效果相同,比如我们800*480的屏幕宽度就是 800px
dip: device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,通常屏幕大时,density就大,屏幕小时,density就小
屏幕实际分辨率为240px*400px时,densityDpi=120
屏幕实际分辨率为320px*533px,densityDpi=160
屏幕实际分辨率为480px*800px,densityDpi=240
而dip与px之间的换算关系是:
pixs =dips * (densityDpi/160),也就是说当densityDpi=160时,1dip=1px
sp: scaled pixels(放大像素),sp的大小取决于系统metrics.scaledDensity值大小
pt: point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业(基本用不到)
pt与px的换算关系:pixs = pt*xdpi * (1.0f/72);xdpi表示1英寸像素个数
in(英寸)长度单位(基本用不到)
in与px的换算关系:pixs = in*xdpi
mm(毫米)长度单位(基本用不到)
mm与px的换算关系:pixs = mm * xdpi * (1.0f/25.4f)
2.看了上面具体长度单位的含义你会产生一个疑问,不同单位换算取决于系统的一些属性,比如densityDpi的值,xdpi的值,那么系统这些值在哪里获取了,直接看我写的测试用例:
public void testgetdisplay(){
WindowManager wm = (WindowManager) this.getInstrumentation().getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics mDisplayMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(mDisplayMetrics);
System.out.println("display.height="+wm.getDefaultDisplay().getHeight());
System.out.println("display.width="+wm.getDefaultDisplay().getWidth());
System.out.println("densityDpi="+mDisplayMetrics.densityDpi);
System.out.println("xdpi="+mDisplayMetrics.xdpi);
System.out.println("density="+mDisplayMetrics.density);
}
3.densityDpi与drawable-(hdpi,mdpi,ldpi)之间的关系
系统drawable有hdpi,mdpi,ldpi三个文件夹下面存放不同尺寸的图片,使用哪个文件下的文件,与系统densityDpi值是有关系的
densityDpi=120:ldpi
densityDpi=160:mdpi
densityDpi=240:hdpi
前面我又说过densityDpi取决于显示屏,这样你就了解了为什么不同显示屏WVGA,HVGA,QVGA会采用不同drawable-(hdpi,mdpi,ldpi)图片
分辨率为240px*400px,densityDpi=120-->QVGA:ldpi
分辨率为320px*533px,densityDpi=160 -->HVGA:mdpi
分辨率为480px*800px,densityDpi=240 -->WVGA:WVGA
4.尽管了解上面这些理论值,但是有时候发现设置了不同长度单位,可显示出来的效果却出人预想,我曾经就碰到过这种挠头的问题,为解决这个问题,只有
深入代码,一探究竟了。
在深入代码前我们首先要搞清楚一个问题,那就是代码中所有长度值的单位都是px,
手上没有现成的例子就以现在我研究的/Launcher2/res/layout-land/workspace_screen.xml为例,看一个自定义属性:
launcher:cellWidth="105pt"
该属性自定义了一个桌面快捷图标的宽度,若读者自己测试,自己写个测试view,设置属性
android:layout_width="800px"
是一样的。
当view 被创建的时候,xml中的属性值存在参数AttributeSet attrs中
public CellLayout(Context context, AttributeSet attrs, int defStyle)
继续看该构造函数的实现代码
public CellLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
//获取自定义属性组CellLayout中的所有自定义属性,关于自定义属性,这里不作展开说明
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
//获取属性cellWidth的值,长度单位将转换为px
mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
。。。
}
实现长度单位换算的关键代码就在a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10),直接深入到关键代码:
public int getDimensionPixelSize(int index, int defValue)
public static int complexToDimensionPixelSize(int data,DisplayMetrics metrics)
public static float applyDimension(int unit, float value,DisplayMetrics metrics){
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
unit就是指单位类型,这个怎么来的我没有,但我想它肯定是在解析xml是根据不同单位转换的。
内容就这些,希望对你有用,欢迎拍砖!
原文:http://blog.csdn.net/stonecao/article/details/6561651
posted @ 2012-02-08 16:38 小小博客小小员 阅读(26) 评论(0) 编辑
android Launcher——拖放功能深入研究
Luancher有一个相对比较复杂的功能就是拖放功能,要深入了解launcher,深入理解拖放功能是有必要的,这篇blog,我将对launcher的拖放功能做深入的了解
1.首先直观感受什么时候开始拖放?我们长按桌面一个应用图标或者控件的时候拖放就开始了,包括在all app view中长按应用图标,下面就是我截取的拖放开始的代码调用堆栈
at com.android.launcher2.DragController.startDrag(DragController.java:170)
at com.android.launcher2.Workspace.startDrag(Workspace.java:1068)
at com.android.launcher2.Launcher.onLongClick(Launcher.java:1683)
at android.view.View.performLongClick(View.java:2427)
at android.widget.TextView.performLongClick(TextView.java:7286)
at android.view.View$CheckForLongPress.run(View.java:8792)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
桌面应用图标由Launcher.onLongClick负责监听处理,插入断点debug进入onLongclick函数
if (!(v instanceof CellLayout)) {
v = (View) v.getParent();
}
//获取桌面CellLayout上一个被拖动的对象
CellLayout.CellInfo cellInfo = (CellLayout.CellInfo) v.getTag();
...
if (mWorkspace.allowLongPress()) {
if (cellInfo.cell == null) {
...
} else {
if (!(cellInfo.cell instanceof Folder)) {
...
//调用Workspace.startDrag处理拖动
mWorkspace.startDrag(cellInfo);
}
}
}
我上面只写出关键代码,首先是获取被拖动的对象v.getTag(),Tag什么时候被设置进去的了
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
if (action == MotionEvent.ACTION_DOWN) {
...
boolean found = false;
for (int i = count - 1; i >= 0; i--) {
final View child = getChildAt(i);
if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
child.getHitRect(frame);
//判断区域是否在这个子控件的区间,如果有把child信息赋给mCellInfo
if (frame.contains(x, y)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
cellInfo.cell = child;
cellInfo.cellX = lp.cellX;
cellInfo.cellY = lp.cellY;
cellInfo.spanX = lp.cellHSpan;
cellInfo.spanY = lp.cellVSpan;
cellInfo.valid = true;
found = true;
mDirtyTag = false;
break;
}
}
}
mLastDownOnOccupiedCell = found;
if (!found) {
...
//没有child view 说明没有点击桌面图标项
cellInfo.cell = null;
}
setTag(cellInfo);
}
看了上面代码知道,当开始点击桌面时,celllayout就会根据点击区域去查找在该区域是否有child存在,若有把它设置为tag.cell,没有,tag.cell设置为null,后面在开始拖放时launcher.onlongclick中对tag进行处理,
这个理顺了,再深入到workspace.startDrag函数,workspace.startDrag调用DragController.startDrag去处理拖放
mDragController.startDrag(child, this, child.getTag(), DragController.DRAG_ACTION_MOVE);
再分析一下上面调用的几个参数
child = tag.cell
this = workspace
child.getTag()是什么呢?在什么时候被设置?再仔细回顾原来launcher加载过程代码,在launcher.createShortcut中它被设置了:注意下面我代码中的注释
View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
favorite.setCompoundDrawablesWithIntrinsicBounds(null,
new FastBitmapDrawable(info.getIcon(mIconCache)),
null, null);
favorite.setText(info.title);
//设置favorite(一个桌面Shortcut类型的图标)的tag
favorite.setTag(info);
favorite.setOnClickListener(this);
return favorite;
}
继续深入解读DragController.startDrag函数
public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
//设置拖放源view
mOriginator = v;
//获取view的bitmap
Bitmap b = getViewBitmap(v);
if (b == null) {
// out of memory?
return;
}
//获取源view在整个屏幕的坐标
int[] loc = mCoordinatesTemp;
v.getLocationOnScreen(loc);
int screenX = loc[0];
int screenY = loc[1];
//该函数功能解读请继续往下看
startDrag(b, screenX, screenY, 0, 0, b.getWidth(), b.getHeight(),
source, dragInfo, dragAction);
b.recycle();
//设置原来view不可见
if (dragAction == DRAG_ACTION_MOVE) {
v.setVisibility(View.GONE);
}
}
////////////////////////////////////////////////////////////
public void startDrag(Bitmap b, int screenX, int screenY,
int textureLeft, int textureTop, int textureWidth, int textureHeight,
DragSource source, Object dragInfo, int dragAction) {
//隐藏软键盘
if (mInputMethodManager == null) {
mInputMethodManager = (InputMethodManager)
mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
}
mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
//mListener = deletezone,在blog laucher ui框架中有说明该函数,主要就是现实deletezone
if (mListener != null) {
mListener.onDragStart(source, dragInfo, dragAction);
}
//记住手指点击位置与屏幕左上角位置偏差
int registrationX = ((int)mMotionDownX) - screenX;
int registrationY = ((int)mMotionDownY) - screenY;
mTouchOffsetX = mMotionDownX - screenX;
mTouchOffsetY = mMotionDownY - screenY;
mDragging = true;
mDragSource = source;
mDragInfo = dragInfo;
mVibrator.vibrate(VIBRATE_DURATION);
//创建DragView对象
DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY,
textureLeft, textureTop, textureWidth, textureHeight);
//显示Dragview对象
dragView.show(mWindowToken, (int)mMotionDownX, (int)mMotionDownY);
}
到这里,拖放开始处理的框框基本清楚,但是DragView的创建和显示还有必要进一步深究
DragView dragView = mDragView = new DragView(mContext, b, registrationX, registrationY,
textureLeft, textureTop, textureWidth, textureHeight);
//函数参数说明:
mContext = launcher
b = 根据拖放源view创建的大小一致的bitmap对象
registrationX = 手指点击位置与拖放源view 坐标x方向的偏移
registrationY = 手指点击位置与拖放源view 坐标y方向的偏移
textureLeft = 0
textureTop = 0
textureWidth = b.getWidth()
textureHeight = b.getHeight()
//函数体
super(context);
//获取window管理器
mWindowManager = WindowManagerImpl.getDefault();
//一个动画,开始拖放时显示
mTween = new SymmetricalLinearTween(false, 110 /*ms duration*/, this);
//对源b 做一个缩放产生一个新的bitmap对象
Matrix scale = new Matrix();
float scaleFactor = width;
scaleFactor = mScale = (scaleFactor + DRAG_SCALE) / scaleFactor;
scale.setScale(scaleFactor, scaleFactor);
mBitmap = Bitmap.createBitmap(bitmap, left, top, width, height, scale, true);
// The point in our scaled bitmap that the touch events are located
mRegistrationX = registrationX + (DRAG_SCALE / 2);
mRegistrationY = registrationY + (DRAG_SCALE / 2);
其实函数很简单,就是记录一些参数,然后对view图片做一个缩放处理,并且准备一个tween动画,在长按桌面图标后图标跳跃到手指上显示该动画,了解这些,有助于理解函数dragView.show
//windowToken来自与workspace.onattchtowindow时候获取的view 所有attch的window标识,有这个参数,可以把dragview添加到
workspace所属的同一个window对象
//touchX,手指点击在屏幕的位置x
//touchy,手指点击在屏幕的位置y
public void show(IBinder windowToken, int touchX, int touchY) {
WindowManager.LayoutParams lp;
int pixelFormat;
pixelFormat = PixelFormat.TRANSLUCENT;
//布局参数值的注意的是view位置参数,
//x=touchX-mRegistrationX=touchX-(registrationX + (DRAG_SCALE / 2))=手指点击位置-view坐标与手指点击位置偏差加上缩放值
lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
touchX-mRegistrationX, touchY-mRegistrationY,
WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
/*| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM*/,
pixelFormat);
// lp.token = mStatusBarView.getWindowToken();
lp.gravity = Gravity.LEFT | Gravity.TOP;
lp.token = windowToken;
lp.setTitle("DragView");
mLayoutParams = lp;
//dragview的父类是Window,也就是说dragview可以拖放到屏幕的任意位置
mWindowManager.addView(this, lp);
mAnimationScale = 1.0f/mScale;
//播放开始拖动动画(直观感觉是图标变大了)
mTween.start(true);
}
2,拖放过程
拖放过程的处理需要深入了解DragController.onTouchEvent(MotionEvent ev)函数的实现,我下面列出关键的MotionEvent.ACTION_MOVE部分代码并作出注释说明
case MotionEvent.ACTION_MOVE:
// 根据手指坐标移动dragview
mDragView.move((int) ev.getRawX(), (int) ev.getRawY());
// 根据手指所在屏幕坐标获取目前所在的拖放目的view
final int[] coordinates = mCoordinatesTemp;
DropTarget dropTarget = findDropTarget(screenX, screenY, coordinates);
// 根据不同状态调用DropTarget的生命周期处理函数
if (dropTarget != null) {
if (mLastDropTarget == dropTarget) {
dropTarget.onDragOver(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX,
(int) mTouchOffsetY, mDragView, mDragInfo);
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
}
dropTarget.onDragEnter(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX,
(int) mTouchOffsetY, mDragView, mDragInfo);
}
} else {
if (mLastDropTarget != null) {
mLastDropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1], (int) mTouchOffsetX,
(int) mTouchOffsetY, mDragView, mDragInfo);
}
}
mLastDropTarget = dropTarget;
//判断是否在delete区域
boolean inDeleteRegion = false;
if (mDeleteRegion != null) {
inDeleteRegion = mDeleteRegion.contains(screenX, screenY);
}
//不在delete区域,在左边切换区
if (!inDeleteRegion && screenX < SCROLL_ZONE) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mScrollRunnable.setDirection(SCROLL_LEFT);
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
}
}
//不在delete区,在右边切换区
else if (!inDeleteRegion && screenX > scrollView.getWidth() - SCROLL_ZONE) {
if (mScrollState == SCROLL_OUTSIDE_ZONE) {
mScrollState = SCROLL_WAITING_IN_ZONE;
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
}
}
//在delete区域
else {
if (mScrollState == SCROLL_WAITING_IN_ZONE) {
mScrollState = SCROLL_OUTSIDE_ZONE;
mScrollRunnable.setDirection(SCROLL_RIGHT);
mHandler.removeCallbacks(mScrollRunnable);
}
}
break;
拖放过程总的处理思路就是根据当前坐标位置获取dropTarget的目标位置,然后又根据相关状态和坐标位置调用dropTarget的对应生命周期函数,这里面有两个点需要进一步深入了解,一是查找dropTarget:findDropTarget(screenX, screenY, coordinates),二是mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
--1.findDropTarget
private DropTarget findDropTarget(int x, int y, int[] dropCoordinates) {
final Rect r = mRectTemp;
//mDropTargets是一个拖放目标view别表,在laucher初始化等被添加
final ArrayList
final int count = dropTargets.size();
//遍历dropTargets列表,查看{x,y}是否落在dropTarget坐标区域,若是,返回dropTarget。
for (int i=count-1; i>=0; i--) {
final DropTarget target = dropTargets.get(i);
target.getHitRect(r);
//获取target左上角屏幕坐标
target.getLocationOnScreen(dropCoordinates);
r.offset(dropCoordinates[0] - target.getLeft(), dropCoordinates[1] - target.getTop());
if (r.contains(x, y)) {
dropCoordinates[0] = x - dropCoordinates[0];
dropCoordinates[1] = y - dropCoordinates[1];
return target;
}
}
return null;
}
--2.mScrollRunnable
// 看mScrollRunnable对象的构造类,通过setDirection设置滚动方向,然后通过一步调用 DragScroller.scrollLeft/scrollRight来对桌面进行向左向右滚动,想深入了解如何实现的,敬请阅读我相关 blog:Launcher——桌面移动详解
private class ScrollRunnable implements Runnable {
private int mDirection;
ScrollRunnable() {
}
public void run() {
if (mDragScroller != null) {
if (mDirection == SCROLL_LEFT) {
mDragScroller.scrollLeft();
} else {
mDragScroller.scrollRight();
}
mScrollState = SCROLL_OUTSIDE_ZONE;
}
}
void setDirection(int direction) {
mDirection = direction;
}
}
3.拖放结束,入口还是在DragController.onTouchEvent(MotionEvent ev)
先看调用堆栈:
at com.android.launcher2.DragController.endDrag(DragController.java:315)
at com.android.launcher2.DragController.onTouchEvent(DragController.java:471)
at com.android.launcher2.DragLayer.onTouchEvent(DragLayer.java:64)
at android.view.View.dispatchTouchEvent(View.java:3766)
onTouchEvent关键代码:
case MotionEvent.ACTION_UP:
mHandler.removeCallbacks(mScrollRunnable);
if (mDragging) {
// 拖动过程手指离开屏幕
drop(screenX, screenY);
}
endDrag();
break;
--1.drop(screenX, screenY);
final int[] coordinates = mCoordinatesTemp;
//获取dropTarget对象
DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
//coordinates=点触点在dropTarget 中的xy坐标
if (dropTarget != null) {
dropTarget.onDragExit(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
//根据相关参数判断是否可dropTarget是否接受该drag view
if (dropTarget.acceptDrop(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo)) {
dropTarget.onDrop(mDragSource, coordinates[0], coordinates[1],
(int) mTouchOffsetX, (int) mTouchOffsetY, mDragView, mDragInfo);
mDragSource.onDropCompleted((View) dropTarget, true);
return true;
} else {
mDragSource.onDropCompleted((View) dropTarget, false);
return true;
}
}
return false;
原文:http://blog.csdn.net/stonecao/article/details/6561631
posted @ 2012-02-08 16:37 小小博客小小员 阅读(140) 评论(4) 编辑
android Launcher——启动过程详解
一LauncherApplication->onCreate
--1.//设置最小堆内存4M
--2.//建立应用图标缓存器
--3.//建立LauncherModel
--4.//注册Intent.ACTION_PACKAGE_ADDED,Intent.ACTION_PACKAGE_REMOVED,Intent.ACTION_PACKAGE_CHANGED事件监听器
LauncherModel作为广播接收器对上面3中事件进行监听
--5.//添加对桌面favorites content provider 数据变化监听器
二Launcher->onCreate
--1.获取LauncherApplication LauncherModel mIconCache等LauncherApplication初始化的对象
--2.新建拖放控制器new DragController(this)
--4.//获取桌面组件管理器,启动桌面组件host
--待深入研究
--5.//从array.hotseats中加载所有的hotseats(热键 如挂机按钮,google浏览器)
--待深入研究
--6.从launcher.preferences加载本地设置
--7.//设置壁纸尺寸宽度=display.getWidth()* WALLPAPER_SCREENS_SPAN,高度=display.getHeight()
--8.//加载布局文件
--9.//初始化所有控件
--10.//从Bundle savedInstanceState获取桌面持久化数据 设置mRestoring = true;
--11.如果mRestoring == false,调用LauncherModel加载桌面项 mModel.startLoader(this, true,isLanguageChange);
--a.LauncherModel.Loader.startLoader() 代码同步处理
--b.新建LauncherModel.Loader.LoaderThread线程并启动线程(桌面项加载,详细见第三步)
--12.注册Intent.ACTION_CLOSE_SYSTEM_DIALOGS广播监听
三 桌面项加载 LauncherModel.Loader.LoaderThread.run:新线程执行
--1.等待主线程运行结束才开始加载
--2.判断是否先加载桌面;loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true;
--3.loadWorkspaceFirst==true
--a.loadAndBindWorkspace()
--loadWorkspace():从数据库launcher.db中查询中所有桌面项构造对应类型的ItemInfo对象存入
mItems,mAppWidgets,mFolders列表。
--bindWorkspace():
--1.laucher.startBinding(),异步调用,在主线程中handle
//do no things
--2.laucher.bindItems(),异步调用,在主线程中handle
--1.根据ItemInfo对象创建桌面图标view对象
--2.获取item.screen, item.cellX, item.cellY, spanX, spanY,
调用workspace.addInScreen添加到对应桌面的cell
--1.重新设置桌面图标view 的layoutparam(类型为cellLayout.layoutparam)
--2.根据item.screen获取桌面的celllayout对象,也就是workspace下5个用户桌面中的一个
--3.调用celllayout.addview方法把桌面图标view对象添加为celllayout的child,也就是为用户桌面添加一个桌面图标
--4.桌面图标view对象添加OnLongClickListener=laucher,由laucher负责监听桌面图标view的longclick事件
--5.如果桌面图标是DropTarget对象,拖放控制器mDragController添加该view到拖放目的地列表
--3.laucher.bindFolders(),异步调用,在主线程中handle
//launcher.mFolders.putAll(mFolders);
--4.laucher.bindAppWidget(),异步调用,在主线程中handle
--1.获取LauncherAppWidgetInfo的appWidgetId
--2.获取AppWidgetProviderInfo appWidgetInfo mAppWidgetManager.getAppWidgetInfo(appWidgetId)
--3.根据appWidgetInfo创建桌面组件的view AppWidgetHostView对象
--4.调用workspace.addInScreen添加到对应桌面的cell
--5.laucher.finishBindingItems(),异步调用,在主线程中handle
--mWorkspaceLoading=false
--b.loadAndBindAllApps();
如果没有加载apps或者改变了语言设置
loadAllAppsByBatch();
--1.//设置package 查询条件Intent.ACTION_MAIN Intent.CATEGORY_LAUNCHER
--2.//清空mAllAppsList mAllAppsList.clear();
--3.//packageManager 查询所有应用pakages
--4.分批次mAllAppsList.add 应用pakages
--5.laucher.bindAllApplications()//异步调用
--mAllAppsGrid.setApps(apps)
--6.若分多个批次加载 laucher.bindAppsAdded()//异步调用
--mAllAppsGrid.addApps(apps);
否则
onlyBindAllApps();
--1.mAllAppsList.data.clone();
--2.laucher.bindAllApplications()//异步调用
--mAllAppsGrid.setApps(apps)
--4.loadWorkspaceFirst==false
--a.loadAndBindAllApps();
--b.loadAndBindWorkspace();
原文:http://blog.csdn.net/stonecao/article/details/6536083
posted @ 2012-02-08 16:35 小小博客小小员 阅读(144) 评论(0) 编辑
android Launcher——数据加载与变更
在前面我的blog中,我已经说过了,Launcher所有的桌面项数据是存储在launcher.db/favorites表中
在 Launcher启动时loadeworkspace函数中会从数据库中查询所有的数据并显示出来,这个桌面项数据加载过程在我的blog Launcher——启动过程详解中有比较详细的描述,这篇blog,我着重讲述Launcher桌面项的添加,变更和删除,下面就进入主题
1.Launcher桌面项的添加:从用户操作的角度来看,我们有两种方式来添加桌面项
--1.从all app view中拖到桌面,从下面的调用堆栈知道最后通过LauncherProvider.insert把拖过来的桌面项添加到了桌面,在拖放结束时执行(launcher2.Workspace.onDrop).
at com.android.launcher2.LauncherProvider.insert(LauncherProvider.java:129)
at android.content.ContentProvider$Transport.insert(ContentProvider.java:174)
at android.content.ContentResolver.insert(ContentResolver.java:587)
at com.android.launcher2.LauncherModel.addItemToDatabase(LauncherModel.java:242)
at com.android.launcher2.LauncherModel.addOrMoveItemInDatabase(LauncherModel.java:133)
at com.android.launcher2.Workspace.onDropExternal(Workspace.java:1203)
at com.android.launcher2.Workspace.onDropExternal(Workspace.java:1165)
at com.android.launcher2.Workspace.onDrop(Workspace.java:1125)
--2. 通过长按桌面打开一个dialog来添加桌面项,通过对象框添加最后调用同样的方法LauncherProvider.insert去实现操作,只是前面 的发起动作不同,它是在选择应用的activity结束后回到Launcher.onActivityResult发起调用的。
at com.android.launcher2.LauncherProvider.insert(LauncherProvider.java:129)
at android.content.ContentProvider$Transport.insert(ContentProvider.java:174)
at android.content.ContentResolver.insert(ContentResolver.java:587)
at com.android.launcher2.LauncherModel.addItemToDatabase(LauncherModel.java:242)
at com.android.launcher2.LauncherModel.addOrMoveItemInDatabase(LauncherModel.java:133)
at com.android.launcher2.Workspace.onDropExternal(Workspace.java:1203)
at com.android.launcher2.Workspace.addApplicationShortcut(Workspace.java:1117)
at com.android.launcher2.Launcher.completeAddApplication(Launcher.java:931)
at com.android.launcher2.Launcher.onActivityResult(Launcher.java:579)
2.Launcher桌面项的变更:Launcher桌面项的变更只有我们拖动桌面项,改变桌面项的位置,当结束拖放时,会通过调用LauncherProvider.update函数来修改launcher.db/favorites表中的记录来变更桌面项的位置信息。
at com.android.launcher2.LauncherProvider.update(LauncherProvider.java:185)
at android.content.ContentProvider$Transport.update(ContentProvider.java:204)
at android.content.ContentResolver.update(ContentResolver.java:707)
at com.android.launcher2.LauncherModel.moveItemInDatabase(LauncherModel.java:159)
at com.android.launcher2.Workspace.onDrop(Workspace.java:1144)
3.Launcher桌面项的删除:把桌面项拖动到垃圾箱时执行,不多说什么了,给个调用堆栈一看就明白,DeleteZone就是垃圾箱对象对应的构造类
at com.android.launcher2.LauncherProvider.delete(LauncherProvider.java:168)
at android.content.ContentProvider$Transport.delete(ContentProvider.java:198)
at android.content.ContentResolver.delete(ContentResolver.java:675)
at com.android.launcher2.LauncherModel.deleteItemFromDatabase(LauncherModel.java:271)
at com.android.launcher2.DeleteZone.onDrop(DeleteZone.java:123)
posted @ 2012-02-08 16:35 小小博客小小员 阅读(49) 评论(0) 编辑
android Launcher——ui框架
着手开发laucher 我个人觉得首先要从ui框架入手,了解清楚了ui框架对laucher就有了一个大致的了解,知道关键的几个类在哪里,对后面进一步深入地研究作出铺垫。废话不多说了,直接进入主题:
1.先看图
这是我正在研究的一个800*480的launcher的ui框架图,对应的桌面显示效果如下:
下面一行应用是我修改桌面后的结果,在前面我的ui框架图中没有。
闲话不多说,下面就一一做出讲解
1.DragLayer--DragLayer继承FrameLayout,并在此基础上组合了DragController实现拖放功能,DragLayer主要监听下面两个用户事件
onInterceptTouchEvent
onTouchEvent
交给DragController进行处理,DragController根据是否在拖放中等信息控制控件拖放过程处理。
DragLayer 是Launcher这个activity的顶层view,Launcher2这个应用只有一个activity那就是Laucher.java
2.DeleteZone--打开launcher.xml,DeleteZone默认是不显示的android:visibility="invisible"
但是我们每次开始拖放图标的时候DeleteZone就显示了,它是怎么实现的呢?DeleteZone实现了DragController.DragListener接口,DragListener提供两个接口方法,
onDragStart:隐藏把手,显示DeleteZone
onDragEnd:显示把手,隐藏DeleteZone
分别在开始DragController开始拖放和结束拖放的时候被调用.
另外DeleteZone实现了DropTarget接口,一旦鼠标把图标拖放到DeleteZone,就会调用DeleteZone
实现的onDrop方法对应用图标进行删除处理。
3.ClippedImageView--屏幕左右移动按钮,正常图标很小,你只能看到小点,我设置view背景不透明为绿色如前面我的截图
ClippedImageView要注意三点,
--1.桌面左右移动时Drawable的变换,变换图标列表可查看home_arrows_right.xml
,ClippedImageView通过把drawable传递给worksapce,当桌面切换时通过调用Drawable.setLevel函数实现不同图标显示。
--2.点击ClippedImageView实现左右桌面切换,查看ClippedImageView的布局文件
android:onClick="previousScreen",该属性定义了一个ClippedImageView onClick事件响应函数,函数在布局文件对应的Activity中定义
也就是在Launcher.java中定义
/**
* @Description:用户点击前一个桌面按钮
* @param v
* @Others:
*/
@SuppressWarnings({"UnusedDeclaration"})
public void previousScreen(View v) {
if (!isAllAppsVisible()) {
mWorkspace.scrollLeft();
}
}
--3.在ClippedImageView初始化(Launcher.setupViews)中添加了长按事件OnLongClickListener有当长按ClippedImageView,会执行
launcher.onlongclick方法,方法执行显示5个桌面的预览微缩图显示,具体实现不做深入说明,后面将作深入研究。
4.RelativeLayout--android:id="@+id/all_apps_button_cluster",如前面截图右边灰色竖状条,它是一个相对布局对象,上面承载了三个view
中间是一个HandleView,是一个进入allappview的按钮,HandleView的上下都是一个进入google搜索的imageview
--HandleView
--1.点击事件 传递给Launcher.onClick进行处理 显示应用菜单view
--2.长按事件 传递给Launcher.onLongClick进行处理,方法执行显示5个桌面的预览微缩图显示
--google搜索的imageview
--onClick响应:android:onClick="launchHotSeat"
5.AllApps2D -- 菜单view,在launcher.xml中引用的是:
all_apps.xml定义如下:
中间增加了一个层次,如果有需要可以定义自己的apps_3d布局
AllApps2D包括两个view
--1.GridView android:id="@+id/all_apps_2d_grid" 应用菜单grid view 它是一个grid view 用来放应用图标
GridView对应的Adapter实现类是AppsAdapter,对应的Adapter布局文件是:application_boxed.xml
--2.view android:id="@+id/all_apps_2d_home" 应用菜单view右边的home按钮 ,点击隐藏 AllApps2D
6.Workspace--用户桌面包括5个workspace_screen,默认显示的是:launcher:defaultScreen="0"
workspace继承了viewgroup,5个workspace_screen作为它的child,值得注意它只接收CellLayout类型的child,workspace重写了addview函数,
添加非CellLayout的child将抛异常
--Workspace长按事件仍由launcher.onLongClick来监听
--Workspace实现了DropTarget, DragSource两个接口,意味着Workspace既是拖放源,又是拖放目的地
--Workspace实现DragScroller接口,DragScroller接口提供两个方法
void scrollLeft();
void scrollRight();
在拖放过程被DragController调用实现桌面的左右滚动
--CellLayout Workspace下的一个桌面布局,CellLayout也是ViewGroup的子类,上面我的桌面截图红色区域就是CellLayout
Workspace下有5个CellLayout顺序排列,Workspace下布局文件:android:scrollbars="horizontal"决定了5个CellLayout排列是横向还是纵向的
CellLayout被划分成不同的cell空间,并使用boolean[][] mOccupied;来标识每个cell是否被占用,先看CellLayout的布局文件workspace_screen.xml:
xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hapticFeedbackEnabled="true"
launcher:cellWidth="115dip"//每一个cell的宽度
launcher:cellHeight="100dip"//每个cell的高度
launcher:longAxisStartPadding="0dip"//cell距离父view CellLayout左边距
launcher:longAxisEndPadding="0dip"//cell距离父view CellLayout右边距
launcher:shortAxisStartPadding="0dip"//cell距离父view CellLayout上边距
launcher:shortAxisEndPadding="80dip"//cell距离父view CellLayout下边距
launcher:shortAxisCells="3"//对横屏来说表示CellLayout cells行数
launcher:longAxisCells="5"//对横屏来说表示CellLayout cells列数
android:background="#FF0000">
当纵向的控件不够cells排列时,cell将产生重叠,横向不产生重叠,横向每个cell间隔至少为0
--CellLayout覆盖重新实现了onMeasure方法,和onlayout方法,它限定了child view 使用的布局参数类型为CellLayout.LayoutParams因此企图通过修改
workspace_screen.xml来改变它的桌面布局是不会得以成功的,你必须修改CellLayout类
--CellLayout.LayoutParams说明,CellLayout.LayoutParams下有几个成员需要说明一下
--cellX:该child view占用的第几列的cell(若横向占用多个cell,表示最左边的cellx)
--cellY: 该child view占用的第几行的cell(若纵向占用多个cell,表示最上边的celly)
--cellHSpan:横向跨越的列数
--cellVSpan: 纵向跨越行数
--isDragging:该child是否正在被拖动
--regenerateId:是否重新生成view id
7.桌面图标的四种类型
ItemInfo--所有类型的父类
--ApplicationInfo 应用图标项 应用菜单view中所有应用图标的数据表示
--FolderInfo 桌面文件夹
--UserFolderInfo 对应实现布局文件R.layout.folder_icon
--LiveFolderInfo 对应实现布局文件R.layout.live_folder_icon
--LauncherAppWidgetInfo 桌面组件
--ShortcutInfo 应用快捷方式 对应实现布局文件R.layout.application
原文:http://blog.csdn.net/stonecao/article/details/6462357
posted @ 2012-02-08 16:32 小小博客小小员 阅读(29) 评论(0) 编辑
Android FrameWork——Touch事件派发过程详解
对于android的窗口window管理,一直感觉很混乱,总想找个时间好好研究,却不知如何入手,现在写的Touch事件派发过程详解,其实跟 android的窗口window管理服务WindowManagerService存在紧密联系,所以从这里入手切入到 WindowManagerService的研究,本blog主要讲述一个touch事件如何从用户消息的采集,到 WindowManagerService对Touch事件的派发,再到一个Activity窗口touch事件的派发,并着重讲了Activity窗口 touch事件的派发,因为这个的理解对我们写应用很好地处理touch事件很重要
一.用户事件采集到WindowManagerService和派发
--1.WindowManagerService,顾名思义,它是是一个窗口管理系统服务,它的主要功能包含如下:
--窗口管理,绘制
--转场动画--Activity切换动画
--Z-ordered的维护,Activity窗口显示前后顺序
--输入法管理
--Token管理
--系统消息收集线程
--系统消息分发线程
这里,我关注的是系统消息的收集和系统消息的分发,其他功能,当我对WindowManagerService有一个完整的研究后在发blog
--2.系统消息收集和分发线程的创建
这个的从WindowManagerService服务的创建说起,与其他系统服务一样,WindowManagerService在systemServer中创建的:
ServerThread.run
-->WindowManagerService.main
-->WindowManagerService.WMThread.run(构建一个专门线程负责WindowManagerService)
-->WindowManagerService s = new WindowManagerService(mContext, mPM,mHaveInputMethods);
--mQueue = new KeyQ();//消息队列,在构造KeyQ中会创建一个InputDeviceReader线程去读取用户输入消息
--mInputThread = new InputDispatcherThread();//创建一个消息分发线程,读取并处理mQueue中消息
整个过程处理原理很简单,典型的生产者消费者模型,我先画个图,后面针对代码进一步说明
--3.InputDeviceReader线程,KeyQ构建时,会启动一个线程去读取用户消息,具体代码在KeyInputQueue.mThread,在构造函数中,mThread会start,接下来,接研究一下mThread.run:
//用户输入事件消息读取线程
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
RawInputEvent ev = new RawInputEvent();
while (true) {//开始消息读取循环
try {
InputDevice di;
//本地方法实现,读取用户输入事件
readEvent(ev);
//根据ev事件进行相关处理
...
synchronized (mFirst) {//mFirst是keyQ队列头指针
...
addLocked(di, curTimeNano, ev.flags,RawInputEvent.CLASS_TOUCHSCREEN, me);
...
}
}
}
}
函数我也没有看大明白:首先调用本地方法readEvent(ev);去读取用户消息,这个消息包括按键,触摸,滚轮等所有用户输入事件,后面不同的事件类型会有不同的处理,不过最后事件都要添加到keyQ的队列中,通过addLocked函数
--4队列添加和读取函数addLocked,getEvent
addLocked函数比较简单,就分析一下,有助于对消息队列KeyQ的数据结构进行理解:
//event加入inputQueue队列
private void addLocked(InputDevice device, long whenNano, int flags,
int classType, Object event) {
boolean poke = mFirst.next == mLast;//poke为true表示消息队列为空
//从QueuedEvent缓存QueuedEvent获取一个QueuedEvent对象,并填入用户事件数据,包装成一个QueuedEvent
QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event);
QueuedEvent p = mLast.prev;//队列尾节点为mLast,把ev添加到mlast前
while (p != mFirst && ev.whenNano < p.whenNano) {
p = p.prev;
}
ev.next = p.next;
ev.prev = p;
p.next = ev;
ev.next.prev = ev;
ev.inQueue = true;
if (poke) {//poke为true,意味着在空队列中添加了一个QueuedEvent,这时系统消息分发线程可能在wait,需要notify一下
long time;
if (MEASURE_LATENCY) {
time = System.nanoTime();
}
mFirst.notify();//唤醒在 mFirst上等待的线程
mWakeLock.acquire();
if (MEASURE_LATENCY) {
lt.sample("1 addLocked-queued event ", System.nanoTime() - time);
}
}
}
很简单,使用mFirst,mLast实现的指针队列,addLocked是QueuedEvent对象添加函数,对应在系统消息分发线程中会有一个getEvent函数来读取inputQueue队列的消息,我在这里也先讲一下:
QueuedEvent getEvent(long timeoutMS) {
long begin = SystemClock.uptimeMillis();
final long end = begin+timeoutMS;
long now = begin;
synchronized (mFirst) {//获取mFirst上同步锁
while (mFirst.next == mLast && end > now) {
try {//mFirst.next == mLast意味队列为空,同步等待mFirst锁对象
mWakeLock.release();
mFirst.wait(end-now);
}
catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
if (begin > now) {
begin = now;
}
}
if (mFirst.next == mLast) {
return null;
}
QueuedEvent p = mFirst.next;//返回mFirst的下一个节点为处理的QueuedEvent
mFirst.next = p.next;
mFirst.next.prev = mFirst;
p.inQueue = false;
return p;
}
}
通过上面两个函数得知,消息队列是通过mFirst,mLast实现的生产者消费模型的同步链表队列
--5.InputDispatcherThread线程
InputDispatcherThread处理InputDeviceReader线程存放在KeyInputQueue队列中的消息,分发到具体的一个客户端的IWindow
InputDispatcherThread.run
-->windowManagerService.process{
...
while (true) {
// 从mQueue(KeyQ)获取一个用户输入事件,正上调用我上面提到的getEvent方法,若队列为空,线程阻塞挂起
QueuedEvent ev = mQueue.getEvent(
(int)((!configChanged && curTime < nextKeyTime)
? (nextKeyTime-curTime) : 0));
...
try {
if (ev != null) {
...
if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件
eventType = eventType((MotionEvent)ev.event);
} else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
ev.classType == RawInputEvent.CLASS_TRACKBALL) {//键盘输入事件
eventType = LocalPowerManager.BUTTON_EVENT;
} else {
eventType = LocalPowerManager.OTHER_EVENT;//其他事件
}
...
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
...
dispatchKey((KeyEvent)ev.event, 0, 0);//键盘输入,派发key事件
mQueue.recycleEvent(ev);
break;
case RawInputEvent.CLASS_TOUCHSCREEN:
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);//touch事件,派发touch事件
break;
case RawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);//滚轮事件,派发Trackball事件
break;
case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
configChanged = true;
break;
default:
mQueue.recycleEvent(ev);//销毁事件
break;
}
}
} catch (Exception e) {
Slog.e(TAG,
"Input thread received uncaught exception: " + e, e);
}
}
}
WindowManagerService.dispatchPointer,一旦判断QueuedEvent为屏幕点击事件,就调用函数WindowManagerService.dispatchPointer进行处理:
WindowManagerService.dispatchPointer
-->WindowManagerService.KeyWaiter.waitForNextEventTarget(获取touch事件要派发的目标windowSate)
-->WindowManagerService.KeyWaiter.findTargetWindow(从一个一个WindowSate的z-order顺序列表mWindow中获取一个能够接收当前touch事件的WindowSate)
-->WindowSate target = waitForNextEventTarget返回的WindowSate对象
-->target.mClient.dispatchPointer(ev, eventTime, true);(往目标window派发touch消息)
target.mClient是一个IWindow代理对象IWindow.Proxy,它对应的代理类是ViewRoot.W,通过远程代理调用,WindowManagerService把touch消息派发到了对应的Activity的PhoneWindow
之后进一步WindowManagerService到Activity消息的派发在下文中说明
二WindowManagerService派发Touch事件到当前top Activity
--1.先我们看一个system_process的touch事件消息调用堆栈,在WindowManagerService中的函数 dispatchPointer,通过一个IWindow的客户端代理对象把消息发送到相应的IWindow服务端,也就是一个IWindow.Stub 子类。
Thread [<21> InputDispatcher] (Suspended (breakpoint at line 321 in IWindow$Stub$Proxy))
IWindow$Stub$Proxy.dispatchPointer(MotionEvent, long, boolean) line: 321
WindowManagerService.dispatchPointer(KeyInputQueue$QueuedEvent, MotionEvent, int, int) line: 5270
WindowManagerService$InputDispatcherThread.process() line: 6602
WindowManagerService$InputDispatcherThread.run() line: 6482
--2.通过IWindow.Stub.Proxy代理对象把消息传递给IWindow.Stub对象。 code=TRANSACTION_dispatchPointer,IWindow.Stub对象被ViewRoot拥有(成员mWindow,它是一 个ViewRoot.W类对象)
--3.在case TRANSACTION_dispatchPointer会调用IWindow.Stub子类的实现方法dispatchPointer
--4.IWindow.Stub.dispatchPointer
-->ViewRoot.W.dispatchPointer
-->ViewRoot.dispatchPointer
public void dispatchPointer(MotionEvent event, long eventTime,
boolean callWhenDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
msg.arg1 = callWhenDone ? 1 : 0;
sendMessageAtTime(msg, eventTime);
}
--5.ViewRoot继承自handle,在handleMessage函数的case-DISPATCH_POINTER会调用mView.dispatchTouchEvent(event),
mView是一个PhoneWindow.DecorView对象,在PhoneWindow.openPanel方法会创建一个ViewRoot对象, 并设置ViewRoot对象的mView为一个PhoneWindow.decorView成员,PhoneWindow.DecorView是真正的 root view,它继承自FrameLayout,这样调用mView.dispatchTouchEvent(event)
其实就是调用PhoneWindow.decorView的dispatchTouchEvent方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
.dispatchTouchEvent(ev);
}
--6.分析上面一段红色代码,可以写成return (cb != null) && (mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev)).当cb不为null执行后面,如果mFeatureId<0,执行 cb.dispatchTouchEvent(ev),否则执行super.dispatchTouchEvent(ev),也就是 FrameLayout.dispatchTouchEvent(ev),那么callback cb是什么呢?是Window类的一个成员mCallback,我下面给一个图你可以看到何时被赋值的:
setCallback(Callback) : void - android.view.Window
-->attach(Context, ActivityThread, Instrumentation, IBinder, int, Application, Intent, ActivityInfo, CharSequence, Activity, String, Object, HashMap
--> performLaunchActivity(ActivityRecord, Intent) : Activity - android.app.ActivityThread
performLaunchActivity我们很熟识,因为我前面在讲Activity启动过程详解时候讲过,在启动一个新的Activity会执行该方法,在该方法里面会执行attach方法,找到attach方法对应代码可以看到:
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow就是一个PhoneWindow,它是Activity的一个内部成员,通过调用mWindow的setCallback(this),把新建立的Activity设置为PhoneWindow一个mCallback成员,这样我们就清楚了,前面的cb就是拥有这个PhoneWindow的Activity,cb.dispatchTouchEvent(ev)也就是执行:Activity.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//getWindow()返回的就是PhoneWindow对象,执行superDispatchTouchEvent,就是执行PhoneWindow.superDispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//执行Activity.onTouchEvent方法
return onTouchEvent(ev);
}
--7.再看PhoneWindow.superDispatchTouchEvent:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
--> public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);//FrameLayout.dispatchTouchEvent
}
}
superDispatchTouchEvent调用super.dispatchTouchEvent,我前面讲过mDector是一个 PhoneWindow.DecorView,它是一个真正Activity的root view,它继承了FrameLayout,通过super.dispatchTouchEvent他会把touchevent派发给各个 activity的子view,也就是我们再Activity.onCreat方法中setContentView时设置的view,touch event时间如何在Activity各个view中进行派发的我后面再作详细说明,但是从上面我们可以看出一点若Activity下面的子view拦截了touchevent事件(返回true),Activity.onTouchEvent就不会执行。
--8.这部分,我再画一个静态类结构图把前面讲到的一些类串起来看一下:
我用红色箭头线把整个消息派发过程过程给串起来,然后system_process进程和ap进程分别用虚线椭圆圈起,这样以后相信你更理解各个类之间关系。
对应的对象空间图如下,与上面图是对应的,只是从不同角度去看:
--9.其实上面所讲的大部分已经是在客户端ap中执行了,也就是在ap进程中,只是执行逻辑基本是框架代码中,还没有到达我们使用 layout.xml布局的view中来,这里我先在我们的一个view中onTouchEvent插入一个断点看一看消息从 WindowManagerService到达Activity.PhoneWindow后执行堆栈情况(我插入的断点在Launcher2的 HandleView中),后面继续讲解:
Thread [<1> main] (Suspended (breakpoint at line 4280 in View))
HandleView(View).onTouchEvent(MotionEvent) line: 4280
HandleView.onTouchEvent(MotionEvent) line: 71
HandleView(View).dispatchTouchEvent(MotionEvent) line: 3766
RelativeLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
DragLayer(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
FrameLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
PhoneWindow$DecorView.superDispatchTouchEvent(MotionEvent) line: 1671
PhoneWindow.superDispatchTouchEvent(MotionEvent) line: 1107
ForyouLauncher(Activity).dispatchTouchEvent(MotionEvent) line: 2086
PhoneWindow$DecorView.dispatchTouchEvent(MotionEvent) line: 1655
ViewRoot.handleMessage(Message) line: 1785
ViewRoot(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 123
ActivityThread.main(String[]) line: 4634
三.Activity中View中的Touch事件派发
--1.首先我画一个Activity中的view层次结构图:
前面我讲过,来自windowManagerService的touch消息最终会派发到到Decorview,Decorview继承子 FrameLayout,它只有一个子view就是mContentParent,我们写ap的view全部添加到到mContentParent。
--2.了解了Activity中的view的层次结构,那先从DecorView开始看touch事件是如何被派发的,前面讲过最终消息会派发到 FrameLayout.dispatchTouchEvent也就是 ViewGroup.dispatchTouchEvent(FrameLayout也没有覆盖该方法),
同样mContentParent也是执行ViewGroup.dispatchTouchEvent来派发touch消息,那我们就详细看一下ViewGroup.dispatchTouchEvent(若要很好掌握应用程序touch事件处理,这部分要重点看):
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//计算是否禁止touch Intercept
if (action == MotionEvent.ACTION_DOWN) {//按下事件,也就是touch开始
if (mMotionTarget != null) {
mMotionTarget = null;//清除mMotionTarget,也就是说每次touch开始,mMotionTarget要被重新设置
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {//判断消息是否需要被viewGroup拦截
// 消息不被viewGroup拦截,找到相应的子view进行touch事件派发
ev.setAction(MotionEvent.ACTION_DOWN);//重新设置event 为action_down
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;//获取viewgroup所有的子view
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {//若子view可见或者有动画在执行的,才能够接收touch事件
child.getHitRect(frame);//获取子view的布局坐标区域
if (frame.contains(scrolledXInt, scrolledYInt)) {//若子view 区域包含当前touch点击区域
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {//派发TouchEvent给包含这个touch区域的子view
// 若该子view消费了对应的touch事件
mMotionTarget = child;//设置viewgroup消息派发的目标子view
return true;//返回true,该touch事件被消费掉
}
}
}
}
}
//若touch事件被拦截,mMotionTarget = null,后面touch消息不再派发给子view
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||//计算是up或者cancel
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
//target为null,意味着在ACTION_DOWN时没有找到能消费touch消息的子view或者在ACTION_DOWN时消息被拦截了,这个时候
//调用父类view的dispatchTouchEvent消息进行派发,也就是说,此时viewgroup处理touch消息跟普通view一致。
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
//target!=null,意味在ACTION_DOWN时touch消息没有被拦截,而且子view target消费了ACTION_DOWN消息,需要再判断消息是否被拦截
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
//消息被拦截,而前面ACTION_DOWN时touch消息没有被拦截,所以需要发送ACTION_CANCEL通知子view target
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// 派发消息ACTION_CANCEL给子view target
}
// mMotionTarget=null,后面消息不再派发给子view
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
//isUpOrCancel,设置mMotionTarget=null,后面消息不再派发给子view
mMotionTarget = null;
}
......
//没有被拦截继续派发消息给子view target
return target.dispatchTouchEvent(ev);
}
--3.ViewGroup.dispatchTouchEvent我查看了一下所有子类,只有PhoneWindow.DecorView覆盖了 该方法,该方法前面讲DecorView消息派发时提过,它会找到对应包含这个PhoneWindow.DecorView对象的Activity把消息 交给Activity去处理,其它所有viewGroup的子类均没有覆盖dispatchTouchEvent,也就是说所有包含子view的父 view对于touch消息派发均采用上面的逻辑,当然,必要的时候我们可以覆盖该方法实现自己的touch消息派发逻辑,如Launcher2中的 workspace类就是重新实现的该dispatchTouchEvent方法,从上面的dispatchTouchEvent函数逻辑其实我们也可以 总结几条touch消息派发逻辑:
(1).onInterceptTouchEvent用来定义是否截取touch消息逻辑,若在groupview中想截取touch消息,必须覆盖viewgroup中该方法。
(2).消息在整个dispatchTouchEvent过程中,若子 view.dispatchTouchEvent返回true,父view中将不再处理该消息,但前提是该消息没有被父view截取,在整个touch消息处理过程中,若处理函数返回true,我们称之为消费了该touch事件,并且后面的父view将不再处理该消息。
(3).在整个touch事件过程中,从action_down到action_up,若父ViewGroup的函数onInterceptTouchEvent一旦返回true,消息将不再派发给子view,细分可为两种情况,若是在action_down时onInterceptTouchEvent返回true,不会派发任何消息给子view,并且后面onInterceptTouchEvent函数将不再会被执行,若是action_down时onInterceptTouchEvent返回false ,而后面touch过程中onInterceptTouchEvent==true,父viewGroup会把action_cancel派发给子view,也之后不再派发消息给子view,并且onInterceptTouchEvent函数后面将不再被执行。
--4.为了更清楚的理解viewGroup消息的派发流程,我画一个流程图如下:
--5.上面我只是讲了父view与子view之间当有touch事件的消息派发流程,对于view的消息是怎么派发的(也包裹viewGroup 没有子view或者有子view但是不消费该touch消息情况),因为从继承结构上看viewgroup继承了view,viewgroup覆盖了 view的dispatchTouchEvent方法,不过从上面流程图也可以看到当mMotionTarget为Null它会执行父类 view.dispatchTouchEvent,其他view的子类都是执行view.dispatchTouchEvent派发touch事件,不过 若我们自定义view是可以覆盖该方法的。下面就仔细研究一下view.dispatchTouchEvent方法的代码:
public final boolean dispatchTouchEvent(MotionEvent event) {
//mOnTouchListener是被View.setOnTouchListener设置的,(mViewFlags & ENABLED_MASK)计算view是否可被点击
//当view可被点击并且mOnTouchListener被设置,执行mOnTouchListener.onTouch
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;//若mOnTouchListener.onTouch返回true,函数返回true
}
return onTouchEvent(event);//若mOnTouchListener.onTouch返回false,调用onToucheEvent
}
函数逻辑很简单,前面的viewGroup touch事件流程图中我已经画出的,为区别我把它着色成青绿色,总结一句话若mOnTouchListener处理了touch消息,不执行onTouchEvent,否则交给onTouchEvent进行处理。
不知道是否讲清楚的,要清楚掌握估计还得写些例子测试一下是否是我上面所说的流程,不过我想了解事件的派发流程,对写应用的事件处理相信很有用,比如我以 前碰到一个问题是手指点击屏幕到底是子view执行onclick还是执行父view的view移动,这个时候就需要深入了解viewde touch事件派发流程,该响应点击的时候响应子view的点击,该父view移动的时候拦截touch事件交给父view进行处理。
原文:http://blog.csdn.net/stonecao/article/details/6759189
posted @ 2012-02-08 16:29 小小博客小小员 阅读(184) 评论(0) 编辑
Android FrameWork——Binder机制详解(1)
1.前面我曾经发表过一篇blog介绍了aidl实现机制(aidl实现机制浅析),不过那只是停留在java表层,并遗留了一个问题,那就是BinderProxy.transact 该方法本地化实现,同时我指出了它的具体代码位置:
\frameworks\base\core\jni\android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,jint code, jobject dataObj,jobject replyObj, jint flags)
要进一步深入进去,其实涉及了android ipc机制,也就是android独有进程通信机制的Binder,本文主要就是想深入说明Binder机制,
首先说明,这个有点难度,只是对我来说,呵呵,我参加完台湾教授,高焕堂先生开设的android高阶培训课程(android软硬件结合开发培训),并反复看了课件android底层Android框架底层结构知多少?以及网上其它同仁的blog后才有所体会,不过,在些这blog前我还是有不少不解之处,我和大家一样,带着疑问去写这个blog,边写边学习,有问题我会遗留,同时可能有不少不正确的地方,希望路过的高手解答一下,小弟不胜感激。
2.好了,结束开场白,开始Binder机制的探究,就从 android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact函数开始,当我们在java层使用java进行进程通信时 BinderProxy.transact是本地方法,它jni实现方法是android_os_BinderProxy_transact:
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj,
jobject replyObj, jint flags)
{
//把java对象转换成c对象
...
//把java层的obj(BinderProxy)对象转换成C层的BpBinder对象
IBinder* target = (IBinder*)
env->GetIntField(obj, gBinderProxyOffsets.mObject);
status_t err = target->transact(code, *data, reply, flags);//BpBinder->transact
...
}
我只写出了关键代码,具体的你可以认真阅读相关代码,其实该函数主要做的工作就是把java对象转成c++对象,然后调用BpBinder->transact方法。
3.BinderProxy,BpBinder等这些对象你看了可能有些晕呼呼,这些是什么呢,在进一步深入研究前先画一个类静态结构图描述一下这些类之间的相关关系:
(图1)
--1.在java层,有一个IBinder接口,这个在读这个blog前你应该已经很熟悉了,它有两个实现类,一个是BinderProxy,是用作客户端的,一个是Binder用作服务端的存根类
--2.类似在C++层,也有一个IBinder接口,同样它也有两个子类,BpBinder用作客户端代理类,BBinber用作服务端存根类
--3.从上图可以很清晰的看出我在段落2中写的BinderProxy.transact调用轨迹,BinderProxy.transact()-->android_util_Binder.cpp(android_os_BiinderProxy_transact())-->BpBinder.transact()
4.接下来我们进入BpBinder.transact()研究一下:
//code:指定调用哪个函数的code常量
//data:java Parcel Object to c++ Parcel Object,不过这里作为引用传递参数防止修改data指针(C++大学学过,工作中一直从事java开发,若有误,请提出)
//reply:返回数据Parcel对象指针
//flags:0:normal调用,同步方式,1:异步调用,发送数据结束后不等待调用结果返回
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
// Once a binder has died, it will never come back to life.
if (mAlive) {
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
////////////////////////////////////////////////////////////////////////////
/*BpBinder::transact-->*/
//handle:==BpBinder.mHandle,它在BpBinder构建的时候被赋值,它代表BpBinder所连接的BBinder所在的地址
//其它参数见上文说明
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err = data.errorCheck();//校验传入参数
flags |= TF_ACCEPT_FDS;
IF_LOG_TRANSACTIONS() {
TextOutput::Bundle _b(alog);
alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand "
<< handle << " / code " << TypeCode(code) << ": "
<< indent << data << dedent << endl;
}
if (err == NO_ERROR) {
LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
(flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);//传输数据
}
if (err != NO_ERROR) {
if (reply) reply->setError(err);//校验或者传输数据出错,设置返回数据错误信息
return (mLastError = err);
}
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
err = waitForResponse(reply);//等待返回数据
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
IF_LOG_TRANSACTIONS() {
TextOutput::Bundle _b(alog);
alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "
<< handle << ": ";
if (reply) alog << indent << *reply << dedent << endl;
else alog << "(none requested)" << endl;
}
} else {
err = waitForResponse(NULL, NULL);
}
return err;
}
我再对代码进行一下梳理,BpBinder.transact()方法很简单,它调用了 IPCThreadState::self()->transact(mHandle, code, data, reply, flags);就结束了,并没有做什么事情,不过增加了一个参数,mHandle,它在BpBinder被构建的时候创建的,而具体mHandle值是怎么得来的,涉及到serviceManager框架,我这里先保留这个问题,在后面我弄清楚serviceManager框架后再对该问题进行说明。
然后再看下IPCThreadState::transact的实现,我再代码中作出了简单的注释,详细的代码逻辑我也没有搞太清楚,阅读C++代码,我能力还是有待提高,我关注两个关键函数调用,一个是传输数据,另一个是等待数据返回,我在函数中已经把它标注成为红色了。
--cmd==BC_TRANSACTION
--binderFlags==处理后的flags, flags |= TF_ACCEPT_FDS;
--handle BpBinder句柄
--data 传输数据引用
--statusBuffer==null
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;//构造传输数据包
tr.target.handle = handle;//设置目标句柄
tr.code = code;//设置目标函数code
tr.flags = binderFlags;//设置调用方式,是否等待返回
const status_t err = data.errorCheck();//传输参数数据校验
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();//设置消息体长度
tr.data.ptr.buffer = data.ipcData();//设置消息体
tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);//??
tr.data.ptr.offsets = data.ipcObjects();
} else if (statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer = statusBuffer;
tr.offsets_size = 0;
tr.data.ptr.offsets = NULL;
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);//写入命令
mOut.write(&tr, sizeof(tr));//写入数据
return NO_ERROR;
}
这里已经可以看到了IPC底层通信的一些轨迹了,不过遗憾我还没有弄懂mOut是怎么被赋值,如何与Binder驱动建立联系的,若路过的高手知道的话,希望能够不吝赐教
--waitForResponse
//reply返回数据的Parcel指针
//acquireResult:??
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
int32_t cmd;//返回消息命令
int32_t err;
while (1) {//消息接收循环
if ((err=talkWithDriver()) < NO_ERROR) break;//跟Binder Driver通信,看是否有要接收数据
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;
cmd = mIn.readInt32();//获取消息命令
...
//BR开头的cmd都是返回数据,否则就应该是当该进程作为服务端的时候接收来自客户端的命令
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
...
case BR_DEAD_REPLY:
...
...
case BR_REPLY://猜测这表示有返回数据的命令
{
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
LOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
if (err != NO_ERROR) goto finish;
if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
reply->ipcSetDataReference(//设置返回数据
reinterpret_cast
tr.data_size,
reinterpret_cast
tr.offsets_size/sizeof(size_t),
freeBuffer, this);
} else {
err = *static_cast
freeBuffer(NULL,
reinterpret_cast
tr.data_size,
reinterpret_cast
tr.offsets_size/sizeof(size_t), this);
}
} else {
freeBuffer(NULL,
reinterpret_cast
tr.data_size,
reinterpret_cast
tr.offsets_size/sizeof(size_t), this);
continue;
}
}
goto finish;
default://不是返回数据,作为服务端接收来自客户端的命令??
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
finish:
if (err != NO_ERROR) {
if (acquireResult) *acquireResult = err;
if (reply) reply->setError(err);
mLastError = err;
}
return err;
}
为了保证文章篇幅,我可能剪裁了一些不是很相关代码,我并不打算细致了解waitForResponse,我只是要知道它是如何接收并设置返回数据的,mIn与mOut一样,我同样不知道它是怎么构建的,我暂且把它理解成Binder驱动设备的输入输出流,这样理解应该是没有问题的,待有进一步的学习了解,我再作说明
5.IPCThreadState* IPCThreadState::self()
为什么我这里要讨论 IPCThreadState::self函数呢,如果你是带着思考来看我的blog的话你可能已经发现了 IPCThreadState::transact函数是有点问题的,什么问题呢?看我在代码中标注为红色的传输数据和等待返回数据的两个函数调用,这样的同步调用显然是需要同步锁的,不然的话多线程执行的话就会有问题的,可能我等待回来的数据是另一个binder调用返回的结果,而一个客户端显然是容许多个线程调用多个binder的,而 IPCThreadState::self()似乎跟java中的单例对象很像,这样的话所有线程都调用transact而又不加同步锁的话是有问题的,我后面仔细看了好几回代码,里外都没有加锁的代码,最后发现问题在IPCThreadState::self():
IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS) {//gHaveTLS:猜测表示IPCThreadState是否已经存在
restart:
const pthread_key_t k = gTLS;
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);//获取当前线程绑定的IPCThreadState
if (st) return st;
return new IPCThreadState;
}
if (gShutdown) return NULL;
pthread_mutex_lock(&gTLSMutex);
if (!gHaveTLS) {//IPCThreadState 没有绑定到当前线程
if (pthread_key_create(&gTLS, threadDestructor) != 0) {//创建并一个IPCThreadState到当前线程,并把引用地址回填gTLS
pthread_mutex_unlock(&gTLSMutex);//绑定失败,释放锁,并返回null
return NULL;
}
gHaveTLS = true;//绑定成功,修改标志变量
}
pthread_mutex_unlock(&gTLSMutex);//释放锁
goto restart;
}
我不知道我上面的注释是否正确,涉及到系统函数调用,我理解不一定正确,如你是C开发者,请帮我指正,谢谢!
从我的注释,你应该已经理解了,self函数返回的是线程绑定对象,而非是单例对象,这个跟数据库事务线程绑定应该是类似的,看完这个我就恍然大悟,原来IPCThreadState是线程持有的,也就是每个线程拥有一个对象,这样的话它
调用writeTransactionData写完数据后调用waitForResponse等待返回数据是没有问题的,就像我原来写ServerSocket的Socket连接池一样,每个Socket连接new一个线程进行通信一样,没有问题的。
原文:http://blog.csdn.net/stonecao/article/details/6657438
posted @ 2012-02-08 16:27 小小博客小小员 阅读(52) 评论(0) 编辑
Android FrameWork——Binder机制详解(2)
6.前面5个段落我主要说明了BinderProxy是如何把数据发送出去的,Ok, 那么接下来,我们肯定想要知道服务端是怎么接收数据并传递给相应的BBinder进行处理的,有没有注意到前面waitForResponse我标注为蓝色的代码,这给我们一个启示,也许接收返回数据(进程作为客户端)和接收命令(进程作为服务端)处理的是同一个函数,但这是我的一个猜测,而实际上我参阅其它blog和代码后并非这么回事,waitForResponse只在客户端发送完数据等待接收数据才被调用的,那么服务端是怎么接收数据的呢?做过 socket编程的同仁们可能知道,服务端为实现接收数据和链接,一般会启动一个监听线程去监听客户端发过来的数据,同样android进程通信的服务端也是这么做的,在这里我先给你看一个Debug线程图:
这是一个简单的Android应用进程Debug图,有没有看到两个Binder Thread线程,每个应用都包含Binder Thread线程,不信你可以试试,它就是负责监听来自Binder驱动的消息的,那么这两个线程在什么时候被起来的呢,这又是我的一个疑问,我也不清楚不同Activity应用的Binder Thread是在哪里被起来的,我后面有待研究,不过System_process进程我倒是清楚它是如何启动这两个线程的,在 init1->system_init() @System_init.cpp函数最后:
if (proc->supportsProcesses()) {
LOGI("System server: entering thread pool.\n");
ProcessState::self()->startThreadPool();//启动Binder监听线程
IPCThreadState::self()->joinThreadPool();
LOGI("System server: exiting thread pool.\n");
}
IPCThreadState::self()函数前面我们已经讲过了,它是线程绑定对象,ProcessState::self函数我列出如下
sp
{
if (gProcess != NULL) return gProcess;
AutoMutex _l(gProcessMutex);
if (gProcess == NULL) gProcess = new ProcessState;
return gProcess;
}
显然ProcessState是单例的,也即每个进程拥有一个ProcessState对象,而每个线程拥有一个IPCThreadState对象,关于 ProcessState,IPCThreadState,网上有一篇转高焕堂先生的blog,建议你此时插读一下,我不作详细说明:
認識Android的ProcessState類別和物件:
(图2:摘自高焕堂先生课件)
ProcessState负责打开Binder驱动,与Binder驱动进行通信,而IPCStateThread负责每个具体线程IPC数据读写
7.服务端监听线程的构建
服务端具体监听线程是怎样构建的的了,前面我只说了是通过ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool();这两个函数创建的,网上很多blog也只是简单说一下,具体是怎样一个过程呢?我这里进行一下说明:
void ProcessState::startThreadPool()
{
AutoMutex _l(mLock);
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
//////////////////////////////////////
//isMain==true
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
int32_t s = android_atomic_add(1, &mThreadPoolSeq);
char buf[32];
sprintf(buf, "Binder Thread #%d", s);
LOGV("Spawning new pooled thread, name=%s\n", buf);
sp
t->run(buf);
}
}
注意线程的创建是在run方法中,run方法并不像java中Thread.run是新线程的工作函数,它仍在当前线程中执行,PoolThread并没有覆盖父类Thread.run方法,因此它执行的是Thread.run方法:
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
。。。
//这个时候才真正创建一个新的线程,新线程的工作方法是_threadLoop,它仍是父类Thread的一个方法
res = createThreadEtc(_threadLoop,
this, name, priority, stack, &mThread);
。。。
}
////////////////////////////////////////////
//新线程工作方法
int Thread::_threadLoop(void* user)
{
。。。
do {
//调用虚函数threadLoop(),该函数被PoolThread实现
result = self->threadLoop();
}
。。。
} while(strong != 0);
return 0;
}
////////////////////////////////////////////
//PoolThread成员方法
virtual bool threadLoop()
{
IPCThreadState::self()->joinThreadPool(mIsMain);//真正线程工作函数
return false;
}
通过上面的代码我们看出,ProcessState::self()->startThreadPool();创建了一个新的线程,并且新线程的工作函数 IPCThreadState::self()->joinThreadPool(true);紧接着当前线程又调用了 IPCThreadState::self()->joinThreadPool(true);我这里是以system_process进程为例,那说明system_process至少有两个binder线程监听Binder驱动消息,接下来我们仔细看一下 joinThreadPool(true)函数的实现:
//服务端线程工作函数
void IPCThreadState::joinThreadPool(bool isMain)
{
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);//通知驱动开始消息监听??
androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);//加入默认线程组??
status_t result;
do {
int32_t cmd;
。。。
// now get the next command to be processed, waiting if necessary
result = talkWithDriver();
if (result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) continue;
cmd = mIn.readInt32();//读取命令
}
result = executeCommand(cmd);//执行命令
}
if(result == TIMED_OUT && !isMain) {
break;
}
} while (result != -ECONNREFUSED && result != -EBADF);
mOut.writeInt32(BC_EXIT_LOOPER);//通知驱动结束消息监听
talkWithDriver(false);
}
通过while循环调用IPCThreadState.mIn读取cmd并执行,这我有一个疑惑就是多个线程去透过IPCThreadState.mIn读取驱动消息会不会存在问题?这个暂且mark一下,以后再分析
到此,我们总算了解服务端的消息监听机制,证明并不是如段落6我猜测的IPCThreadState.waitForResponse去接收消息的,而是 IPCThreadState::joinThreadPool中直接透过IPCThreadState.mIn读取消息的。
8.消息处理IPCThreadState::executeCommand(int32_t cmd)
走到这一步视乎离目标已经不远了,回到我们原来的问题,别让我们身陷代码堆栈而迷失了方向,我们前面做的这些工作,目的都是为了客户端的BpBinder 对象能够调到服务端的BBinder对象,而在BpBinder中有一个mHandle参数指定了服务端的Binder通信地址,因此消息才得以发送到服务端,现在我们有一个问题要解决就是,服务端可能存在多个BBinder对象,我们接收消息后,如何找到对应的BBinder对象把消息传给它处理了,我们先看消息处理函数
executeCommand:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;//BBinder对象地址
RefBase::weakref_type* refs;
status_t result = NO_ERROR;
switch (cmd) {
case BR_ERROR:
...
...
//这是BC_TRANSACTION消息到服务端对应的cmd(客户端发送的是cmd==BC_TRANSACTION,服务端cmd变成BR_TRANSACTION?待研究)
case BR_TRANSACTION:
{
//构造接收消息结构体,与前面客户端writeTransactionData构造的传输数据包一致的结构体
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));//读取消息到结构体tr
LOG_ASSERT(result == NO_ERROR,
"Not enough command data for brTRANSACTION");
if (result != NO_ERROR) break;
Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast
tr.data_size,
reinterpret_cast
tr.offsets_size/sizeof(size_t), freeBuffer, this);
const pid_t origPid = mCallingPid;
const uid_t origUid = mCallingUid;
mCallingPid = tr.sender_pid;//客户端进程pid,uid(uid代表什么??)
mCallingUid = tr.sender_euid;
。。。
Parcel reply;
if (tr.target.ptr) {//tr.target.ptr值在客户端writeTransactionData中并未设置,只是设置了tr.target.handle = handle;猜测在Binder驱动,根据handle找到了对应BBinder的地址并填写到这个字段了。
sp
const status_t error = b->transact(tr.code, buffer, &reply, 0);//调用对应的BBinder对象的transact方法
if (error < NO_ERROR) reply.setError(error);
} else {//tr.target.ptr为空,没有指定BBinder对象,调用the_context_object->transact方法,the_context_object具体是什么对象?我不能够搜索到代码
const status_t error = the_context_object->transact(tr.code, buffer, &reply, 0);//猜测the_context_object应该是ApplicationThread对象,代表本应用进程上下文,不知道是否正确
if (error < NO_ERROR) reply.setError(error);
}
if ((tr.flags & TF_ONE_WAY) == 0) {//默认同步方式,需要发送返回数据
LOG_ONEWAY("Sending reply to %d!", mCallingPid);
sendReply(reply, 0);//发送返回数据
} else {
LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);//ONEWAY方式客户端并不等待调用返回,因此不需要发送返回数据
}
mCallingPid = origPid;
mCallingUid = origUid;
IF_LOG_TRANSACTIONS() {
TextOutput::Bundle _b(alog);
alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
<< tr.target.ptr << ": " << indent << reply << dedent << endl;
}
}
break;
case BR_DEAD_BINDER:
...
...
}
...
return result;
}
尽管还是存在很多疑问,但是大概脉络应该已经清楚了,收到数据包binder_transaction_data tr后根据tr.target.ptr得到BBinder对象指针,然后调用该对象的transact方法。
9.回到java层的Binder对象
在图一中,对c层的IBinder类继承结构已有一个清楚的说明,BBinder有两个子类,一个是JavaBBinder,一个是 BnInterface,若Binder存根对象用C实现的,那它会继承BnInterface,以MediaPlayerService为例,它的继承结构如下:MediaPlayerService-->BnMediaPlayerService-->BnInterface
代码调用过程图如下
(图3)
这个很简单,再看怎么调用到java层的Binder对象,前面在图1中已经描述了,java Binder对象对应到C空间的对象是JavaBBinder对象,所以,在BBinder::tansact方法中,调用到得是 JavaBBinder.onTransact方法,在深入JavaBBinder.onTransact前我们先了解一下JavaBBinder是一个什么对象,它怎么建立与java层Binder对象联系的,下面是JavaBBinder的构造函数:
//env:java虚拟机指针
//object:java层的Binder对象
JavaBBinder(JNIEnv* env, jobject object)
: mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
{
LOGV("Creating JavaBBinder %p\n", this);
android_atomic_inc(&gNumLocalRefs);
incRefsCreated(env);
}
通过JavaBBinder的构造函数,我们可以推测,在构建java层Binder对象时也构造了对应的C层的一个JavaBBinder,JavaBBinder对象有两个成员,
JavaVM* const mVM;
jobject const mObject;
显然,JavaBBinder其实就是对java层Binder对象的一个包装对象,理解了这个,我们再看JavaBBinder.onTransact
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
{
JNIEnv* env = javavm_to_jnienv(mVM);
jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
code, (int32_t)&data, (int32_t)reply, flags);
jthrowable excep = env->ExceptionOccurred();
。。。
return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
}
我用红色标注了关键代码,可以看到,它通过虚拟机的CallBooleanMethod反向去调用了java层的Binder对象mObject的一个方法,该方法由函数指针gBinderOffsets.mExecTransact引用,我们看一下该函数指针的定义:
gBinderOffsets.mExecTransact
= env->GetMethodID(clazz, "execTransact", "(IIII)Z");
总算找到了execTransact方法,总算浮出水面到了我们熟悉的java层,我就不详细解读代码了,画个调用图如下:
(图4)
到此,Binder通信的内部机制总算介绍完了,也遗留了不少问题,路过的同仁们若对我提出的这些问题有清楚的也希望分享一下!
posted @ 2012-02-08 16:27 小小博客小小员 阅读(9) 评论(0) 编辑
Android FrameWork——StatusBar
Android系统顶上的状态栏是属于FrameWork的东东,由于项目上需要对状态栏进行一些修改调整,我对其作了一个初步研究,写出来大家共享一下,其实这些早已写了,只是想等研究StatusBar中ExtendsView后再整理一个blog,不过现在已经没有时间了,目前深入研究 Android Binder机制,废话不多少,开始进入statusbar的探索
1.先从StatusBar的布局文件入手,文件位置位置:frameworks/base/core/res/res/layout/status_bar.xml
2.我对status_bar.xml布局文件进行分析,画出结构图,以便对StatusBar有一个整体的了解:
3.com.android.server.status.StatusBarView--statusbar的最顶层view,直观上我们是看不到它的
4.LinearLayout android:id="@+id/icons" 我们看到的状态栏,系统默认是左边放通知图标notificationIcons,右边放状态图标statusIcons
--1.通知图标区域: IconMerger android:id="@+id/notificationIcons"
--2.状态图标区域:LinearLayout android:id="@+id/statusIcons"
我对status_bar.xml做了修改,notificationIcons的background="#ff0000",statusIcons的background="#0000ff",下面就是现实效果
(图1)
5.那么LinearLayout android:id="@+id/ticker"显示在哪里呢,在正常情况下ticker是不显示的,只有在StatusBarService收到通知时它才显示,比如SD卡拔出时,我也截了一张图,在前面我已经修改了status_bar.xml并修改它 android:background="#0000ff"大家可以看一下效果:
(图2)
6.最后一个是DateView,它是在点击statusbar时才显示的,默认是隐藏的
7.StatusBar的ui框架就这些,是不是很简单,接下来,我们肯定还有些问题:StatusBarView是如何被创建的呢?上面那个状态时钟图标如何被加载的呢?通知图标如何被加载的?下面我将继续讲解。
8.StatusBarView创建
StatusBarView是如何被创建的呢,这得从system_process说起,在system_process构建时,会运行ServerThread.run方法加载各种系统服务,其中有一个服务就是StatusBarService:
看上面调用堆栈图,StatusBarService会调用一个makeStatusBarView的方法,在里面它创建了StatusBarView
9.状态图标的加载
我们前面讲过,StatusBarView的子View LinearLayout android:id="@+id/icons"它包含一个通知图标栏,见(图1)中的红色部分,一个状态图标栏:见(图1)中的蓝色部分,但是这个并没有包含上面的图标,如时间图标(注意右边显示的时间也是一个Text类型的图标,刚开始我也不理解,在status_bar.xml中找了半天没有找到它),那么这些图标又是怎么样被加载上去的呢?还是看ServerThread.run,在ServerThread.run中调用了
com.android.server.status.StatusBarPolicy.installIcons(context, statusBar);//装载状态栏的图标
进入这个函数看它如何实现的,installIcons静态方法直接调用了构造函数StatusBarPolicy去实现这个操作了:
private StatusBarPolicy(Context context, StatusBarService service) {
// 构建设置图标...
// 注册广播接收器,接收各种图标状态变化消息,以此更新状态栏图标
}
代码太多,我就以时间图标为例,进行代码说明,代码在StatusBarPolicy构造函数中:
//构建时间图标的IconData对象,类型为TEXT
IconData mClockData = IconData.makeText("clock", "");
--IconData.makeText(String slot, CharSequence text) {
IconData data = new IconData();
data.type = TEXT;
data.slot = slot;
data.text = text;
return data;
}
//调用addIcon方法添加一个状态图标到状态栏
IBinder mClockIcon = service.addIcon(mClockData, null);
-->IBinder service.addIcon(IconData data, NotificationData n) {
int slot;
// assert early-on if they using a slot that doesn't exist.
if (data != null && n == null) {
slot = getRightIconIndex(data.slot);
if (slot < 0) {
throw new SecurityException("invalid status bar icon slot: "
+ (data.slot != null ? "'" + data.slot + "'" : "null"));
}
} else {
slot = -1;
}
IBinder key = new Binder();
addPendingOp(OP_ADD_ICON, key, data, n, -1);
return key;
}
//获取系统时间,并更新时间图标
updateClock();
-->updateClock() {
mCalendar.setTimeInMillis(System.currentTimeMillis());
mClockData.text = getSmallTime();
mService.updateIcon(mClockIcon, mClockData, null);
}
注意代码中我标注为绿色的一个StatusBarService.addPendingOp方法,它会创建一个PendingOp对象op,然后
设置op.code=OP_ADD_ICON,然后交给StatusBarService.mHandler去处理,我们根据OP_ADD_ICON搜索mHandler.handleMessage方法:
//前面函数逻辑我省略了
if (doOp) {
switch (op.code) {
case OP_ADD_ICON:
case OP_UPDATE_ICON:
performAddUpdateIcon(op.key, op.iconData, op.notificationData);
break;
根据op.code==OP_ADD_ICON,线程会调用performAddUpdateIcon方法:
void performAddUpdateIcon(IBinder key, IconData data, NotificationData n)
由于这个函数是更新状态图标和通知图标的统一入口函数,它提供了两个入口参数,IconData data, NotificationData n,当我们添加的是时间图标的时候,n==null:
void performAddUpdateIcon(IBinder key, IconData data, NotificationData n){
// n != null means to add or update notification
if (n != null) {
...
}
// to add or update icon ,this also incluce Notification icons
synchronized (mIconMap) {//mIconMap中缓存了所有显示的和不显示的通知图标和状态图标
StatusBarIcon icon = mIconMap.get(key);//first to get icon from mIconMap
if (icon == null) {//if get icon from mIconMap is null,we should to add it
// add icon
LinearLayout v = n == null ? mStatusIcons : mNotificationIcons;
//创建一个状态图标
icon = new StatusBarIcon(mContext, data, v);
mIconMap.put(key, icon);
mIconList.add(icon);//mIconList应该是显示的状态图标
if (n == null) {//n==null,说明在添加状态图标
int slotIndex = getRightIconIndex(data.slot);
StatusBarIcon[] rightIcons = mRightIcons;
if (rightIcons[slotIndex] == null) {
int pos = 0;
for (int i=mRightIcons.length-1; i>slotIndex; i--) {
StatusBarIcon ic = rightIcons[i];
if (ic != null) {
pos++;
}
}
rightIcons[slotIndex] = icon;
mStatusIcons.addView(icon.view, pos);//mStatusIcons==LinearLayout android:id="@+id/statusIcons"
} else {
Slog.e(TAG, "duplicate icon in slot " + slotIndex + "/" + data.slot);
mIconMap.remove(key);
mIconList.remove(icon);
return ;
}
} else {// is notification add notification icon
...
}
} else {//icon!=null说明是图标更新
if (n == null) {//状态图标更新
// right hand side icons -- these don't reorder
icon.update(mContext, data);
} else {//is notification to update notification icon
...
}
}
}
结合我的红色部分的注释,认真看完该函数的代码,相信你已经知道状态图标添加的详细过程。
10.通知图标的加载
我以sd卡插入状态栏通知为例进行说明。当sd 插入,代码会执行怎样的逻辑,先看一下调用堆栈:
StatusBarService.addIcon(IconData, NotificationData) line: 423
NotificationManagerService.enqueueNotificationWithTag(String, String, int, Notification, int[]) line: 745
NotificationManager.notify(String, int, Notification) line: 110
NotificationManager.notify(int, Notification) line: 90
StorageNotification.setMediaStorageNotification(int, int, int, boolean, boolean, PendingIntent) line: 478
调用进入了StatusBarService.addIcon函数,跟前面添加状态图标调用的是同一个函数,只是参数IconData==null,而 NotificationData!=null。接下来执行逻辑差不多,StatusBarService.addPendingOp方法,它会创建一个 PendingOp对象op,然后
设置op.code=OP_ADD_ICON,然后交给StatusBarService.mHandler去处理,根据op.code==OP_ADD_ICON,线程会调用performAddUpdateIcon方法:
void performAddUpdateIcon(IBinder key, IconData data, NotificationData n)
throws StatusBarException {
//省略无关代码
if (n != null) {
//这里我省略了一段逻辑了,是跟ExpandedView相关,当我们点击statusbar往下拖动会展开一个ExpandedView,这个以后再说
//不深入展开了,当收到一个通知,这里判断通知view列表中是否存在这个通知view,若不存在添加,若存在,更新
// 添加要显示的通知到队列中,tickerview会依次显示出来,效果如(图2)
if (n.tickerText != null && mStatusBarView.getWindowToken() != null
&& (oldData == null
|| oldData.tickerText == null
|| !CharSequences.equals(oldData.tickerText, n.tickerText))) {
if (0 == (mDisabled &
(StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
mTicker.addEntry(n, StatusBarIcon.getIcon(mContext, data), n.tickerText);//添加一个通知到tickerview
}
}
}
// to add or update icon ,this also incluce Notification icons
synchronized (mIconMap) {
StatusBarIcon icon = mIconMap.get(key);//从缓存中查看是否已存在该图标
if (icon == null) {//if get icon from mIconMap is null,we should to add it
// add
LinearLayout v = n == null ? mStatusIcons : mNotificationIcons;
icon = new StatusBarIcon(mContext, data, v);
mIconMap.put(key, icon);
mIconList.add(icon);
if (n == null) {
//状态图标处理
} else {// is notification add notification icon
//添加左边通知图标,如sd卡,usb图标等,mNotificationIcons=IconMerger android:id="@+id/notificationIcons"
int iconIndex = mNotificationData.getIconIndex(n);
mNotificationIcons.addView(icon.view, iconIndex);
}
} else {
if (n == null) {
// right hand side icons -- these don't reorder
icon.update(mContext, data);
} else {//is notification to update notification icon
//更新通知图标
// remove old
ViewGroup parent = (ViewGroup)icon.view.getParent();
parent.removeView(icon.view);
// add new
icon.update(mContext, data);
int iconIndex = mNotificationData.getIconIndex(n);
mNotificationIcons.addView(icon.view, iconIndex);
}
}
}
}
11.状态图标更新
--1.通过广播接收器的方式
当StatusBarPolicy被构建的时候,会注册一个广播消息接收器mIntentReceiver:
IntentFilter filter = new IntentFilter();
// Register for Intent broadcasts for...
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
......
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
一旦接收到状态图标变化消息,就会通知StatusBarService去变更状态图标,还是以状态栏的时钟为例:
if (action.equals(Intent.ACTION_TIME_TICK)) {
updateClock();//此处接收时间变化消息
}
else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
updateClock();//此处接收时间变更消息
-->updateClock()
-->StatusBarService.updateIcon(IBinder key, IconData data, NotificationData n)
-->addPendingOp(OP_UPDATE_ICON, key, data, n, -1);
}
这个跟前面添加状态图标调用是一致的,只是addIcon中addPendingOp(OP_UPDATE_ICON, key, data, n, -1);操作是 OP_UPDATE_ICON,所以,同样,
方法会执行到performAddUpdateIcon,后面逻辑见前面讲述的状态图标更新。
--2.通过远程代理方式
StatusBarManager有一个更新图标的方法: public void updateIcon(IBinder key, String slot, int iconId, int iconLevel),不过StatusBarManager并未把方法公开在sdk中,但是应该有方法可以访问的,
像launcher就有访问framework中未公开在sdk中的方法,如何实现这里我不作讨论。
//////////////////StatusBarManager.updateIcon//////////////////////////////
public void updateIcon(IBinder key, String slot, int iconId, int iconLevel) {
try {
mService.updateIcon(key, slot, mContext.getPackageName(), iconId, iconLevel);
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
}
}
mService是StatusBarManager的一个成员变量,StatusBarManager被构建的时候被赋值,他是IStatusBar的一个代理对象
StatusBarManager(Context context) {
mContext = context;
//
mService = IStatusBar.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
posted @ 2012-02-08 16:25 小小博客小小员 阅读(82) 评论(0) 编辑
Android FrameWork——PackageManager框架
1.接着前面讲的ActivityManager框架,继续说一下系统另一个重要的框架,PackagerManager
同样先看一下静态类结构图:
大 部分情况我们是在Activity中使用getPackageManager方法获取一个ApplicationPackageManager的对 象,ApplicationPackageManager实际上是包装了一个IPackageManager.Stub.Proxy的对象
由IPackageManager.Stub.Proxy代理执行PackageManager相关操作,IPackageManager.Stub.Proxy实际代理的是PackageManagerService,
2.看了前面说的,可能你有点晕,我们再来重新理一下:
首先是IPackageManager是通过IPackageManager.aidl文件生成,同时生成了存根类IPackageManager.Stub,代理类:IPackageManager.Stub.Proxy
这个是packageManager进程通信的基本框架,我前面blog有说,不多加说明了
然后PackageManagerService,它继承了IPackageManager.Stub,它作为PackageManager动作的实际执行者,在system_process中存在
再是我们用户应用程序中的ApplicationPackageManager,先看它如何被获取的:
ContextImpl.java中有一个方法:
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
ApplicationPackageManager实际上是包装了一个IPackageManager对象(IPackageManager.Stub.Proxy),当我们调用queryIntentActivities时,实际通过代理对象去执行:
public List
int flags) {
try {
return mPM.queryIntentActivities(//mPM是IPackageManager.Stub.Proxy对象
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
进过进程通信,在PackageManagerService执行对应操作:
3.PackageManagerService的构建与获取
--PackageManagerService 的构建:在system_process进程加载时,PackageManagerService被构建,在 SystemServer.ServerThread.run中有如下一段代码,它就是加载 PackageManagerService的:
Slog.i(TAG, "Package Manager");
pm = PackageManagerService.main(context,
factoryTest != SystemServer.FACTORY_TEST_OFF);//启动PackageManagerService
///////////////////////PackageManagerService///////////////////////////////////////////////////////////////////////////
public static final IPackageManager main(Context context, boolean factoryTest) {
PackageManagerService m = new PackageManagerService(context, factoryTest);
ServiceManager.addService("package", m);
return m;
}
--PackageManagerService获取:
先看前面在ContextImpl.java->getPackagerManager中:
IPackageManager pm = ActivityThread.getPackageManager();
/////////////////////ActivityThread////////////////
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
从ServiceManager中获取的服务pakager,该服务在.PackageManagerService的构建时被注册到 ServiceManager中的,ServiceManager机制暂时没有深入了解,后面再发blog专门说一下ServiceManager
原文:http://blog.csdn.net/stonecao/article/details/6591454
posted @ 2012-02-08 16:24 小小博客小小员 阅读(178) 评论(0) 编辑
Android FrameWork——Activity启动过程详解
前面发了blog分析了ActivityManager框架的大体结构,主要就是一个进程通信机制,今天我通过深入Activity的启动过程再次深入到ActivityManager框架,对其进行一个更深入的了解
以桌面启动一个应用Activity为例,onClick事件后,会调用startActivityForResult(Intent, int)
public void startActivityForResult(Intent intent, int requestCode) {
if (mParent == null) {
//Activity启动执行交由Instrumentation对象去处理
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode);
//mMainThread 在attach方法中被设置,当ActivityThread PerformLauchActivity,调用attach把ActivityThread.this传送过来
//mMainThread.getApplicationThread()它是一个进程通信服务端存根对象,提供了很多操作 ActivityThread的方法,它继承了ApplicationThreadNative
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
} else {
mParent.startActivityFromChild(this, intent, requestCode);
}
}
Instrumentation.execStartActivity
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
......
try {
//ActivityManagerNative.getDefault()实际返回的是一个ActivityManagerProxy对象
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
null, 0, token, target != null ? target.mEmbeddedID : null,
requestCode, false, false);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}
ActivityManagerProxy是实现IActivityManager接口的一个进程通信代理对象,在该方法ActivityManagerProxy.startActivity中,它只负责
准备相关的数据发送到system_process进程去处理startActivity:
public int startActivity(IApplicationThread caller, Intent intent,
String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
IBinder resultTo, String resultWho,
int requestCode, boolean onlyIfNeeded,
boolean debug) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeTypedArray(grantedUriPermissions, 0);
data.writeInt(grantedMode);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
data.writeInt(onlyIfNeeded ? 1 : 0);
data.writeInt(debug ? 1 : 0);
//mRemote是一个BinderProxy对象,transact方法本地化实现
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result;
}
到此前面3步都是在Laucher2进程中执行,调用mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);后,系统转到
system_process进程中执行,根据我前面讲的进程通信机制,入口函数是ActivityManagerNative.onTransact方法,在system_process进程中
ActivityManagerNative被继承,实际执行的是ActivityManagerService.onTransact方法,调用堆栈如下:
ActivityManagerService(ActivityManagerNative).onTransact(int, Parcel, Parcel, int) line: 129
ActivityManagerService.onTransact(int, Parcel, Parcel, int) line: 1481
ActivityManagerService(Binder).execTransact(int, int, int, int) line: 288
NativeStart.run() line: not available [native method]
在ActivityManagerService(ActivityManagerNative).onTransact中根据前面的参数START_ACTIVITY_TRANSACTION,执行对应的case代码:
case START_ACTIVITY_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
int grantedMode = data.readInt();
IBinder resultTo = data.readStrongBinder();
String resultWho = data.readString();
int requestCode = data.readInt();
boolean onlyIfNeeded = data.readInt() != 0;
boolean debug = data.readInt() != 0;
//执行对应Stub的IActivityManager接口方法
int result = startActivity(app, intent, resolvedType,
grantedUriPermissions, grantedMode, resultTo, resultWho,
requestCode, onlyIfNeeded, debug);
reply.writeNoException();
reply.writeInt(result);
return true;
}
前面红色的startActivity方法实际是执行的ActivityManagerService中的startActivity,接下来的执行很复杂,要想搞清楚这些细节还需要一些时间,我我们跳过这些,继续沿着主干道前行:
ActivityManagerService.startProcessLocked(ProcessRecord, String, String) line: 2043
ActivityManagerService.startProcessLocked(String, ApplicationInfo, boolean, int, String, ComponentName, boolean) line: 1982
ActivityManagerService.startSpecificActivityLocked(HistoryRecord, boolean, boolean) line: 1908
ActivityManagerService.resumeTopActivityLocked(HistoryRecord) line: 2855
ActivityManagerService.completePauseLocked() line: 2237
ActivityManagerService.activityPaused(IBinder, Bundle, boolean) line: 5963
ActivityManagerService.activityPaused(IBinder, Bundle) line: 5941
ActivityManagerService(ActivityManagerNative).onTransact(int, Parcel, Parcel, int) line: 387
ActivityManagerService.onTransact(int, Parcel, Parcel, int) line: 1481
ActivityManagerService(Binder).execTransact(int, int, int, int) line: 288
SystemServer.init1(String[]) line: not available [native method]
SystemServer.main(String[]) line: 582
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 521
ZygoteInit$MethodAndArgsCaller.run() line: 868
ZygoteInit.main(String[]) line: 626
NativeStart.main(String[]) line: not available [native method]
在用例进程onPause后,通过ActivityManagerProxy.activityPaused执行相关操作,这也是一个用例进程到 system_process进程的远程调用,原来的用例进程需要进栈,并启动一个新的Activity在屏幕最前端,我们只关注新Activity的启动,首先关注新Activity应用进程的创建,看上面的调用堆栈,在函数startProcessLocked:
//app记录的是一个要启动ActivityThread进程的信息,hostingType=activity,hostingNameStr="com.iaiai.activity/.IaiaiActivity"
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
......
//Process.start是一个静态方法,它将启动一个新的进程,新进程的的入口main方法为android.app.ActivityThread.main
int pid = Process.start("android.app.ActivityThread",
mSimpleProcessManagement ? app.processName : null, uid, uid,
gids, debugFlags, null);
......
现 在,我们终于看到了一个新的应用进程的创建,别急,在启动主Activity我们还有很多工作要做,我我们可以想象到得,一个新的应用肯定要建立闭环的消 息循环,然后它要把的一个ApplicationThreadProxy代理对象传递给system_process进程,这样 system_process进程就可以通过ApplicationThreadProxy代理对象来控制我们的应用进程了,比如它把广播消息转给应用进 程,关闭应用进程等
先看新进程的main函数:
public static final void main(String[] args) {
SamplingProfilerIntegration.start();
Process.setArgV0("
//建立looper消息循环队列
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
//开始主线程消息循环
Looper.loop();
if (Process.supportsProcesses()) {
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "
Slog.i(TAG, "Main thread of " + name + " is now exiting");
}
在 main函数中建立了闭环的消息循环,这个是一般ui程序做法,很容易理解,但是后续应用的启动工作是如何进程的,关注我上面标注的红色代码,这里创建了 一个ActivityThread对象,ActivityThread构造时初始化了该应用进程的一些基本成员,最重要的我们关注
final Looper mLooper = Looper.myLooper();
final H mH = new H();//消息处理handler
在这里,我们建立了消息处理器,它将负责处理main线程中Looper消息循环中的消息。
还用一个成员对象值得我们关注,那就是ApplicationThread对象,在ActivityThread对象被创建时,它也被构造了,我前面已经 提到过了,它继承了ApplicationThreadNative类,熟悉进程通信代理机制的朋友就清楚了,ApplicationThread就是一 个通信代理存根实现类,我们可以看它的实现方法,都是调用queueOrSendMessage方法,派发消息交给ActivityThread的mH去 处理,那么我们很清楚了,ActivityThread代理存根对象,它负责执行来自远程的调用,这些远程的调用大部分来自 system_process,所以,system_process很容易通过ApplicationThread的客户端代理对象控制 ActivityThread,事实就是如此,后面我们可以很好地看到这一点,
继续看thread.attach(false)函数,参数标识是否系统进程,系统进程的入口函数是systemMain,而不是main方法,
private final void attach(boolean system) {//system==false
sThreadLocal.set(this);//ActivityThread对象关联到主线程
mSystemThread = system;
if (!system) {//是非系统进程
...
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
//把ApplicationThread mAppThread attach到系统进程system_process,以便system_process控制当前应用的ActivityThread
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
}
} else {
//系统进程要作的处理
...
}
//接收来自ViewRoot的ConfigurationChanged消息,派发给mH处理(H.CONFIGURATION_CHANGED),
//一旦配置发生变更,mH将执行H.CONFIGURATION_CHANGED
ViewRoot.addConfigCallback(new ComponentCallbacks() {
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mPackages) {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
if (applyConfigurationToResourcesLocked(newConfig)) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
}
}
public void onLowMemory() {
}
});
}
再来看一下attach方法的调用堆栈:
ActivityManagerProxy.attachApplication(IApplicationThread) line: 1542
ActivityThread.attach(boolean) line: 4555
ActivityThread.main(String[]) line: 4632
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 521
ZygoteInit$MethodAndArgsCaller.run() line: 868
ZygoteInit.main(String[]) line: 626
NativeStart.main(String[]) line: not available [native method]
这里你又会看到一个熟悉的身影,ActivityManagerProxy,是的,这里又使用了进程通信,通知ActivityManagerService执行attachApplication
看一下ActivityManagerProxy.attachApplication方法的代码:
public void attachApplication(IApplicationThread app) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
//参数IApplicationThread app通过进程通信传送到system_process进程,而app是一个ApplicationThread对象,不要被它的名称所迷惑
//这里它只是一个对象,它继承了ApplicationThreadNative,而ApplicationThreadNative是实现 IApplicationThread接口的一个进程通信接口存根类,当它到达system_process,system_process得到的是它的一个代理类ActivityManagerProxy
data.writeStrongBinder(app.asBinder());
mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
再次回到了system_process进程,先看一下system接收到来自新的Activity的远程调用堆栈:
ActivityManagerService.attachApplicationLocked(IApplicationThread, int) line: 5591
ActivityManagerService.attachApplication(IApplicationThread) line: 5677
ActivityManagerService(ActivityManagerNative).onTransact(int, Parcel, Parcel, int) line: 363
ActivityManagerService.onTransact(int, Parcel, Parcel, int) line: 1481
ActivityManagerService(Binder).execTransact(int, int, int, int) line: 288
NativeStart.run() line: not available [native method]
我们看attachApplicationLocked的实现,由于函数比较长,而且我也没有深入仔细看,所以我只列出我理解的关键部分代码
//thread来用户进程的ApplicationThread代理对象,pid是用户进程的pid
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
......
ProcessRecord app;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
//当用户进程创建时有一个标识用户进程的pid,它关联了ProcessRecord记录,现在根据pid或者该记录
app = mPidsSelfLocked.get(pid);
}
} else if (mStartingProcesses.size() > 0) {
app = mStartingProcesses.remove(0);
app.setPid(pid);
} else {
app = null;
}
......
//设置app相关参数
app.thread = thread;//设置app的thread为用户进程代理对象ActivityManagerProxy
app.curAdj = app.setAdj = -100;
app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
app.setSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.forcingToForeground = null;
app.foregroundServices = false;
app.debugging = false;
......
thread.bindApplication(processName, app.instrumentationInfo != null
? app.instrumentationInfo : app.info, providers,
app.instrumentationClass, app.instrumentationProfileFile,
app.instrumentationArguments, app.instrumentationWatcher, testMode,
isRestrictedBackupMode || !normalMode,
mConfiguration, getCommonServicesLocked());
updateLruProcessLocked(app, false, true);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
......
HistoryRecord hr = topRunningActivityLocked(null);
if (hr != null && normalMode) {
if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid
&& processName.equals(hr.processName)) {
try {
//realStartActivityLocked会调用thread.scheduleLaunchActivity
if (realStartActivityLocked(hr, app, true, true)) {
didSomething = true;
}
}
这 里通过远程调用后thread并不是一个ApplicationThread对象,而是其一个远程代理对象 ApplicationThreadProxy,通过thread,可以操作ApplicationThread对象调用bindApplication 和scheduleLaunchActivity:
先看bindApplication:
ActivityThread$ApplicationThread.bindApplication(String, ApplicationInfo, List, ComponentName, String, Bundle, IInstrumentationWatcher, int, boolean, Configuration, Map) line: 1655
ActivityThread$ApplicationThread(ApplicationThreadNative).onTransact(int, Parcel, Parcel, int) line: 251
ActivityThread$ApplicationThread(Binder).execTransact(int, int, int, int) line: 288
NativeStart.run() line: not available [native method]
public final void bindApplication(String processName,
ApplicationInfo appInfo, List
ComponentName instrumentationName, String profileFile,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
int debugMode, boolean isRestrictedBackupMode, Configuration config,
Map
//获取来自system_process远程调用传递过来的相关参数
if (services != null) {
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
}
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.profileFile = profileFile;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.debugMode = debugMode;
data.restrictedBackupMode = isRestrictedBackupMode;
data.config = config;
//派发给ActivityThread.mH去处理执行H.BIND_APPLICATION
queueOrSendMessage(H.BIND_APPLICATION, data);
}
我们看mH是如何处理的,mH接收到H.BIND_APPLICATION消息执行的对应是handleBindApplication函数,handleBindApplication函数中做了大量初始化ActivityThread的操作:
初始化mConfiguration
设置进程名
本地语言设置
设置包名称
设置应用程序根路径
设置应用程序data路径
设置activity的context
等等,我没有详细看,有不正之处希望高手指正,小弟不胜感激
接着是第二步,scheduleLaunchActivity,先看调用堆栈:
ActivityThread$ApplicationThread.scheduleLaunchActivity(Intent, IBinder, int, ActivityInfo, Bundle, List, List, boolean, boolean) line: 1526
ActivityThread$ApplicationThread(ApplicationThreadNative).onTransact(int, Parcel, Parcel, int) line: 130
ActivityThread$ApplicationThread(Binder).execTransact(int, int, int, int) line: 288
NativeStart.run() line: not available [native method]
同样,该函数操作也是交给ActivityThread.mH来处理,对应case消息为:H.LAUNCH_ACTIVITY
交 由函数handleLaunchActivity处理,在这个函数中,根据ActivityRecord对像,获取要启动的Activity类信息,然后 创建一个Activity,执行Activity的生命周期函数:onCreate,onResume,至此ActivitThread的 handleBindApplication和handleLaunchActivity完成Activity的启动操作。
最后,我再总结一下从桌面启动一个应用Activity的启动过程,画了一个时序图,有助于理解,希望走过路过的朋友别忘了留个脚印,或者留个砖板,这个blog花费了我不少时间:
原文:http://blog.csdn.net/stonecao/article/details/6591847
posted @ 2012-02-08 16:23 小小博客小小员 阅读(72) 评论(0) 编辑
Android FrameWork——ActivityManager框架
1.ActivityManager是android框架的一个重要部分,它负责一新ActivityThread进程创建,Activity生命周期的维护,本blog就是着手对ActivityManager框架作一个整体的了解
2.先看一个静态类结构图:
该图摘自 曹文斌blog
上图很清楚地描述了ActivityManager框架的几个主要类之间的关系,我们做应用开发接触很多的其实就是ActivityManager类,该类也在SDK中公布,应用可以直接访问,它提供了我们管理Activity的一些基本的方法
如下:
public void testgetRecentTasks()
//获取最近的应用,最后启动的排前
public void testgetRunningTasks()
//获取当前运行的Activity应用
public void testgetRunningServices()
//获取当前运行的service应用
public void testgetRunningAppProcesses()
//获取所用系统运行的进程
而这些操作都依赖于ActivityManagerProxy代理类的实现,IActivitManager接口定义了所有 ActivityManager框架的操作,ActivityManagerProxy实现了接口IActivitManager,但并不真正实现这些方 法,它只是一个代理类,真正动作的执行为Stub类ActivityManagerService,ActivityManagerService对象只 有一个并存在于system_process进程中,ActivityManagerService继承于ActivityManagerNative存 根类。
3.从前面分析知,ActivityManager存在于用户进程中,由用户进程调用获取Activity管理的一些基本信息,但是 ActivityManager类并不真正执行这些操作,操作的真正执行在system_process进程中的 ActivityManagerService,ActivityManagerService作为一个服务在system_process启动时被加 载,关于ActivityManagerService如何被加载这里不展开讨论,后面在讨论android系统启动时在探讨,那么从 ActivityManager到ActivityManagerService中间经过一个环节,那就是进程通信,而IActivityManager 以及实现接口的代理类ActivityManagerProxy,存根类ActivityManagerNative起着负责进程通信的作用,我在前面的 blog aidl实现机制浅析中有对进程通信作了较深入的分析,虽然这里没有使用aidl文件定义进程通信接口IActivityManager,其实是一样的, 我们可以把它看做是自己手动编译的aidl进程通信java类实现,ActivityManagerProxy是代理 类,ActivityManagerNative是Stub类,IActivityManager是aidl接口,这样就很容易理解了。
4.ActivityManager提供了很少的方法,要能够使用IActivityManager接口提供的其他方法我们可以直接使用ActivityManagerProxy对象,如何获取?
return ActivityManagerNative.getDefault()
不要被方法名称所迷惑,由于我们在用户进程调用,是不可能获取一个ActivityManagerNative对象的(再说 ActivityManagerNative是一个abstract类),我们实际获取的是一个ActivityManagerProxy对象
理解以上ActivityManager框架基本结构,后面深入研究它就要容易许多了。
原文:http://blog.csdn.net/stonecao/article/details/6579710
更多相关文章
- Android 页面自动跳转方法(比如进入app的广告,通过Timer计时器,通过
- android开源代码编译方法
- Activity中那些需要重写的方法
- Android为什么会有65536的方法数量限制
- Android Studio ——Android 使用Pull方法解析XML文件的方法
- 记录关于Gradle : Build Running的解决方法
- Android 开机震动的调用位置以及打开关闭方法
- Android中计算text文字大小的几个方法