IOS监测其他APP是否打开的思路

来源:互联网 发布:单片机与嵌入式的联系 编辑:程序博客网 时间:2024/04/27 12:52

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

之所以写这篇文章是因为碰到一个问题,因为最近要做一个app去鼓励用户下载其他的app,所以需要我们去监测用户是否下载了指定的软件并且运行试玩了,重点就是我们的软件在用户点击去appstore下载之后是在后台运行的,软件状态就是在后台运行情况下去监测其他app的安装运行,因为ios是沙盒运行,所以自己的app去检测其他软件肯定是被苹果禁止的,现在总结下曲线救国的一点思路。

一、获取所有已经安装的软件

可以参考这个文章《ios开发之UIDevice使用总结》中的方案,可以获取到手机中所有已经安装的软件。

通过这个方法可以获得所有已经安装的软件和路径

  1. //这个头文件记得包含,不然有可能会报objc_getClass没定义的错误
  2. #include <objc/runtime.h>
  3. Class LSApplicationWorkspace_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下载:

https://github.com/nst/iOS-Runtime-Headers

https://github.com/nst/RuntimeBrowser/

但是看这个库下面的介绍,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.     Class FBProcessManager = NSClassFromString(@"FBProcessManager");
  6.     id manager = [FBProcessManager valueForKey:@"sharedInstance"];

在ios9上面,我用ipad和iphone测试,读取库的log一直失败,所以估计这个库也不行了

通过文件读写来判断(ios10已失效)

这个方法是使用IOKIT.Framework这个库,来读取软件是否读取了资源来判断,参考问答《ios9屏蔽了sysctl方法,现在有什么办法可以获取进程呢?》。

这个库有一个demo,demo的github地址:https://github.com/matthiasgasser/IOKitBrowser

demo说明地址:http://www.lyonanderson.org/blog/2014/02/12/ios-iokit-browser/

通过这个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 = +new Date();
  8. window.setTimeout(function(){
  9.     document.body.removeChild(ifr);
  10.     //如果setTimeout 回调超过2500ms,则弹出下载
  11.     if( (+new Date()) - 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包的前提下,知道的最佳的方案了。

四、参考文章

  • ios开发之UIDevice使用总结

  • IOS的软件之间的调用(URL Schemes)

  • iOS私有库的正确使用

  • ios9屏蔽了sysctl方法,现在有什么办法可以获取进程呢?

  • Is it possible to get information about all apps installed on iPhone?

  • 使用 Charles 抓取 App 网络请求

  • About Apple URL Schemes

  • Promoting Apps with Smart App Banners

  • iOS使用schema协议调起APP

  • iOS IOKit Browser

  • Mobile Safari调用本地App, 否则进入App Store下载

  • 从浏览器或者Webview 中唤醒APP

  • 使用通用链接(Universal Link)打通深度链接 - 适用iOS 9或者以上版本

0 0