Android Hook拦截onConfigurationChanged

前言

  • 目标

有些时候Android应用在没有Activity实例的情况下,需要回调Activity生命周期及权限、ActivityResult以及屏幕旋转事件。生命周期回调可以从Application着手,可以采用如下方式

Application application = activity.getApplication();
ActivityLifecycleCallbacksImpl activityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl();
application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks);

public class ActivityLifecycleCallbacksImpl implements Application.ActivityLifecycleCallbacks {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }
}

屏幕旋转事件如何捕获呢,这就是我要跟大家分享的内容了。

实现

1.了解onConfigurationChanged实现过程
通过查看源码发现,屏幕旋转的实现主要涉及Activity、ActivityThread两个类。Activity中onConfigurationChanged方法如下

    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
        mCalled = true;

        mFragments.dispatchConfigurationChanged(newConfig);

        if (mWindow != null) {
            // Pass the configuration changed event to the window
            mWindow.onConfigurationChanged(newConfig);
        }

        if (mActionBar != null) {
            // Do this last; the action bar will need to access
            // view changes from above.
            mActionBar.onConfigurationChanged(newConfig);
        }
    }

那Activity中这个方法是谁来调用呢,在ActivityThread这个类中找到了结果

private Configuration performActivityConfigurationChanged(Activity activity,
            Configuration newConfig, Configuration amOverrideConfig, int displayId,
            boolean movedToDifferentDisplay) {
......
......
        if (shouldChangeConfig) {
            activity.mCalled = false;
            activity.onConfigurationChanged(configToReport);
            if (!activity.mCalled) {
                throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
                                " did not call through to super.onConfigurationChanged()");
            }
        }

        return configToReport;
    }

    @Override
    public void handleActivityConfigurationChanged(IBinder activityToken,
            Configuration overrideConfig, int displayId) {
......
......
if (callbacks != null) {
            final int N = callbacks.size();
            for (int i=0; i<N; i++) {
                ComponentCallbacks2 cb = callbacks.get(i);
                if (cb instanceof Activity) {
                    // If callback is an Activity - call corresponding method to consider override
                    // config and avoid onConfigurationChanged if it hasn't changed.
                    Activity a = (Activity) cb;
                    performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
                            config);
                } else if (!equivalent) {
                    performConfigurationChanged(cb, config);
                }
            }
        }
}

    @Override
    public void handleConfigurationChanged(Configuration config) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
        mCurDefaultDisplayDpi = config.densityDpi;
        mUpdatingSystemConfig = true;
        try {
            handleConfigurationChanged(config, null /* compat */);
        } finally {
            mUpdatingSystemConfig = false;
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }

private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
            Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
        r.tmpConfig.setTo(newBaseConfig);
        if (r.overrideConfig != null) {
            r.tmpConfig.updateFrom(r.overrideConfig);
        }
        final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
                r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
        freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
        return reportedConfig;
 }

private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
        ......
        ......
        if (callbacks != null) {
            final int N = callbacks.size();
            for (int i=0; i<N; i++) {
                ComponentCallbacks2 cb = callbacks.get(i);
                if (cb instanceof Activity) {
                    // If callback is an Activity - call corresponding method to consider override
                    // config and avoid onConfigurationChanged if it hasn't changed.
                    Activity a = (Activity) cb;
                    performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
                            config);
                } else if (!equivalent) {
                    performConfigurationChanged(cb, config);
                }
            }
        }
    }


class H extends Handler {
  ......
  ......
  public void handleMessage(Message msg) {
    ......
    ......

    case CONFIGURATION_CHANGED:
                    handleConfigurationChanged((Configuration) msg.obj);
                    break;
    ......
    }
  }
}
......
......
final H mH = new H();

2.通过上面的源码追溯,我们发现onConfigurationChanged(Configuration newConfig)方法是ActivityThread的Handler下触发的,OK,我们可以获取当前的ActivityThread,而后替换mH为自己的,这样我们就可以获取到系统下发的消息了。

public void process(Activity activity) {
        this.mActivity = activity;
        try {
            Handler mH = getHandlerFormActivityThread();
            Field mCallBackField = Handler.class.getDeclaredField("mCallback");
            mCallBackField.setAccessible(true);
            mCallBackField.set(mH, mHCallback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 获取ActivityThread.class
    private Class<?> getActivityThreadClazz() throws ClassNotFoundException {
        return Class.forName("android.app.ActivityThread");
    }

    // 先获取到当前的ActivityThread对象
    private Object getCurrentActivityThread() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Field currentActivityThreadField = getActivityThreadClazz().getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        return currentActivityThreadField.get(null);
    }

    // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
    private Handler getHandlerFormActivityThread() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Field mHField = getActivityThreadClazz().getDeclaredField("mH");
        mHField.setAccessible(true);
        return (Handler) mHField.get(getCurrentActivityThread());
    }

    private boolean isHostActivity() {
            ActivityManager am = (ActivityManager) mActivity.getSystemService(Context.ACTIVITY_SERVICE);
            ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
            Log.d(TAG, mActivity.getClass().getName() + "/" + cn.getClassName());
            return (mActivity.getClass().getName()).equals(cn.getClassName());
    }

实现Handler接收系统消息,拦截屏幕旋转事件。

private Handler.Callback mHCallback = new Handler.Callback() {
        private final static int CONFIGURATION_CHANGED      = 118; //CONFIGURATION_CHANGED

        @Override
        public boolean handleMessage(Message msg) {
            //过滤其他Activity消息
            if (!isHostActivity()) {
                return false;
            }
            if (msg.what == CONFIGURATION_CHANGED) {
                onConfigurationChanged(msg);
            } 
            return false;
        }
        private void onConfigurationChanged(Message msg) {
            ResultCallback.getInstance().onConfigurationChanged(mActivity, (Configuration) msg.obj);
        }
}

以上是通过Hook方式拦截到屏幕旋转事件,如果大家有更好的方法欢迎留言,后面我会把如果通过hook方式拦截onActivityResult和onRequestPermissionsResult分享出来。谢谢观看~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,635评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,543评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,083评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,640评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,640评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,262评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,833评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,736评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,280评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,369评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,503评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,870评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,340评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,460评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,909评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,512评论 2 359