and*_*per 27 android internet-connection battery-saver android-doze
注意:事实证明,原始问题在其假设中是不正确的.在底部查看有关其编辑的更多详细信息.
它现在关于电池节电器,而不是电池节电和打盹模式.它也不是关于Service&BroadcastReceiver,而是仅仅是BroadcastReceiver.
从Android Lollipop开始,Google推出了新的,手动和自动的方式来帮助节省电池:
"打盹"模式和"电池节电器".
在某些情况下,由于这些技术,应用程序可能无法访问Internet.
我在一个应用程序上工作,该应用程序需要使用在特定情况下触发的后台服务来访问Internet,如果收到重要内容,则会显示一些UI.
作为用户,我注意到在某些情况下,它无法访问Internet.
检查应用程序是否可以访问Internet是这样的:
public static boolean isInternetOn(Context context) {
final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
return !(info == null || !info.isConnectedOrConnecting());
}
Run Code Online (Sandbox Code Playgroud)
问题是我需要检查为什么这有时会返回false,所以如果它失败了,我们应该告诉用户(可能通过通知)无法访问数据,因为设备限制了应用程序,并提供给用户从电池优化中列出应用程序.
我不确定哪些影响这个:打盹,节电或两者兼而有之,如果总是这样,对于所有设备,在所有情况下.
我找到的是如何查询打盹模式和省电模式(省电模式):
public class PowerSaverHelper {
public enum PowerSaveState {
ON, OFF, ERROR_GETTING_STATE, IRRELEVANT_OLD_ANDROID_API
}
public enum WhiteListedInBatteryOptimizations {
WHITE_LISTED, NOT_WHITE_LISTED, ERROR_GETTING_STATE, IRRELEVANT_OLD_ANDROID_API
}
public enum DozeState {
NORMAL_INTERACTIVE, DOZE_TURNED_ON_IDLE, NORMAL_NON_INTERACTIVE, ERROR_GETTING_STATE, IRRELEVANT_OLD_ANDROID_API
}
@NonNull
public static DozeState getDozeState(@NonNull Context context) {
if (VERSION.SDK_INT < VERSION_CODES.M)
return DozeState.IRRELEVANT_OLD_ANDROID_API;
final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm == null)
return DozeState.ERROR_GETTING_STATE;
return pm.isDeviceIdleMode() ? DozeState.DOZE_TURNED_ON_IDLE : pm.isInteractive() ? DozeState.NORMAL_INTERACTIVE : DozeState.NORMAL_NON_INTERACTIVE;
}
@NonNull
public static PowerSaveState getPowerSaveState(@NonNull Context context) {
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)
return PowerSaveState.IRRELEVANT_OLD_ANDROID_API;
final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm == null)
return PowerSaveState.ERROR_GETTING_STATE;
return pm.isPowerSaveMode() ? PowerSaveState.ON : PowerSaveState.OFF;
}
@NonNull
public static WhiteListedInBatteryOptimizations getIfAppIsWhiteListedFromBatteryOptimizations(@NonNull Context context, @NonNull String packageName) {
if (VERSION.SDK_INT < VERSION_CODES.M)
return WhiteListedInBatteryOptimizations.IRRELEVANT_OLD_ANDROID_API;
final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm == null)
return WhiteListedInBatteryOptimizations.ERROR_GETTING_STATE;
return pm.isIgnoringBatteryOptimizations(packageName) ? WhiteListedInBatteryOptimizations.WHITE_LISTED : WhiteListedInBatteryOptimizations.NOT_WHITE_LISTED;
}
//@TargetApi(VERSION_CODES.M)
@SuppressLint("BatteryLife")
@RequiresPermission(permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
@Nullable
public static Intent prepareIntentForWhiteListingOfBatteryOptimization(@NonNull Context context, @NonNull String packageName, boolean alsoWhenWhiteListed) {
if (VERSION.SDK_INT < VERSION_CODES.M)
return null;
if (ContextCompat.checkSelfPermission(context, permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_DENIED)
return null;
final WhiteListedInBatteryOptimizations appIsWhiteListedFromPowerSave = getIfAppIsWhiteListedFromBatteryOptimizations(context, packageName);
Intent intent = null;
switch (appIsWhiteListedFromPowerSave) {
case WHITE_LISTED:
if (alsoWhenWhiteListed)
intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
break;
case NOT_WHITE_LISTED:
intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).setData(Uri.parse("package:" + packageName));
break;
case ERROR_GETTING_STATE:
case IRRELEVANT_OLD_ANDROID_API:
default:
break;
}
return intent;
}
/**
* registers a receiver to listen to power-save events. returns true iff succeeded to register the broadcastReceiver.
*/
@TargetApi(VERSION_CODES.M)
public static boolean registerPowerSaveReceiver(@NonNull Context context, @NonNull BroadcastReceiver receiver) {
if (VERSION.SDK_INT < VERSION_CODES.M)
return false;
IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
context.registerReceiver(receiver, filter);
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
我想我也找到了一种在连接到设备时检查它们的方法:
省电:
./adb shell settings put global low_power [1|0]
Run Code Online (Sandbox Code Playgroud)
打盹状态:
./adb shell dumpsys deviceidle step [light|deep]
Run Code Online (Sandbox Code Playgroud)
而且:
./adb shell dumpsys deviceidle force-idle
Run Code Online (Sandbox Code Playgroud)
简而言之,我只是想知道无法访问互联网的原因,确实是因为没有互联网连接,或者由于某些电池优化而导致应用程序目前受限制.
仅限于被限制的情况,我可以警告用户,如果对他没问题,该应用程序将被列为白色,以便它仍然可以正常工作.
以下是我的问题:
以上哪项会阻止应用的后台服务访问互联网?他们所有人都这样做吗?它是特定于设备的吗?"互动"会影响它吗?
什么是"强制闲置",如果已经有办法去"轻"和"深"打瞌睡状态?还有办法将打盹模式恢复正常吗?我尝试了多个命令,但只是重新启动设备才真正让它恢复正常...
我创建的BroadcastReceiver是否允许正确检查?在所有情况下都会触发由于所有特殊情况而拒绝访问互联网吗?是不是我无法在清单中注册?
是否可以检查无法访问互联网的原因,是因为没有互联网连接,还是由于某些电池优化导致应用程序目前受限制?
在Android O上是否更改了特殊情况下后台服务的Internet连接限制?也许更多的情况我应该检查?
假设我将服务更改为在前台运行(带通知),这将涵盖所有情况,并且无论设备处于什么特殊状态,它始终都可以访问Internet?
编辑:它似乎根本不是服务的错误,它也发生在节电模式下,没有打盹模式.
该服务的触发器是一个侦听电话呼叫事件的BroadcastReceiver,即使我检查其onReceive
功能上的Internet连接,我也看到它返回false.从它启动的服务也是如此,即使它是前台服务.查看NetworkInfo结果,它是"BLOCKED",其状态确实是"DISCONNECTED".
现在问题,这就是为什么会这样.
这是一个新的样本POC来检查这个.要重现,您需要打开省电模式(使用./adb shell settings put global low_power 1
命令或作为用户),然后启动它,接受权限,关闭活动,并从另一部手机拨打此电话.您会注意到,在活动中,它显示有Internet连接,而在BroadcastReceiver上,它表示没有.
请注意,连接USB电缆时,节电模式可能会自动关闭,因此您可能需要在未连接设备时进行尝试.使用adb命令可以阻止它,而不是启用它的用户方法.
样本项目也可以在这里找到,即使它原本是关于Doze模式.只需使用节电模式,即可发现问题.
PhoneBroadcastReceiver
public class PhoneBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
Log.d("AppLog", "PhoneBroadcastReceiver:isInternetOn:" + isInternetOn(context));
}
public static boolean isInternetOn(Context context) {
final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
return !(info == null || !info.isConnectedOrConnecting());
}
}
Run Code Online (Sandbox Code Playgroud)
表现
<manifest package="com.example.user.myapplication" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name=".PhoneBroadcastReceiver">
<intent-filter >
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
</application>
</manifest>
Run Code Online (Sandbox Code Playgroud)
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("AppLog", "MainActivity: isInternetOn:" + PhoneBroadcastReceiver.isInternetOn(this));
if (VERSION.SDK_INT >= VERSION_CODES.M) {
requestPermissions(new String[]{permission.READ_PHONE_STATE, permission.PROCESS_OUTGOING_CALLS}, 1);
}
}
}
Run Code Online (Sandbox Code Playgroud)
因此,我从问题跟踪器下载了您的示例应用程序,按照您描述的方式对其进行了测试,并在运行 Android 6.0.1 的 Nexus 5 上发现了以下结果:
adb shell settings put global low_power 1
adb tcpip <port>
和adb connect <ip>:<port>
在此测试中,应用程序的功能正如您所提到的:
后台应用程序 -
D/AppLog: PhoneBroadcastReceiver:isInternetOn:false
D/AppLog: PhoneBroadcastReceiver:isInternetOn:false
Run Code Online (Sandbox Code Playgroud)
与测试 1 相同,但有以下更改
BroadcastReceiver启动Service(下面的例子)
public class PhoneService extends Service {
public void onCreate() {
super.onCreate();
startForeground(1, new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher_foreground)
.setContentTitle("Test title")
.setContentText("Test text")
.getNotification());
}
public int onStartCommand(Intent intent, int flags, int startId) {
final String msg = "PhoneService:isInternetOn:" + isInternetOn(this);
Log.d("AppLog", msg);
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
return START_STICKY;
}
public static boolean isInternetOn(Context context) {
final NetworkInfo info = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
return !(info == null || !info.isConnectedOrConnecting());
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
这次测试给了我与上面相同的结果。
后台应用程序 -
D/AppLog: PhoneService:isInternetOn:false
D/AppLog: PhoneService:isInternetOn:true
Run Code Online (Sandbox Code Playgroud)
这个测试很有趣,因为第一个日志没有为我提供互联网连接,但第二个日志却提供了互联网连接,这是在第一个日志之后大约 4 秒,并且在它建立前台服务之后很久。当我第二次运行测试时,两个日志都是正确的。startForeground
这似乎表明被调用的函数和系统将应用程序置于前台之间存在延迟。
我什至使用 运行测试 2 和 3 adb shell dumpsys deviceidle force-idle
,并得到与测试 3 类似的结果,其中第一个日志未连接,但所有后续日志都显示互联网连接。
我相信这一切都按预期运行,因为设备上的省电模式指出:
为了帮助延长电池寿命,省电模式会降低设备的性能并限制振动、定位服务和大多数后台数据。电子邮件、消息传递和其他依赖同步的应用程序可能不会更新,除非您打开它们。
因此,除非您当前正在使用该应用程序,或者您将该应用程序列入白名单并且正在运行前台服务,否则如果您的应用程序处于省电模式或打瞌睡模式,则您的应用程序将无法使用任何互联网连接。
与使用计时器重新检查互联网连接相比,这可能是一种不同的解决方法:
MyService.java
@Override
public void onCreate() {
super.onCreate();
Log.d("AppLog", "MyService:onCreate isInternetOn:" + PhoneBroadcastReceiver.isInternetOn(this));
if (!PhoneBroadcastReceiver.isInternetOn(this)) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
final ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE);
connectivityManager.registerNetworkCallback(new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build(), new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
//Use this network object to perform network operations
connectivityManager.unregisterNetworkCallback(this);
}
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果有可以使用的网络连接,这将立即返回,或者等到有连接为止。Handler
从这里,如果您在一段时间后没有收到任何结果,您可能可以使用 a来取消注册,尽管我可能只是将其保持活动状态。
这就是我所推荐的。这是基于您之前在评论中给出的答案(Android 检查互联网连接):
@Override
public void onCreate() {
super.onCreate();
Log.d("AppLog", "MyService:onCreate isInternetOn:" + PhoneBroadcastReceiver.isInternetOn(this));
if (!PhoneBroadcastReceiver.isInternetOn(this)) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
final ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE);
connectivityManager.registerNetworkCallback(new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build(), new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
isConnected(network); //Probably add this to a log output to verify this actually works for you
connectivityManager.unregisterNetworkCallback(this);
}
});
}
}
}
public static boolean isConnected(Network network) {
if (network != null) {
try {
URL url = new URL("http://www.google.com/");
HttpURLConnection urlc = (HttpURLConnection)network.openConnection(url);
urlc.setRequestProperty("User-Agent", "test");
urlc.setRequestProperty("Connection", "close");
urlc.setConnectTimeout(1000); // mTimeout is in seconds
urlc.connect();
if (urlc.getResponseCode() == 200) {
return true;
} else {
return false;
}
} catch (IOException e) {
Log.i("warning", "Error checking internet connection", e);
return false;
}
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1724 次 |
最近记录: |