- 浏览: 634805 次
文章分类
最新评论
-
涛声依旧是:
怎么提示掉线,怎么才能在线
基于Tomcat7、Java、WebSocket的服务器推送聊天室 -
dcode:
楼主写的不错,正好遇到点问题,看着你的文章解决了,感谢分享。。 ...
在 Visual Studio 2010 中配置SharpPcap
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<String, Object>, Configuration) : void - android.app.Activity
--> 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进行处理。
相关推荐
Audio框架介绍、 Audio相关服务的创建、Audio的数据流、Audio的控制流等一些内容的介绍及代码流程分析
android framework 详解
Android Framework精编内核解析
android framework 实现细节,常见面试问题汇总
robotFramework——FOR循环语句 循环语句 robotframework⽀持FOR循环语句,语法和的语法基本相同,但robotframework中,"FOR"关键字前⾯需要增加⼀个":",写成":FOR",其它 与Python的语法相同。下⾯我们看看如何...
android Framework精编内核解析
3.Android编译过程分析 4.android.mk初识 5.Android.mk学习1 6.Android.mk学习2 7.Android.mk学习3 8.Android启动课程大纲 9.Android启动模式 10.init启动分析 11.走入init启动脚本 12.init脚本解析分析 13.init脚本...
robotframework安装_RobotFramework——基于关键字驱动的 ⾃动化测。。。 突然发现官⽅Robot Framework RIDE项⽬更新了,新的版本基于wxPython4.0.4⽀持了Python3.x,⽆疑是对于Robot Framework推 ⼴的重⼤利好(由于...
This is a android framework full classes jar file
Android Framework精编内核解析.pdf 百度网盘永久地址
android framework相关面试题,详细深入的介绍了android framework相关知识点,可以很好帮助对android framework的深入理解
android framework 源代码
android Framework层的修改规则,包括了编译过程和jni调用等。
android framework面试题集 自己亲自总结的,各位有需要的可以随时下载
android framework开发经验总结
Android Framework 分析Android Framework 分析Android Framework 分析Android Framework 分析
Android 13.0 framework jar 包
自己编译的 Android12版本-framework源码jar 来源:AOSP源分支 android-12.1.0_r11
Android framework 技术详解,framework层核心技术
《Android Framework 开发揭秘》,系统框架底层原理解密,腾讯技术团队最新出品!