1. <nobr id="easjo"><address id="easjo"></address></nobr>

      <track id="easjo"><source id="easjo"></source></track>
      1. 
        

      2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
      3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>
          貴州做網站公司
          貴州做網站公司~專業!靠譜!
          10年網站模板開發經驗,熟悉國內外開源網站程序,包括DEDECMS,WordPress,ZBlog,Discuz! 等網站程序,可為您提供網站建設,網站克隆,仿站,網頁設計,網站制作,網站推廣優化等服務。我們專注高端營銷型網站,企業官網,集團官網,自適應網站,手機網站,網絡營銷,網站優化,網站服務器環境搭建以及托管運維等。為客戶提供一站式網站解決方案?。?!

          懸浮窗權限(Android中怎么實現懸浮窗權限)

          來源:互聯網轉載 時間:2024-05-10 20:31:01

          懸浮窗適配

          懸浮窗適配有兩種方法:第一種是按照正規的流程,如果系統沒有賦予 APP 彈出懸浮窗的權限,就先跳轉到權限授權界面,等用戶打開該權限之后,再去彈出懸浮窗,比如 QQ 等一些主流應用就是這么做得;第二種就是利用系統的漏洞,繞過權限的申請,簡單粗暴,這種方法我不是特別建議,但是現在貌似有些應用就是這樣,比如 UC 和有道詞典,這樣適配在大多數手機上都是 OK 的,但是在一些特殊的機型不行,比如某米的 miui8。

          正常適配流程

          在 4.4~5.1.1 版本之間,和 6.0~最新版本之間的適配方法是不一樣的,之前的版本由于 google 并沒有對這個權限進行單獨處理,所以是各家手機廠商根據需要定制的,所以每個權限的授權界面都各不一樣,適配起來難度較大,6.0 之后適配起來就相對簡單很多了。

          Android 4.4 ~ Android 5.1.1

          由于判斷權限的類 AppOpsManager 是 API19 版本添加,所以Android 4.4 之前的版本(不包括4.4)就不用去判斷了,直接調用 WindowManager 的 addView 方法彈出即可,但是貌似有些特殊的手機廠商在 API19 版本之前就已經自定義了懸浮窗權限,如果有發現的,請聯系我。

          眾所周知,國產手機的種類實在是過于豐富,而且一個品牌的不同版本還有不一樣的適配方法,比如某米(嫌棄臉),所以我在實際適配的過程中總結了幾種通用的方法, 大家可以參考一下:

          1. 直接百度一下,搜索關鍵詞“小米手機懸浮窗適配”等;

          2. 看看 QQ 或者其他的大公司 APP 是否已經適配,如果已經適配,跳轉到相關權限授權頁面之后,或者自己能夠直接在設置里找到懸浮窗權限授權頁面也是一個道理,使用 adb shell dumpsys activity 命令,找到相關的信息,如下圖所示


          可以清楚看到授權 activity 頁面的包名和 activity 名,而且可以清楚地知道跳轉的 intent 是否帶了 extra,如果沒有 extra 就可以直接跳入,如果帶上了 extra,百度一下該 activity 的名字,看能否找到有用信息,比如適配方案或者源碼 APK 之類的;

          依舊利用上面的方法,找到 activity 的名字,然后 root 準備適配的手機,直接在相關目錄 /system/app 下把源碼 APK 拷貝出來,反編譯,根據 activity 的名字找到相關代碼,之后的事情就簡單了;

          還有一個方法就是發動人力資源去找,看看已經適配該手機機型的 app 公司是否有自己認識的人,或者干脆點,直接找這個手機公司里面是否有自己認識的手機開發朋友,直接詢問,方便快捷。

          常規手機

          由于 6.0 之前的版本常規手機并沒有把懸浮窗權限單獨拿出來,所以正常情況下是可以直接使用 WindowManager.addView 方法直接彈出懸浮窗。

          如何判斷手機的機型,辦法很多,在這里我就不貼代碼了,一般情況下在 terminal 中執行 getprop 命令,然后在打印出來的信息中找到相關的機型信息即可,這里貼出國產幾款常見機型的判斷:

          /***獲取emui版本號*@return*/publicstaticdoublegetEmuiVersion(){try{StringemuiVersion=getSystemProperty("ro.build.version.emui");Stringversion=emuiVersion.substring(emuiVersion.indexOf("_")+1);returnDouble.parseDouble(version);}catch(Exceptione){e.printStackTrace();}return4.0;}/***獲取小米rom版本號,獲取失敗返回-1**@returnmiuiromversioncode,iffail,return-1*/publicstaticintgetMiuiVersion(){Stringversion=getSystemProperty("ro.miui.ui.version.name");if(version!=null){try{returnInteger.parseInt(version.substring(1));}catch(Exceptione){Log.e(TAG,"getmiuiversioncodeerror,version:"+version);}}return-1;}publicstaticStringgetSystemProperty(StringpropName){Stringline;BufferedReaderinput=null;try{Processp=Runtime.getRuntime().exec("getprop"+propName);input=newBufferedReader(newInputStreamReader(p.getInputStream()),1024);line=input.readLine();input.close();}catch(IOExceptionex){Log.e(TAG,"Unabletoreadsysprop"+propName,ex);returnnull;}finally{if(input!=null){try{input.close();}catch(IOExceptione){Log.e(TAG,"ExceptionwhileclosingInputStream",e);}}}returnline;}publicstaticbooleancheckIsHuaweiRom(){returnBuild.MANUFACTURER.contains("HUAWEI");}/***checkifismiuiROM*/publicstaticbooleancheckIsMiuiRom(){return!TextUtils.isempty(getSystemProperty("ro.miui.ui.version.name"));}publicstaticbooleancheckIsMeizuRom(){//returnBuild.MANUFACTURER.contains("Meizu");StringmeizuFlymeOSFlag=getSystemProperty("ro.build.display.id");if(TextUtils.isEmpty(meizuFlymeOSFlag)){returnfalse;}elseif(meizuFlymeOSFlag.contains("flyme")||meizuFlymeOSFlag.toLowerCase().contains("flyme")){returntrue;}else{returnfalse;}}/***checkifis360ROM*/publicstaticbooleancheckIs360Rom(){returnBuild.MANUFACTURER.contains("QiKU");}

          小米

          首先需要適配的就應該是小米了,而且比較麻煩的事情是,miui 的每個版本適配方法都是不一樣的,所以只能每個版本去單獨適配,不過還好由于使用的人數多,網上的資料也比較全。首先第一步當然是判斷是否賦予了懸浮窗權限,這個時候就需要使用到 AppOpsManager 這個類了,它里面有一個 checkop 方法:

          /***Doaquickcheckforwhetheranapplicationmightbeabletoperformanoperation.*Thisis<em>not</em>asecuritycheck;youmustuse{@link#noteOp(int,int,String)}*or{@link#startOp(int,int,String)}foryouractualsecuritychecks,whichalso*ensurethatthegivenuidandpackagenameareconsistent.Thisfunctioncanjustbe*usedforaquickchecktoseeifanoperationhasbeendisabledfortheapplication,*asanearlyrejectofsomework.Thisdoesnotmodifythetimestamporotherdata*abouttheoperation.*@paramopTheoperationtocheck.OneoftheOP_*constants.*@paramuidTheuseridoftheapplicationattemptingtoperformtheoperation.*@parampackageNameThenameoftheapplicationattemptingtoperformtheoperation.*@returnReturns{@link#MODE_ALLOWED}iftheoperationisallowed,or*{@link#MODE_IGNORED}ifitisnotallowedandshouldbesilentlyignored(without*causingtheapptocrash).*@throwsSecurityExceptionIftheapphasbeenconfiguredtocrashonthisop.*@hide*/publicintcheckOp(intop,intuid,StringpackageName){try{intmode=mService.checkOperation(op,uid,packageName);if(mode==MODE_ERRORED){thrownewSecurityException(buildSecurityExceptionMsg(op,uid,packageName));}returnmode;}catch(RemoteExceptione){}returnMODE_IGNORED;}

          找到懸浮窗權限的 op 值是:

          /**@hide*/publicstaticfinalintOP_SYSTEM_ALERT_WINDOW=24;

          注意到這個函數和這個值其實都是 hide 的,所以沒辦法,你懂的,只能用反射:

          /***檢測miui懸浮窗權限*/publicstaticbooleancheckFloatWindowPermission(Contextcontext){finalintversion=Build.VERSION.SDK_INT;if(version>=19){returncheckOp(context,24);//OP_SYSTEM_ALERT_WINDOW=24;}else{//if((context.getApplicationInfo().flags&1<<27)==1){//returntrue;//}else{//returnfalse;//}returntrue;}}@TargetApi(Build.VERSION_CODES.KITKAT)privatestaticbooleancheckOp(Contextcontext,intop){finalintversion=Build.VERSION.SDK_INT;if(version>=19){AppOpsManagermanager=(AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);try{Classclazz=AppOpsManager.class;Methodmethod=clazz.getDeclaredMethod("checkOp",int.class,int.class,String.class);returnAppOpsManager.MODE_ALLOWED==(int)method.invoke(manager,op,Binder.getCallingUid(),context.getPackageName());}catch(Exceptione){Log.e(TAG,Log.getStackTraceString(e));}}else{Log.e(TAG,"BelowAPI19cannotinvoke!");}returnfalse;}

          檢測完成之后就是跳轉到授權頁面去開啟權限了,但是由于 miui 不同版本的權限授權頁面不一樣,所以需要根據不同版本進行不同處理:

          /***獲取小米rom版本號,獲取失敗返回-1**@returnmiuiromversioncode,iffail,return-1*/publicstaticintgetMiuiVersion(){Stringversion=RomUtils.getSystemProperty("ro.miui.ui.version.name");if(version!=null){try{returnInteger.parseInt(version.substring(1));}catch(Exceptione){Log.e(TAG,"getmiuiversioncodeerror,version:"+version);Log.e(TAG,Log.getStackTraceString(e));}}return-1;}/***小米ROM權限申請*/publicstaticvoidapplyMiuiPermission(Contextcontext){intversionCode=getMiuiVersion();if(versionCode==5){goToMiuiPermissionActivity_V5(context);}elseif(versionCode==6){goToMiuiPermissionActivity_V6(context);}elseif(versionCode==7){goToMiuiPermissionActivity_V7(context);}elseif(versionCode==8){goToMiuiPermissionActivity_V8(context);}else{Log.e(TAG,"thisisaspecialMIUIromversion,itsversioncode"+versionCode);}}privatestaticbooleanisIntentAvailable(Intentintent,Contextcontext){if(intent==null){returnfalse;}returncontext.getPackageManager().queryIntentActivities(intent,PackageManager.MATCH_DEFAULT_ONLY).size()>0;}/***小米V5版本ROM權限申請*/publicstaticvoidgoToMiuiPermissionActivity_V5(Contextcontext){Intentintent=null;StringpackageName=context.getPackageName();intent=newIntent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);Uriuri=Uri.fromParts("package",packageName,null);intent.setData(uri);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if(isIntentAvailable(intent,context)){context.startActivity(intent);}else{Log.e(TAG,"intentisnotavailable!");}//設置頁面在應用詳情頁面//Intentintent=newIntent("miui.intent.action.APP_PERM_EDITOR");//PackageInfopInfo=null;//try{//pInfo=context.getPackageManager().getPackageInfo//(HostInterfaceManager.getHostInterface().getApp().getPackageName(),0);//}catch(PackageManager.NameNotFoundExceptione){//AVLogUtils.e(TAG,e.getMessage());//}//intent.setClassName("com.android.settings","com.miui.securitycenter.permission.AppPermissionsEditor");//intent.putExtra("extra_package_uid",pInfo.applicationInfo.uid);//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//if(isIntentAvailable(intent,context)){//context.startActivity(intent);//}else{//AVLogUtils.e(TAG,"Intentisnotavailable!");//}}/***小米V6版本ROM權限申請*/publicstaticvoidgoToMiuiPermissionActivity_V6(Contextcontext){Intentintent=newIntent("miui.intent.action.APP_PERM_EDITOR");intent.setClassName("com.miui.securitycenter","com.miui.permcenter.permissions.AppPermissionsEditorActivity");intent.putExtra("extra_pkgname",context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if(isIntentAvailable(intent,context)){context.startActivity(intent);}else{Log.e(TAG,"Intentisnotavailable!");}}/***小米V7版本ROM權限申請*/publicstaticvoidgoToMiuiPermissionActivity_V7(Contextcontext){Intentintent=newIntent("miui.intent.action.APP_PERM_EDITOR");intent.setClassName("com.miui.securitycenter","com.miui.permcenter.permissions.AppPermissionsEditorActivity");intent.putExtra("extra_pkgname",context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if(isIntentAvailable(intent,context)){context.startActivity(intent);}else{Log.e(TAG,"Intentisnotavailable!");}}/***小米V8版本ROM權限申請*/publicstaticvoidgoToMiuiPermissionActivity_V8(Contextcontext){Intentintent=newIntent("miui.intent.action.APP_PERM_EDITOR");intent.setClassName("com.miui.securitycenter","com.miui.permcenter.permissions.PermissionsEditorActivity");intent.putExtra("extra_pkgname",context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if(isIntentAvailable(intent,context)){context.startActivity(intent);}else{Log.e(TAG,"Intentisnotavailable!");}}

          getSystemProperty 方法是直接調用 getprop 方法來獲取系統信息:

          publicstaticStringgetSystemProperty(StringpropName){Stringline;BufferedReaderinput=null;try{Processp=Runtime.getRuntime().exec("getprop"+propName);input=newBufferedReader(newInputStreamReader(p.getInputStream()),1024);line=input.readLine();input.close();}catch(IOExceptionex){Log.e(TAG,"Unabletoreadsysprop"+propName,ex);returnnull;}finally{if(input!=null){try{input.close();}catch(IOExceptione){Log.e(TAG,"ExceptionwhileclosingInputStream",e);}}}returnline;}

          最新的 V8 版本有些機型已經是 6.0 ,所以就是下面介紹到 6.0 的適配方法了,感謝 @pinocchio2mx 的反饋,有些機型的 miui8 版本還是5.1.1,所以 miui8 依舊需要做適配,非常感謝,希望大家一起多多反饋問題,謝謝~~。

          魅族

          魅族的適配,由于我司魅族的機器相對較少,所以只適配了 flyme5.1.1/android 5.1.1 版本 mx4 pro 的系統。和小米一樣,首先也要通過 API19 版本添加的 AppOpsManager 類判斷是否授予了權限:

          /***檢測meizu懸浮窗權限*/publicstaticbooleancheckFloatWindowPermission(Contextcontext){finalintversion=Build.VERSION.SDK_INT;if(version>=19){returncheckOp(context,24);//OP_SYSTEM_ALERT_WINDOW=24;}returntrue;}@TargetApi(Build.VERSION_CODES.KITKAT)privatestaticbooleancheckOp(Contextcontext,intop){finalintversion=Build.VERSION.SDK_INT;if(version>=19){AppOpsManagermanager=(AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);try{Classclazz=AppOpsManager.class;Methodmethod=clazz.getDeclaredMethod("checkOp",int.class,int.class,String.class);returnAppOpsManager.MODE_ALLOWED==(int)method.invoke(manager,op,Binder.getCallingUid(),context.getPackageName());}catch(Exceptione){Log.e(TAG,Log.getStackTraceString(e));}}else{Log.e(TAG,"BelowAPI19cannotinvoke!");}returnfalse;}

          然后是跳轉去懸浮窗權限授予界面:

          /***去魅族權限申請頁面*/publicstaticvoidapplyPermission(Contextcontext){Intentintent=newIntent("com.meizu.safe.security.SHOW_APPSEC");intent.setClassName("com.meizu.safe","com.meizu.safe.security.AppSecActivity");intent.putExtra("packageName",context.getPackageName());intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);}

          如果有魅族其他版本的適配方案,請聯系我。

          華為

          華為的適配是根據網上找的方案,外加自己的一些優化而成,但是由于華為手機的眾多機型,所以覆蓋的機型和系統版本還不是那么全面,如果有其他機型和版本的適配方案,請聯系我,我更新到 github 上。和小米,魅族一樣,首先通過 AppOpsManager 來判斷權限是否已經授權:

          /***檢測Huawei懸浮窗權限*/publicstaticbooleancheckFloatWindowPermission(Contextcontext){finalintversion=Build.VERSION.SDK_INT;if(version>=19){returncheckOp(context,24);//OP_SYSTEM_ALERT_WINDOW=24;}returntrue;}@TargetApi(Build.VERSION_CODES.KITKAT)privatestaticbooleancheckOp(Contextcontext,intop){finalintversion=Build.VERSION.SDK_INT;if(version>=19){AppOpsManagermanager=(AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);try{Classclazz=AppOpsManager.class;Methodmethod=clazz.getDeclaredMethod("checkOp",int.class,int.class,String.class);returnAppOpsManager.MODE_ALLOWED==(int)method.invoke(manager,op,Binder.getCallingUid(),context.getPackageName());}catch(Exceptione){Log.e(TAG,Log.getStackTraceString(e));}}else{Log.e(TAG,"BelowAPI19cannotinvoke!");}returnfalse;}

          然后根據不同的機型和版本跳轉到不同的頁面:

          /***去華為權限申請頁面*/publicstaticvoidapplyPermission(Contextcontext){try{Intentintent=newIntent();intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//componentnamecomp=newComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//華為權限管理//ComponentNamecomp=newComponentName("com.huawei.systemmanager",//"com.huawei.permissionmanager.ui.SingleAppActivity");//華為權限管理,跳轉到指定app的權限管理位置需要華為接口權限,未解決ComponentNamecomp=newComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//懸浮窗管理頁面intent.setComponent(comp);if(RomUtils.getEmuiVersion()==3.1){//emui3.1的適配context.startActivity(intent);}else{//emui3.0的適配comp=newComponentName("com.huawei.systemmanager","com.huawei.notificationmanager.ui.NotificationManagmentActivity");//懸浮窗管理頁面intent.setComponent(comp);context.startActivity(intent);}}catch(SecurityExceptione){Intentintent=newIntent();intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//ComponentNamecomp=newComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//華為權限管理ComponentNamecomp=newComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//華為權限管理,跳轉到本app的權限管理頁面,這個需要華為接口權限,未解決//ComponentNamecomp=newComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//懸浮窗管理頁面intent.setComponent(comp);context.startActivity(intent);Log.e(TAG,Log.getStackTraceString(e));}catch(ActivityNotFoundExceptione){/***手機管家版本較低HUAWEISC-UL10*///Toast.makeText(MainActivity.this,"act找不到",Toast.LENGTH_LONG).show();Intentintent=newIntent();intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);ComponentNamecomp=newComponentName("com.Android.settings","com.android.settings.permission.TabItem");//權限管理頁面android4.4//ComponentNamecomp=newComponentName("com.android.settings","com.android.settings.permission.single_app_activity");//此處可跳轉到指定app對應的權限管理頁面,但是需要相關權限,未解決intent.setComponent(comp);context.startActivity(intent);e.printStackTrace();Log.e(TAG,Log.getStackTraceString(e));}catch(Exceptione){//拋出異常時提示信息Toast.makeText(context,"進入設置頁面失敗,請手動設置",Toast.LENGTH_LONG).show();Log.e(TAG,Log.getStackTraceString(e));}}

          emui4 之后就是 6.0 版本了,按照下面介紹的 6.0 適配方案即可。

          360

          360手機的適配方案在網上可以找到的資料很少,也沒有給出最后的適配方案,不過最后居然直接用最簡單的辦法就能跳進去了,首先是權限的檢測:

          /***檢測360懸浮窗權限*/publicstaticbooleancheckFloatWindowPermission(Contextcontext){finalintversion=Build.VERSION.SDK_INT;if(version>=19){returncheckOp(context,24);//OP_SYSTEM_ALERT_WINDOW=24;}returntrue;}@TargetApi(Build.VERSION_CODES.KITKAT)privatestaticbooleancheckOp(Contextcontext,intop){finalintversion=Build.VERSION.SDK_INT;if(version>=19){AppOpsManagermanager=(AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);try{Classclazz=AppOpsManager.class;Methodmethod=clazz.getDeclaredMethod("checkOp",int.class,int.class,String.class);returnAppOpsManager.MODE_ALLOWED==(int)method.invoke(manager,op,Binder.getCallingUid(),context.getPackageName());}catch(Exceptione){Log.e(TAG,Log.getStackTraceString(e));}}else{Log.e("","BelowAPI19cannotinvoke!");}returnfalse;}

          如果沒有授予懸浮窗權限,就跳轉去權限授予界面:

          publicstaticvoidapplyPermission(Contextcontext){Intentintent=newIntent();intent.setClassName("com.android.settings","com.android.settings.Settings$OverlaySettingsActivity");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);}

          哈哈哈,是不是很簡單,有時候真相往往一點也不復雜,OK,適配完成。

          Android 6.0 及之后版本

          懸浮窗權限在 6.0 之后就被 google 單獨拿出來管理了,好處就是對我們來說適配就非常方便了,在所有手機和 6.0 以及之后的版本上適配的方法都是一樣的,首先要在 Manifest 中靜態申請<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />權限,然后在使用時先判斷該權限是否已經被授權,如果沒有授權使用下面這段代碼進行動態申請:

          privatestaticfinalintREQUEST_CODE=1;//判斷權限privatebooleancommonROMPermissionCheck(Contextcontext){Booleanresult=true;if(Build.VERSION.SDK_INT>=23){try{Classclazz=Settings.class;MethodcanDrawOverlays=clazz.getDeclaredMethod("canDrawOverlays",Context.class);result=(Boolean)canDrawOverlays.invoke(null,context);}catch(Exceptione){Log.e(TAG,Log.getStackTraceString(e));}}returnresult;}//申請權限privatevoidrequestAlertWindowPermission(){Intentintent=newIntent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);intent.setData(Uri.parse("package:"+getPackageName()));startActivityForResult(intent,REQUEST_CODE);}@Override//處理回調protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){super.onActivityResult(requestCode,resultCode,data);if(requestCode==REQUEST_CODE){if(Settings.canDrawOverlays(this)){Log.i(LOGTAG,"onActivityResultgranted");}}}

          上述代碼需要注意的是:

          • 使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION 啟動隱式Intent;

          • 使用 “package:” + getPackageName() 攜帶App的包名信息;

          • 使用 Settings.canDrawOverlays 方法判斷授權結果。

          在用戶開啟相關權限之后才能使用 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR ,要不然是會直接崩潰的哦。

          特殊適配流程

          如何繞過系統的權限檢查,直接彈出懸浮窗?需要使用mParams.type = WindowManager.LayoutParams.TYPE_TOAST; 來取代 mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;,這樣就可以達到不申請權限,而直接彈出懸浮窗,至于原因嘛,我們看看 PhoneWindowManager 源碼的關鍵處:

          @OverridepublicintcheckAddPermission(WindowManager.LayoutParamsattrs,int[]outAppOp){....switch(type){caseTYPE_TOAST://XXXrightnowtheappprocesshascompletecontrolover//this...shouldintroduceatokentoletthesystem//monitor/controlwhattheyaredoing.outAppOp[0]=AppOpsManager.OP_TOAST_WINDOW;break;caseTYPE_DREAM:caseTYPE_INPUT_METHOD:caseTYPE_WALLPAPER:caseTYPE_PRIVATE_PRESENTATION:caseTYPE_VOICE_INTERACTION:caseTYPE_ACCESSIBILITY_OVERLAY://Thewindowmanagerwillcheckthese.break;caseTYPE_PHONE:caseTYPE_PRIORITY_PHONE:caseTYPE_SYSTEM_ALERT:caseTYPE_SYSTEM_ERROR:caseTYPE_SYSTEM_OVERLAY:permission=android.Manifest.permission.SYSTEM_ALERT_WINDOW;outAppOp[0]=AppOpsManager.OP_SYSTEM_ALERT_WINDOW;break;default:permission=android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;}if(permission!=null){if(permission==android.Manifest.permission.SYSTEM_ALERT_WINDOW){finalintcallingUid=Binder.getCallingUid();//systemprocesseswillbeautomaticallyallowedprivilegetodrawif(callingUid==Process.SYSTEM_UID){returnWindowManagerGlobal.ADD_OKAY;}//checkifuserhasenabledthisoperation.SecurityExceptionwillbethrownif//thisapphasnotbeenallowedbytheuserfinalintmode=mAppOpsManager.checkOp(outAppOp[0],callingUid,attrs.packageName);switch(mode){caseAppOpsManager.MODE_ALLOWED:caseAppOpsManager.MODE_IGNORED://althoughwereturnADD_OKAYforMODE_IGNORED,theaddedwindowwill//actuallybehiddeninWindowManagerServicereturnWindowManagerGlobal.ADD_OKAY;caseAppOpsManager.MODE_ERRORED:returnWindowManagerGlobal.ADD_PERMISSION_DENIED;default://inthedefaultmode,wewillmakeadecisionherebasedon//checkCallingPermission()if(mContext.checkCallingPermission(permission)!=PackageManager.PERMISSION_GRANTED){returnWindowManagerGlobal.ADD_PERMISSION_DENIED;}else{returnWindowManagerGlobal.ADD_OKAY;}}}if(mContext.checkCallingOrSelfPermission(permission)!=PackageManager.PERMISSION_GRANTED){returnWindowManagerGlobal.ADD_PERMISSION_DENIED;}}returnWindowManagerGlobal.ADD_OKAY;}

          從源碼中可以看到,其實 TYPE_TOAST 沒有做權限檢查,直接返回了 WindowManagerGlobal.ADD_OKAY,所以呢,這就是為什么可以繞過權限的原因。還有需要注意的一點是 addView 方法中會調用到 mPolicy.adjustWindowParamsLw(win.mAttrs);,這個方法在不同的版本有不同的實現:

          //Android2.0-2.3.7PhoneWindowManagerpublicvoidadjustWindowParamsLw(WindowManager.LayoutParamsattrs){switch(attrs.type){caseTYPE_SYSTEM_OVERLAY:caseTYPE_SECURE_SYSTEM_OVERLAY:caseTYPE_TOAST://Thesetypesofwindowscan'treceiveinputevents.attrs.flags|=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;break;}}//Android4.0.1-4.3.1PhoneWindowManagerpublicvoidadjustWindowParamsLw(WindowManager.LayoutParamsattrs){switch(attrs.type){caseTYPE_SYSTEM_OVERLAY:caseTYPE_SECURE_SYSTEM_OVERLAY:caseTYPE_TOAST://Thesetypesofwindowscan'treceiveinputevents.attrs.flags|=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;attrs.flags&=~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;break;}}//Android4.4PhoneWindowManager@OverridepublicvoidadjustWindowParamsLw(WindowManager.LayoutParamsattrs){switch(attrs.type){caseTYPE_SYSTEM_OVERLAY:caseTYPE_SECURE_SYSTEM_OVERLAY://Thesetypesofwindowscan'treceiveinputevents.attrs.flags|=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;attrs.flags&=~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;break;}}

          可以看到,在4.0.1以前, 當我們使用 TYPE_TOAST, Android 會偷偷給我們加上 FLAG_NOT_FOCUSABLE 和 FLAG_NOT_TOUCHABLE,4.0.1 開始,會額外再去掉FLAG_WATCH_OUTSIDE_TOUCH,這樣真的是什么事件都沒了。而 4.4 開始,TYPE_TOAST 被移除了, 所以從 4.4 開始,使用 TYPE_TOAST 的同時還可以接收觸摸事件和按鍵事件了,而4.4以前只能顯示出來,不能交互,所以 API18 及以下使用 TYPE_TOAST 是無法接收觸摸事件的,但是幸運的是除了 miui 之外,這些版本可以直接在 Manifest 文件中聲明 android.permission.SYSTEM_ALERT_WINDOW權限,然后直接使用 WindowManager.LayoutParams.TYPE_PHONE 或者 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 都是可以直接彈出懸浮窗的。

          還有一個需要提到的是 TYPE_APPLICATION,這個 type 是配合 Activity 在當前 APP 內部使用的,也就是說,回到 Launcher 界面,這個懸浮窗是會消失的。

          雖然這種方法確確實實可以繞過權限,至于適配的坑呢,有人遇到之后可以聯系我,我會持續完善。不過由于這樣可以不申請權限就彈出懸浮窗,而且在最新的 6.0+ 系統上也沒有修復,所以如果這個漏洞被濫用,就會造成一些意想不到的后果,因此我個人傾向于使用 QQ 的適配方案,也就是上面的正常適配流程去處理這個權限。

          更新:7.1.1之后版本

          最新發現在 7.1.1 版本之后使用 type_toast 重復添加兩次懸浮窗,第二次會崩潰,跑出來下面的錯誤:

          E/AndroidRuntime:FATALEXCEPTION:mainandroid.view.WindowManager$BadTokenException:Unabletoaddwindow--windowandroid.view.ViewRootImpl$W@d7a4e96hasalreadybeenaddedatandroid.view.ViewRootImpl.setView(ViewRootImpl.java:691)atandroid.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)atandroid.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)atcom.tencent.ysdk.module.icon.impl.a.g(UnknownSource)atcom.tencent.ysdk.module.icon.impl.floatingviews.q.onAnimationEnd(UnknownSource)atandroid.view.animation.Animation$3.run(Animation.java:381)atandroid.os.Handler.handleCallback(Handler.java:751)atandroid.os.Handler.dispatchMessage(Handler.java:95)atandroid.os.Looper.loop(Looper.java:154)atandroid.app.ActivityThread.main(ActivityThread.java:6119)atjava.lang.reflect.Method.invoke(NativeMethod)atcom.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

          去追溯源碼,發現是這里拋出來的錯誤:

          try{mOrigWindowType=mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes=true;collectViewAttributes();res=mWindowSession.addToDisplay(mWindow,mSeq,mWindowAttributes,getHostVisibility(),mDisplay.getDisplayId(),mAttachInfo.mContentInsets,mAttachInfo.mStableInsets,mAttachInfo.mOutsets,mInputChannel);}catch(RemoteExceptione){.....}finally{if(restore){attrs.restore();}}.....if(res<WindowManagerGlobal.ADD_OKAY){.....switch(res){....caseWindowManagerGlobal.ADD_DUPLICATE_ADD:thrownewWindowManager.BadTokenException("Unabletoaddwindow--window"+mWindow+"hasalreadybeenadded");}}

          然后去查看拋出這個異常處的代碼:

          if(mWindowMap.containsKey(client.asBinder())){Slog.w(TAG_WM,"Window"+client+"isalreadyadded");returnWindowManagerGlobal.ADD_DUPLICATE_ADD;}

          然后我們從 mWindowMap 這個變量出發去分析,但是最后發現,根本不行,這些代碼從 5.X 版本就存在了,而且每次調用 addview 方法去添加一個 view 的時候,都是一個新的 client 對象,所以 mWindowMap.containsKey(client.asBinder()) 一直是不成立的,所以無法從這里去分析,于是繼續分析在 7.0 版本是沒有問題的,但是在 7.1.1 版本就出現問題了,所以我們去查看 7.1.1 版本代碼的變更:

          https://android.googlesource.com/platform/frameworks/base/+log/master/services/core/java/com/android/server/wm/WindowManagerService.java?s=28f0e5bf48e2d02e1f063670e435b1232f07ba03

          我們從里面尋找關于 type_toast 的相關變更:


          最終定位到了 aa07653 那個提交,我們看看這次提交修改的內容:


          然后點開 WMS 的修改:


          去到 canAddToastWindowForUid:


          我們于是定位到了關鍵 7.1.1 上面不能重復添加 type_toast 類型 window 的原因!!

          另外還有一點需要注意的是,在 7.1.1 上面還增加了如下的代碼:  


            


          可以看到在 25 版本之后,注意是之后,也就是 8.0,系統將會限制 type_toast 的使用,會直接拋出異常,這也是需要注意的地方。

          最新適配結果

          非常感謝ruanqin0706同學的大力幫忙,通過優測網的機型的測試適配,現在統計結果如下所示:

          6.0/6.0+

          更新,6.0魅族的適配方案不能使用google API,依舊要使用 6.0 之前的適配方法,已經適配完成~

          6.0 上絕大部分的機型都是可以的,除了魅族這種奇葩機型:


          機型版本詳細信息適配完成具體表現
          魅族 PRO66.0型號:PRO6;版本:6.0;分辨率:1920*1080檢測權限結果有誤,微信可正??s小放大,而我方檢測為未開啟權限,為跳轉至開啟權限頁
          魅族 U206.0型號:U20;版本:6.0;分辨率:1920*1080檢測權限結果有誤,微信可正??s小放大,而我方檢測為未開啟權限,為跳轉至開啟權限頁

          結論:


          匯總結果
          Android6.0 及以上機型覆蓋:58款,其中:
          三星:10款,均正常
          華為:21款,均正常
          小米:5款,均正常
          魅族:2款,異常(1.檢測權限未開啟,點擊 Android 6.0 及以上跳轉,無法跳轉,卻可以選擇魅族手機設置,設置后,懸浮窗打開縮小正常;2.在魅族上,及時設置懸浮窗關閉,微信也可正??s小,但是我們檢測的懸浮窗是否開發結果,和實際系統的設置是匹配的。)
          其他:20款,均正常

          已適配完成,針對魅族的手機,在 6.0 之后仍然使用老的跳轉方式,而不是使用新版本的 Google API 進行跳轉。

          huawei

          這里是華為手機的測試結果:


          機型版本適配完成具體表現默認設置
          華為榮耀x25.0跳轉至通知中心頁面,而非懸浮窗管理處默認關閉
          華為暢玩4x(電信版)4.4.4可以優化跳轉至通知中心標簽頁面,用戶需切換標簽頁(通知中心、懸浮窗為兩個不同標簽頁)默認關閉
          華為 p8 lite4.4.4可以優化跳轉至通知中心標簽頁面,用戶需切換標簽頁(通知中心、懸浮窗為兩個不同標簽頁)默認關閉
          華為榮耀 6 移動版4.4.2可以優化跳轉至通知中心標簽頁面,用戶需切換標簽頁(通知中心、懸浮窗為兩個不同標簽頁)默認關閉
          華為榮耀 3c 電信版4.3跳轉至通知中心,但默認是開啟懸浮窗的默認關閉
          華為 G5204.1.2直接點擊華為跳轉設置頁按鈕,閃退默認開啟

          結論:


          匯總結果完全兼容機型數量次兼容機型數量總測試機型數兼容成功率
          華為6.0以下機型覆蓋:18款,其中:
          5.0.1以上:11款,均默認開啟,且跳轉設置頁面正確;5.0:1款,處理異常
          (默認未開啟懸浮窗權限,且點擊跳轉至通知欄,非懸浮窗設置入口)
          4.4.4、4.4.2:3款,處理可接受
          (默認未開啟懸浮窗權限,點擊跳轉至通知中心的“通知欄”標簽頁,可手動切換至“懸浮窗”標簽頁設置)
          4.3:1款,處理可接受
          (默認開啟,但點擊華為跳轉設置頁,跳轉至通知中心,無懸浮窗設置處)
          4.2.2:1款,默認開啟,處理正常
          4.1.2:1款,處理有瑕疵
          (默認開啟,但若直接點擊華為跳轉按鈕,出現閃退)
          1251894.44%

          正在適配中…

          xiaomi

          大部分的小米機型都是可以成功適配,除了某些奇怪的機型:


          機型版本適配完成具體表現
          小米 MI 4S5.1.1無懸浮窗權限,點擊小米手機授權頁跳轉按鈕,無反應
          小米 紅米NOTE 1S4.4.4未執行未修改開啟懸浮窗成功,真機平臺不支持(為權限與之前系統有別)
          小米 紅米1(聯通版)4.2.2未執行未安裝成功

          結論:


          匯總結果完全兼容機型數量次兼容機型數量總測試機型數兼容成功率
          小米6.0以下機型覆蓋:10款,其中:
          5.1.1 小米 MI 4S:1款,兼容失敗
          (默認未開啟,點擊小米手機授權按鈕,無跳轉)
          其他:9款,均成功
          901090%

          samsung

          幾乎 100% 的機型都是配完美,結論:

          匯總結果完全兼容機型數量次兼容機型數量總測試機型數兼容成功率
          三星6.0以下機型覆蓋:28款,全部檢測處理成功
          (默認均開啟懸浮窗權限)
          28028100%

          oppo&&vivo

          藍綠大廠的機器,只測試了幾款機型,都是OK的:


          機型版本適配完成是否默認開啟
          OPPO R7sm5.1.1默認開啟
          OPPO R7 Plus5.0默認開啟
          OPPO R7 Plus(全網通)5.1.1默認開啟
          OPPO A37m5.1未執行默認未開啟,且無法設置開啟(平臺真機限制修改權限導致)
          OPPO A59m5.1.1默認開啟

          結論:


          匯總結果
          抽查3款,2個系統版本,均兼容,100%

          others

            其他的機型,HTC 和 Sony 大法之類的機器,隨機抽取了幾款,也都是 OK 的:


          機型是否正常
          藍魔 R3
          HTC A9
          摩托羅拉 Nexus 6
          VIVO V3Max A
          金立 M5
          HTC One E8
          努比亞 Z11 Max
          Sony Xperia Z3+ Dual
          酷派 大神Note3
          三星 GALAXY J3 Pro(雙4G)
          三星 Note 5
          中興 威武3
          中興 Axon Mini

          關于Android中怎么實現懸浮窗權限問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注本站行業資訊頻道了解更多相關知識。

          標簽:懸浮窗權限-

          c語言中正確的字符常量是用一對單引號將一個字符括起表示合法的字符常量。例如‘a’。數值包括整型、浮點型。整型可用十進制,八進制,十六進制。八進制前面要加0,后面...

          2022年天津專場考試原定于3月19日舉行,受疫情影響確定延期,但目前延期后的考試時間推遲。 符合報名條件的考生,須在規定時間登錄招考資訊網(www.zha...

          :喜歡聽,樂意看。指很受歡迎?!巴卣官Y料”喜聞樂見:[ xǐ wén lè jiàn ]詳細解釋1. 【解釋】:喜歡聽,樂意看。指很受歡迎。2. 【示例】:這是...

          PE投資,全稱為Private Equity,是私募股權投資的意思,簡稱為PE。此類投資主要是投資一-些不公開發行的公司股權,一般是通過私下的非公開渠道進行募集資金,因此而得名。PE投資的特點有哪些?1、期限長:從投資到收益一般需要五到七年;2、金額大:投資項目需要的資金| ]檻較高,大部分是百萬、千萬元起步;3、風險大:私募股權投資實現收益的方式主要是收購、兼并和上市。其中任一方式的風險都很大;...

          一千萬美金等于多少人民幣?按照最新的換算比例來看,1美元約等于6.9719人民幣,1元約等于0.1434美元。但是需要注意的是,通常情況下貨幣的價值比較可以有三種,比較常用的是交換價值,也就是常說的匯率。但是人民幣和美元的匯率是波動的,需要兌換者時刻關注。截止至2021年6月19日美金與人民幣的兌匯率是1玩=6.4525人民幣,因此1000萬美金等于人民幣64525000元人民幣,但美金與人民幣的...

          中聯重科股份有限公司創立于1992年,總部位于湖南省長沙市岳麓區銀盆南路361號。公司生產具有完全自主知識產權的10大類別、56個產品系列,600多個品種的主導產品,為全球產品鏈最齊備的工程機械企業。那么,中聯重科是國企還是私企?中聯重科和三一重工哪個好?一起來看看吧!中聯重科是國企還是私企?中科重科是國企。“中聯重科”一般指中聯重科股份有限公司,主要從事工程機械、農業機械...

          TOP
          国产初高中生视频在线观看|亚洲一区中文|久久亚洲欧美国产精品|黄色网站入口免费进人
          1. <nobr id="easjo"><address id="easjo"></address></nobr>

              <track id="easjo"><source id="easjo"></source></track>
              1. 
                

              2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
              3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>