1. 程式人生 > >IOS監測其他APP是否開啟的思路

IOS監測其他APP是否開啟的思路

原地址:http://www.hudongdong.com/talk/369.html

之所以寫這篇文章是因為碰到一個問題,因為最近要做一個app去鼓勵使用者下載其他的app,所以需要我們去監測使用者是否下載了指定的軟體並且執行試玩了,重點就是我們的軟體在使用者點選去appstore下載之後是在後臺執行的,軟體狀態就是在後臺執行情況下去監測其他app的安裝執行,因為ios是沙盒執行,所以自己的app去檢測其他軟體肯定是被蘋果禁止的,現在總結下曲線救國的一點思路。

一、獲取所有已經安裝的軟體

可以參考這個文章《ios開發之UIDevice使用總結》中的方案,可以獲取到手機中所有已經安裝的軟體。

通過這個方法可以獲得所有已經安裝的軟體和路徑

  1. //這個標頭檔案記得包含,不然有可能會報objc_getClass沒定義的錯誤
  2. #include<objc/runtime.h>
  3. ClassLSApplicationWorkspace_class= objc_getClass("LSApplicationWorkspace");
  4. SEL selector=NSSelectorFromString(@"defaultWorkspace");
  5. NSObject* workspace =[LSApplicationWorkspace_class performSelector:selector];
  6. SEL selectorALL =NSSelectorFromString
    (@"allApplications");
  7. NSLog(@"apps: %@",[workspace performSelector:selectorALL]);

得到的結果是每個軟體的budleid和路徑

  1. "<LSApplicationProxy: 0x16d5fbc0> com.tencent.xin <file:///private/var/containers/Bundle/Application/81FEB796-5EF7-4084-B293-54487A39BA1B/WeChat.app>",
  2. "<LSApplicationProxy: 0x16d602b0> com.tencent.mipadqq <file:///private/var/containers/Bundle/Application/2D3A6D07-903E-4C60-9D77-C834B5C7C201/IPadQQ.app>"
    ,
  3. "<LSApplicationProxy: 0x16d60880> com.apple.MobileReplayer <file:///Developer/Applications/MobileReplayer.app>",
  4. "<LSApplicationProxy: 0x16d60b40> com.jdnet.kuaifa <file:///private/var/containers/Bundle/Application/479A4DBD-570E-46D0-ACBD-1B25A63C1ADD/HBuilder.app>",
  5. "<LSApplicationProxy: 0x16d611b0> damon.testsssss <file:///private/var/containers/Bundle/Application/5E98B067-354C-4B59-8D0D-DF17E4305CB4/testsssss.app>",
  6. "<LSApplicationProxy: 0x16d61640> com.electriclabs.IOKitBrowser <file:///private/var/containers/Bundle/Application/A748EDB5-9115-4CAA-B92C-AF0BC50F6A92/IOKitBrowser.app>"

但是這個只是得到了所有已經安裝的軟體

二、判斷是否有指定的軟體

如果想獲取某個軟體安裝沒有,可以參考這個文章《IOS的軟體之間的呼叫(URL Schemes)》中的,通過呼叫scheme的來獲取又沒有

  1. NSString* url =@"damon://ssss";
  2. if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]){
  3. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
  4. }

如果canOpenURL返回值為true,那麼就是安裝了這個軟體,但是前提是需要知道軟體的scheme,否則這個方法是不可以的,並不是每個軟體都有scheme,需要和他們的開發確認。測試了下,如果你的app在後臺執行狀態下去呼叫其他app,比如微信,那麼canOpenURL會在有微信的情況下立馬返回值為true,但是openURL並不會返回true,只有當你的app轉為活躍狀態才可以,就是隻有當你的app從後臺切換到前臺,oepnURL才有效。

總結

通過第一步和第二步可以知道軟體的安裝與否,如果之前沒有,後面有了就知道是新安裝的,如果沒有,就是還沒有安裝,如果原來就有了,那就是老軟體了,就是使用者之前下載過的軟體。

三、獲取軟體是否已經執行

這個是一個難點,搜尋了很多都沒有解決方案,這個也是我們軟體最重要的一步判斷,當然如果對方app在啟動時可以自己呼叫我們的app,或者給我們伺服器傳送一個請求,那是最簡單的,但是現在就是想在不修改對方軟體包的前提下去獲得軟體執行狀況,所以這個有點蛋疼。

所以思路是這樣的

1、通過軟體後臺的程序來判斷

2、通過私有庫來判斷

3、通過檔案讀寫來判斷

4、通過網路請求來判斷

5、通過網頁呼叫來判斷

系統在ios9.0以下可以獲得程序(只適合ios9.0以下)

如果手機的系統在ios9.0以下,那麼可以參考這個文章《ios開發之UIDevice使用總結》裡面,通過sys/sysctl.h這系統方法來獲取軟體的程序,從而得到這個軟體是否執行,但是現在ios10.2都發布了,所以如果只想讓軟體給ios9.0以下的使用者用不現實,所以這個方案是有缺陷的。

通過私有庫來判斷(ios9測試無效果)

網上有一個使用FrontBoard.framework這個庫的方案《iOS私有庫的正確使用》,這個庫的路徑在/System/Library/PrivateFrameworks/FrontBoard.framework/FrontBoard這個,但是這個庫並沒有,所以在github上搜了下,在github上面搜尋到了

Github下載:

但是看這個庫下面的介紹,ios8.0之上好像也不支援了,通過呼叫發現讀取不到這個庫

  1. NSBundle*=[NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/FrontBoard.framework/FrontBoard"];
  2. //    NSBundle *b = [NSBundle bundleWithPath:@"/Users/damon/github/iOS-Runtime-Headers/PrivateFrameworks/FrontBoard.framework/FrontBoard"];
  3.     BOOL success =[b load];
  4. NSLog(@"%d",success);
  5. ClassFBProcessManager=NSClassFromString(@"FBProcessManager");
  6.     id manager =[FBProcessManager valueForKey:@"sharedInstance"];

在ios9上面,我用ipad和iphone測試,讀取庫的log一直失敗,所以估計這個庫也不行了

通過檔案讀寫來判斷(ios10已失效)

這個方法是使用IOKIT.Framework這個庫,來讀取軟體是否讀取了資源來判斷,參考問答《ios9遮蔽了sysctl方法,現在有什麼辦法可以獲取程序呢?》。

通過這個demo可以獲取到後臺有操作的程式的程序pid和工程的target名字

6219a0003af3d7bba49bdfa3a358a1e2194c5c32.png

但是網友測試

既然是IOKit ,我推斷目標app必須要有IO動作才會被這個IOKit掃出來。拿一個空殼APP做測試,發現:

1。進行寫檔案操作。 結果 : 掃不出。

2。進行讀檔案操作。 結果 : 掃不出。

3。載入一個webview。 結果 : 掃出。

4。載入一個UIImageView並賦上圖。 結果 : 掃出。

我試了下,使用[[NSUserDefaults standardUserDefaults] setObject:@"sss" forKey:@"sss"];存資料也讀取不到,給button賦值等,都不行,所以這個雖然可以掃出一部分,但是還是有侷限的。

通過網路請求(僅限於思路)

這個只是我在想,但是並沒有實施方案,我的思路就是能不能抓包來獲取哪個app傳送了資料請求,從而判斷這個app在後臺執行,但是這個我現在也沒有想好怎麼做,僅僅是一個思路,但是根據實際情況來看,好像單從網路請求來說是判斷不出來哪個請求的。

通過網頁呼叫來判斷

網頁呼叫這個方法就是通過oc去呼叫html的網頁,通過網頁邏輯來實現呼叫軟體的方案,

有用一個a標籤同樣跳轉app的scehemes方式去手動呼叫,但是這種手動呼叫是要使用者點選的,不是去自動開啟的,所以不符合要求。

  1. <!--開啟考拉APP -->
  2. <a href="intent://www.kaola.com/product/8342.html#Intent;scheme=kaola;package=com.kaola;S.browser_fallback_url=http%3A%2F%2Fapp.kaola.com;end">開啟APP</a>

使用蘋果官方的在meta資訊中新增欄位也是需要使用者手動點選,並且僅限於safari瀏覽器,也是不符合要求的

  1. meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"

所以大部分邏輯都是類似於這樣呼叫

  1. window.location ='weixin://';
  2. setTimeout(function(){
  3.     window.location ='itms-apps://itunes.apple.com/cn/app/nuan-dao/id222222?mt=8'
  4. },30);

通過app的schemes碼去呼叫,或者使用frame去呼叫app

  1. //建立一個隱藏的iframe
  2. var ifr = document.createElement('iframe');
  3. ifr.src ='com.baidu.tieba://';
  4. ifr.style.display ='none';
  5. document.body.appendChild(ifr);
  6. //記錄喚醒時間
  7. var openTime =+newDate();
  8. window.setTimeout(function(){
  9.     document.body.removeChild(ifr);
  10. //如果setTimeout 回撥超過2500ms,則彈出下載
  11. if((+newDate())- openTime >2500){
  12.         window.location ='http://exam.com/xxxx.apk';
  13. }
  14. },2000)

但是經過測試這些方案在ios9.0之後,蘋果增加了確認機制,都會彈出一個提示框,只有使用者點選確認之後才會去開啟對應的app,否則就直接超時開啟失敗。

Universal Links方式

Universal Links是另外一個可以想到的解決方案,在apple的官方說明中,Universal Links是一種能夠方便的通過傳統 HTTP 連結啟動應用程式, 使用相同的網址開啟網站和App。通過一個通用的連結便可實現,當移動裝置裡面已經有了某個應用,在點選了這個連結後便可實現深度連結而直接進入應用內的某個特定頁面;如果手機內並沒有該應用,可開啟設定的網址(例如應用的落地下載頁面)。可以不用提供app的scheme碼,並且會自動判斷手機有沒有該軟體,一個連結就搞定了app和網站。

但是這個看了開發過程之後,發現還是不可行,有一步是在 Xcode的capabilities裡新增你的 APP 域名, 必須用applinks: 前置它,意思就是去修改別人的軟體,並不是只用修改自己的軟體就可以搞定的,其實已經算跑題了。

通過後臺不停呼叫scheme方式

我們的需求是判斷軟體是否開啟,但是並沒有強調執行期間的動作,考慮到判斷僅僅是打開了,所以想到了後臺定時呼叫scheme,定時判斷手機裡面有沒有這個指定的app,這樣的話只要我們的app檢測到手機存在其他app,就自動開啟他,開啟之後傳送請求標識已經開啟

這樣的話就是後臺執行時定時,比如2秒,去一直呼叫開啟,比如微信

  1. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"weixin://"]];

但是發現在後臺執行狀態下,這個函式一直返回的是false,一直開啟失敗

但是判斷有沒有微信這個函式卻在安裝微信之後,立馬返回true

  1. [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"weixin://"]]

但是這個canOpenUrl函式在ios9之上只能加50個白名單,所以也是很無奈

暫時採用的方案在前臺呼叫scheme方式

因為openURL方法在前臺呼叫才可以,所以最後我們還是決定做一個按鈕,去讓手動提交了。

因為如果在前臺自動呼叫的話,會在手機有該軟體的情況下自動開啟,比如使用者在用其他無關應用的時候,會自動切換出來,影響使用者體驗。

比如我們的軟體a讓使用者下載軟體b,使用者下載軟體b的同時在使用軟體c,但是一旦軟體b下載完畢,軟體a就自動檢測到b安裝了,直接開啟b,這樣使用者就從c切換出來了,所以不完美,但是這個是現在不去修改軟體b包的前提下,知道的最佳的方案了。

四、參考文章