Android禁止讀取已安裝列表后獲取是否安裝某應(yīng)用的方法

問題引入

由于應(yīng)用的支付、登錄和分享的場景需要,需要去根據(jù)應(yīng)用是否安裝微信、QQ等應(yīng)用,去做相應(yīng)的處理。但是經(jīng)常有線上用戶反饋安裝了微信、QQ,但是沒有相應(yīng)的支付方式。猜測是用戶禁止了用戶讀取已安裝應(yīng)用列表的權(quán)限。于是針對此問題進(jìn)行了調(diào)研和驗證。


已安裝應(yīng)用列表權(quán)限

Android原生系統(tǒng)是沒有這個權(quán)限的,該權(quán)限不需要也沒辦法向系統(tǒng)進(jìn)行注冊申請。但是(重點來了),國內(nèi)ROM(EMUI,MIUI,F(xiàn)untouch OS等)把這個權(quán)限暴露給用戶,在應(yīng)用要讀取已安裝應(yīng)用列表的時候會彈出彈窗供用戶選擇,用戶如果拒絕以后,應(yīng)用將無法讀取到已安裝應(yīng)用列表。
在驗證demo的時候發(fā)現(xiàn)一個有意思的現(xiàn)象,在華為手機上,如果應(yīng)用在Manifest文件中未申請任何權(quán)限,在權(quán)限管理“讀取已安裝應(yīng)用列表”中是看不到demo應(yīng)用的,應(yīng)用也會顯示未申請任何權(quán)限,也就無法禁止應(yīng)用讀取已安裝應(yīng)用列表權(quán)限。


微信圖片_20180530115529.jpg

獲取某應(yīng)用是否已安裝

通過Application拿到PackageManager,然后通過PackageManager的getInstalledPackages(int flags)獲取到安裝應(yīng)用列表的PackageInfo,遍歷看是否有某應(yīng)用包名來判斷是否安裝了某應(yīng)用。

  public boolean isWxInstall() {
    PackageManager packageManager = MyApplication.getInstance().getPackageManager();
    List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);
    if (pinfo != null) {
      for (int i = 0; i < pinfo.size(); i++) {
        String pn = pinfo.get(i).packageName;
        if (pn.equals("com.tencent.mm")) {
          return true;
        }
      }
    }
    return false;
  }

如果禁止讀取獲取已安裝應(yīng)用列表權(quán)限后,上述方法可能會導(dǎo)致實際的結(jié)果不正確,于是分別找了vivo、華為和小米手機進(jìn)行了驗證。


驗證

  • vivo x9 Funtouch OS_3.1 android 7.1.2
    不禁止權(quán)限,通過getInstalledPackages()方法拿到的List<PackageInfo>的size是204,包含了系統(tǒng)應(yīng)用、用戶安裝的應(yīng)用等所有的應(yīng)用。
    1.png

    禁止讀取已安裝應(yīng)用列表的權(quán)限以后,通過getInstalledPackages()方法拿到的List<PackageInfo>的size僅僅為15。
    2.png

查看此時數(shù)據(jù),發(fā)現(xiàn)除了一部分系統(tǒng)應(yīng)用能被讀取到以外,微博、微信和QQ的PackageInfo信息也能讀取到,這里猜測vivo的系統(tǒng)針對該權(quán)限被禁止以后,可能設(shè)置有白名單,使得無論是否禁止讀取已安裝應(yīng)用都能拿到相應(yīng)的PackageInfo。

3.png
  • HUAWEI Mate10 Pro EMUI 8.0.0 android 8.0.0
    不禁止讀取已安裝應(yīng)用列表的權(quán)限,通過getInstalledPackages()方法拿到的List<PackageInfo>的size是236,包含了所有系統(tǒng)安裝的應(yīng)用。
    4.png

    但是禁止該權(quán)限以后,通過getInstalledPackages()方法僅僅能讀取到3個應(yīng)用的PackageInfo,包括應(yīng)用本身、華為云文件夾(com.huawei.hifolder)和一個系統(tǒng)內(nèi)置應(yīng)用(com.android.cts.priv.ctsshim)。由此可見,華為對該權(quán)限限制的太嚴(yán)重了,一旦禁止該權(quán)限,連System級別的應(yīng)用的PackageInfo都無法獲取到了。
    5.png
  • MI 5 MIUI 9.5.2.0 android 7.0
    不禁止讀取應(yīng)用列表權(quán)限之前,通過getInstalledPackages()方法拿到的List<PackageInfo>的size是286,可以拿到所有應(yīng)用的PackageInfo。
    9.png

但是禁止讀取應(yīng)用列表權(quán)限后,通過getInstalledPackages()方法拿到的List<PackageInfo>的size是188,包含了所有Android System級別和MIUI系統(tǒng)自帶的應(yīng)用,但是第三方應(yīng)用的PackageInfo無法獲取到。


10.png

由此可以看出,國內(nèi)廠商的這個讀取已安裝應(yīng)用列表的權(quán)限在被禁了以后,第三方應(yīng)用的PackageInfo用getInstalledPackages()方法是獲取不到的。


另一種方案

  public boolean isWxInstall2() {
    PackageManager packageManager = MyApplication.getInstance().getPackageManager();
    String packageName = "com.tencent.mm";
    boolean hasInstallWx;
    try {
      PackageInfo packageInfo = packageManager.getPackageInfo(packageName,PackageManager.GET_GIDS);
      hasInstallWx = packageInfo != null;
    } catch (PackageManager.NameNotFoundException e) {
      hasInstallWx = false;
      e.printStackTrace();
    }
    return hasInstallWx;
  }

這種方案用的是PackageManager里的getPackageInfo()方法,該方法不能獲得已安裝應(yīng)用的列表,但是可以獲得指定包名的PackageInfo,當(dāng)指定包名的PackageInfo不存在的時候,系統(tǒng)會拋出PackageManager.NameNotFoundException異常,可以以此為依據(jù)來進(jìn)行判斷系統(tǒng)是否安裝某應(yīng)用。
同樣,Android原生系統(tǒng)用上述方法是肯定能夠獲得正確結(jié)果的,但不知道國內(nèi)手機廠商的ROM是否對該方法也進(jìn)行了修改,于是用上述三部手機分別進(jìn)行了驗證,得到的結(jié)果是一樣的。在禁止讀取已安裝列表權(quán)限以后,能夠獲得正確的結(jié)果:


7.png

所以,就目前手機廠商的ROM來說,用上述方案是能夠正確獲得手機是否安裝某應(yīng)用的。
查看微信SDK、QQSDK里封裝的判斷應(yīng)用是否安裝了應(yīng)用的原理是一樣的
微信:

 public final boolean isWXAppInstalled() {
    if(this.detached) {
      throw new IllegalStateException("isWXAppInstalled fail, WXMsgImpl has been detached");
    } else {
      try {
        PackageInfo var1;
        return (var1 = this.context.getPackageManager().getPackageInfo("com.tencent.mm", 64)) == null?false:WXApiImplComm.validateAppSignature(this.context, var1.signatures, this.checkSignature);
      } catch (NameNotFoundException var2) {
        return false;
      }
    }
  }

QQ:

  public static boolean checkMobileQQ(Context var0) {
    PackageManager var1 = var0.getPackageManager();
    PackageInfo var2 = null;

    try {
      var2 = var1.getPackageInfo("com.tencent.mobileqq", 0);
    } catch (NameNotFoundException var7) {
      Log.d("checkMobileQQ", "error");
      var7.printStackTrace();
    }

    if(var2 != null) {
      String var3 = var2.versionName;

      try {
        Log.d("MobileQQ verson", var3);
        String[] var4 = var3.split("\\.");
        int var5 = Integer.parseInt(var4[0]);
        int var6 = Integer.parseInt(var4[1]);
        return var5 > 4 || var5 == 4 && var6 >= 1;
      } catch (Exception var8) {
        var8.printStackTrace();
        return false;
      }
    } else {
      return false;
    }
  }

PMS的源碼太多了,PackageManager.getPackageInfo()到底做了什么暫時不做分析,感興趣的同學(xué)可以自行去查看下源碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容