首页 文章

Google的自定义iOS键盘Gboard如何以编程方式解散最前端的应用程序?

提问于
浏览
35

谷歌的自定义iOS应用程序Gboard具有一个有趣的功能,无法使用iOS SDK中的公共API实现(从iOS 10开始) . I'd like to know exactly how Google accomplishes the task of programmatically popping back one app in the App Switching stack in Gboard.

自定义iOS键盘有两个主要组件:容器应用程序和键盘应用程序扩展 . 键盘应用程序扩展程序在单独的操作系统进程中运行,只要用户在手机上需要文本输入的任何应用程序中,该进程就会启动 .

这些是使用Gboard可以遵循的近似步骤,以查看以编程方式返回到以前的应用程序的效果:

  • 用户在iPhone上启动Apple Messages应用程序并点击文本字段以开始输入文本 .

  • Gboard键盘扩展程序已启动,用户可以看到Gboard自定义键盘(当他们仍在Apple Messages应用程序中时) .

  • 用户点击Gboard键盘扩展内的麦克风键进行语音到文本输入 .

  • Gboard使用custom url scheme启动Gboard容器应用程序 . Gboard键盘和Apple消息应用程序在App堆栈中向下推送一层,而Gboard容器应用程序现在是App堆栈中最前面的应用程序 . Gboard容器应用程序使用麦克风收听用户的语音并将其转换为放置在屏幕上的文本 .

  • 当用户对屏幕上显示的文本输入感到满意时,用户点按"Done"按钮 .

  • This is where the magic happens… as the text input screen is dismissed, the Gboard container app is also dismissed automatically. The Gboard container app goes away and is replaced by the Apple Messages app (sometimes the Gboard keyboard extension process is still alive, sometimes it is relaunched, and sometimes it needs to be re-launched manually by tapping inside a text field.) . How does Google accomplish this?

  • 最后,用户在文本输入字段中看到刚刚翻译的文本自动插入 . 据推测,Google通过Gboard容器应用和键盘扩展之间的sharing data实现了这一点 .

我假设谷歌正在使用私有API,通过探索状态栏's view hierarchy using Objective-C runtime introspection and somehow synthesizing tap events or calling an exposed target / action. I' ve探索了这一点,并且能够在状态栏中找到有趣的UIView子类,如UIStatusBarBreadcrumbItemView,其中包含UISystemNavigationAction的数组 . 我正在继续探索这些类,希望我能找到一些复制用户交互的方法 .

我知道使用私有API是一种很好的方式来让您的应用程序提交从App Store中被拒绝 - 这不是我想在答案中解决的问题 . 我主要想找到关于Google如何完成以编程方式弹出Gboard中应用程序切换堆栈中的一个应用程序的具体任务的具体答案 .

1 回答

  • 33

    您的猜测是正确的 - Gboard正在使用私有API来执行此操作 .

    ...虽然不是通过探索视图层次结构或事件注入 .

    当语音到文本操作完成后,我们可以从Xcode或Console中检查它调用 -[AVAudioSession setActive:withOptions:error:] 方法的syslog . 所以我对Gboard应用程序进行了逆向工程,并寻找与此相关的堆栈跟踪 .

    爬上调用堆栈,我们可以找到 -[GKBVoiceRecognitionViewController navigateBackToPreviousApp] 方法,并且......

    enter image description here

    …_systemNavigationAction?是的,绝对私密的API .

    由于 class_getInstanceVariable 是一个公共API而 "_systemNavigationAction" 是一个字符串文字,因此自动检查程序无法记录私有API使用情况,并且人工审阅者可能看不到"jump back to the previous app"行为有任何问题 . 或者可能是因为他们是谷歌而你不是......


    执行“跳回以前的应用程序”操作的实际代码如下所示:

    @import UIKit;
    @import ObjectiveC.runtime;
    
    @interface UISystemNavigationAction : NSObject
    @property(nonatomic, readonly, nonnull) NSArray<NSNumber*>* destinations;
    -(BOOL)sendResponseForDestination:(NSUInteger)destination;
    @end
    
    inline BOOL jumpBackToPreviousApp() {
        Ivar sysNavIvar = class_getInstanceVariable(UIApplication.class, "_systemNavigationAction");
        UIApplication* app = UIApplication.sharedApplication;
        UISystemNavigationAction* action = object_getIvar(app, sysNavIvar);
        if (!action) {
            return NO;
        }
        NSUInteger destination = action.destinations.firstObject.unsignedIntegerValue;
        return [action sendResponseForDestination:destination];
    }
    

    特别是, -sendResponseForDestination: 方法执行实际的"go back"操作 .

    (由于API没有记录,Gboard实际上使用的API不正确 . 他们使用了错误的签名 -(void)sendResponseForDestination:(id)destination . 但是,除了 1 以外的所有数字都会起作用,所以Google开发人员这次很幸运)

相关问题