您当前的位置:首页 > 互联网教程

Android 取消后台运行限制

发布时间:2025-05-13 11:55:09    发布人:远客网络

Android 取消后台运行限制

一、Android 取消后台运行限制

1.搜索发现下面这个方法可以判断当前是否限制后台运行:!!API level 28

isBackgroundRestricted Added in API level 28

当处于这个模式下,即便应用充电也不行,除非前台在运行

2.关于打开控制APP后台限制的设置页面的搜索:

Open Background Restriction in settings

"Background Restriction"(or"Allow Background Activity" on some devices) is intended to stop ALL background activity regardless of whether your service has called setForeground()

There is no way around this setting. You cannot programmatically disable it. Your only option is to programmatically check if it's enabled using ActivityManager.isBackgroundRestricted() and display a pop-up informing your users on how to disable this setting

Google's Universal Music Player sample project on GitHub happens to work(as of the writing of this answer) only because a service bind is not released when the main Activity is paused. The sample project's service is however killed when the main Activity is garbage collected(typically 30-45 minutes depending on the device).

3.尝试搜索后的另外一条路:忽略电源管理

首先要说一下由于Android系统UI的定制化严重,所以很多设备不能很好地匹配UI,或者

设置成功后无法取消,但三星的设备表现还是中规中矩的。

This is part of the new App Standby(应用待机) feature introduced with API 23(Marshmallow) alongside Doze Battery Optimization aimed to optimize power and resource usage while the app is in background(App Standby) or when the device has long been in sleep(Doze).

Following is the explanation from the Android Developer's site page:

Specifically, in the App Standby mode, the system determines that an app is idle when the user is not actively using it. The system makes this determination when the user does not touch the app for a certain period of time and none of the following conditions applies:

When the user plugs the device into a power supply, the system releases apps from the standby state, allowing them to freely access the network and to execute any pending jobs and syncs. If the device is idle for long periods of time, the system allows idle apps network access around once a day.

So, this means that starting from API 23(Marshmallow), the device may actively put your app on standby, preventing network access(say for task like sync) and limiting(or disabling) background executions. Now, for most of the apps this behavior is fine or you could easily optimize for such behavior, but for some apps out there this may cause some unexpected behavior, especially for apps that have been poorly optimized or use non-standard sync strategies or some other means of background sync/execution. So to circumvent that, the user can explicitly mark the app as non-optimized and the system will fallback and not put the app to standby, although this leads to a poorer user experience and you should not be doing this for regular apps that could be optimized.

Also, you need to add the following permission in manifest

二、android开发设置屏蔽录制

项目开发中,为了用户信息的安全,会有禁止页面被截屏、录屏的需求。

这类资料,在网上有很多,一般都是通过设置Activity的Flag解决,如:

//禁止页面被截屏、录屏getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);

这种设置可解决一般的防截屏、录屏的需求。

如果页面中有弹出Popupwindow,在录屏视频中的效果是:

但Popupwindow区域仍然是可以看到的

未设置FLAG_SECURE,录屏的效果,如下图(git图片中间的水印忽略):

设置了FLAG_SECURE之后,录屏的效果,如下图(git图片中间的水印忽略):

看到了上面的效果,我们可能会有疑问PopupWindow不像Dialog有自己的window对象,而是使用WindowManager.addView方法将View显示在Activity窗体上的。那么,Activity已经设置了FLAG_SECURE,为什么录屏时还能看到PopupWindow?

我们先通过getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);来分析下源码:

//window布局参数private final WindowManager.LayoutParams mWindowAttributes= new WindowManager.LayoutParams();//添加标识public void addFlags(int flags){

}//通过mWindowAttributes设置标识public void setFlags(int flags, int mask){ final WindowManager.LayoutParams attrs= getAttributes();

attrs.flags=(attrs.flags&~mask)|(flags&mask);

dispatchWindowAttributesChanged(attrs);

}//获得布局参数对象,即mWindowAttributespublic final WindowManager.LayoutParams getAttributes(){ return mWindowAttributes;

通过源码可以看到,设置window属性的源码非常简单,即:通过window里的布局参数对象mWindowAttributes设置标识即可。

//显示PopupWindowpublic void showAtLocation(View parent, int gravity, int x, int y){

mParentRootView= new WeakReference<>(parent.getRootView());

showAtLocation(parent.getWindowToken(), gravity, x, y);

}//显示PopupWindowpublic void showAtLocation(IBinder token, int gravity, int x, int y){ if(isShowing()|| mContentView== null){ return;

TransitionManager.endTransitions(mDecorView);

final WindowManager.LayoutParams p=createPopupLayoutParams(token);

}//创建Window布局参数对象protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token){ final WindowManager.LayoutParams p= new WindowManager.LayoutParams();

p.flags= computeFlags(p.flags);

p.softInputMode= mSoftInputMode;

p.windowAnimations= computeAnimationResource(); if(mBackground!= null){

p.format= mBackground.getOpacity();

p.format= PixelFormat.TRANSLUCENT;

p.height= mLastHeight= mHeightMode;

p.height= mLastHeight= mHeight;

p.width= mLastWidth= mWidthMode;

p.privateFlags= PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH

| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;

p.setTitle("PopupWindow:"+ Integer.toHexString(hashCode())); return p;

}//将PopupWindow添加到Window上private void invokePopup(WindowManager.LayoutParams p){ if(mContext!= null){

p.packageName= mContext.getPackageName();

} final PopupDecorView decorView= mDecorView;

decorView.setFitsSystemWindows(mLayoutInsetDecor);

setLayoutDirectionFromAnchor();

mWindowManager.addView(decorView, p); if(mEnterTransition!= null){

decorView.requestEnterTransition(mEnterTransition);

通过PopupWindow的源码分析,我们不难看出,在调用showAtLocation时,会单独创建一个WindowManager.LayoutParams布局参数对象,用于显示PopupWindow,而该布局参数对象上并未设置任何防止截屏Flag。

原因既然找到了,那么如何处理呢?

再回头分析下Window的关键代码:

//通过mWindowAttributes设置标识public void setFlags(int flags, int mask){ final WindowManager.LayoutParams attrs= getAttributes();

attrs.flags=(attrs.flags&~mask)|(flags&mask);

dispatchWindowAttributesChanged(attrs);

其实只需要获得WindowManager.LayoutParams对象,再设置上flag即可。

但是PopupWindow并没有像Activity一样有直接获得window的方法,更别说设置Flag了。我们再分析下PopupWindow的源码:

//将PopupWindow添加到Window上private void invokePopup(WindowManager.LayoutParams p){ if(mContext!= null){

p.packageName= mContext.getPackageName();

final PopupDecorView decorView= mDecorView;

decorView.setFitsSystemWindows(mLayoutInsetDecor);

setLayoutDirectionFromAnchor();//添加View

mWindowManager.addView(decorView, p); if(mEnterTransition!= null){

decorView.requestEnterTransition(mEnterTransition);

我们调用showAtLocation,最终都会执行mWindowManager.addView(decorView, p);

那么是否可以在addView之前获取到WindowManager.LayoutParams呢?

答案很明显,默认是不可以的。因为PopupWindow并没有公开获取WindowManager.LayoutParams的方法,而且mWindowManager也是私有的。

我们可以通过hook的方式解决这个问题。我们先使用动态代理拦截PopupWindow类的addView方法,拿到WindowManager.LayoutParams对象,设置对应Flag,再反射获得mWindowManager对象去执行addView方法。

不过,通过hook的方式也有一定的风险,因为mWindowManager是私有对象,不像Public的API,谷歌后续升级Android版本不会考虑其兼容性,所以有可能后续Android版本中改了其名称,那么我们通过反射获得mWindowManager对象不就有问题了。不过从历代版本的Android源码去看,mWindowManager被改的几率不大,所以hook也是可以用的,我们尽量写代码时考虑上这种风险,避免以后出问题。

...... private WindowManager mWindowManager;

而addView方法是ViewManger接口的公共方法,我们可以放心使用。

public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);

考虑到hook的可维护性和扩展性,我们将相关代码封装成一个独立的工具类吧。

package com.ccc.ddd.testpopupwindow.utils;

import android.view.WindowManager;

import android.widget.PopupWindow;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;public class PopNoRecordProxy implements InvocationHandler{ private Object mWindowManager;//PopupWindow类的mWindowManager对象

public static PopNoRecordProxy instance(){ return new PopNoRecordProxy();

} public void noScreenRecord(PopupWindow popupWindow){ if(popupWindow== null){ return;

} try{//通过反射获得PopupWindow类的私有对象:mWindowManager

Field windowManagerField= PopupWindow.class.getDeclaredField("mWindowManager");

windowManagerField.setAccessible(true);

mWindowManager= windowManagerField.get(popupWindow); if(mWindowManager== null){ return;

}//创建WindowManager的动态代理对象proxy

Object proxy= Proxy.newProxyInstance(Handler.class.getClassLoader(), new Class[]{WindowManager.class}, this);//注入动态代理对象proxy(即:mWindowManager对象由proxy对象来代理)

windowManagerField.set(popupWindow, proxy);

} catch(IllegalAccessException e){

} catch(NoSuchFieldException e){

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ try{//拦截方法mWindowManager.addView(View view, ViewGroup.LayoutParams params);

if(method!= null&& method.getName()!= null&& method.getName().equals("addView")

&& args!= null&& args.length== 2){//获取WindowManager.LayoutParams,即:ViewGroup.LayoutParams

WindowManager.LayoutParams params=(WindowManager.LayoutParams) args[1];//禁止录屏

} return method.invoke(mWindowManager, args);

private void setNoScreenRecord(WindowManager.LayoutParams params){

setFlags(params, WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);

private void setAllowScreenRecord(WindowManager.LayoutParams params){

setFlags(params, 0, WindowManager.LayoutParams.FLAG_SECURE);

*设置WindowManager.LayoutParams flag属性(参考系统类Window.setFlags(int flags, int mask))

*@param params WindowManager.LayoutParams

*@param flags The new window flags(see WindowManager.LayoutParams).

*@param mask Which of the window flag bits to modify.

private void setFlags(WindowManager.LayoutParams params, int flags, int mask){ try{ if(params== null){ return;

} params.flags=(params.flags&~mask)|(flags& mask);

Popwindow禁止录屏工具类的使用,代码示例:

//正常项目中,该方法可改成工厂类

//正常项目中,也可自定义PopupWindow,在其类中设置禁止录屏

private PopupWindow createPopupWindow(View view, int width, int height){

PopupWindow popupWindow= new PopupWindow(view, width, height);//PopupWindow禁止录屏

PopNoRecordProxy.instance().noScreenRecord(popupWindow); return popupWindow;

View view= LayoutInflater.from(this).inflate(R.layout.pm1, null);

PopupWindow pw= createPopupWindow(view,ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);

pw1.showAtLocation(this.getWindow().getDecorView(), Gravity.BOTTOM| Gravity.RIGHT, PopConst.PopOffsetX, PopConst.PopOffsetY);

三、如何处理android 6.0的运行时权限

对于6.0以下的权限及在安装的时候,根据权限声明产生一个权限列表,用户只有在同意之后才能完成app的安装,造成了我们想要使用某个app,就要默默忍受其一些不必要的权限(比如是个app都要访问通讯录、短信等)。而在6.0以后,我们可以直接安装,当app需要我们授予不恰当的权限的时候,我们可以予以拒绝(比如:单机的象棋对战,请求访问任何权限,我都是不同意的)。当然你也可以在设置界面对每个app的权限进行查看,以及对单个权限进行授权或者解除授权。

新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。

ACCESS_LOCATION_EXTRA_COMMANDS