Android?AccessibilityService?事件分發(fā)原理分析總結(jié)
前言:
在了解了無障礙服務(wù)基礎(chǔ)使用之后,我們來探究一下 AccessibilityService 的事件接收方法回調(diào)的時機和它深層次的實現(xiàn)邏輯。
AccessibilityService 監(jiān)聽事件的調(diào)用邏輯
AccessibilityService 有很多用來接收外部調(diào)用事件變化的方法,這些方法封裝在內(nèi)部接口 Callbacks 中:
public interface Callbacks {
void onAccessibilityEvent(AccessibilityEvent event);
void onInterrupt();
void onServiceConnected();
void init(int connectionId, IBinder windowToken);
boolean onGesture(AccessibilityGestureEvent gestureInfo);
boolean onKeyEvent(KeyEvent event);
void onMagnificationChanged(int displayId, @NonNull Region region, float scale, float centerX, float centerY);
void onSoftKeyboardShowModeChanged(int showMode);
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
void onFingerprintCapturingGesturesChanged(boolean active);
void onFingerprintGesture(int gesture);
void onAccessibilityButtonClicked(int displayId);
void onAccessibilityButtonAvailabilityChanged(boolean available);
void onSystemActionsChanged();
}以最常用的 onAccessibilityEvent 為例,介紹一下調(diào)用流程。
onAccessibilityEvent 在 AccessibilityService 中,AccessibilityService 在 onBind 生命周期中,返回了一個IAccessibilityServiceClientWrapper 對象,它是一個 Binder ,所以外部實際上通過 Binder 機制跨進程調(diào)用到無障礙服務(wù)的。
外部通過 Binder 調(diào)用到 Service 具體的實現(xiàn)方法的調(diào)用棧是:
- frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java#IAccessibilityServiceClientWrapper#onAccessibilityEvent - frameworks/base/core/java/com/android/internal/os/HandlerCaller.java#sendMessage - frameworks/base/core/java/com/android/internal/os/HandlerCaller.java#Callback#executeMessage - frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java#IAccessibilityServiceClientWrapper#executeMessage - frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java#Callbacks#onAccessibilityEvent - frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java#onAccessibilityEvent
首先是外部調(diào)用到 IAccessibilityServiceClientWrapper 的 onAccessibilityEvent 方法:
// IAccessibilityServiceClientWrapper
public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
Message message = mCaller.obtainMessageBO(
DO_ON_ACCESSIBILITY_EVENT, serviceWantsEvent, event);
mCaller.sendMessage(message);
}在這個方法中通過 HandlerCaller 切換到主線程,然后發(fā)送了一個消息。
這里的 HandlerCaller 的源碼是:
public class HandlerCaller {
final Looper mMainLooper;
final Handler mH;
final Callback mCallback;
class MyHandler extends Handler {
MyHandler(Looper looper, boolean async) {
super(looper, null, async);
}
@Override
public void handleMessage(Message msg) {
mCallback.executeMessage(msg);
}
}
public interface Callback {
public void executeMessage(Message msg);
}
public HandlerCaller(Context context, Looper looper, Callback callback,
boolean asyncHandler) {
mMainLooper = looper != null ? looper : context.getMainLooper();
mH = new MyHandler(mMainLooper, asyncHandler);
mCallback = callback;
}
...
}從它的源碼中可以看出,這是一個向主線程發(fā)消息的 Handler 。 在主線程中執(zhí)行它的內(nèi)部類 Callback 的 executeMessage 方法。
而IAccessibilityServiceClientWrapper 實現(xiàn)了 AccessibilityService.Callback接口,所以調(diào)用到了IAccessibilityServiceClientWrapper.executeMessage 方法中。IAccessibilityServiceClientWrapper 對象的創(chuàng)建是在 onBind 生命周期中。它接收一個 AccessibilityService.Callback 對象作為 IAccessibilityServiceClientWrapper 的構(gòu)造參數(shù):
@Override
public final IBinder onBind(Intent intent) {
return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
@Override
public void onServiceConnected() {
AccessibilityService.this.dispatchServiceConnected();
}
@Override
public void onInterrupt() {
AccessibilityService.this.onInterrupt();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityService.this.onAccessibilityEvent(event);
}
...
});IAccessibilityServiceClientWrapper 中的 executeMessage中,根據(jù)不同的 Handler 消息調(diào)用了 AccessibilityService.Callback 的對應(yīng)方法:
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub implements HandlerCaller.Callback {
private final HandlerCaller mCaller;
public IAccessibilityServiceClientWrapper(Context context, Looper looper,
Callbacks callback) {
// ...
mCaller = new HandlerCaller(context, looper, this, true);
}
@Override
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT: {
// ...
mCallback.onAccessibilityEvent(event);
return;
}
case DO_ON_INTERRUPT: {
// ...
mCallback.onInterrupt();
return;
}
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
}
}而剛才傳入的 AccessibilityService.Callback 方法的實現(xiàn)中,調(diào)用了AccessibilityService 的 onAccessibilityEvent 方法:
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityService.this.onAccessibilityEvent(event);
}這樣整個調(diào)用鏈就清晰了:
- 外部通過 Binder 機制調(diào)用到
AccessibilityService的內(nèi)部 Binder 代理實現(xiàn)IAccessibilityServiceClientWrapper對象 IAccessibilityServiceClientWrapper對象內(nèi)部通過 Handler 機制切換到主線程執(zhí)行AccessibilityService.Callback中對應(yīng)的方法。AccessibilityService.Callback中的方法調(diào)用到了AccessibilityService對應(yīng)的生命周期方法。
接下來關(guān)注一下一些重要的事件接收方法。
onAccessibilityEvent
Handler 中的 DO_ON_ACCESSIBILITY_EVENT 事件會調(diào)用到 onAccessibilityEvent 。 在 executeMessage(Message message) 方法中的邏輯是:
case DO_ON_ACCESSIBILITY_EVENT: {
AccessibilityEvent event = (AccessibilityEvent) message.obj;
boolean serviceWantsEvent = message.arg1 != 0;
if (event != null) {
// Send the event to AccessibilityCache via AccessibilityInteractionClient
AccessibilityInteractionClient.getInstance(mContext).onAccessibilityEvent(
event);
if (serviceWantsEvent
&& (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
// Send the event to AccessibilityService
mCallback.onAccessibilityEvent(event);
}
// Make sure the event is recycled.
try {
event.recycle();
} catch (IllegalStateException ise) {
/* ignore - best effort */
}
}
return;
}- 取出
message.obj轉(zhuǎn)換為AccessibilityEvent,并根據(jù)message.arg1檢查 Service 是否想要處理這個事件。 - 檢查
AccessibilityEvent對象是否為 null,為空直接 return - 將
AccessibilityEvent對象通過AccessibilityInteractionClient加入到 AccessibilityCache 緩存中,然后根據(jù) service 是否要處理事件和AccessibilityInteractionClient連接狀態(tài),決定是否要將事件發(fā)送給AccessibilityService - 最后回收事件對象。
這里的AccessibilityInteractionClient連接狀態(tài)檢查時通過 mConnectionId 屬性來判斷的,在IAccessibilityServiceClientWrapper 的 init 時被賦值,init 也是通過 Handler 傳遞來的消息切換到主線程進行的:
case DO_INIT: {
mConnectionId = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
IAccessibilityServiceConnection connection =
(IAccessibilityServiceConnection) args.arg1;
IBinder windowToken = (IBinder) args.arg2;
args.recycle();
if (connection != null) {
AccessibilityInteractionClient.getInstance(mContext).addConnection(
mConnectionId, connection);
mCallback.init(mConnectionId, windowToken);
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance(mContext).removeConnection(
mConnectionId);
mConnectionId = AccessibilityInteractionClient.NO_ID;
AccessibilityInteractionClient.getInstance(mContext).clearCache();
mCallback.init(AccessibilityInteractionClient.NO_ID, null);
}
return;
}onIntercept
與onAccessibilityEvent 事件一樣都是通過 Handler 機制進行處理的:
case DO_ON_INTERRUPT: {
if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
mCallback.onInterrupt();
}
return;
}只檢查了與AccessibilityInteractionClient 的連接狀態(tài)。
AccessibilityService 事件的外部來源
經(jīng)過上面的分析,我們知道外部通過 binder 機制觸發(fā)了 AccessibilityService 的事件監(jiān)聽方法,那么它們來自哪里呢? 接下來從 AccessibilityServiceInfo 開始,分析系統(tǒng)事件是如何傳遞到無障礙服務(wù)的。
AccessibilityServiceInfo
AccessibilityServiceInfo 用來描述 AccessibilityService 。系統(tǒng)根據(jù)這個類中的信息,將 AccessibilityEvents 通知給一個 AccessibilityService。
AccessibilityServiceInfo 中定義了一些屬性,用來控制無障礙服務(wù)的一些權(quán)限和能力。我們在 AndroidManifest.xml 中為無障礙服務(wù)指定的meta-data 標簽中,指定的配置文件中的配置,和AccessibilityServiceInfo 中的屬性一一對應(yīng)。
AccessibilityServiceInfo 的引用:

查看 AccessibilityServiceInfo 的引用棧,發(fā)現(xiàn)有很多地方都用到了這個類,關(guān)于無障礙的重點看 AccessibilityManagerService 和 AccessibilityManager 這一套邏輯。 從名稱上看,無障礙功能提供了類似 AMS 一樣的系統(tǒng)服務(wù),并通過一個 Manager 類來進行調(diào)用。
AccessibilityManager
AccessibilityManager 是一個系統(tǒng)服務(wù)管理器,用來分發(fā) AccessibilityEvent 事件。當(dāng)用戶界面中發(fā)生一些值得注意的事件時,例如焦點變化和 Activity 啟動等,會生成這些事件。
AccessibilityManager 內(nèi)部有一個看起來與發(fā)送消息有關(guān)的方法:
public void sendAccessibilityEvent(AccessibilityEvent event) {
final IAccessibilityManager service;
final int userId;
final AccessibilityEvent dispatchedEvent;
synchronized (mLock) {
service = getServiceLocked();
if (service == null) return;
event.setEventTime(SystemClock.uptimeMillis());
if (event.getAction() == 0) {
event.setAction(mPerformingAction);
}
if (mAccessibilityPolicy != null) {
dispatchedEvent = mAccessibilityPolicy.onAccessibilityEvent(event, mIsEnabled, mRelevantEventTypes);
if (dispatchedEvent == null) return;
} else {
dispatchedEvent = event;
}
if (!isEnabled()) {
Looper myLooper = Looper.myLooper();
if (myLooper == Looper.getMainLooper()) {
throw new IllegalStateException("Accessibility off. Did you forget to check that?");
} else {
// 當(dāng)不是在主線程(mainLooper)運行時,調(diào)用檢查無障礙開啟狀態(tài)可能會異常。因此直接拋出異常
Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
return;
}
}
userId = mUserId;
}
try {
final long identityToken = Binder.clearCallingIdentity();
try {
service.sendAccessibilityEvent(dispatchedEvent, userId);
} finally {
Binder.restoreCallingIdentity(identityToken);
}
if (DEBUG) {
Log.i(LOG_TAG, dispatchedEvent + " sent");
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error during sending " + dispatchedEvent + " ", re);
} finally {
if (event != dispatchedEvent) {
event.recycle();
}
dispatchedEvent.recycle();
}
}這個方法是用來發(fā)送一個 AccessibilityEvent 事件的,簡化里面的邏輯:
- 加鎖
- getServiceLocked() 獲取 service 對象,service 獲取不到直接 return
- event 設(shè)置一個時間,然后設(shè)置 action
- 檢查 AccessibilityPolicy 對象是否為 null
- 不為空,dispatchedEvent 根據(jù) AccessibilityPolicy 的 onAccessibilityEvent(event) 賦值,賦值后仍為空直接 return
- 為空, dispatchedEvent = event
- 檢查系統(tǒng)是否開啟無障礙功能
- 解鎖
- try
- try
- service.sendAccessibilityEvent(dispatchedEvent, userId); 通過 AccessibilityManagerService 的 sendAccessibilityEvent 發(fā)送事件
- finally
- Binder.restoreCallingIdentity(identityToken);
- finally
- 回收 event 和 dispatchedEvent 對象這里 getServiceLocked() 內(nèi)部調(diào)用了 tryConnectToServiceLocked 方法:
private void tryConnectToServiceLocked(IAccessibilityManager service) {
if (service == null) {
IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
if (iBinder == null) {
return;
}
service = IAccessibilityManager.Stub.asInterface(iBinder);
}
// ...
}而 AccessibilityManagerService 實現(xiàn)了 IAccessibilityManager.Stub ,所以這里的 service 時 AccessibilityManagerService。 這里調(diào)用了 service.sendAccessibilityEvent(dispatchedEvent, userId);
通過 Binder 機制,調(diào)用到的是 AccessibilityManagerService 里的 sendAccessibilityEvent 方法。
AccessibilityManager.sendAccessibilityEvent 的調(diào)用位置有很多,其中比較顯眼的是在 ViewRootImpl 中的,因為 ViewRootImpl 是 View 添加到 Window 的重要實現(xiàn)類。sendAccessibilityEvent 在 ViewRootImpl 的內(nèi)部類中存在調(diào)用:
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
// ...
final int eventType = event.getEventType();
final View source = getSourceForAccessibilityEvent(event);
switch (eventType) {
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
if (source != null) {
AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
if (provider != null) {
final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(event.getSourceNodeId());
final AccessibilityNodeInfo node;
node = provider.createAccessibilityNodeInfo(virtualNodeId);
setAccessibilityFocus(source, node);
}
}
} break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
if (source != null && source.getAccessibilityNodeProvider() != null) {
setAccessibilityFocus(null, null);
}
} break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
handleWindowContentChangedEvent(event);
} break;
}
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}這是一個 override 方法,它的定義在接口 ViewParent 中。 這個方法的調(diào)用棧很多:

可以跟到 View 中存在的同名方法 :
public void sendAccessibilityEvent(int eventType) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
} else {
sendAccessibilityEventInternal(eventType);
}
}它在 View 中的調(diào)用:

可以看出,常見的 View 的事件,包括:點擊、長按、焦點變化等,都會調(diào)用sendAccessibilityEvent方法。
在 ViewRootImpl中,AccessibilityManager也存在很多處調(diào)用邏輯:

可以看出在 Android 在 View 體系中,提供了很多對無障礙能力的支持。所有的 View 的事件都會被系統(tǒng)的無障礙服務(wù)捕獲到。
回到調(diào)用邏輯,AccessibilityManager內(nèi)部調(diào)用到的是AccessibilityManagerService里的sendAccessibilityEvent方法。下面介紹 AccessibilityManagerService里面的流程。
AccessibilityManagerService
sendAccessibilityEvent偽代碼邏輯:
synchronized {
1. 解析配置文件中的屬性,并對其進行配置
2. 設(shè)置配置的包名范圍
}
if (dispatchEvent) {
3. 確保接收此事件的 client 能夠獲取 window 當(dāng)前的狀態(tài),因為 Window Manager 可能會出于性能原因延遲計算,通過配置 shouldComputeWindows = true/false
if (shouldComputeWindows) {
4. 獲取 WindowManagerInternal wm
5. wm.computeWindowsForAccessibility(displayId);
}
synchoronized {
notifyAccessibilityServicesDelayedLocked(event, false)
notifyAccessibilityServicesDelayedLocked(event, true)
mUiAutomationManager.sendAccessibilityEventLocked(event);
}
}
...最后的關(guān)鍵三行代碼中,調(diào)用了兩個方法:
notifyAccessibilityServicesDelayedLocked :
private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event, boolean isDefault) {
try {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
service.notifyAccessibilityEvent(event);
}
}
} catch (IndexOutOfBoundsException oobe) {}
}AccessibilityManagerService從這個方法中,調(diào)用 AccessibilityServiceConnection的同名方法notifyAccessibilityEvent 。
這個意思是,先通知service.mIsDefault = false的無障礙服務(wù)連接發(fā)送事件,然后再通知 等于 true 的無障礙服務(wù)連接發(fā)送事件。
mUiAutomationManager.sendAccessibilityEventLocked(event):
mUiAutomationManager的類型是UiAutomationManager,它的sendAccessibilityEventLocked方法實現(xiàn)是:
void sendAccessibilityEventLocked(AccessibilityEvent event) {
if (mUiAutomationService != null) {
mUiAutomationService.notifyAccessibilityEvent(event);
}
}mUiAutomationService的類型是 UiAutomationService,它是UiAutomationManager的內(nèi)部類,繼承自AbstractAccessibilityServiceConnection,內(nèi)部操作都是切換到主線程進行的,notifyAccessibilityEvent方法的實現(xiàn)在父類中,后續(xù)會和上面的AccessibilityServiceConnection一起說明。
AccessibilityServiceConnection
此類用來表示一個無障礙服務(wù)。 它存儲著服務(wù)管理所需的所有每個服務(wù)數(shù)據(jù),提供用于啟動/停止服務(wù)的 API,并負責(zé)在服務(wù)管理的數(shù)據(jù)結(jié)構(gòu)中添加/刪除服務(wù)。 該類還公開了配置接口,該接口在綁定后立即傳遞給它所代表的服務(wù)。 它還用作服務(wù)的連接。
AccessibilityServiceConnection與UiAutomationService 一樣,繼承自AbstractAccessibilityServiceConnection。
它們的notifyAccessibilityEvent方法,在AbstractAccessibilityServiceConnection中:
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
...
// copy 一個副本,因為在調(diào)度期間,如果接收的服務(wù)沒有訪問窗口內(nèi)容的權(quán)限,則可能會修改并刪除事件。
AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
Message message;
if ((mNotificationTimeout > 0)
&& (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
// 最多允許一個待處理事件
final AccessibilityEvent oldEvent = mPendingEvents.get(eventType);
mPendingEvents.put(eventType, newEvent);
if (oldEvent != null) {
mEventDispatchHandler.removeMessages(eventType);
oldEvent.recycle();
}
message = mEventDispatchHandler.obtainMessage(eventType);
} else {
// 發(fā)送所有消息,繞過 mPendingEvents
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
message.arg1 = serviceWantsEvent ? 1 : 0;
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}這個方法中,通過一個 Handler 來處理和發(fā)送消息。
mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
@Override
public void handleMessage(Message message) {
final int eventType = message.what;
AccessibilityEvent event = (AccessibilityEvent) message.obj;
boolean serviceWantsEvent = message.arg1 != 0;
notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
}
};內(nèi)部調(diào)用notifyAccessibilityEventInternal:
private void notifyAccessibilityEventInternal(int eventType, AccessibilityEvent event, boolean serviceWantsEvent) {
IAccessibilityServiceClient listener;
synchronized (mLock) {
listener = mServiceInterface;
// 如果在消息分發(fā)無障礙事件時 service die 或 關(guān)閉,listener 可能為空
if (listener == null) return;
// 我們有兩種通知事件的方式,節(jié)流和非節(jié)流。 如果我們不進行節(jié)流,那么消息會隨事件一起出現(xiàn),我們會毫不費力地處理這些事件。
if (event == null) {
// 我們正在限制事件,所以只要它為空,我們就會在 mPendingEvents 中發(fā)送這種類型的事件。 由于競爭條件,它只能為空:
// 1) 一個 binder 線程調(diào)用 notifyAccessibilityServiceDelayedLocked,它發(fā)布一條用于調(diào)度事件的消息并將該事件存儲在 mPendingEvents 中。
// 2) 消息由服務(wù)線程上的處理程序從隊列中拉出,此方法即將獲取鎖。
// 3) 另一個 binder 線程在 notifyAccessibilityEvent 中獲取鎖
// 4) notifyAccessibilityEvent 回收該方法即將處理的事件,替換為新的,并發(fā)布第二條消息
// 5) 此方法抓取新事件,對其進行處理,然后將其從 mPendingEvents 中刪除
// 6) (4) 中發(fā)送的第二條消息到達,但事件已在 (5) 中刪除。
event = mPendingEvents.get(eventType);
if (event == null) {
return;
}
mPendingEvents.remove(eventType);
}
if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {
event.setConnectionId(mId);
} else {
event.setSource((View) null);
}
event.setSealed(true);
}
try {
listener.onAccessibilityEvent(event, serviceWantsEvent);
} catch (RemoteException re) {} finally {
event.recycle();
}
}備注中說明了消息處理和分發(fā)邏輯,但我們這里只需要關(guān)注最后的:
listener.onAccessibilityEvent(event, serviceWantsEvent);
這里的 listener 是一個IAccessibilityServiceClient,是個 AIDL 文件。這個 AIDL 在我們的 AccessibilityService 中有實現(xiàn)類!
調(diào)用到這個IAccessibilityServiceClient , 就能調(diào)用到AccessibilityService中的代碼了。
至此無障礙服務(wù)的調(diào)用,從系統(tǒng)的 View 和其他事件位置,經(jīng)過AccessibilityManager.notifyAccessibilityEvent用 Binder 機制調(diào)用AccessibilityManagerService.notifyAccessibilityEvent ;然后AccessibilityManagerService內(nèi)部通過調(diào)用AccessibilityServiceConnection.notifyAccessibilityEvent來調(diào)用我們可以實現(xiàn)的AccessibilityService中接收事件。
到此這篇關(guān)于Android AccessibilityService 事件分發(fā)原理分析總結(jié)的文章就介紹到這了,更多相關(guān)Android AccessibilityService 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實現(xiàn)瘋狂連連看游戲之游戲效果預(yù)覽(一)
這篇文章主要為大家詳細介紹了Android實現(xiàn)瘋狂連連看游戲之游戲的效果預(yù)覽,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
android教程之intent的action屬性使用示例(intent發(fā)短信)
這篇文章主要介紹了android中intent的action屬性使用示例,提供了使用intent撥打電話、發(fā)送短信、播放mp3的代碼2014-01-01
Android Studio 3.6 正式版終于發(fā)布了,快來圍觀
Android Studio 3.6 正式版終于發(fā)布了,值得興奮呀,畢竟 3.5 大版本更新也已經(jīng)差不多半年了,撒花撒花!這次更新又更新了什么呢?快來跟隨小編一起看看吧2020-02-02
Android App中ViewPager與Fragment結(jié)合的一些問題解決
這篇文章主要介紹了Android App中ViewPager與Fragment結(jié)合的一些問題解決,重點講解了如何更新及替換ViewPager中的Fragment,需要的朋友可以參考下2016-03-03

