之所以写这篇文章是因为碰到一个问题,因为最近要做一个app去鼓励用户下载其他的app,所以需要我们去监测用户是否下载了指定的软件并且运行试玩了,重点就是我们的软件在用户点击去appstore下载之后是在后台运行的,软件状态就是在后台运行情况下去监测其他app的安装运行,因为ios是沙盒运行,所以自己的app去检测其他软件肯定是被苹果禁止的,现在总结下曲线救国的一点思路。
一、获取所有已经安装的软件
可以参考这个文章《ios开发之UIDevice使用总结》中的方案,可以获取到手机中所有已经安装的软件。
通过这个方法可以获得所有已经安装的软件和路径
//这个头文件记得包含,不然有可能会报objc_getClass没定义的错误 #include <objc/runtime.h> Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace"); SEL selector=NSSelectorFromString(@"defaultWorkspace"); NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector]; SEL selectorALL = NSSelectorFromString(@"allApplications"); NSLog(@"apps: %@", [workspace performSelector:selectorALL]);
得到的结果是每个软件的budleid和路径
"<LSApplicationProxy: 0x16d5fbc0> com.tencent.xin <file:///private/var/containers/Bundle/Application/81FEB796-5EF7-4084-B293-54487A39BA1B/WeChat.app>", "<LSApplicationProxy: 0x16d602b0> com.tencent.mipadqq <file:///private/var/containers/Bundle/Application/2D3A6D07-903E-4C60-9D77-C834B5C7C201/IPadQQ.app>", "<LSApplicationProxy: 0x16d60880> com.apple.MobileReplayer <file:///Developer/Applications/MobileReplayer.app>", "<LSApplicationProxy: 0x16d60b40> com.jdnet.kuaifa <file:///private/var/containers/Bundle/Application/479A4DBD-570E-46D0-ACBD-1B25A63C1ADD/HBuilder.app>", "<LSApplicationProxy: 0x16d611b0> damon.testsssss <file:///private/var/containers/Bundle/Application/5E98B067-354C-4B59-8D0D-DF17E4305CB4/testsssss.app>", "<LSApplicationProxy: 0x16d61640> com.electriclabs.IOKitBrowser <file:///private/var/containers/Bundle/Application/A748EDB5-9115-4CAA-B92C-AF0BC50F6A92/IOKitBrowser.app>"
但是这个只是得到了所有已经安装的软件
二、判断是否有指定的软件
如果想获取某个软件安装没有,可以参考这个文章《IOS的软件之间的调用(URL Schemes)》中的,通过调用scheme的来获取又没有
NSString * url = @"damon://ssss"; if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; }
如果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之上好像也不支持了,通过调用发现读取不到这个库
NSBundle *b = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/FrontBoard.framework/FrontBoard"]; // NSBundle *b = [NSBundle bundleWithPath:@"/Users/damon/github/iOS-Runtime-Headers/PrivateFrameworks/FrontBoard.framework/FrontBoard"]; BOOL success = [b load]; NSLog(@"%d",success); Class FBProcessManager = NSClassFromString(@"FBProcessManager"); 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名字
但是网友测试
既然是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方式去手动调用,但是这种手动调用是要用户点击的,不是去自动打开的,所以不符合要求。
<!-- 打开考拉APP --> <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浏览器,也是不符合要求的
meta"apple-itunes-app"content"app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"
所以大部分逻辑都是类似于这样调用
window.location = 'weixin://'; setTimeout(function() { window.location = 'itms-apps://itunes.apple.com/cn/app/nuan-dao/id222222?mt=8' }, 30);
通过app的schemes码去调用,或者使用frame去调用app
//创建一个隐藏的iframe var ifr = document.createElement('iframe'); ifr.src = 'com.baidu.tieba://'; ifr.style.display = 'none'; document.body.appendChild(ifr); //记录唤醒时间 var openTime = +new Date(); window.setTimeout(function(){ document.body.removeChild(ifr); //如果setTimeout 回调超过2500ms,则弹出下载 if( (+new Date()) - openTime > 2500 ){ window.location = 'http://exam.com/xxxx.apk'; } },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秒,去一直调用打开,比如微信
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"weixin://"]];
但是发现在后台运行状态下,这个函数一直返回的是false,一直打开失败
但是判断有没有微信这个函数却在安装微信之后,立马返回true
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"weixin://"]]
但是这个canOpenUrl函数在ios9之上只能加50个白名单,所以也是很无奈
暂时采用的方案在前台调用scheme方式
因为openURL方法在前台调用才可以,所以最后我们还是决定做一个按钮,去让手动提交了。
因为如果在前台自动调用的话,会在手机有该软件的情况下自动打开,比如用户在用其他无关应用的时候,会自动切换出来,影响用户体验。
比如我们的软件a让用户下载软件b,用户下载软件b的同时在使用软件c,但是一旦软件b下载完毕,软件a就自动检测到b安装了,直接打开b,这样用户就从c切换出来了,所以不完美,但是这个是现在不去修改软件b包的前提下,知道的最佳的方案了。
四、参考文章
版权属于:东哥笔记 - DongGe.org
本文链接:https://dongge.org/blog/369.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!
40 条评论
楼主能获取到手机上 安装的其它应用的版本信息吗
不能了,已经没有再深入了
收藏了,赞赞。
这些是之前搜了好多网站找到的,希望对你有所帮助
博主,现在可以实现监测用户是否打开某个App吗?
可以联系我
不可以了
老哥,你可以实现监测用户是否打开某个App吗
之前用了按钮或者app保持在前台,使用penurl打开指定app,返回true的话就是正常打开了,但是如果用户不操作或者app没在前台在后台运行的话,openUrl会一直返回false
我们也在做类似应用,有几个问题想请教一下,我的QQ:1282990794,还望添加一下,谢谢。
现在公司年会,晚点聊
有一个app 进入后打开一个网址, 这个网址能在浏览器里实时监测到app是否在活跃状态或者已关闭状态提醒用户,请问大神这个应该怎么做到啊.
浏览器应该也是调用的打开接口,如果能打开就是活跃的,打不开就是关闭的,具体的这块我们没有实践过,不太清楚
好的谢谢了
现在iOS10 已经弃用了私有api的使用,请问 博主 还有没有其他 可以尝试的方法,根据bundleID打开其他APP,并且能顺序通过苹果的审核呢?
可以找我
现在已经在原公司辞职半年多了,这方面的没有再深入接触了,所以这个已经爱莫能助了
你好,我现在自己开发这样的平台,很需要你们这样的人才帮手。详细qq上请教你
十分感谢您的及时回复。
怎么能获取到该app是否曾经在这台手机上安装呢?(包含已经卸载的可能性)我发现有些试用平台的app可以做到获取历史安装的app,,可现在我只找到了获取当前手机已经安装的app list的办法,,还是蛋疼啊,,求楼主赐教
曾经安装的app列表我们没有试过,我们之前做的是只要没有安装就算没有
我测试了又几个平台可以获取得到历史安装记录,也不知道它们怎么实现的,昨天用look up去app store获取某个应用信息,里头也没有是否安装过的标志,暂时也想不到啥办法了,还有就是用canOpenUrl白名单50个太少了,而又没有办法网络更新info.plist的白名单列表,用私有API获取不能上架商店,楼主之前是不上架商店用企业证书来做的吗?
使用openurl,不用can openurl,我们是直接打开了,openurl没有数量限制
目前公司也在讨论这个话题,不知你们是否有好的解决方案
我们最后用的openurl直接循环计时调用的方案
可以详细讲讲解决方案吗?公司用的私有API获取第三方App的安装状态,貌似不是很准确,,,有些奇葩的bug
昨天回复的估计没有发出去,我们公司的最后采用的解决方案就是使用一个按钮,用户主动去点击,然后调用openurl,如果打不开就没有,打开的话就使用定时器定时检测
openurl用户手动点击调用之后,定时器定时调用openurl就不用经过用户点击了嘛》?还是说这个定时器不是定时调用openurl而是调用其它方法呢?
定时器是用来计时的,每次打开要用户点击的,后台调用openurl没用的
使用私有API来获取安装列表能通过store的审核吗?
就是在做交叉推广的一个sdk,想实现的功能是用户已经安装了我们的这个app的时候,就不再推广,不再为这个app浪费推广资源。刚开始的思路是获取已安装的app,但现在看来都行不通;然后转而通过共享数据,来记录安装,现在看来这个思路也不行。UIPasteboard ios7以上沙盒化了,对不同证书下的是无法做到共享。
UIPasteboard可以不同app间复制粘贴吧,或者你考虑服务端数据共享,不知道你需求是什么,昨天网站消耗资源太大,正在回复呢就被关了
刚开始我打算用数据共享来实现的,本来想用 UIPasteboard,但是发现不同证书下也沙盒化了。对于不同证书的情况下,有什么数据共享的好办法吗[可爱]
也有可能他们后台数据有对接,你说的这个我没注意过
那一般广告sdk是怎么做到安装了某个应用就不再推广这个应用呢。感觉有些头疼,scheme肯定是不行的。
不能,会被拒的
目前没有找到完美的解决方案~
canOpenUrl是50个白名单,但是直接用openURL是不需要加白名单的,通过bundle id 打开应用这个方法在ios8之后就已经失效了,使用私有 API 检测安装列表只能判断有没有安装,不能判断打开
iOS 9 之后 scheme 都要先注册,而且只有50个白名单,满足不了需求。目前只有使用私有 API 检测安装列表,判断是否安装,还有通过 bundle id 打开应用。
现在就是前端做个按钮,按钮的功能调用scheme,让用户手动调用,后台运行状态下自动调用scheme打开软件还没有找到方法