iOS - App Extension 整體總結


 

一、App Extension的介紹

   App Extension可以讓你擴展你APP的自定義功能和內容,使用戶可以在與其他應用或者系統進行互動的時候去使用它app extension即為本文所說的extension。extension並不是一個獨立的app,它有一個包含在app bundle中的獨立bundle,extension的bundle后綴名是.appex。其生命周期也和普通app不同,這些后文將會詳述。extension不能單獨存在,必須有一個包含它的containing app。擴展(app Extension )是 iOS 8 中引入的一個非常重要的新特性

  extension的激活方式:不同的extension激活方式也不同,有的extension需要用戶 手動激活而有的可以在任何應用里被激活,比如:Today中的widget需要在Today中激活和關閉;Custom keyboard需要在設置中進行相關設置;Photo Editing需要在使用照片時在照片管理器中激活或關閉;Storage Provider可以在選擇文件時出現;Share和Action可以在任何應用里被激活,但前提是開發者需要設置Activation Rules,以確定extension需要在合適出現。

  我們平時看到的Widget、微信和QQ的share等等,都是App Extension,下圖是一些例子:

                                     

 

 

幾個關鍵詞
 
extension point
系統中支持extension的區域叫做extension point(擴展點),extension的類別也是據此區分的,iOS上共有Today、Share、Action、Photo Editing、Storage Provider、Custom keyboard幾種,其中Today中的extension又被稱為widget。
每種extension point的使用方式和適合干的活都不一樣,因此不存在通用的extension。
 
containing app
containing app我們可以把它理解為容器App,就像上圖的微信share extension,容器app就是微信。
盡管蘋果開放了extension,但是在iOS中extension並不能單獨存在,要想提交到AppStore,必須將extension包含在一個app中提交,並且app的實現部分不能為空,這個包含extension的app就叫containing app。
extension會隨着containing app的安裝而安裝,同時隨着containing app的卸載而卸載。
 
host app
我們可以把它理解為宿主的App,能夠調起extension的app被稱為host app,比如:Safari app 里面網頁分享到微信,  Safari就是 host app ; widget的host app就是Today。

 

 

二、 Extension的種類

 

我們可以在Xcode的File--->New--->Target里面看到不同平台的Extension,包括iOS、watchOS、tvOS、macOS等等。這里主要介紹iOS,主要包括以下幾種Extensions:

也可直接參考官方文檔 

iOS 8 系統有 6 個支持擴展的系統區域,分別是 Today 、 Share 、 Action 、 Photo Editing 、 Storage Provider 、 Custom keyboard 。支持擴展的系統區域也被稱為擴展點。

Today Widget

Today擴展 可以快速獲取更新或者在通知中心的近日視圖中執行一項快速任務。對於賽事比分,股票、天氣、快遞這類需要實時獲取的信息,可以在通知中心的Today 視圖中創建一個 Today 擴展實現。 Today 擴展又稱為 Widget 。

Today擴展效果圖
 

Share Extension

分享擴展,發布一個共享網站或者與其他應用共享內容,在 iOS 8 之前,用戶只有 Facebook,Twitter 等有限的幾個分享選項可以選擇。在 iOS 8 中,開發者可以創建自定義的分享選項。

Share擴展效果圖

Action Extension

動作擴展,在另一個應用程序的上下文中操作或者查看內容, 在所有支持的擴展點中擴展性最強的一個。它可以實現轉換另一個 app 上下文中的內容。蘋果在 WWDC 大會上演示了一個 Bing 翻譯動作擴展,它可以將在 Safari 中選中的文本翻譯成不同的語言。

      技術分享
Action擴展效果圖
注意⚠️: Host App(照片、Safari、郵件、語音等)分享菜單第一行是: Share Extension ;第二行是:  Action Extension;

Photo Editing

圖片編輯擴展,在照片app中編輯照片或者視頻,在 iOS 8 之前,如果你想為你的照片添加一個特殊的濾鏡,你需要進入第三方 app 中,這個過程是相當繁瑣的。在 iOS 8 中,你可以直接在 Photos 中使用第三方 app ,如 Instagram , VSCO cam 、 Aviary 提供的 Photo Editing 擴展完成對圖片的編輯,而無需離開當前的 app 。

 
Photo Editing擴展效果圖

Document Provider

Document Provider 讓跨多個文件存儲服務之間的管理變得更簡單。類似 Dropbox 、 Google Drive 等存儲提供商通過在 iOS 8 中提供一個 Document Provider 擴展, app 直接可以使用這些擴展檢索和存儲文件而不再需要創建不必要的拷貝。

 
Document Provider擴展效果圖

Custom Keyboard

鍵盤擴展,例如第三方的鍵盤,搜狗輸入法,百度輸入法等。蘋果公司在 2007 年率先推出了觸摸屏鍵盤,但一直沒多大改進。在這一方面, Android 則將鍵盤權限開放給了第三方開發者,所以出現了許多像 Swype , SwiftKey 等優秀的鍵盤輸入法。在 iOS 8 中,蘋果終於將鍵盤權限開發給了第三方開發者,自定義鍵盤輸入法可以讓用戶在整個系統范圍內使用。

 
Custom Keyboard擴展效果圖
 

以下是iOS 9和之后中新增擴展

1.Audio Unit Extension:音頻單元擴展

2.Broadcast UI Extension:廣播UI 擴展

3.Broadcast Upload Extension:廣播上傳擴展

4.Call Directory Extension:呼叫目錄擴展

5.Content Blocker Extension:內容攔截器擴展

6.iMessage Extension:消息的擴展

7.Intents Extension:Intents擴展

8.Intents UI Extension:Intents UI擴展

9.Notification Content Extension:通知內容擴展

10.Notification Service Extension:通知服務擴展

11.Shared Links Extension:分享鏈接擴展

12.Spotlight Index Extension:Spotlight 索引擴展

13.Sticker Pack Extension:貼紙包擴展

 

三、App Extensions的生命周期

以下是蘋果官方提供的圖片:

 

 

1.用戶選擇要使用的App extension

2.系統啟動App Extension

3.App Extension 代碼運行

4.運行完之后系統kill掉App Extension

這就是App Extension的生命周期,舉個例子:

一個Share Extension,在圖庫里面你選擇了一張圖片,然后點擊分享,選擇你的Share Extension(第一步),此時系統會啟動你的Share Extension(第二步)。然后你將選擇的圖片分享到指定的程序(例如微信的發送給朋友)(第三步)。接下來分享頁面關閉,系統kill掉了Share Extension。

 

四、App Extension的通信方式

App Extension主要的通信是和他的host app 

Host app (如微信)  ; App extension (safari里面分享點擊出來的微信extension);Containing app (safari)

這個展示的就是正在運行的App Extension、host app和containing app之間的關系。可以看出:Containing App和app Extension並沒有直接的溝通。甚至有的時候Containing app可以不運行,而App Extension直接運行。Containing app和Host app沒有任何的溝通。

在一個典型的request/response中,系統打開代表host app(圖庫)的extension(微信分享的share extension),把host app提供的數據(圖片和選擇的好友)輸送到extension的context,然后extension展示界面,提供一些功能任務(例如微信的分享到朋友)。

 

還有一種是app extension可以直接和他的containing app溝通:

例如Today Widget,可以直接告訴系統打開他的Containing app,只需要調用NSExtensionContext的openURL:CompletionHandler:方法即可。

app extension和containing app可以共同讀寫一個被稱為Shared resources的存儲區域,這是通過App Groups實現的。

 

這里需要注意的是:

只有 Today Extension 才支持通過調用 -[ NSExtensionContext  openURL:completionHandler:] 訪問 URL Scheme 鏈接打開 Containing App。
在 iOS 8.3 之前,Share/Action Extension 要想實現  URL Scheme,可通過創建一個 Sink UIWebVew  所謂“Sink”是指隱而不顯,例如frame=CGRectZero)對 URL 進行  loadRequest 實現曲線救國。但是在 iOS 8.3 之后蘋果在系統層面和評審階段都 槍斃了這種違規的做法。
根據蘋果官方對 Share Extension 的 原教旨,其被建議用於 Facebook/WeiBo 那種社交分享(social sharing websites)或 WeChat/WunderList 那種上傳會話服務(upload services)場景,被設定(限定)在 Host App 彈出的模態窗口中輕量交互完成分享,不建議(禁止)調起 Containing App 這種大動作來完成分享任務!

但是如果你不怕蘋果審核不通過 或者 下架 也可以用以下方法打開:

 

//    UIWebView *webView = [[UIWebView alloc]init];
//    webView.hidden = YES;
//    NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:@"weixin://"]];
//    [webView loadRequest:request];
//    [self.view addSubview:webView]; //這個方法已經打不開了
    
    UIResponder* responder = self;
    while ((responder = [responder nextResponder]) != nil) {
        if ([responder respondsToSelector:@selector(openURL:)] == YES) {
            [responder performSelector:@selector(openURL:) withObject:[NSURL URLWithString:[NSString stringWithFormat:@"weixin://"]]];
        }
    }

 

stackoverflow 里面有關於 Share Extension to open containing app 相關的解答 

 

五、App Groups 實現數據共享

這是iOS8新開放的功能,在OS X上早就可用了。它主要用於同一group下的app共享同一份讀寫空間,以實現數據共享。
extension和containing app共同讀寫一份數據是很合理的需求,比如系統的股市應用,widget和app中都需要展示幾個公司的股票數據,這就可以通過App Groups實現。
 
功能開啟 
為了便於后續操作,請先確保你的開發者賬號在Xcode上處於登錄狀態。(或者在開發者賬號中先創建好app group id )
 
一、在app中開啟
App Groups位於:
  1. TARGETS-->AppExtensionDemo-->Capabilities-->App Groups 
找到以后,將App Groups右上角的開關打開,然后選擇添加groups,比如我的是group.wangzz,當然這是為了測試隨便起得名字,正規點得命名規則應該是:group.com.company.app。
 
添加成功以后如下圖所示:
 
 
二、在extension中也要開啟
我創建的是widget,target名稱為TodayExtension,對應的App Groups位於:
  1. TARGETS-->TodayExtension-->Capabilities-->App Groups 
開啟方式和app中一樣,需要注意的是必須保證這里地App Groups名稱和app中的相同,即為group.wangzz。
 

三、extension和containing app數據共享

App Groups給我們提供了同一group內app可以共同讀寫的區域,可以通過以下方式實現數據共享:
 
3.1 通過NSUserDefaults共享數據
存數據
通過以下方式向NSUserDefaults中保存數據:
- (void)saveTextByNSUserDefaults { 

    NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; 

    [shared setObject:_textField.text forKey:@"wangzz"]; 

    [shared synchronize]; 

} 

 

 

需要注意的是:
 
1.保存數據的時候必須指明group id;
 
2.而且要注意NSUserDefaults能夠處理的數據只能是可plist化的對象,詳情見Property List Programming Guide。
 
3.為了防止出現數據同步問題,不要忘記調用[shared synchronize];
 
讀數據
對應的讀取數據方式:
- (NSString *)readDataFromNSUserDefaults { 
    NSUserDefaults *shared = [[NSUserDefaults alloc] initWithSuiteName:@"group.wangzz"]; 

    NSString *value = [shared valueForKey:@"wangzz"]; 

 return value; 
} 

 

 

3.2 通過NSFileManager共享數據
 
NSFileManager在iOS7提供了containerURLForSecurityApplicationGroupIdentifier方法,可以用來實現app group共享數據。
 
保存數據

 

- (BOOL)saveTextByNSFileManager { 

    NSError *err = nil; 
    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 

    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; 

    NSString *value = _textField.text; 
    BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err]; 

    if (!result) { 

        NSLog(@"%@",err); 
    } else { 

        NSLog(@"save value:%@ success.",value); 
    } 

    return result; 

} 

 

讀數據
- (NSString *)readTextByNSFileManager { 

    NSError *err = nil; 

    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 

    containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/good"]; 

    NSString *value = [NSString stringWithContentsOfURL:containerURL encoding:NSUTF8StringEncoding error:&err]; 

   return value; 
} 

 

 

 
在這里我試着保存和讀取的是字符串數據,但讀寫SQlite我相信也是沒問題的。
 
數據同步
兩個應用共同讀取同一份數據,就會引發數據同步問題。WWDC2014的視頻中建議使用NSFileCoordination實現普通文件的讀寫同步,而數據庫可以使用CoreData,Sqlite也支持同步。
 
四、extension和containing app代碼共享
 
和數據共享類似,extension和containing app很自然地會有一些業務邏輯上可以共用的代碼,這時可以通過iOS8中剛開放使用的framework實現。蘋果在 App Extension Programming Guide中是這樣描述的:
 
In iOS 8.0 and later, you can use an embedded framework to share code between your extension and its containing app. For example, if you develop image-processing code that you want both your Photo Editing extension and its containing app to share, you can put the code into a framework and embed it in both targets.
 
即將framework分別嵌入到extension和containing app的target中實現代碼共享。(意思是:需要分別要將framework分別copy到extension和containing app的main bundle中。) 也就是說代碼是不可以共享的 要想用containing app的代碼只有 在copy 一份。
 
 
以下自己思考的作為參考:
參考extension和containing app數據共享,我試想能不能將framework只保存一份放在App Groups區域?
4.1 copy framework到App Groups
 
在app首次啟動的時候將framework放到App Groups區域:
- (BOOL)copyFrameworkFromMainBundleToAppGroup { 

    NSFileManager *manager = [NSFileManager defaultManager]; 

    NSError *err = nil; 

    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 

    NSString *sorPath = [NSString stringWithFormat:@"%@/Dylib.framework",[[NSBundle mainBundle] bundlePath]]; 

    NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; 

    BOOL removeResult = [manager removeItemAtPath:desPath error:&err]; 

    if (!removeResult) { 
        NSLog(@"%@",err); 
    } else { 
        NSLog(@"remove success."); 
    } 

    BOOL copyResult = [[NSFileManager defaultManager] copyItemAtPath:sorPath toPath:desPath error:&err]; 

    if (!copyResult) { 
        NSLog(@"%@",err); 
    } else { 
        NSLog(@"copy success."); 
    } 

    return copyResult; 

} 
 
4.2 使用framework:
- (BOOL)loadFrameworkInAppGroup 
{ 

    NSError *err = nil; 

    NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.wangzz"]; 

    NSString *desPath = [NSString stringWithFormat:@"%@/Library/Caches/Dylib.framework",containerURL.path]; 

    NSBundle *bundle = [NSBundle bundleWithPath:desPath]; 

    BOOL result = [bundle loadAndReturnError:&err]; 

    if (result) { 

        Class root = NSClassFromString(@"Person"); 

        if (root) { 

            Person *person = [[root alloc] init]; 

            if (person) { 

                [person run]; 

            } 

        } 

    } else { 

        NSLog(@"%@",err); 

    } 

return result; 

}      
經過測試,竟然能夠加載成功。
 
需要說明的是,這里只是說那么用是可以成功加載framework,但還面臨不少問題,比如如果用戶在啟動app之前去使用extension,這時framework還沒有copy過去,怎么處理;另外iOS的機制或者蘋果的審核是否允許這樣使用等。
 
在一切確定下來之前還是乖乖按文檔中的方式使用吧。
 

六、在App Extension中不可以做的事情

一個app extension不能有以下情況:

1.訪問sharedApplication對象。因此不能使用任何該對象的防范

2.使用任何標記NS_EXTENSION_UNAVAILABLE宏的API,或者類似的宏,或者不可用framework里面的API,例如HealthKit framework不能用於app extensions

3.iOS設備訪問相機或者麥克風(iMessage app可以訪問這些資源,只要在Info.plist里面進行配置使用描述即可)

4.運行一個長時間的后台任務(根據不同平台而異)

5.使用AirDrop接收數據

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM