Android 监听应用集成的SDK中的 Activity。
不是我们自己写的Activity,也就是我们无法改变其源码。那么我们监听他们什么呢?
在本篇文章主要讲监听它被点击返回键了。
当然到最后我们会发现可以监听其很多东西,这里的监听指的是在不改变其原有的逻辑,监听其行为。
例如给用户展示应用中集成的某个SDK中弹出的Activity时,用户发生了点击返回键操作,可能SDK内部自己会做一些业务逻辑的处理。
这些我们不去管他,因为我们只想监听其点击返回键这个操作,然后执行一些我们自己的逻辑。
既然提到了返回键,那么我们就先来看看如果是我们自己书写的Activity,要怎么监听点击返回键。
这对于之后我们如何监听SDK中的 Activity 非常有用。
一、应用内自己书写的 Activity
我们有三种方法可以监听到用户点击了Back键,都是通过重写 Activity 中的方法实现:
其中前两种方法相信很多同学都知道,但是可能很多同学都没怎么用过第三种方法。
1 、在 Activity 内重写 onBackPressed:
@Override
public void onBackPressed() {
//do something
super.onBackPressed();
}
看这个方法的介绍:
Called when the activity has detected the user's press of the back key.
The default implementation simply finishes the current activity, but you can override this to do whatever you want.
当 activity 检测到用户点击了返回键,默认实现会去关闭 activity。我们可以按照自己所想,去覆盖它。
如果只是在点击 Back 键时做一些逻辑,而不想影响页面的正常关闭,那么一定要记得调用 super.onBackPressed();
super.onBackPressed(); 便是调用系统的一些逻辑,去正常的关闭页面。
如果希望屏蔽返回键就需要去掉 super.onBackPressed(); 这行代码。
2 、重写 onKeyDown() 方法:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == event.KEYCODE_BACK) {
//do something
return true;
}
return super.onKeyDown(keyCode, event);
}
当 keyCode 为 event.KEYCODE_BACK 时,即点击Back键,执行相应的逻辑。
同时直接返回 true 表示消耗了这个事件。不会再向下执行。相当于屏蔽了Back键的功能。
如果不想屏蔽返回键功能,需要调用 super.onKeyDown(keyCode, event);
3、重写 dispatchKeyEvent() 方法
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if(event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
//do something
return true;
}
return super.dispatchKeyEvent(event);
}
和 onKeyDown() 方法一样我们可以判断,当 event.getKeyCode() == KeyEvent.KEYCODE_BACK 时说明用户点击了返回按钮。
这是我们可以做一些自己的逻辑。如果直接返回 true。说明消耗了此事件,不再向下继续传递了。也就是说拦截了返回按钮。
如果想让其继续向下走只要调用 super.dispatchKeyEvent(event); 就可以了。
4 、区别
上面我们介绍了三种方法,可以监听返回按钮。
如果业务逻辑需要我们可以拦截返回按钮,我们轻轻松松通过上面三种方法做到。
那么这三种方法有什么区别呢?如果一起用会怎么样呢?
简单来讲如果同时重写了以上三个方法,那么只有 dispatchKeyEvent() 方法会其作用,其他两个方法将不会执行。
我们可以看一下 dispatchKeyEvent() 的介绍:
Called to process key events. You can override this to intercept all key events before they are dispatched to the window.
Be sure to call this implementation for key events that should be handled normally.
如果我们重写了 dispatchKeyEvent() 方法,那么我们其实就拦截了其分发事件。这点相信大家都应该很理解。
那么如果 onKeyDown() 和 onBackPressed() 两个方法同时使用的话,onBackPressed() 方法会不起作用。
我们看onKeyDown的源码:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
...
}
从之前看对于 onBackPressed() 方法的介绍我们知道,在 Activity 中 onBackPressed() 是真正执行返回键逻辑的方法。
我们会发现当 keyCode == KeyEvent.KEYCODE_BACK 时调用了 onBackPressed() 方法,所以上面我们将onKeyDown() 和 onBackPressed()方法重写后,
由于onBackPressed() 不会被调用了,所以其自然就不会有什么作用了。
所以这里我们可以将这三种方法的层级排个序 :dispatchKeyEvent() > onKeyDown() > onBackPressed()
二 、当该 Activity 不是我们自己书写的 Activity 。而是项目中集成的某个aar或者jar包中的Activity呢?
有这样一下需求,那就是监听需要监听某个SDK 中弹出的Activity,当用户点击了Back键时,执行一些我们自己的逻辑。
该如何做呢?
1、首先我们要知道的是我们能做什么,或者说能获取到什么?
上面重写onKeyDown() 或者 onBackPressed() 方法肯定是不行了。因为我们无法改变其源码(当然其实也是可以改变的,不过那绝不是比较优的方式)。
我在之前有一篇文章中有提到过如何监听应用内 Activity 的生命周期,那么我们再次看看能否通过它来做一些什么。
虽然我们无法修改该 Activity 的源码,但是由于该 Activity 还是在我们应用内部,那么我们就能通过 ActivityLifecycleCallbacks 来监听
其生命周期。
2 、自定义类实现 Application.ActivityLifecycleCallbacks
public class LifecycleCallback implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
3 、注册:
LifecycleCallback callback = new LifecycleCallback();
getApplication().registerActivityLifecycleCallbacks(callback);
当然在合适的时机(例如界面销毁,逻辑结束等),需要取消注册:
getApplication().unregisterActivityLifecycleCallbacks(callback);
当我们注册了 ActivityLifecycleCallback 后,应用内所有的 Activity 在经历生命周期时,都会回调过来。
所以当我们要监听的Activity 启动起来后,我们就可以知道了。
if(activity instanceof com.xxx.xxActivity) {
this.activity = activity;
}
这样我们就可以获得到我们监听的 Activity 的对象。
当我们得到这个Activity 对象后,便能够做很多事情了。所以 LifecycleCallback 完整代码应该如下:
public class LifecycleCallback implements Application.ActivityLifecycleCallbacks {
private Activity activity;
public LifecycleCallback() {
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
if(activity instanceof com.xxx.xxActivity) {
this.activity = activity;
//注册Window.Callback
registerCallback();
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
public void registerCallback() {
//自定义 Window.Callback 对象
WindowCallback callback = new WindowCallback(activity);
//注册Window.Callback
activity.getWindow().setCallback(callback);
}
}
可以看到,当我获取到Activity 对象后,便调用 getWindow() 方法获取 Window 对象。
然后调用 Window 中的 setCallback() 方法注册 Window.Callback。
4 、自定义Window.Callback 对象 WindowCallback 如下:
public class WindowCallback implements Window.Callback {
private Activity activity;
public WindowCallback(Activity activity) {
this.activity = activity;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d("KeyEvent", "Action :"+event.getAction());
if(event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
//执行我们的逻辑
}
return activity.dispatchKeyEvent(event);
}
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
return activity.dispatchKeyShortcutEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return activity.dispatchTouchEvent(event);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
return activity.dispatchTrackballEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
return activity.dispatchGenericMotionEvent(event);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
return activity.dispatchPopulateAccessibilityEvent(event);
}
@Nullable
@Override
public View onCreatePanelView(int featureId) {
return activity.onCreatePanelView(featureId);
}
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
return activity.onCreatePanelMenu(featureId, menu);
}
@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
return activity.onPreparePanel(featureId, view, menu);
}
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
return activity.onMenuOpened(featureId, menu);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
return activity.onMenuItemSelected(featureId, item);
}
@Override
public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
activity.onWindowAttributesChanged(attrs);
}
@Override
public void onContentChanged() {
activity.onContentChanged();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
activity.onWindowFocusChanged(hasFocus);
}
@Override
public void onAttachedToWindow() {
activity.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
activity.onDetachedFromWindow();
}
@Override
public void onPanelClosed(int featureId, Menu menu) {
activity.onPanelClosed(featureId, menu);
}
@Override
public boolean onSearchRequested() {
return activity.onSearchRequested();
}
@Override
public boolean onSearchRequested(SearchEvent searchEvent) {
return activity.onSearchRequested(searchEvent);
}
@Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
return activity.onWindowStartingActionMode(callback);
}
@Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
return activity.onWindowStartingActionMode(callback, type);
}
@Override
public void onActionModeStarted(ActionMode mode) {
activity.onActionModeStarted(mode);
}
@Override
public void onActionModeFinished(ActionMode mode) {
activity.onActionModeFinished(mode);
}
}
那么这个 Window.Callback 到底是什么呢?
看其介绍:
API from a Window back to its caller. This allows the client to intercept key dispatching, panels and menus, etc.
我们可以通过它拦截所有 key 的分发。而且是从 Window 回调回来的,那么其应该是非常顶层的接口了。这就太好了。
我们仔细一看居然有 dispatchKeyEvent() ,如果去看 Activity 的源码,你会发现上面所有的方法 Activity 中全部都有。
因为 Activity 也实现了 Window.Callback 接口。
Activity 源码:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this);
//添加Callback回调
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
...
}
5、那么接下来就好办了
依然和上面介绍的一样, 在 dispatchKeyEvent() 方法中判断如果 event.getKeyCode() == KeyEvent.KEYCODE_BACK ,
表示点击了返回按钮,我们可以执行自己的逻辑。
在执行我们自定义的逻辑后,调用我们监听的 Activity 对象,再次调用其dispatchKeyEvent() 方法,及不要直接返回true 或是 fale
而是 : return activity.dispatchKeyEvent(event);
因为我们监听的是SDK中的 Activity ,那么可能其还有一些逻辑需要处理,我们不能消耗这个事件。
其他的方法同样需要调用 Activity 中相对应的方法,防止SDK无法执行SDK中的逻辑。
6、其他方法
在 dispatchKeyEvent() 方法中我们除了可以监听返回键之外还可以监听很多物理按键。
其中我们经常关注的除了返回键还有菜单键(Menu)以及Home键。
但是在测试过程中,我发现有的手机可以拦截 Menu 菜单键,有的手机就不可以。
这个具体可能要看系统了。例如华为的某款机型就可以。但是在红米Note2上是不好用的。
至于Home键利用这种方法是监听不到的。
其中还有一个方法值得说,那就是 dispatchTouchEvent() 方法。如果我们直接返回 true 。
消耗了此事件那么就屏蔽所有的Activity 上面的事件。
三 、OK,Android 监听应用集成的SDK中的 Activity,就说这么多。