内存
数据类型
- byte 1 字节 8bit
- short 2 字节 16bit
- int 4 字节 32 bit
- long 8 字节 64bit
- float 4 字节 32bit
- double 8 字节 64bit
- boolean 1 字节 8bit
- chart 2 字节 16bit
自动装箱
基本类型与包装类之间的自动转换
Integer i = 0; 等价与 Integer i = new Integer(0);
对应循环的自增自减不要使用包装类
Sparse 稀疏
安卓提供了很多来避免自动装箱拆箱的数据结构
SparseBooleanArray ==> HashMap<Integer,Boolean>
SparseLongArray ==> HashMap<Integer,Long>
SparseArray<E> ==> HashMap<Integer,E>
ArrayMap 他是安卓提供的一个用数组实现的Map 接口的一个类 内部使用了两个数组 一个对于key 一个对应value
提供了扩容和缩容的实现 相比HashMap 在数据量小的情况下效率更高
集合
Iterator 循环
while 循环
for 循环
在耗时上 Iterator 最慢 for 最快
枚举
在安卓中尽量不使用枚举
替代方案
@IntDef
public static final int VALUE1 = 1;
public static final int VALUE2 = 2;
public static final int VALUE3 = 3;
@IntDef(flag = true,value = {VALUE1,VALUE2,VALUE3})
public @interface MODE{
}
public void setShape(@MODE int mode){
}
常量
public static int PS1 = 3; //静态变量
public final static int PS2 = 3; //静态常量
//代码中常量应该用final 和 static 来修饰 这样才节约内存
流
错误写法
public void stream(){
InputStream in = null;
OutputStream out = null;
try{
in = new FileInputStream("xxx");
out = new FileOutputStream("xxxy");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (in!=null){
in.close();
}
if (out!=null){
out.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
正确写法 防止一个关闭异常导致 一个没有关闭
public void stream(){
InputStream in = null;
OutputStream out = null;
try{
in = new FileInputStream("xxx");
out = new FileOutputStream("xxxy");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (in!=null){
in.close();
}
}catch (IOException e){
e.printStackTrace();
}
try {
if (out!=null){
out.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
内存设计模式
对象池
对象池用于重用已分配的对象避免了内存抖动
注意 对象池要有上限 当向对象池请求一个对象时如果对象池中没有可用的对象并且在创建对象后大于了对象池的上限者要等待一个对象被释放才行
/**
* 对象池 freePool+lentPool 中的对象数量 不能大于最大值
* 还可以模仿 Message 中的对象来写对象池
* @param <T>
*/
public abstract class ObjectPool<T> {
private SparseArray<T> freePool;
private SparseArray<T> lentPool;
private int maxCapacity;
public ObjectPool(int initialCapacity,int maxCapacity){
initialize(initialCapacity);
this.maxCapacity = maxCapacity;
}
public ObjectPool(int maxCapacity){
this(maxCapacity/2,maxCapacity);
}
public T acquire(){
T t = null;
synchronized (freePool){
int freeSize = freePool.size();
for (int i = 0; i <freeSize ; i++) {
int key = freePool.keyAt(i);
t = freePool.get(key);
if (t!=null){
this.lentPool.put(key,t);
this.freePool.remove(key);
return t;
}
}
if (t == null && lentPool.size()+freeSize<maxCapacity){
t = create();
lentPool.put(lentPool.size()+freeSize,t);
}
}
return t;
}
public void release(T t){
if (t == null){
return;
}
int key = lentPool.indexOfValue(t);
restore(t);
this.freePool.put(key,t);
this.lentPool.remove(key);
}
protected abstract void restore(T t);
protected void initialize(int initialCapacity){
freePool = new SparseArray<>();
lentPool = new SparseArray<>();
for (int i = 0; i <initialCapacity ; i++) {
lentPool.put(i,create());
}
}
protected abstract T create();
}
享元模式
Android 组件泄漏
Activity
泄漏的场景 Context
静态字段
eg private static View view;
非静态内部类
public class MemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// 这个非静态内部类 持有一个 MemoryActivity
private class MyAsyncTask extends AsyncTask{
@Override
protected Object doInBackground(Object[] objects) {
return doSomeStuff();
}
private Object doSomeStuff() {
//执行一些耗时任务
return new Object();
}
}
}
如何改进 :转化为 静态类 并使用软引用来处理 activity的引用问题
public class MemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private static class MyAsyncTask extends AsyncTask{
private WeakReference<MemoryActivity> activity;
public MyAsyncTask(MemoryActivity activity){
this.activity = new WeakReference<>(activity);
}
@Override
protected Object doInBackground(Object[] objects) {
return doSomeStuff();
}
private Object doSomeStuff() {
//执行一些耗时任务
return new Object();
}
}
}
单例
在单例中存在activity的引用是很可怕的 会导致activity 一直释放不了
public class Singleton {
private static Singleton singleton;
private CallBack callBack;
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
public CallBack getCallBack(){
return callBack;
}
public void setCallBack(CallBack callBack){
this.callBack = callBack;
}
public interface CallBack{
void callBack();
}
}
public class MemoryActivity extends AppCompatActivity implements Singleton.CallBack {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 导致内存泄漏
Singleton.getInstance().setCallBack(this);
}
@Override
public void callBack() {
}
}
public class MemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 导致内存泄漏
Singleton.getInstance().setCallBack(new Singleton.CallBack() {
@Override
public void callBack() {
}
});
}
}
// 解决这个问题 可以在 onDestory 中 setCallBack(null)
//解决方案二 使用 弱引用
public class Singleton {
private static Singleton singleton;
private WeakReference< CallBack> callBack;
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
public CallBack getCallBack(){
return callBack.get();
}
public void setCallBack(CallBack callBack){
this.callBack =new WeakReference<>(callBack);
}
public interface CallBack{
void callBack();
}
}
Handler 导致内存泄漏
public class MemoryActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 导致内存泄漏
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
textView.setText("xxcc");
}
},1000);
}
}
//改进方案
public class MemoryActivity extends AppCompatActivity {
private TextView textView;
private Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 导致内存泄漏
handler.postDelayed(new MyRunnable(textView),1000);
}
private static class MyRunnable implements Runnable{
private WeakReference<TextView> textView;
public MyRunnable(TextView textView){
this.textView = new WeakReference<>(textView);
}
@Override
public void run() {
TextView txt = textView.get();
if(txt!=null){
txt.setText("ffff");
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
内存API
ActivityManager
void memoryInfo(){
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//返回给应用程序的兆字节 可据此估算应用程序可用内存空间
int memoryClass = am.getMemoryClass();
//与getMemoryClass 一样 但该方法适用于向系统申请大块堆内存的情况
int largeMemoryClass = am.getLargeMemoryClass();
//获取系统内存状态
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
am.getMemoryInfo(memoryInfo);
//可用的系统内存
long availMem = memoryInfo.availMem;
//用于指示系统是否处于低内存状态
boolean lowMemory = memoryInfo.lowMemory;
//一个内存的阈值 高于该值 我们就认为系统处于低内存状态 便会开始删除进程
long threshold = memoryInfo.threshold;
//得到运行进程的信息
List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo runInfo : runningAppProcesses) {
//进程 最新的内存level 报告 该值对应
//onTrimMemory(int level) 值指示当前内存的状态
int lastTrimLevel = runInfo.lastTrimLevel;
//进程的重要性 每个进程有自己的优先级 系统会根据进程的优先级来决定移除谁
int importance = runInfo.importance;
}
// 当前设备是否被认为是一个低内存的设备 可以根据该值关闭程序的某些特性
boolean lowRamDevice = am.isLowRamDevice();
//在调试期间找出内存相关的问题 只能在调试的时候使用
//可以为堆内存设置一个观察报警 当堆的大小到达所设定的值时
// 设备会自动生成一个堆转储 我们可以对该结果进行分析 看是否发生内存泄漏
long pssSize = 10000;
am.setWatchHeapLimit(pssSize);
//移除所设置的观察报警
am.clearWatchHeapLimit();
}
组件和内存管理
应用可见:它正在运行处于LRU 顶部
应用不可见:它在LRU 列表中的位置开始下降 直到列表的底部被系统销毁 或者有变为可见
ComponentCallbacks
ComponentCallbacks2
void onTrimMemory(@TrimMemoryLevel int level);
//应用不可见 它已经到LRU的底部 应用可能随时会被杀死
static final int TRIM_MEMORY_COMPLETE = 80;
//应用不可见 它已经到LRU的中间位置 并且当前运行环境内存较少
static final int TRIM_MEMORY_MODERATE = 60;
//应用不可见 它在LRU 中的位置在下降 并且当前运行环境内存较少
static final int TRIM_MEMORY_BACKGROUND = 40;
//应用程序不可见 告诉您你的应用不可见 可以释放一些内存 用于自保
static final int TRIM_MEMORY_UI_HIDDEN = 20;
//应用程序可见 内存紧张并且其他进程可能被销毁来获得更多的内存
static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;
//应用程序可见 设备的可用内存越来越少
static final int TRIM_MEMORY_RUNNING_LOW = 10;
//应用程序可见 系统即将进入低内存状态
static final int TRIM_MEMORY_RUNNING_MODERATE = 5;
调试工具
LogCate
Dalvik 内存日子在LogCate 中打印的格式如下
<GcReason> <AmountFreed>,<HeapStats>,<ExternalMemoryStats>,<PauseTime>
eg:
D/dalvikvm(9932): GC_CONCURRENT freed 1394k, 14% free 32193K/37262K,
external 18524K/24185K, paused 2ms
GcReason: 垃圾回收器触发的原因 当发生GC 后 所有的线程都被阻塞等待回收完成
- GC_CONCURRENT: 当堆空间需要被清理时 所触发的GC事件
- GC_FOR_MALLOC: 当请求分配新内存 但是已经没有足够的内存
- GC_HPPOF_DUMP_HEAP: 为分析堆内存而发起的一个debug 请求
- GC_EXPLICIT: 强制调用Ssystem.gc()
- GC_EXTERNAL_ALLOC: 对外部分配的内存进行GC
AmountFreed: 垃圾回收器所要释放的内存数量
HeapStats:这个代指的是内部堆 它有以下这些组成
- 可用的堆空间占总量的百分百
- 已经分配的堆大小
- 堆的总大小
ExternalMemoryStats:这个指的是系统版小于等于 API Level 10 的设备的外部存储
- 分配的外部空间大小
-
外部空间的总大小
ART 打印格式
I/art: <GcReason> <GcName> <ObjectsFreed>(<SizeFreed>) AllocSpace Objects,
<LargeObjectsFreed>(<LargeObjectSizeFreed>) <HeapStats> LOS Objects,<PauseTimes>
eg:
I/art: Explicit concurrent mark sweep GC freed 125742(6MB) AllocSpace objects,37(576KB) LOS objects, 22% free, 25MB/32MB, paused 1.621ms total 73.285ms
GcReason 垃圾回收器触发的原因
- Concurrent: 一种并发执行的GC 事件 此类事件执行在一个不同的线程 所以不会迫使其他程序的线程挂起包括UI
- Alloc :当请求分配新内存然而 已经没有足够的空间所触发的GC事件 此时 所有应用程序线程被阻塞直到垃圾回收结束
- Explicit: 主动调用System.gc
- NativeAlloc: 请求原生内存分配时所触发的gc
- CollectorTransition:在低内存设备上 转变垃圾回收器所引发的GC事件
- HomogenousSpaceCompact: 当系统需要减少内存使用对内存碎片进行整理所触发的gc
- DisableMovingGc:当系统调用一个特殊的内部方法GetPrimitiveArrayCritical 时用于提示垃圾回收事件被阻塞
- HeapTrim:它用于提示在堆内存完成整理之前,垃圾回收事件被阻塞
GcName: ART 使用了不同的垃圾回收器来释放内存
- Concurrent mark sweep (CMS)
- Concurrent partial mark sweep
- Concurrent sticky mark sweep
- Marksweep+semispace
ObjectFreed: 释放的对象数量
SizeFreed:释放的对象大小
LargeObjectFreed: 从大对象空间 释放的对象的数量
LargeObjectSizeFreed: 从大对象空间释放对象的大小
HeapStats: 与dalvik类似
PauseTimes: 与dalvik类似
StrictMode 严格模式
StrictMode启用后 在后台运行当问题发生时 他会按照我们定制的策略来通知我们
我们需要定义两个东西:要追踪什么 以及如何追踪
if (BuildConfig.DEBUG){
StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog() // 当问题发生时 日志会记录到 LogCat 中
.build();
StrictMode.setVmPolicy(policy);
}
detectAll() 检测每个可疑的行为
detectActivityLeaks: 检测Activity 泄漏
detectLeakedClosableObjects 检测是否存在未关闭的可关闭的对象
detectLeakedRegistrationObjects 检测当Context被销毁时 是否存在泄漏
penaltyDropBox 检测出问题时会弹出对话框
penaltyDeath 检测出问题时 进程会被杀死
注意 不要在正式环境中使用
Dumpsys
adb shell service list :
adb shell dumpsys meminfo
adb shell dumpsys procstats -hours 3
adb shell dumpsys procstats 包名 -hours 3
线程
创建线程
继承Thread 或 实现 Runnable 接口
设置 线程名 设置 线程优先级
Thread thread = new Thread();
thread.setName("thread");
thread.setPriority(Thread.MAX_PRIORITY);
//最小优先级
public final static int MIN_PRIORITY = 1;
//中等
public final static int NORM_PRIORITY = 5;
//最高
public final static int MAX_PRIORITY = 10;
线程安全
synchronized
java.util.concurrent
Lock
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
UI线程
安卓为什么不能通过非UI线程访问View
因为安卓的UI线程非线程安全 如果View 能被不同的线程访问或修改 那么就可能会在程序执行期间产生不可预期的行为或 并发错误
主线程可以处理一些非UI 的操作 但尽量不要这样做
Handler
Message
MessageQueue
Handler
ThreadLocal
Looper
从操作的角度看
Handler handler = new Handler();
handler.post(null);
handler.postDelayed();
handler.sendMessage();
handler.removeCallbacks();
handler.removeMessages();
handler.removeCallbacksAndMessages();
HandlerThread
AsyncTask
Executor
因AsyncTask 容易导致内存泄漏 因此提供Loader 框架来解决
LoaderManager loaderManager = getLoaderManager();
loaderManager.initLoader(1, null, new LoaderManager.LoaderCallbacks<Object>() {
@Override
public Loader<Object> onCreateLoader(int id, Bundle args) {
//创建Loader
AsyncTaskLoader loader = new AsyncTaskLoader(MyThreadActivity.this) {
@Override
public Object loadInBackground() {
return null;
}
};
return loader;
}
@Override
public void onLoadFinished(Loader<Object> loader, Object data) {
// Loader 加载完成 结果返回
}
@Override
public void onLoaderReset(Loader<Object> loader) {
//Loader 重新加载 数据不再可用
}
});
Loader<Object> loader = loaderManager.getLoader(1);
Service
从使用的角度来我们说下
/**
*
* @param intent startService 传递过来的值
* @param flags 可根据该值或Intent的类型
* 0 默认值
* START_FLAG_REDELIVERY 按照返回策略被重新递送 该Intent之前已经递送过一次但任务未完成被终止因此Intent再次递送
* START_FLAG_RETRY:Intent 正要递送Service意外终止接着Intent再次递送
* @param startId 服务调用者的id 可根据该值获知服务是否被同一组件再次启动还是被终断后重新启动
* @return
* START_STICKY 返回该值 当中断发生后 Servie 会被重新创建 在重新创建过程中系统会给它发送一个值为null的Intent
* 如果希望Service 遇到意外而终止可以使用该值
* START_NOT_STICKY 当Service终止时 不会被重新创建
* START_REDELIVER_INTENT :当Service不是有stopSelf stopService方法终止时 当再次启动时会收到Service 被销毁前所收到的最后一个Intent
* START_STICKY与START_REDELIVER_INTENT类似 不过系统给的值不一样
*
*
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = START_REDELIVER_INTENT;
return super.onStartCommand(intent, flags, startId);
}
bindService 何时使用
如果你希望让某个组件与Service直接通信 那么bindService 是一个正确的选择
IntentService
平台提供的一个Service 内置了一个ThreadHandler
在onStartCommand() 方法中 默认返回值为START_NOT_STICKY 因此当以外终止不会重新递送但可以使用 setIntentRedelivery 方法来改变这中行为
onBind 方法默认为返回null
handleIntent 方法来处理传入的Intent
Messenger
public class MessengerService extends Service {
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
Messenger messenger;
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
public void onCreate() {
super.onCreate();
messenger = new Messenger(handler);
}
}
public class MyThreadActivity extends AppCompatActivity {
Messenger messenger;
@Override
protected synchronized void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action = "service-start";
Intent intent = new Intent(action);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
messenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
void onClick(){
Message message = Message.obtain();
try {
if (messenger!=null){
messenger.send(message);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
BroadcastReceiver 异步
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//goAsync 延长BroadcastReceiver 生命周期
//PendingResult 用于处理后台线程的状态 它的生命周期一直延续到pendingResult.finish() 调用为止
final PendingResult pendingResult = goAsync();
new Thread(new Runnable() {
@Override
public void run() {
//执行一些任务
pendingResult.finish();
}
}).start();
}
}
ContentProvider 异步
大部分时候ContentProvider都是位于不同的进程中的远程对象 它不能直接被访问但可以使用
ContentResolver 对ContentProvider 进行 查 增 删 操作 但是要避免在UI线程中操作
为此安卓提供了AsyncQueryHandler 类 它包装了ContentResolver 便于在ContentProvider 上开启异步操作
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AsyncQueryHandler asyncQueryHandler = new MyAsyncQueryHandler(getContentResolver());
asyncQueryHandler.startQuery(1,null,null,null,null,null,null);
}
class MyAsyncQueryHandler extends AsyncQueryHandler{
public MyAsyncQueryHandler(ContentResolver cr) {
super(cr);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 处理查询
super.onQueryComplete(token, cookie, cursor);
}
@Override
protected void onInsertComplete(int token, Object cookie, Uri uri) {
// 处理新增
super.onInsertComplete(token, cookie, uri);
}
@Override
protected void onUpdateComplete(int token, Object cookie, int result) {
//处理更新
super.onUpdateComplete(token, cookie, result);
}
@Override
protected void onDeleteComplete(int token, Object cookie, int result) {
//处理删除操作的代码
super.onDeleteComplete(token, cookie, result);
}
}
重复性任务
Timer
//是创建周期性任务最常用的方法
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
}
},1000,10); //第一执行延迟 1000 毫秒 以后 延迟10 毫秒
//当任务完成后一定 调用该方法 否者资源一直被占用
timer.cancel();
ScheduledExecutorService
它比Timer 更灵活更强大 因此对于短时间周期性任务 它是首选
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(index++);
}
},2,1,TimeUnit.SECONDS);
//当不需要在执行时使用
//executorService.shutdown();
AlarmManager
它可以在特殊的时间启动一个新的组件 开启一些周期性的操作
我们可以使用两种方式启动一个新的重复闹铃
相对于其他类更高效 但它不适合短期任务 因此在可能的情况下尽量使用它代替Timer和ScheduledExecutorService
alarmManager.setRepeating();
alarmManager.setInexactRepeating();
AlarmManager alarmManager = (AlarmManager) getSystemService(Activity.ALARM_SERVICE);
Intent intent = new Intent();
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,0,intent,0);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,1,1,pendingIntent);
网络
协议
URLConnection 子类所支持的协议
- HTTP HTTPS 主要是HttpUrlConnection
- FTP
- File 文件系统的本地文件 它是基于文件的URI
- JAR 处理jar文件 JarUrlConnection
该类也允许开发者使用URLStreamHandlerFactory 对象添加其它协议
方法
HttpURLConnection 提供的主要请求方法
- GET :默认 如果你正好使用它就不要在进行设置了
- POST:可通过调用 setDoOutput() 方法使用POST
其他方法可以通过setRequestMethod() 方法进行设置
头部
在请求期间添加一些额外的数据 让服务器知道应用程序的状态用户相关的一些信息
将Header添加到请求中 以及从响应中得到header 分别有下面两个特殊方法来完成
connection.setRequestProperty("key","value");
connection.getHeaderFields();
超时
支持两种类型的超时 连接超时 读取超时
connection.setConnectTimeout(0);
connection.setReadTimeout(0);
这个两个方法默认值都是0 客户端本身没有设置超时因此超时时间交由TCP 传输层处理
内容
connection.getContent() 方法以InputStream 的形式 得到响应的内容
内容中的一些参数可被读取 而在响应中有3 个Header 控制着如何读取
- Content length: 内容字节长度 通过 connection.getContentLength(); 获取
- Content type 内容的mime类型 connection.getContentType() 获取
- Content encoding 内容所采取的编码类型 connection.getContentEncoding(); 获取
压缩
content-encoding 的值被用于指定响应内容的压缩类型 客户端可使用Accept-Encoding Header 以请求对响应内容进行特殊编码 下方列出该Header可以指定的值
- null 或 identity :要求不对响应内容进行编码
- gzip:这是默认值 客户端总是要求使用gzip压缩内容
还有一点很重要 在请求中添加Accept-Encoding 并不会自动对响应进行解压缩 如果响应内容被压缩过 则需要使用GZIPInputStream 而不是InputStream 对内容进行读取
连接类型
我们在请求时根据当前可用网络的类型动态改变应用程序的行为
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
int type = activeNetworkInfo.getType();
//TYPE_MOBILE
//TYPE_WIFI
//TYPE_WIMAX
//TYPE_ETHERNET 以太网
//TYPE_BLUETOOTH 蓝牙
预先获取
为了尽量减少连接的次数避免应用程序打开后出现空白情况可以使用预先为应用发起请求将各个部分的数据内容预先载入好 如果可能的话可以让应用程序在后台下载数据
如果设计不当会导致宽带过量使用 耗电严重
队列连接
请求可以不被立即执行他们会组成一个队列在将来的某个时刻进行批量连接 这样可以避免频繁的操作无线网导致更多的耗电
对响应进行缓存
HttpResponseCache 类它将响应以缓存的形式保存到文件中 可以重用响应
使用时 要对它设置缓存大小 他需要设定一个上限 当达到该上限时 便删除不必要的条目释放磁盘空间
try {
File httpcacheDir = new File(getCacheDir(),"http");
long httpCacheSize = 10000;
//先缓存到内存中
HttpResponseCache.install(httpcacheDir,httpCacheSize);
} catch (IOException e) {
e.printStackTrace();
}
//当某个时候同步到磁盘
HttpResponseCache httpResponseCache = HttpResponseCache.getInstalled();
if (httpResponseCache!=null){
httpResponseCache.flush();
}
下一步要做的就是 决定每个请求是否需要缓存
这个就取决于我们对请求的需求
在请求中指定我们期望的行为
connection.setRequestProperty("Cache-Control","POLICY");
POLICY 有如下值
- no-cache: 不缓存 所有相关数据都会重新下载
- max-age=SECONDS: 客户端如果在指定的时间内 再次访问 则不会从服务器请求 而读取缓存数据
- max-stale=SECONDS:客户端能接受过期对象但其在过期时间不超过指定的值
- only-if-cached:客户端强制使用所缓存的响应,如果没有缓存的响应对象 则抛出异常
Last-Modified
面对服务器的某个静态资源时,我们可以通过Last-Modified Header得到该资源上次被修改的日期
除此之外 我们可以通过Expire Header 来获知上一次请求所返回的内容是否仍然有效,一个好的做法将资源
以及该资源的上次修改日期一起缓存再将日期与服务器上的日期做对比 我们可以运用我们的缓存策略对缓存资源以及图形界面布局进行更新
long headerFieldDate = connection.getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
If-Modified-Since
如果请求中含有If-Modified-Since Header 并且Header中带有客户端上次请求该资源的日期那么服务器会根据
If-Modified Header 响应一个不同的状态码
- 200 :远程资源在客户端的上一次请求之后发生过修改 响应包含预期的资源
- 304:远程资源没有修改过 响应不包含内容
这个需要服务器实现http 1.1的协议
long lastCheckTime = System.currentTimeMillis();
URL url = new URL("http://www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("If-Modified-Since",lastCheckTime+"");
//单独提供一个函数来设置
conn.setIfModifiedSince(lastCheckTime);
int statusCode = conn.getResponseCode();
switch (statusCode){
case 200:
//内容已修改
//更新缓存中的内容
// 更新缓存中的lastCheckTime
break;
case 304:
//内容没有修改
//获取缓存中的内容
break;
default:
break;
}
指数退避算法
有时候我们不得不用轮询 轮询会给服务器带来问题 当问题发生时要采取应对策略
指数退避可以让服务器从大量无用的请求中释放出来
public class Backoff{
private static final int BASE_DURATION = 1000;
private static final int [] BACK_OFF = new int[]{1,2,4,8,16,32,64};
public static InputStream execute(String urlString){
for (int i = 0; i < BACK_OFF.length; i++) {
try{
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
return connection.getInputStream();
}catch (SocketException | SSLHandshakeException e){
try {
Thread.sleep(BACK_OFF[i]*BASE_DURATION);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}catch (Exception e){
return null;
}
}
return null;
}
}