背景:最近公司在搞一个简易的分享到国外app的功能。起点涉及PC端浏览器,移动端浏览器(ios\aos)、移动端app。跳转时需要检测是否安装了应用,安装了之后直接跳转(不需要跳转到应用拉起分享页,进到应用就可以了),没有安装跳转到下载页面(后面因为技术原因改成不跳转下载页面,而是拉起动画)。
起点,拉起的自定义分享页productShare.html中,放一个按钮示例,其他按钮就不写上了
<div class="productShareItem" ng-click="goShareFor('facebook','#unInstall1')">
<div >
<img style="width: 67px;height: 50px" class="float-l" src="./images/facebook.png" />
<button id="unInstall1" type="button" class="btn btn-primary btn-cancel productShareUnInstall">未安装应用</button>
</div>
</div>
文档
https://faq.whatsapp.com/425247423114725/?locale=zh_CN&cms_platform=iphone
line
文档:里面提到line://
已经弃用
https://developers.line.biz/en/docs/messaging-api/using-line-url-scheme/
跳转功能,首先要解决URL Scheme,就是B - app设置好的跳转协议,A-app或者A-浏览器可以通过location跳到被跳转的B-app。但是记得注意一下app内过滤的协议,因为我不是安卓ios工程师,所以一开始点击faceBook的时候无法跳转,后面才知道需要app那边设置一些过滤协议。
$scope.goShare = function () {
SwalService.open('productShare.html', $scope, {
allowOutsideClick: false
});
}
$scope.goShareFor = function (type,unInstall_id) {
let shareUrl = "";
let failUrl = "";
if (isMobile){ //mobile
if (unInstall_id != undefined && !isAppIn()){
setTimeout(function () {
$(unInstall_id).css({'margin-top':'65px'});
$(unInstall_id).css({'visibility': 'visible'});
$(unInstall_id).animate({marginTop:'5px'},500);
setTimeout(function () {
$(unInstall_id).css({'visibility': 'hidden'});
}, 500)
}, 1000)
}
switch (type) {
case"facebook":
$scope.copyLink();
shareUrl ="fb://";
failUrl = "https://apps.apple.com/us/app/facebook/id284882215";
if ($scope.isIosIn()){
$scope.checkShareUrlForIos(shareUrl,failUrl);
}else {
$scope.checkShareUrlForAos(shareUrl,failUrl);
}
break
case"whatsapp":
$scope.copyLink();
shareUrl ="whatsapp://";
failUrl = "https://apps.apple.com/us/app/whatsapp-messenger/id310633997";
if ($scope.isIosIn()){
$scope.checkShareUrlForIos(shareUrl,failUrl);
}else {
$scope.checkShareUrlForAos(shareUrl,failUrl);
}
break
case"wechat":
$scope.copyLink();
shareUrl = "weixin://"
if ($scope.isIosIn()){
failUrl = "https://apps.apple.com/us/app/wechat/id414478124";
$scope.checkShareUrlForIos(shareUrl,failUrl);
}else {
failUrl = "https://sj.qq.com/appdetail/com.tencent.mm";
$scope.checkShareUrlForAos(shareUrl,failUrl);
}
break
case"line":
// https://line.me/R/msg/text/?(url)
$scope.copyLink();
shareUrl ="https://line.me/R/";
//如果用户安装了LINE,则当用户使用LINEURL方案访问URL时,将自动启动应用程序,
// 并将用户重定向到指定内容。如果用户未安装LINE,则行为取决于LINEURL方案的格式
failUrl = "https://apps.apple.com/us/app/line/id443904275";
if ($scope.isIosIn()){
$scope.checkShareUrlForIos(shareUrl,failUrl);
}else {
$scope.checkShareUrlForAos(shareUrl,failUrl);
}
break
case"messager":
$scope.copyLink();
shareUrl = "fb-messenger://"
failUrl = "https://apps.apple.com/us/app/messenger/id454638411";
if ($scope.isIosIn()){
$scope.checkShareUrlForIos(shareUrl,failUrl);
}else {
$scope.checkShareUrlForAos(shareUrl,failUrl);
}
break
case"email":
$scope.copyLink();
$scope.ShareEmail();
return;
break
case"copyLink":
$scope.copyLink();
$('#copSucc2').css({'margin-top':'65px'});
$("#copSucc2").animate({marginTop:'5px'},500);
$scope.showCopySuccess = true;
$timeout(function () {
$scope.showCopySuccess = false;
},500);
return;
break
}
}else { //web
switch (type) {
case"facebook":
$scope.copyLink();
shareUrl ="fb://";
failUrl = "https://www.facebook.com";
$scope.checkShareUrlForWeb(shareUrl,failUrl);
break
case"whatsapp":
$scope.copyLink();
shareUrl = "whatsapp://";
failUrl = "https://wa.whagsuwso.cc/whatsapp";
$scope.checkShareUrlForWeb(shareUrl,failUrl);
break
case"wechat":
$scope.copyLink();
shareUrl = "weixin://"
failUrl = "https://wx.qq.com/";
$scope.checkShareUrlForWeb(shareUrl,failUrl);
break
case"line":
// https://line.me/R/msg/text/?(url)
$scope.copyLink();
shareUrl = "line://"
failUrl = "https://access.line.me/oauth2/v2.1/login?returnUri=%2Foauth2%2Fv2.1%2Fauthorize%2Fconsent%3Fscope%3Dopenid%2Bprofile%2Bfriends%2Bgroups%2Btimeline.post%2Bmessage.write%26response_type%3Dcode%26redirect_uri%3Dhttps%253A%252F%252Fsocial-plugins.line.me%252Fwidget%252FloginCallback%253FreturnUrl%253Dhttps%25253A%25252F%25252Fsocial-plugins.line.me%25252Fwidget%25252Fclose%26state%3D9fec98665820574ebc349f47d089a6%26client_id%3D1446101138&loginChannelId=1446101138#/";
$scope.checkShareUrlForWeb(shareUrl,failUrl);
break
case"messager":
$scope.copyLink();
shareUrl = "messenger://"
failUrl = "https://www.facebook.com/messages";
$scope.checkShareUrlForWeb(shareUrl,failUrl);
break
case"email":
$scope.copyLink();
$scope.ShareEmail();
return;
break
case"copyLink":
$scope.copyLink();
$('#copSucc2').css({'margin-top':'65px'});
$("#copSucc2").animate({marginTop:'5px'},500);
$scope.showCopySuccess = true;
$timeout(function () {
$scope.showCopySuccess = false;
},500);
return;
break
}
}
}
$scope.checkShareUrlForAos = function (shareUrl,failUrl) {
var timeout, t = 1000, hasApp = true;
setTimeout(function () {
if (hasApp) {
} else {
// window.location = failUrl;
}
document.body.removeChild(ifr);
}, 2000)
var t1 = Date.now();
var ifr = document.createElement("iframe");
ifr.setAttribute('src', shareUrl);
ifr.setAttribute('style', 'display:none');
document.body.appendChild(ifr);
timeout = setTimeout(function () {
var t2 = Date.now();
if (!t1 || t2 - t1 < t + 100) {
hasApp = false;
}
}, t);
}
$scope.checkShareUrlForIos = function (shareUrl,failUrl) {
var timeout, t = 1000, hasApp = true;
setTimeout(function () {
if (hasApp) {
} else {
// window.location = failUrl;
}
}, 2000)
var t1 = Date.now();
window.location = shareUrl;
timeout = setTimeout(function () {
var t2 = Date.now();
if (!t1 || t2 - t1 < t + 100) {
hasApp = false;
}
}, t);
}
$scope.checkShareUrlForWeb = function (shareUrl,failUrl) {
$scope.callapp_PC(shareUrl,failUrl);
}
$scope.callapp_PC = function (url, failUrl) {
var t = setTimeout(function(){
window.open(failUrl);
}, 1000);
var inp = document.createElement("input");
inp.style.position = "absolute";
inp.style.clip = "rect(0, 0, 0, 0)";
// 出现下载框的时候往下跳的问题
inp.style.top = '0'
inp.style.left = '0'
function blur() {
window.clearTimeout(t);
}
inp.addEventListener("blur", blur); // 监听blur事件
document.body.appendChild(inp);
inp.focus(); // 获取焦点
setTimeout(function () { // 删除无用的标签
inp.removeEventListener("blur", blur);
document.body.removeChild(inp);
}, 1000);
//有客户端 如果有本地exe应用,就会弹框,input失去焦点,然后执行blur()事件 清空t定时器 - 删除无用的标签定时器
//无客户端 不会弹框,input也不会失去焦点 触发t定时器- 执行callback - 删除无用的标签定时器
location.href = url;
}
$scope.copyLink = function (){
$(document).find('body').eq(0).append('<div id="ngCopyableId">' + window.location.href + '</div>');
var newElem = angular.element(document.getElementById('ngCopyableId'))[0];
var range = document.createRange();
range.selectNode(newElem);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
var successful = document.execCommand('copy');
var oldElem = document.getElementById('ngCopyableId');
oldElem.parentNode.removeChild(oldElem);
window.getSelection().removeAllRanges();
}
上面可以看到未安装的动画加了!appin过滤,因为发现安卓和苹果那边可以做到识别是否安装了动画。因此就不在这里做了。有类似这样的方法去判断:
private boolean schemeValid() {
PackageManager manager = mContext.getPackageManager();
Intent action = new Intent(Intent.ACTION_VIEW);
action.setData(Uri.parse("weixin://"));
List list = manager.queryIntentActivities(action, PackageManager.GET_RESOLVED_FILTER);
return list != null && list.size() > 0;
}
总结一下:上面的功能,是一个很简单的分享功能,就是点击分享之后,把当前链接放进剪切板,然后跳转到app应用即可。
但是都是通过其他侧面实现的方式,比如PC端好实现的,在安装了相应的软件之后,点击分享会弹出弹窗,而如果没有安装,则不会弹出,利用这一点,可以通过绑定一个input按钮并且有一个blur事件,因为有弹窗,blur就会失焦。利用这点就可以间接判定是否安装了相应应用,如果没有安装就跳转到相应网页版聊天。这样的方式是有缺点的,如果屏蔽了弹窗,那就这个方法就失效了,还有就是有些浏览器可能不弹窗,目前试了edge、fireFox、chrom都有弹。
移动端的浏览器就是大问题了,因为每个手机的系统版本迭代,有些分享方式就不能适用了。比如,跳转之后,苹果的safari会弹窗拦截,那么用延迟显示实现判断是否安装了应用的方式就不行了,这样一搞就是就算安装了应用,点击打开之后还是会跳转到failUrl或者未安装的下载链接。因此做了一个求其次的方案,就是未安装不再跳转下载url那里,而是换成显示一个未安装标签动画。这样的话就算安装了也会跳转过去,看不到未安装的标签。移动端的主要问题还是苹果那里,安卓测试了下没问题,因为不会弹窗,利用iframe, 使得请求跳转的时候不会中断js。可以把上面aos跳转的failUrl解开注释试一试。ios的话就没用了。
在查资料的时候看到,由于安全隐私的原因,目前网页端无法通过js脚本直接判断某个APP是否已安装,只能首先通过js尝试性的启动app,然后再进入安装流程。启动app的方案有schema,universal link,具体详情请参考openinstall。
如果有什么更好的分享方式,可以评论一下。目前上述的分享很简单,如果要跳转到具体页面拉起被跳转app的分享窗口,可能需要在URl Scheme上面加一些参数,这个得看后面有没有这个需求了。后面加需求的话,只关注pc和移动的浏览器即可,app那边只需传参,让工程师处理