App Extension

栏目: 服务器 · 发布时间: 5年前

内容简介:这里有一个来自官方的概念。App Extension 是用来扩展系统或者其他应用的功能的类似插件的东西。一般创建一个 App Extension 是为了针对性的完成一个任务,例如图片处理,或者分享。

这里有一个来自官方的概念。

App Extension App Extension

An app extension lets you extend custom functionality and content beyond your app and make it available to users while they’re interacting with other apps or the system.

App Extension 是用来扩展系统或者其他应用的功能的类似插件的东西。一般创建一个 App Extension 是为了针对性的完成一个任务,例如图片处理,或者分享。

另外,App Extension 的运行是线程独立的,这意味着无需打开主应用即可使用这部分分化出来的功能。

Extension Points

  • 标记系统的可扩展部分
  • 打包在系统框架中
  • 提供不同的 API 和政策

App Extension 的优点

这个问题更像是在问,为什么不直接使用原生应用。以苹果的角度来看,App Extension 相对臃肿的应用有什么区别?

1.更小的内存资源消耗,更少的启动时间。

开发商会希望的自己的应用常驻在内存中,以便为用户提供更快捷的服务,提高用户粘性,但是对于系统而言,减少这样的行为可以提高流畅度。

2.一个更重要的原因是增加进程数,创造隔绝环境。

如果多个应用使用到了相同 App Extension 的功能,就可以通过同时开启多个进程来为不同的应用提供服务(资源消耗少)。同时将隔绝 App Extension 之间的影响,避免因为一个崩溃而影响其他。

3.最重要的原因放在后面,更细致的权限划分

苹果希望开发者的应用更深层次的丰富系统内容,那不可避免的需要赋予应用更大的权限。以和业务息息相关的 Notification Service 来说,下发给应用的推送,经过这种类型的 Extension 可以做一些预先的处理,例如图片下载或者字段解码等,但这也意味着这个 Extension 需要在接收到推送后被系统唤醒。

如果唤醒的对象不是扩展而是应用,那除了资源方面的考虑,安全方面也比较难以防备。

App Extension 与 Host App 的通信

App Extension

Host App 请求 App Extension 这部分的内容,是通过系统来进行的(UIActivityViewController)。

App Extension 响应 Host App,会调用 beginRequestWithExtensionContext: 的代理方法。在这个代理里面可以获取到一个 NSExtensionContext 的实例。这个实例对象包括了 inputItems ,并可以进行 openURL: 操作。

激活规则匹配

Info.plist

<Key>NSExtension<Key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <key>NSExtensionActivationSupportsImageWithMaxCount</key>
                <integer>10</integer>
                <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
                <integer>1</integer>
                <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                <integer>1</integer>
            </dict>
        </dict>
    </dict>
复制代码

自定义规则

- NSExtensionContext
    - NSArray *inputItems;
        - NSExtensionItem *inputItem;
            - NSArray *attachments;
                - NSItemProvider *attachment;
                    - NSArray<NSString *> *registeredTypeIdentifiers;
复制代码

典型的过滤规则可以满足大部分的情况,而自定义规则可以满足更复杂更具体的过滤条件。

SUBQUERY (
    extensionItems,
    $extensionItem,
    SUBQUERY (
        $extensionItem.attachments,
        $attachment,
        ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image"
    ).@count == 10
    OR
    SUBQUERY (
        $extensionItem.attachments,
        $attachment,
        ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url"
    ).@count == 1
    OR
    SUBQUERY (
        $extensionItem.attachments,
        $attachment,
        ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" 
    ).@count == 1
).@count == 1
复制代码

App Extension 与 Containing App 的通信

App Extension

常规解决方案

App Extension 和其 Containing App 的沙盒是分开管理的,所以数据并不共享。按照苹果文档 Sharing Data with Your Containing App 中提供的方法,App Extension 和其 Containing App 要想共享数据,必须处于同一个 App Group 中,使用 NSUserDefaults 来存储和读取内容。

// Create and share access to an NSUserDefaults object
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: @"com.example.domain.MyShareExtension"];

// Use the shared user defaults object to update the user's account
[mySharedDefaults setObject:theAccountName forKey:@"lastAccountName"];
复制代码

无法满足实时性要求高的情况。

Open Url

作用范围很受局限。因为各种 App Extension 之中只有 Today Extension 有权限进行 Open Url 打开 Containing App。

WatchConnectivity (iOS 9.0+)

仅用于 WatchOS 与 iOS 之间的双向通信。

App Extension

允许在前台进行数据的实时传输。支持所有可序列化的数据(NSCoding Protocol)。

探究其他实时数据共享的方案

可以明白,在数据交流上,除了苹果所提供的 App Group 的数据共享方案,其他都是基于通用的进程通讯方式来实现的。

只讨论 iOS 的前提下,下面的几种都是可用的方案。

MacOS 的话还包括 Mach Port(iOS7 以后不可用),NSConnection 和 XPC 等。

File Coordination (iOS5+)

NSFileCoordinator 类协调在同一进程中的多个进程和对象之间读取和写入文件和目录。 只要另一个进程在磁盘上更改了文件,就可以通知对象。

- (instancetype)initWithFilePresenter:(id<NSFilePresenter>)filePresenterOrNil;
复制代码
// 写
// NSFileCoordinatorWritingOptions 对应文件操作时的 presenter 代理调用。
- (void)coordinateWritingItemAtURL:(NSURL *)url
                           options:(NSFileCoordinatorWritingOptions)options 
                             error:(NSError * _Nullable *)outError 
                        byAccessor:(void (^)(NSURL *newURL))writer;
                        
// 读
- (void)coordinateReadingItemAtURL:(NSURL *)url 
                           options:(NSFileCoordinatorReadingOptions)options 
                             error:(NSError * _Nullable *)outError 
                        byAccessor:(void (^)(NSURL *newURL))reader;
复制代码

它有助于确保进程在写入文件时获得对文件的独占访问权限。

这是苹果官方建议的方案。不过TN2408 文档同时也说到的是:

Important: When you create a shared container for use by an app extension and its containing app in iOS 8.0 or later, you are obliged to write to that container in a coordinated manner to avoid data corruption. However, you must not use file coordination APIs directly for this in iOS 8.1.x and earlier. If you use file coordination APIs directly to access a shared container from an extension in iOS 8.1.x and earlier, there are certain circumstances under which the file coordination machinery deadlocks.

谷歌翻译: 重要提示:当您在iOS 8.0或更高版本中创建供应用程序扩展及其包含应用程序使用的共享容器时,您必须以协调的方式写入该容器以避免数据损坏。 但是,在iOS 8.1.x及更早版本中,不得直接使用文件协调API。 如果直接使用文件协调API从iOS 8.1.x及更早版本的扩展访问共享容器,则在某些情况下文件协调机制会死锁。

在 iOS8.1.X 及其早前的 iOS 版本,使用这种方案可能会导致死锁,这是需要避免的。这个问题在 iOS8.2 版本得到修复。

notify.h

notify 是 iOS 一直都支持的进程通信机制,允许进程之间进行事件传递。就像 API 注释里面说的。

These routines allow processes to exchange stateless notification events. Processes post notifications to a single system-wide notification server, which then distributes notifications to client processes that have registered to receive those notifications, including processes run by other users.

谷歌翻译: 这些例程允许进程交换无状态通知事件。 进程将通知发布到单个系统范围的通知服务器,然后通知服务器将通知分发给已注册接收这些通知的客户端进程,包括其他用户运行的进程。

uint32_t notify_register_dispatch(const char *name, int *out_token, dispatch_queue_t queue, notify_handler_t handler)
复制代码

问题在于 notify 无法直接传递数据。所公布出来的唯一 Post Notification 的 API,只带了一个 name 的参数。

uint32_t notify_post(const char *name);
复制代码

所以需要配合前面的 NSUserDefaults 使用。进程的某一端修改数据后,随之发送对应的事件,接受端于是去取对应的共享数据并解析。伪造出实时数据共享。

附: MMWormhole 实现参考。

BSD Socket

使用 Client-Server 这种架构方式,可以通过让 Containing App 充当服务器,在磁盘上创建套接字并等待连接,其他主机(App Extension)通过连接到服务器充当客户端。

苹果的 DTS10003794-CH1-SECTION31 文档 也指出:

A UNIX domain socket appears as an item in the file system. The client and server usually hard code the path to this socket. You should use a path to an appropriate directory (like /var/tmp) and then give the socket a unique name within that directory. For example, Sample Code 'CFLocalServer' uses a path of /var/tmp/com.apple.dts.CFLocalServer/Socket.

谷歌翻译: UNIX域套接字显示为文件系统中的项。 客户端和服务器通常把路径硬编码到此套接字中。 您应该使用适当目录的路径(如/ var / tmp),然后在该目录中为套接字指定唯一的名称。 例如,示例代码'CFLocalServer'使用/var/tmp/com.apple.dts.CFLocalServer/Socket的路径。

这种 Socket 的建立,要求客户端和服务端拥有相同路径的读写权限。

而 App Extension 与 Containing App 正好同时拥有同一 App Group 下文件的访问和写入权限。

Server 监听

// 文件路径
const char *socket_path = ...

//AF_INET 表示IPv4网络协议
//AF_INET6 表示IPv6
//AF_UNIX 表示本地套接字(使用一个文件)
int fd = socket(AF_UNIX, 0, 0);

struct sockaddr addr;
addr.sun_family = AF_UNIX;
addr.sun_path = socket_path;

// 地址分配
bind(fd, (struct sockaddr *)&addr, sizeof(addr));

// kLLBSDServerConnectionsBacklog => 允许的连接上限
listen(fd, kLLBSDServerConnectionsBacklog);
复制代码

Server 接受连接

// DISPATCH_SOURCE_TYPE_READ => A dispatch source that monitors a file descriptor for pending
dispatch_source_t listeningSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)fd, 0, self.queue);
dispatch_source_set_event_handler(listeningSource, ^ {
    // 返回客户端的文件描述符
    int client_fd = accept(fd, &client_addr, &client_addrlen);
});
dispatch_resume(listeningSource);
复制代码

Client 请求连接

// 文件路径,同上
const char *socket_path = ...

int fd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
addr.sun_path = socket_path;

// 连接
connect(fd, (struct sockaddr *)&addr, sizeof(addr));
复制代码

Common 收发消息

// 与文件描述符关联的 I/O 通道
dispatch_io_t channel = dispatch_io_create(0, fd, self.queue, ^ (__unused int error) {}); 

// 收取消息
dispatch_io_read(channel, 0, SIZE_MAX, self.queue, ^ (bool done, dispatch_data_t data, int error) {
    // ...
});

// 发送消息
dispatch_io_write(channel, 0, message_data, self.queue, ^ (bool done, __unused dispatch_data_t data, int write_error) {
    // ...
});
复制代码

socket_path 的读写权限是连接得以建立的前提。

BSD Socket 这种连接的建立,简单猜测是通过监听同一文件的读写来实现的。这与前面的 notify 这种方案有些类似,不同的地方在于 notify 方案需要主动发送通知。

数据共享方案的总结

不同的场景使用不同的方案。

  • 没有实时性要求的,可以直接使用 NSUserDefault 或者 NSFileManager 方案。

  • 如果 >= iOS8.2 的,建议使用 NSFileCoordinator 方案。

BSD Socket 相对 notify 方案有更多的处理,但是维持连接的建立在 App Extension 与 Containing App 之间变的不那么容易。

如果考虑到应用本身是常驻内存的,或者是允许后台运行的媒体播放应用,那么 BSD Socket 方案会更合适。

而如果是普通应用,因为切换后台或者被系统干掉发生的行为比较频繁,那么使用 notify 来接受信息配合一部分的启动刷新操作会更合适。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ACM国际大学生程序设计竞赛题解

ACM国际大学生程序设计竞赛题解

赵端阳//袁鹤 / 电子工业 / 2010-6 / 38.00元

《ACM国际大学生程序设计竞赛题解(1)》可以作为高等院校有关专业的本科和大专学生参加国际大学生程序设计竞赛的辅导教材,或者作为高等院校数据结构、C/C++程序设计或算法设计与分析等相关课程的教学参考书。随着各大专院校参加ACM/ICPC热情的高涨,迫切需要有关介绍ACM国际大学生程序设计竞赛题解的书籍。《ACM国际大学生程序设计竞赛题解(1)》根据浙江大学在线题库的前80题,进行了解答(个别特别......一起来看看 《ACM国际大学生程序设计竞赛题解》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器