編輯:Android編程入門
導讀
增強的Doze模式
後台優化
Data Saver
一.增強的Doze模式
Android N對Android M引進的Doze模式進行了進一步的增強,變化體現在兩個方面.一方面是降低了進入Doze模式的條件,Android M中的條件是不插電,屏幕熄滅且靜置一段時間,在Android N中去掉了靜置的條件,這個改變大大增加了設備進入Doze模式的機會,因而使得Doze對應用程序的影響大大增加.另一方面,Doze模式被分成了兩個階段,當設備切斷電源,熄滅屏幕一段時間,會進入到第一階段,切斷網絡連接,推遲任務和同步.如果設備在第一階段的基礎上再靜置一段時間,就會進入第二階段,在第一階段的基礎上增加對維持喚醒(PowerManager.WakeLock),定時任務(AlarmManager alarms),GPS和Wi-Fi掃描的限制,如下圖所示:
First level
Second level
應對方案:
方案一:在Android 6.0中AlarmManager中增加了兩個方法setAndAllowWhileIdle() and setExactAndAllowWhileIdle(),通過使用這兩個方法可以讓alarm在Doze模式下運行.
需要注意的是官方文檔指出,使用這兩個方法時,每個應用每9分鐘只能喚醒一次alarm.
Note: Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 minutes, per app.
以一個定時任務為例進行測試,核心代碼如下:
Service:
在Service的onStartCommand()方法中,獲取一個AlarmManager的實例,設置任務執行的時間是10s後,處理定時任務的廣播接收器是AlarmReceiver.
public class LongRunningService extends Service {
public static final String TAG = "LongRunningService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.i(TAG,"executed at " + new Date().toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int offset= 10 * 1000;//間隔時間10s
long triggerAtTime = SystemClock.elapsedRealtime() + offset;
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, i, 0);
manager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
// manager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
return super.onStartCommand(intent, flags, startId);
}
}
BroadcastReceiver:
重寫onReceive()方法,創建一個Intent,啟動LongRunningService.這樣一來,就形成了一個每隔10s執行一次的定時任務.
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}
測試結果:
I/LongRunningService: executed at Thu Apr 14 22:32:58 GMT+08:00 2016
I/LongRunningService: executed at Thu Apr 14 22:33:08 GMT+08:00 2016
I/LongRunningService: executed at Thu Apr 14 22:33:18 GMT+08:00 2016
I/LongRunningService: executed at Thu Apr 14 22:42:18 GMT+08:00 2016
I/LongRunningService: executed at Thu Apr 14 22:51:18 GMT+08:00 2016
從測試結果可以看出,設備在正常使用的情況下(前三行),每隔10s運行一次,進入到Doze模式後(後三行),每隔9分鐘執行一次.
為方便操作,這裡介紹一下測試步驟:
Step1.運行應用程序
Step2.關閉設備的屏幕
Step3.使用如下命令強制系統進入Doze模式
$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step
需要多次運行第二條命令,直到設備進入到空閒狀態.
方案二:在應用程序運行時,引導用戶將該應用添加到白名單中.
實現方式1(不推薦):使用ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS使用戶跳轉到電池優化設置頁,手動將該應用添加到白名單中.
核心代碼如下:
Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
該方法可行,但存在一個缺點:跳轉到電池優化頁後,用戶需要在”所有應用”列表裡(應用安裝後會默認設置為”優化”)找到該應用進行設置,而且系統提示會引導用戶選擇優化,如圖所示:
實現方式2(推薦):給應用添加權限REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,並使用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,系統會彈出設置窗口,用戶可以直接將該應用添加到白名單中,如下圖所示:
核心代碼:
在清單文件中添加權限:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
使用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS作為參數創建一個Intent,並以"package:com.example.xxx.xxx"的Uri形式將包名傳入.
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:com.example.janiszhang.dozedemo3"));
startActivity(intent);
方案三:使用GCM(Google Cloud Messaging ),該方法不可行,不再贅述.
二.後台優化
優化點1.針對預覽版,應用不再接收靜態注冊的CONNECTIVITY_ACTION廣播.但是應用在前台時仍然能夠監聽到動態注冊的CONNECTIVITY_CHANGE廣播.
優化點2.應用程序不能發送或者接收ACTION_NEW_PICTURE 和ACTION_NEW_VIDEO廣播,這個優化會影響到所有應用,不只是針對預覽版.
應對方案:
為了應對CONNECTIVITY_ACTION的變化所帶來的影響,官方給出了兩種緩解方案.
方案一:使用JobScheduler在無計量網絡下調度網絡任務.
核心代碼:
Activity:
在使用JobInfo.Builder()創建JobInfo對象時,調用setRequiredNetworkType()方法,並將
JobInfo.NETWORK_TYPE_UNMETERED作為參數傳遞進去,這段代碼的作用是當設備接入無計量網絡時將調起MyJobService.
public static final int MY_BACKGROUND_JOB = 0;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void scheduleJob(Context context) {
JobScheduler js =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo job = new JobInfo.Builder(
MY_BACKGROUND_JOB,
new ComponentName(context, MyJobService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.build();
js.schedule(job);
}
Service:
當條件滿足時(在該例中為接入無計量網絡),MyJobService中的回調方法onStartJob()將被執行,在實際業務中,可以在這裡執行網絡任務.
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService{
public static final String TAG = "MyJobService";
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.i(TAG, "on start job: " + jobParameters.getJobId());
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.i(TAG, "on stop job: " + jobParameters.getJobId());
return false;
}
}
注意:
需要在清單文件中為該Service設置權限:android.permission.BIND_JOB_SERVICE”
<service android:name=".MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
方案二:在應用運行時監控網絡連接
方式一:動態注冊BroadcastReceiver,監聽
“android.net.conn.CONNECTIVITY_CHANGE”
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG,"onReceive");
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
this.registerReceiver(broadcastReceiver,intentFilter);
方式二:使用ConnectivityManager
首先,使用NetworkRequest.Builder創建一個NetworkRequest對象,然後使用registerNetworkCallback()把這個NetworkRequest對象傳遞給系統.當網絡條件被滿足時,應用將收到一個回調去執行定義在ConnectivityManager.MetworkCallback類中的onAvailable()方法.
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
NetworkRequest networkRequest = builder.build();
ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
Log.i(TAG, "onAvailable");
}
};
connectivityManager.registerNetworkCallback(networkRequest, networkCallback);
三.Data Saver
當用戶在計量網絡下啟用數據節約功能時,系統會封鎖後台數據的使用,運行在前台的應用也會盡量少的使用數據流量.用戶可以使用白名單允許指定的應用在數據節約模式下使用後台數據.Android N開發者預覽版中擴展了ConnectivityManager API的能力,向用戶提供了查看和監控數據節約設置的接口.
檢測Data Saver首選項的變化
動態注冊監聽ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED ("android.net.conn.RESTRICT_BACKGROUND_CHANGED")的廣播接收者.
核心代碼:
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Data Saver Changed");
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.RESTRICT_BACKGROUND_CHANGED");
this.registerReceiver(broadcastReceiver,intentFilter);
注意:必須使用動態注冊的方式才能夠監聽到該廣播,不能在清單文件中靜態注冊.
檢查數據節約設置
ConnectivityManager的getResrictBackgroundStatus()方法的返回值如下:
RESTRICT_BACKGROUND_STATUS_DISABLED
禁用數據節約
RESTRICT_BACKGROUND_STATUS_ENABLED
啟用數據節約
RESTRICT_BACKGROUND_STATUS_WHITELISTED
用戶啟用了數據節約,但是該應用在白名單中,故不受限制.
下面的示例代碼來自官方文檔,給出了使用ConnectivityManager.isActiveNetworkMetered()
和ConnectivityManager.getRestrictBackgroundStatus()來判斷當前Data Saver設置狀態的方法.
注意:這段代碼目前還不能使用,RESTRICT_BACKGROUND_STATUS_ENABLED等三個狀態值尚不可用.
使用adb命令進行測試
$ adb shell dumpsys netpolicy
生成一個報告,包括當前的全部後台網絡限制的設置,白名單中的包的UID,其他已知包的網絡限制.
$ adb shell cmd netpolicy
顯示一個完整的網絡策略管理命令列表.
$ adb shell cmd netpolicy set restrict-background <boolean>
啟用或者禁用數據節約命令
$ adb shell cmd netpolicy add restrict-background-whitelist <UID>
將指定的包的UID加入白名單中
$ adb shell cmd netpolicy remove restrict-background-whitelist <UID>
從白名單中將指定包的UID移除
Android 活動(Activity)活動代表了一個具有用戶界面的單一屏幕,如 Java 的窗口或者幀。Android 的活動是 ContextThem
Android Fragment的生命周期和Activity類似,實際可能會涉及到數據傳遞,onSaveInstanceState
一、Android四大組件1. Activity生命周期:2. Service生命周期:Service的生命周期長,沒有用戶界面,可以用來開發監控程序。Service有兩
NDK的發布,使“Java+C”的開發方式終於轉正,成為官方支持的開發方式。NDK將是Android平台支持C開發的開端,今天我們開始ndk的學習