版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領
文檔簡介
【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么
android中事件分發(fā)機制的實現(xiàn)原理是什么,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。android中的事件處理,以及解決滑動沖突問題都離不開事件分發(fā)機制,android中的事件流,即MotionEvent都會經(jīng)歷一個從分發(fā),攔截到處理的一個過程。即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一個過程,在dispatchTouchEvent()負責了事件的分發(fā)過程,在dispatchTouchEvent()中會調(diào)用onInterceptEvent()與onTouchEvent(),如果onInterceptEvent()返回true,那么會調(diào)用到當前view的onTouchEvent()方法,如果不攔截,事件就會下發(fā)到子view的dispatchTouchEvent()中進行同樣的操作。本文將帶領大家從源碼角度來分析android是如何進行事件分發(fā)的。android中的事件分發(fā)流程最先從activity的dispatchTouchEvent()開始:public
boolean
dispatchTouchEvent(MotionEvent
ev)
{
if
(ev.getAction()
==
MotionEvent.ACTION_DOWN)
{
onUserInteraction();
}
if
(getWidow().superDispatchTouchEvent(ev))
{
return
true;
}
return
onTouchEvent(ev);
}這里調(diào)用了getWindow().superDispatchTouchEvent(ev),這里可以看出activity將MotionEvent傳寄給了Window。而Window是一個抽象類,superDispatchTouchEvent()也是一個抽象方法,這里用到的是window的子類phoneWindow。@Override
public
boolean
superDispatchTouchEvent(MotionEvent
event)
{
return
mDecor.superDispatchTouchEvent(event);
}從這里可以看出,event事件被傳到了DecorView,也就是我們的頂層view.我們繼續(xù)跟蹤:public
boolean
superDispatchTouchEvent(MotionEvent
event)
{
return
super.dispatchTouchEvent(event);
}這里調(diào)用到了父類的dispatchTouchEvent()方法,而DecorView是繼承自FrameLayout,F(xiàn)rameLayout繼承了ViewGroup,所以這里會調(diào)用到ViewGroup的dispatchTouchEvent()方法。所以整個事件流從activity開始,傳遞到window,最后再到我們的view(viewGroup也是繼承自view)中,而view才是我們整個事件處理的核心階段。我們來看一下viewGroup的dispatchTouchEvent()中的實現(xiàn):if
(actionMasked
==
MotionEvent.ACTION_DOWN)
{
//
Throw
away
all
previous
state
when
starting
a
new
touch
gesture.
//
The
framework
may
have
dropped
the
up
or
cancel
event
for
the
previous
gesture
//
due
to
an
app
switch,
ANR,
or
some
other
state
change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}這是dispatchTouchEvent()開始時截取的一段代碼,我們來看一下,首先,當我們手指按下view時,會調(diào)用到resetTouchState()方法,在resetTouchState()中:private
void
resetTouchState()
{
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags
&=
~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes
=
SCROLL_AXIS_NONE;
}我們繼續(xù)跟蹤clearTouchTargets()方法:private
void
clearTouchTargets()
{
TouchTarget
target
=
mFirstTouchTarget;
if
(target
!=
null)
{
do
{
TouchTarget
next
=
target.next;
target.recycle();
target
=
next;
}
while
(target
!=
null);
mFirstTouchTarget
=
null;
}
}在clearTouchTargets()方法中,我們最終將mFirstTouchTarget賦值為null,我們繼續(xù)回到dispatchTouchEvent()中,接著執(zhí)行了下段代碼://
Check
for
interception.
final
boolean
intercepted;
if
(actionMasked
==
MotionEvent.ACTION_DOWN
||
mFirstTouchTarget
!=
null)
{
final
boolean
disallowIntercept
=
(mGroupFlags
&
FLAG_DISALLOW_INTERCEPT)
!=
0;
if
(!disallowIntercept)
{
intercepted
=
onInterceptTouchEvent(ev);
ev.setAction(action);
//
restore
action
in
case
it
was
changed
}
else
{
intercepted
=
false;
}
}
else
{
//
There
are
no
touch
targets
and
this
action
is
not
an
initial
down
//
so
this
view
group
continues
to
intercept
touches.
intercepted
=
true;
}當view被按下或mFirstTouchTarget!=null的時候,從前面可以知道,當每次view被按下時,也就是重新開始一次事件流的處理時,mFirstTouchTarget都會被設置成null,一會我們看mFirstTouchTarget是什么時候被賦值的。從disallowIntercept屬性我們大概能猜到是用來判斷是否需要坐攔截處理,而我們知道可以通過調(diào)用父view的requestDisallowInterceptTouchEvent(true)可以讓我們的父view不能對事件進行攔截,我們先來看看requestDisallowInterceptTouchEvent()方法中的實現(xiàn):@Override
public
void
requestDisallowInterceptTouchEvent(boolean
disallowIntercept)
{
if
(disallowIntercept
==
((mGroupFlags
&
FLAG_DISALLOW_INTERCEPT)
!=
0))
{
//
We're
already
in
this
state,
assume
our
ancestors
are
too
return;
}
if
(disallowIntercept)
{
mGroupFlags
|=
FLAG_DISALLOW_INTERCEPT;
}
else
{
mGroupFlags
&=
~FLAG_DISALLOW_INTERCEPT;
}
//
Pass
it
up
to
our
parent
if
(mParent
!=
null)
{
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}這里也是通過設置標志位做判斷處理,所以這里是通過改變mGroupFlags標志,然后在dispatchTouchEvent()剛發(fā)中變更disallowIntercept的值判斷是否攔截,當為true時,即需要攔截,這個時候便會跳過onInterceptTouchEvent()攔截判斷,并標記為不攔截,即intercepted=false,我們繼續(xù)看viewGroup的onInterceptTouchEvent()處理:public
boolean
onInterceptTouchEvent(MotionEvent
ev)
{
if
(ev.isFromSource(InputDevice.SOURCE_MOUSE)
&&
ev.getAction()
==
MotionEvent.ACTION_DOWN
&&
ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&&
isOnScrollbarThumb(ev.getX(),
ev.getY()))
{
return
true;
}
return
false;
}即默認情況下,只有在ACTION_DOWN時,viewGroup才會表現(xiàn)為攔截。我們繼續(xù)往下看:final
int
childrenCount
=
mChildrenCount;
if
(newTouchTarget
==
null
&&
childrenCount
!=
0)
{
final
float
x
=
ev.getX(actionIndex);
final
float
y
=
ev.getY(actionIndex);
//
Find
a
child
that
can
receive
the
event.
//
Scan
children
from
front
to
back.
final
ArrayList<View>
preorderedList
=
buildTouchDispatchChildList();
final
boolean
customOrder
=
preorderedList
==
null
&&
isChildrenDrawingOrderEnabled();
final
View[]
children
=
mChildren;
for
(int
i
=
childrenCount
-
1;
i
>=
0;
i--)
{
final
int
childIndex
=
getAndVerifyPreorderedIndex(
childrenCount,
i,
customOrder);
final
View
child
=
getAndVerifyPreorderedView(
preorderedList,
children,
childIndex);
//
If
there
is
a
view
that
has
accessibility
focus
we
want
it
//
to
get
the
event
first
and
if
not
handled
we
will
perform
a
//
normal
dispatch.
We
may
do
a
double
iteration
but
this
is
//
safer
given
the
timeframe.
if
(childWithAccessibilityFocus
!=
null)
{
if
(childWithAccessibilityFocus
!=
child)
{
continue;
}
childWithAccessibilityFocus
=
null;
i
=
childrenCount
-
1;
}
if
(!canViewReceivePointerEvents(child)
||
!isTransformedTouchPointInView(x,
y,
child,
null))
{
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget
=
getTouchTarget(child);
if
(newTouchTarget
!=
null)
{
//
Child
is
already
receiving
touch
within
its
bounds.
//
Give
it
the
new
pointer
in
addition
to
the
ones
it
is
handling.
newTouchTarget.pointerIdBits
|=
idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if
(dispatchTransformedTouchEvent(ev,
false,
child,
idBitsToAssign))
{
//
Child
wants
to
receive
touch
within
its
bounds.
mLastTouchDownTime
=
ev.getDownTime();
if
(preorderedList
!=
null)
{
//
childIndex
points
into
presorted
list,
find
original
index
for
(int
j
=
0;
j
<
childrenCount;
j++)
{
if
(children[childIndex]
==
mChildren[j])
{
mLastTouchDownIndex
=
j;
break;
}
}
}
else
{
mLastTouchDownIndex
=
childIndex;
}
mLastTouchDownX
=
ev.getX();
mLastTouchDownY
=
ev.getY();
newTouchTarget
=
addTouchTarget(child,
idBitsToAssign);
alreadyDispatchedToNewTouchTarget
=
true;
break;
}
//
The
accessibility
focus
didn't
handle
the
event,
so
clear
//
the
flag
and
do
a
normal
dispatch
to
all
children.
ev.setTargetAccessibilityFocus(false);
}
if
(preorderedList
!=
null)
preorderedList.clear();
}這段代碼首先會通過一個循環(huán)去遍歷所有的子view,最終會調(diào)用到dispatchTransformedTouchEvent()方法,我們繼續(xù)看dispatchTransformedTouchEvent()的實現(xiàn):private
boolean
dispatchTransformedTouchEvent(MotionEvent
event,
boolean
cancel,
View
child,
int
desiredPointerIdBits)
{
final
boolean
handled;
//
Canceling
motions
is
a
special
case.
We
don't
need
to
perform
any
transformations
//
or
filtering.
The
important
part
is
the
action,
not
the
contents.
final
int
oldAction
=
event.getAction();
if
(cancel
||
oldAction
==
MotionEvent.ACTION_CANCEL)
{
event.setAction(MotionEvent.ACTION_CANCEL);
if
(child
==
null)
{
handled
=
super.dispatchTouchEvent(event);
}
else
{
handled
=
child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return
handled;
}
//
Calculate
the
number
of
pointers
to
deliver.
final
int
oldPointerIdBits
=
event.getPointerIdBits();
final
int
newPointerIdBits
=
oldPointerIdBits
&
desiredPointerIdBits;
//
If
for
some
reason
we
ended
up
in
an
inconsistent
state
where
it
looks
like
we
//
might
produce
a
motion
event
with
no
pointers
in
it,
then
drop
the
event.
if
(newPointerIdBits
==
0)
{
return
false;
}
//
If
the
number
of
pointers
is
the
same
and
we
don't
need
to
perform
any
fancy
//
irreversible
transformations,
then
we
can
reuse
the
motion
event
for
this
//
dispatch
as
long
as
we
are
careful
to
revert
any
changes
we
make.
//
Otherwise
we
need
to
make
a
copy.
final
MotionEvent
transformedEvent;
if
(newPointerIdBits
==
oldPointerIdBits)
{
if
(child
==
null
||
child.hasIdentityMatrix())
{
if
(child
==
null)
{
handled
=
super.dispatchTouchEvent(event);
}
else
{
final
float
offsetX
=
mScrollX
-
child.mLeft;
final
float
offsetY
=
mScrollY
-
child.mTop;
event.offsetLocation(offsetX,
offsetY);
handled
=
child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX,
-offsetY);
}
return
handled;
}
transformedEvent
=
MotionEvent.obtain(event);
}
else
{
transformedEvent
=
event.split(newPointerIdBits);
}
//
Perform
any
necessary
transformations
and
dispatch.
if
(child
==
null)
{
handled
=
super.dispatchTouchEvent(transformedEvent);
}
else
{
final
float
offsetX
=
mScrollX
-
child.mLeft;
final
float
offsetY
=
mScrollY
-
child.mTop;
transformedEvent.offsetLocation(offsetX,
offsetY);
if
(!
child.hasIdentityMatrix())
{
transformedEvent.transform(child.getInverseMatrix());
}
handled
=
child.dispatchTouchEvent(transformedEvent);
}
//
Done.
transformedEvent.recycle();
return
handled;
}這段代碼就比較明顯了,如果child不為null,始終會調(diào)用到child.dispatchTouchEvent();否則調(diào)用super.dispatchTouchEvent();如果child不為null時,事件就會向下傳遞,如果子view處理了事件,即dispatchTransformedTouchEvent()即返回true。繼續(xù)向下執(zhí)行到addTouchTarget()方法,我們繼續(xù)看addTouchTarget()方法的執(zhí)行結(jié)果:private
TouchTarget
addTouchTarget(@NonNull
View
child,
int
pointerIdBits)
{
final
TouchTarget
target
=
TouchTarget.obtain(child,
pointerIdBits);
target.next
=
mFirstTouchTarget;
mFirstTouchTarget
=
target;
return
target;
}這個時候我們發(fā)現(xiàn)mFirstTouchTarget又出現(xiàn)了,這時候會給mFirstTouchTarget重新賦值,即mFirstTouchTarget不為null。也就是說,如果事件被當前view或子view消費了,那么在接下來的ACTION_MOVE或ACTION_UP事件中,mFirstTouchTarget就不為null。但如果我們繼承了該viewGroup,并在onInterceptTouchEvent()的ACTION_MOVE中攔截了事件,那么后續(xù)事件將不會下發(fā),將由該viewGroup直接處理,從下面代碼我們可以得到://
Dispatch
to
touch
targets,
excluding
the
new
touch
target
if
we
already
//
dispatched
to
it.
Cancel
touch
targets
if
necessary.
TouchTarget
predecessor
=
null;
TouchTarget
target
=
mFirstTouchTarget;
while
(target
!=
null)
{
final
TouchTarget
next
=
target.next;
if
(alreadyDispatchedToNewTouchTarget
&&
target
==
newTouchTarget)
{
handled
=
true;
}
else
{
final
boolean
cancelChild
=
resetCancelNextUpFlag(target.child)
||
intercepted;
if
(dispatchTransformedTouchEvent(ev,
cancelChild,
target.child,
target.pointerIdBits))
{
handled
=
true;
}
if
(cancelChild)
{
if
(predecessor
==
null)
{
mFirstTouchTarget
=
next;
}
else
{
predecessor.next
=
next;
}
target.recycle();
target
=
next;
continue;
}
}
predecessor
=
target;
target
=
next;
}當存在子view并且事件被子view消費時,即在ACTION_DOWN階段mFirstTouchTarget會被賦值,即在接下來的ACTION_MOVE事件中,由于intercepted為true,所以將ACTION_CANCEL事件傳遞過去,從dispatchTransformedTouchEvent()中可以看到:if
(cancel
||
oldAction
==
MotionEvent.ACTION_CANCEL)
{
event.setAction(MotionEvent.ACTION_CANCEL);
if
(child
==
null)
{
handled
=
super.dispatchTouchEvent(event);
}
else
{
handled
=
child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return
handled;
}并將mFirstTouchTarget最終賦值為next,而此時mFirstTouchTarget位于TouchTarget鏈表尾部,所以mFirstTouchTarget會賦值為null,那么接下來的事件將不會進入到onInterceptTouchEvent()中。也就會直接交由該view處理。如果我們沒有進行事件的攔截,而是交由子view去處理,由于ViewGroup的onInterceptTouchEvent()默認并不會攔截除了ACTION_DOWN以外的事件,所以后續(xù)事件將繼續(xù)交由子view去處理,如果存在子view且事件位于子view內(nèi)部區(qū)域的話。所以無論是否進行攔截,事件流都會交由view的dispatchTouchEvent()中進行處理,我們接下來跟蹤一下view中的dispatchTouchEvent()處理過程:if
(actionMasked
==
MotionEvent.ACTION_DOWN)
{
//
Defensive
cleanup
for
new
gesture
stopNestedScroll();
}
if
(onFilterTouchEventForSecurity(event))
{
if
((mViewFlags
&
ENABLED_MASK)
==
ENABLED
&&
handleScrollBarDragging(event))
{
result
=
true;
}
//noinspection
SimplifiableIfStatement
ListenerInfo
li
=
mListenerInfo;
if
(li
!=
null
&&
li.mOnTouchListener
!=
null
&&
(mViewFlags
&
ENABLED_MASK)
==
ENABLED
&&
li.mOnTouchListener.onTouch(this,
event))
{
result
=
true;
}
if
(!result
&&
onTouchEvent(event))
{
result
=
true;
}
}當被按下時,即ACTION_DOWN時,view會停止內(nèi)部的滾動,如果view沒有被覆蓋或遮擋時,首先會進行mListenerInfo是否為空的判斷,我們看下mListenerInfo是在哪里初始化的:ListenerInfo
getListenerInfo()
{
if
(mListenerInfo
!=
null)
{
return
mListenerInfo;
}
mListenerInfo
=
new
ListenerInfo();
return
mListenerInfo;
}這里可以看出,mListenerInfo一般不會是null,知道在我們使用它時調(diào)用過這段代碼,而當view被加入window中的時候,會調(diào)用下面這段代碼,從注釋中也可以看出來:/**
*
Add
a
listener
for
attach
state
changes.
*
*
This
listener
will
be
called
whenever
this
view
is
attached
or
detached
*
from
a
window.
Remove
the
listener
using
*
{@link
#removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.
*
*
@param
listener
Listener
to
attach
*
@see
#removeOnAttachStateChangeListener(OnAttachStateChangeListener)
*/
public
void
addOnAttachStateChangeListener(OnAttachStateChangeListener
listener)
{
ListenerInfo
li
=
getListenerInfo();
if
(li.mOnAttachStateChangeListeners
==
null)
{
li.mOnAttachStateChangeListeners
=
new
CopyOnWriteArrayList<OnAttachStateChangeListener>();
}
li.mOnAttachStateChangeListeners.add(listener);
}到這里我們就知道,mListenerInfo一開始就是被初始化好了的,所以li不可能為null,li.mOnTouchListener!=null即當設置了TouchListener時不為null,并且view是enabled狀態(tài),一般情況view都是enable的。這個時候會調(diào)用到onTouch()事件,當onTouch()返回true時,這個時候result會賦值true。而當result為true時,onTouchEvent()將不會被調(diào)用。從這里可以看出,onTouch()會優(yōu)先onTouchEvent()調(diào)用;當view設置touch監(jiān)聽并返回true時,那么它的onTouchEvent()將被屏蔽。否則會調(diào)用onTouchEvent()處理。那么讓我們繼續(xù)來看看onTouchEvent()中的事件處理:if
((viewFlags
&
ENABLED_MASK)
==
DISABLED)
{
if
(action
==
MotionEvent.ACTION_UP
&&
(mPrivateFlags
&
PFLAG_PRESSED)
!=
0)
{
setPressed(false);
}
//
A
disabled
view
that
is
clickable
still
consumes
the
touch
//
events,
it
just
doesn't
respond
to
them.
return
(((viewFlags
&
CLICKABLE)
==
CLICKABLE
||
(viewFlags
&
LONG_CLICKABLE)
==
LONG_CLICKABLE)
||
(viewFlags
&
CONTEXT_CLICKABLE)
==
CONTEXT_CLICKABLE);
}首先,當view狀態(tài)是DISABLED時,只要view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,都會返回true,而button默認是CLICKABLE的,textview默認不是CLICKABLE的,而view一般默認都不是LONG_CLICKABLE的。我們繼續(xù)向下看:if
(mTouchDelegate
!=
null)
{
if
(mTouchDelegate.onTouchEvent(event))
{
return
true;
}
}如果有代理事件,仍然會返回true.if
(((viewFlags
&
CLICKABLE)
==
CLICKABLE
||
(viewFlags
&
LONG_CLICKABLE)
==
LONG_CLICKABLE)
||
(viewFlags
&
CONTEXT_CLICKABLE)
==
CONTEXT_CLICKABLE)
{
switch
(action)
{
case
MotionEvent.ACTION_UP:
boolean
prepressed
=
(mPrivateFlags
&
PFLAG_PREPRESSED)
!=
0;
if
((mPrivateFlags
&
PFLAG_PRESSED)
!=
0
||
prepressed)
{
//
take
focus
if
we
don't
have
it
already
and
we
should
in
//
touch
mode.
boolean
focusTaken
=
false;
if
(isFocusable()
&&
isFocusableInTouchMode()
&&
!isFocused())
{
focusTaken
=
requestFocus();
}
if
(prepressed)
{
//
The
button
is
being
released
before
we
actually
//
showed
it
as
pressed.
Make
it
show
the
pressed
//
state
now
(before
scheduling
the
click)
to
ensure
//
the
user
sees
it.
setPressed(true,
x,
y);
}
if
(!mHasPerformedLongPress
&&
!mIgnoreNextUpEvent)
{
//
This
is
a
tap,
so
remove
the
longpress
check
removeLongPressCallback();
//
Only
perform
take
click
actions
if
we
were
in
the
pressed
state
if
(!focusTaken)
{
//
Use
a
Runnable
and
post
this
rather
than
calling
//
performClick
directly.
This
lets
other
visual
state
//
of
the
view
update
before
click
actions
start.
if
(mPerformClick
==
null)
{
mPerformClick
=
new
PerformClick();
}
if
(!post(mPerformClick))
{
performClick();
}
}
}
if
(mUnsetPressedState
==
null)
{
mUnsetPressedState
=
new
UnsetPressedState();
}
if
(prepressed)
{
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
}
else
if
(!post(mUnsetPressedState))
{
//
If
the
post
failed,
unpress
right
now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent
=
false;
break;
case
MotionEvent.ACTION_DOWN:
mHasPerformedLongPress
=
false;
if
(performButtonActionOnTouchDown(event))
{
break;
}
//
Walk
up
the
hierarchy
to
determine
if
we're
inside
a
scrolling
container.
boolean
isInScrollingContainer
=
isInScrollingContainer();
//
For
views
inside
a
scrolling
container,
delay
the
pressed
feedback
for
//
a
short
period
in
case
this
is
a
scroll.
if
(isInScrollingContainer)
{
mPrivateFlags
|=
PFLAG_PREPRESSED;
if
(mPendingCheckForTap
==
null)
{
mPendingCheckForTap
=
new
CheckForTap();
}
mPendingCheckForTap.x
=
event.getX();
mPendingCheckForTap.y
=
event.getY();
postDelayed(mPendingCheckForTap,
ViewConfiguration.getTapTimeout());
}
else
{
//
Not
inside
a
scrolling
container,
so
show
the
feedback
right
away
setPressed(true,
x,
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 湖鹽采掘工持續(xù)改進評優(yōu)考核試卷含答案
- 硅晶片拋光工崗前核心考核試卷含答案
- 軟膏劑工QC考核試卷含答案
- 總?cè)軇┥a(chǎn)工崗前基礎模擬考核試卷含答案
- 苯基氯硅烷生產(chǎn)工常識考核試卷含答案
- 白銀熔池熔煉工測試驗證評優(yōu)考核試卷含答案
- 2024年河北?。?31所)輔導員考試筆試真題匯編附答案
- 2025《行測》考試試題完美版
- 栲膠生產(chǎn)工變革管理水平考核試卷含答案
- 粗紗工成果轉(zhuǎn)化知識考核試卷含答案
- 吳江三小英語題目及答案
- 供水管道搶修知識培訓課件
- 司法警察協(xié)助執(zhí)行課件
- 廣東物業(yè)管理辦法
- 業(yè)務規(guī)劃方案(3篇)
- 雙向晉升通道管理辦法
- 集團債權(quán)訴訟管理辦法
- 上海物業(yè)消防改造方案
- 鋼結(jié)構(gòu)施工進度計劃及措施
- 供應商信息安全管理制度
- 智慧健康養(yǎng)老服務與管理專業(yè)教學標準(高等職業(yè)教育??疲?025修訂
評論
0/150
提交評論