- 本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布
寫在前面的話,前段時間寫了一篇文章 二十三種設(shè)計模式,寫的不詳細,因為如果要寫的很詳細,估計一年半載都寫不完,完全都是按照自己理解,每個設(shè)計模式就畫了一個簡單的圖,同時完成了一個小Demo,哪知道這篇文章成了我在簡書點贊最高的一篇文章,實在有點受寵若驚,謝謝各位大佬點贊?。?!
這篇文章分為五個部分代碼優(yōu)化、圖片優(yōu)化、布局優(yōu)化、網(wǎng)絡(luò)優(yōu)化、電量優(yōu)化,盡量每個方法都寫了小的Demo!

代碼優(yōu)化:不要做多余的工作,盡量避免次數(shù)過多的內(nèi)存的分配,(需要對api有一定的熟悉)
數(shù)據(jù)集合的使用:建議最佳的做法是可能使用ArrayList作為首選,只要你需要使用額外的功能的時候,或者當程序性能由于經(jīng)常從表的中間進行插入和刪除而變差的時候,才會去選擇LinkedList。HashMap性能上于HashTable相當,因為HashMap和HashTable在底層的存儲和查找機制是一樣的,但是TreeMap通常比HashMap要慢。HashSet總體上的性能比TreeSet好,特別實在添加和查詢元素的時候,而這兩個操作也是最重要的操作。TreeSet存在的唯一的原因是它可以維持元素的排序的狀態(tài),所以當需要一個排好序的Set,才使用TreeSet。因為其內(nèi)部的結(jié)果歐支持排序,并且因為迭代是我們更有可能執(zhí)行的操作,所以,用TreeSet迭代通常比用HashSet要快。
/**
* 建議最佳的做法是可能使用ArrayList作為首選,只要你需要使用額外的功能的時候,或者當程序性能由于經(jīng)常從表的中間進行
* 插入和刪除而變差的時候,才會去選擇LinkedList
*/
//數(shù)據(jù)結(jié)構(gòu)的選擇,對于ArrayList,插入的操作特別高昂,并且其代價將隨著列表的尺寸的增加而增加
ArrayList list=new ArrayList();
//需要執(zhí)行大量的隨機的訪問,這個不是一個好的選擇,如果是使用迭代器在列表中插入新的數(shù)據(jù),使用這個,比較低廉(插入和移除的代價比較低廉)
LinkedList linkedList=new LinkedList();
//HashMap性能上于HashTable相當,因為HashMap和HashTable在底層的存儲和查找機制是一樣的,但是TreeMap通常比HashMap要慢
HashMap<String,String> hashMap=new HashMap<>();
/**
* HashSet總體上的性能比TreeSet好,特別實在添加和查詢元素的時候,而這兩個操作也是最重要的操作。TreeSet存在的唯一的原因是它
* 可以維持元素的排序的狀態(tài),所以當需要一個排好序的Set,才使用TreeSet。因為其內(nèi)部的結(jié)果歐支持排序,并且因為迭代是我們更有可能
* 執(zhí)行的操作,所以,用TreeSet迭代通常比用HashSet要快
*/
HashSet<String> hashSet=new HashSet<>();
TreeSet<String> treeSet=new TreeSet<>();
SparseArray是Android特有的稀疏數(shù)組的實現(xiàn),他是Integer和Object的為例進行的一個映射用于代替 HsahMap<Integer,<E>>,提高性能。
- SparseArray
- 線程不安全(多線程中需要注意)
- 由于要進行二分查找,(可以是有序的),SparseArray會對插入的數(shù)據(jù)按照Key的大小順序插入
- SparseArray對刪除操作做了優(yōu)化,它并不會立刻刪除這個元素,而是通過設(shè)置標記位(DELETED)的方法,后面嘗試重用。
內(nèi)部核心的實現(xiàn)(二分查找)
/**
* 二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。
* 但是,折半查找要求線性表必須采用順序存儲結(jié)構(gòu),而且表中元素按關(guān)鍵字有序排列。
* @param array
* @param size
* @param value
* @return
*/
//二分查找
static int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
/**
* >>>與>>唯一的不同是它無論原來的最左邊是什么數(shù),統(tǒng)統(tǒng)都用0填充。
* —比如你的例子,byte是8位的,-1表示為byte型是11111111(補碼表示法)
* b>>>4就是無符號右移4位,即00001111,這樣結(jié)果就是15。
* 這里相當移動一位,除以二
*/
final int mid = (lo + hi) >>> 1;
final int midVal = array[mid];
if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid; // value found
}
}
//按位取反(~)運算符 ,沒有找到,這個數(shù)據(jù)
return ~lo; // value not present
}
SpareArray 家族有以下的四類
//SpareArray 家族有以下的四類
//用于替換 HashMap<Integer,boolean>
SparseBooleanArray sparseBooleanArray=new SparseBooleanArray();
sparseBooleanArray.append(1,false);
//用于替換 HashMap<Integer,Interger>
SparseIntArray SparseIntArray=new SparseIntArray();
SparseIntArray.append(1,1);
//用于替換 HashMap<Integer,boolean>
@SuppressLint({"NewApi", "LocalSuppress"})
SparseLongArray SparseLongArray=new SparseLongArray();
SparseLongArray.append(1,1111000L);
//用于替換 HashMap<Integer,boolean>
SparseArray<String> SparseArray11=new SparseArray<String>();
SparseArray11.append(1,"dd");
SpareArray中的設(shè)計模式:原型模式:這里有使用到了的,原型模式內(nèi)存中復制數(shù)據(jù)的,不會調(diào)用到類的構(gòu)造的方法,而且訪問的權(quán)限對原型模式無效
- 優(yōu)點: 1、性能提高。 2、逃避構(gòu)造函數(shù)的約束。
- 缺點:
1、配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難,但對于已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環(huán)結(jié)構(gòu)的時候。
2、必須實現(xiàn) Cloneable 接口。
SparseArray<String> clone = sparseArray.clone();
Handler正確的使用姿勢(???)
下面的代碼是很多人都會這樣寫,這樣會造成內(nèi)存泄漏
原因:Handler是和Looper以及MessageQueue一起工作的,在安卓中,一個 應用啟動了,系統(tǒng)會默認創(chuàng)建一個主線程服務(wù)的Looper對象 ,該Looper對象處理主線程的所有的Message消息,他的生命周期貫穿整個應用。在主線程中使用的Handler的都會默認的綁定到這個looper的對象,咋主線程中創(chuàng)建handler的時候,它會立即關(guān)聯(lián)主線程Looper對象的MessageQueue,這時發(fā)送到的MessageQueue 中的Message對象都會持有這個Handler的對象的引用,這樣Looper處理消息時Handler的handlerMessage的方法,因此,如果Message還沒有處理完成,那么handler的對象不會立即被垃圾回收
/*-------------old ide 已經(jīng)告訴我們這里可能內(nèi)存泄露-------------------*/
@SuppressLint("HandlerLeak")
private final Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
// mHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// // TODO: 2018/4/28 用戶即使退出了應用的話,這里也是會執(zhí)行的 ,通過日記的觀察
// //這里有可能用戶退出了Activity
// System.out.println("shiming mHandler --todo");
// }
// },5000);
如何避免,有兩點的可以嘗試
- 1、在子線程中使用Handler,但是Handler不能再子線程中使用,需要開發(fā)者自己創(chuàng)建一個Looper對象,實現(xiàn)難,方法怪
- 2、將handler聲明為靜態(tài)的內(nèi)部類,靜態(tài)內(nèi)部類不會持有外部類的引用,因此,也不會引起內(nèi)存泄露,
InnerHandler innerHandler = new InnerHandler(this);
innerHandler.postDelayed(new Runnable() {
@Override
public void run() {
//這里這要 退出了 就不會執(zhí)行了
System.out.println("shiming innerHandler --todo");
}
},5000);
public class InnerHandler extends Handler{
//弱應用,在另外一個地方會講到
private final WeakReference<HandlerActivity> mActivityWeakReference;
public InnerHandler(HandlerActivity activity){
mActivityWeakReference=new WeakReference<HandlerActivity>(activity);
}
}
Context正確的姿勢
//Context的種類
//Application 全局唯一的Context實例
Application application = getApplication();
Context applicationContext = application.getApplicationContext();
//不同的Activity,得到這個Context,是獨立的,不會進行復用
Context baseContext = this.getBaseContext();
MyBroadcaseRecriver myBroadcaseRecriver = new MyBroadcaseRecriver();
//ContentProvider 中的Context
/**
*如果創(chuàng)建單利必須需要使用到context對象
*/
//這樣不會內(nèi)存泄露,不用改動單利類中代碼
SingleInstance.getSingleInstance(getApplication().getApplicationContext());
- 單例模式,如果不得不傳入Context,由于單例一直存在會導致Activity或者是Service的單例引用,從而不會被垃圾回收, Activity中的關(guān)聯(lián)的View和數(shù)據(jù)結(jié)構(gòu)也不會被釋放,正確的方式應該使用Application中的Context
class SingleInstance {
private static SingleInstance sSingleInstance;
private final Context mContext;
private SingleInstance(Context context){
mContext = context;
}
// 因為每次調(diào)用實例都需要判斷同步鎖,很多項目包括很多人都是用這種的
// 雙重判斷校驗的方法,這種的方法看似很完美的解決了效率的問題,但是它
// 在并發(fā)量不多,安全性不太高的情況下能完美的運行,但是,
// 在jvm編譯的過程中會出現(xiàn)指令重排的優(yōu)化過程,這就會導致singleton實際上
// 沒有被初始化,就分配了內(nèi)存空間,也就是說singleton!=null但是又沒有被初始化,
// 這就會導致返回的singletonthird返回的是不完整的
public static SingleInstance getSingleInstance(Context context){
if (sSingleInstance==null){
synchronized (SingleInstance.class){
if (sSingleInstance==null) {
// TODO: 2018/4/28 注意外面?zhèn)魅氲腸onext對象是否,是哪個
sSingleInstance= new SingleInstance(context);
//第二種是改動代碼,使用application 中的context變量
sSingleInstance= new SingleInstance(context.getApplicationContext());
}
}
}
return sSingleInstance;
}
}
- java 四種引用方式和引用隊列的解釋
和java一樣,Android也是基于垃圾回收(GC)機制實現(xiàn)內(nèi)存的自動的回收,垃圾回收的算法“標記-清除(Mark-Sweep)” “標記壓縮(Mark-Compact)“復制算法(Copying)以及引用計數(shù)算法(Reference-Counting),安卓的虛擬機(Dalvik還是Art),都是使用標記清除算法。 在Android中,內(nèi)存泄露是指不再使用的對象依然占有內(nèi)存,或者是他們占用的內(nèi)存沒有得到釋放, 從而導致內(nèi)存空間不斷的減少,由于可用的空間比較少,發(fā)生內(nèi)存泄露會使得內(nèi)存更加的緊張,甚至最終由于內(nèi)存耗盡而發(fā)生的OOM,導致應用的崩潰。
* 和java一樣,Android也是基于垃圾回收(GC)機制實現(xiàn)內(nèi)存的自動的回收,垃圾回收的算法“標記-清除(Mark-Sweep)”
* “標記壓縮(Mark-Compact)“復制算法(Copying)以及引用計數(shù)算法(Reference-Counting),安卓的虛擬機(Dalvik還是Art),
* 都是使用標記清除算法”
*
mTextView1.setText(des1);
/**
* 在Android中,內(nèi)存泄露是指不再使用的對象依然占有內(nèi)存,或者是他們占用的內(nèi)存沒有得到釋放,
* 從而導致內(nèi)存空間不斷的減少,由于可用的空間比較少,發(fā)生內(nèi)存泄露會使得內(nèi)存更加的緊張,
* 甚至最終由于內(nèi)存耗盡而發(fā)生的OOM,導致應用的崩潰
*/
mTextView2.setText(des2);
- 強引用:Java中里面最廣泛的使用的一種,也是對象默認的引用類型,如果又一個對象具有強引用,那么垃圾回收器是不會對它進行回收操作的,當內(nèi)存的空間不足的時候,Java虛擬機將會拋OutOfMemoryError錯誤,這時應用將會被終止運行
- 軟引用:一個對象如果只有一個軟引用,那么當內(nèi)存空間充足是,垃圾回收器不會對他進行回收操作,只有當內(nèi)存空間不足的時候,這個對象才會被回收,軟引用可以用來實現(xiàn)內(nèi)存敏感的高速緩存,如果配合引用隊列(ReferenceQueue使用,當軟引用指向?qū)ο蟊焕厥掌骰厥蘸?,java會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中)
- 弱引用:弱引用是比軟引用更弱的一種的引用的類型,只有弱引用指向的對象的生命周期更短,當垃圾回收器掃描到只有具有弱引用的對象的時候,不敢當前空間是否不足,都會對弱引用對象進行回收,當然弱引用也可以和一個隊列配合著使用
- 引用隊列:ReferenceQueue一般是作為WeakReference SoftReference 的構(gòu)造的函數(shù)參數(shù)傳入的,在WeakReference 或者是 softReference 的指向的對象被垃圾回收后,ReferenceQueue就是用來保存這個已經(jīng)被回收的Reference
String des3="強引用:Java中里面最廣泛的使用的一種,也是對象默認的引用類型,如果又一個對象具有強引用,那么垃圾回收器是不會對它進行回收操作的,當內(nèi)存的空間不足的時候,Java虛擬機將會拋出OutOfMemoryError錯誤,這時應用將會被終止運行";
mTextView3.setText(des3);
String des4="軟引用:一個對象如果只有一個軟引用,那么當內(nèi)存空間充足是,垃圾回收器不會對他進行回收操作,只有當內(nèi)存空間不足的時候,這個對象才會被回收,軟引用可以用來實現(xiàn)內(nèi)存敏感的高速緩存,如果配合引用隊列(ReferenceQueue使用,當軟引用指向?qū)ο蟊焕厥掌骰厥蘸?,java會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中)";
Object obj=new Object();
SoftReference<Object> sr = new SoftReference<>(obj);//這里使用了軟引用...
/*
*在這個期間,有可能會出現(xiàn)內(nèi)存不足的情況發(fā)生,那么GC就會直接把所有的軟引用全部清除..并釋放內(nèi)存空間
*如果內(nèi)存空間足夠的話,那么就GC就不會進行工作...
*GC的工作取決于內(nèi)存的大小,以及其內(nèi)部的算法,,,,
*/
if(sr!=null){
//如果軟引用還存在,那么直接就可以獲取這個對象的相關(guān)數(shù)據(jù)...這樣就實現(xiàn)了cache...
obj = sr.get();
}else{
//如果已經(jīng)不存在,表示GC已經(jīng)將其回收,我們需要重新實例化對象,獲取數(shù)據(jù)信息...
obj = new Object();
sr = new SoftReference<>(obj);
}
mTextView4.setText(des4);
String des5="弱引用:弱引用是比軟引用更弱的一種的引用的類型,只有弱引用指向的對象的生命周期更短,當垃圾回收器掃描到只有具有弱引用的對象的時候,不敢當前空間是否不足,都會對弱引用對象進行回收,當然弱引用也可以和一個隊列配合著使用";
Object obj1 = new Object();
WeakReference<Object> weakProductA = new WeakReference<>(obj1);
mTextView5.setText(des5);
String des6="虛引用:和軟引用和弱引用不同,虛引用并不會對所指向的對象生命周期產(chǎn)生任何影響,也就是對象還是會按照它原來的方式別垃圾回收期回收,虛引用本質(zhì)上只是有一個標記作用,主要用來跟蹤對象被垃圾回收的活動,虛引用必須和引用隊列配合使用,當對象被垃圾回收時,如果存在虛引用,那么Java虛擬機會將這個虛引用加入到與之關(guān)聯(lián)的引用隊列中";
mTextView6.setText(des6);
/**
* 如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
* 虛引用主要用來跟蹤對象被垃圾回收器回收的活動
*/
// TODO: 2018/5/2 程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用,
// 來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,
// 那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference<Object>(obj1, queue);
String des7="引用隊列:ReferenceQueue一般是作為WeakReference SoftReference 的構(gòu)造的函數(shù)參數(shù)傳入的,在WeakReference 或者是 softReference 的指向的對象被垃圾回收后,ReferenceQueue就是用來保存這個已經(jīng)被回收的Reference";
mTextView7.setText(des7);
- 將HashMap封裝成一個線程安全的集合,并且使用軟引用的方式防止OOM(內(nèi)存不足)。由于在ListView中會加載大量的圖片.那么為了有效的防止OOM導致程序終止的情況
/**
* list中使用大量的bitmap,這種情況的話,我自己感覺使用的比較少
*/
public class MemoryCache {
//將HashMap封裝成一個線程安全的集合,并且使用軟引用的方式防止OOM(內(nèi)存不足)...
//由于在ListView中會加載大量的圖片.那么為了有效的防止OOM導致程序終止的情況...
private Map<String,SoftReference<Bitmap>> cache=Collections.synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
public Bitmap get(String id){
if(!cache.containsKey(id))
return null;
SoftReference<Bitmap>ref=cache.get(id);
return ref.get();
}
public void put(String id,Bitmap bitmap){
cache.put(id, new SoftReference<Bitmap>(bitmap));
}
public void clear(){
cache.clear();
}
}
一個簡單的Demo
/**
* author: Created by shiming on 2018/5/2 14:50
* mailbox:lamshiming@sina.com
*/
public class EmployeeCache {
static private EmployeeCache cache;// 一個Cache實例
private Hashtable<String, EmployeeRef> employeeRefs;// 用于Chche內(nèi)容的存儲
private ReferenceQueue<Employee> q;// 垃圾Reference的隊列
// 繼承SoftReference,使得每一個實例都具有可識別的標識。
// 并且該標識與其在HashMap內(nèi)的key相同。
public class EmployeeRef extends SoftReference<Employee> {
private String _key = "";
public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
super(em, q);
_key = em.getID();
}
}
// 構(gòu)建一個緩存器實例
private EmployeeCache() {
employeeRefs = new Hashtable<String, EmployeeRef>();
q = new ReferenceQueue<Employee>();
}
// 取得緩存器實例
public static EmployeeCache getInstance() {
if (cache == null) {
cache = new EmployeeCache();
}
return cache;
}
// 以軟引用的方式對一個Employee對象的實例進行引用并保存該引用
private void cacheEmployee(Employee em) {
cleanCache();// 清除垃圾引用
EmployeeRef ref = new EmployeeRef(em, q);
employeeRefs.put(em.getID(), ref);
}
// 依據(jù)所指定的ID號,重新獲取相應Employee對象的實例
public Employee getEmployee(String ID) {
Employee em = null;
// 緩存中是否有該Employee實例的軟引用,如果有,從軟引用中取得。
if (employeeRefs.containsKey(ID)) {
EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);
em = (Employee) ref.get();
}
// 如果沒有軟引用,或者從軟引用中得到的實例是null,重新構(gòu)建一個實例,
// 并保存對這個新建實例的軟引用
if (em == null) {
em = new Employee(ID);
System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);
this.cacheEmployee(em);
}
return em;
}
// 清除那些所軟引用的Employee對象已經(jīng)被回收的EmployeeRef對象
private void cleanCache() {
EmployeeRef ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
employeeRefs.remove(ref._key);
}
}
// 清除Cache內(nèi)的全部內(nèi)容
public void clearCache() {
cleanCache();
employeeRefs.clear();
//告訴垃圾收集器打算進行垃圾收集,而垃圾收集器進不進行收集是不確定的
System.gc();
//強制調(diào)用已經(jīng)失去引用的對象的finalize方法
System.runFinalization();
}
/**
* 當垃圾收集器認為沒有指向?qū)ο髮嵗囊脮r,會在銷毀該對象之前調(diào)用finalize()方法。
* 該方法最常見的作用是確保釋放實例占用的全部資源。java并不保證定時為對象實例調(diào)用該方法,
* 甚至不保證方法會被調(diào)用,所以該方法不應該用于正常內(nèi)存處理。
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
}
}
/**
* author: Created by shiming on 2018/5/2 14:49
* mailbox:lamshiming@sina.com
*/
public class Employee {
private String id;// 雇員的標識號碼
private String name;// 雇員姓名
private String department;// 該雇員所在部門
private String Phone;// 該雇員聯(lián)系電話
private int salary;// 該雇員薪資
private String origin;// 該雇員信息的來源
// 構(gòu)造方法
public Employee(String id) {
this.id = id;
getDataFromlnfoCenter();
}
// 到數(shù)據(jù)庫中取得雇員信息
private void getDataFromlnfoCenter() {
// 和數(shù)據(jù)庫建立連接井查詢該雇員的信息,將查詢結(jié)果賦值
// 給name,department,plone,salary等變量
// 同時將origin賦值為"From DataBase"
}
public String getID() {
return id;
}
}
其他需要注意到的地方:
- 1、不要重復的創(chuàng)建相同的對象,對象的創(chuàng)建都是需要內(nèi)存分配的,對象的銷毀需要垃圾回收,這些都在一定程度上影響程序的性能
- 2、對常量使用static final修飾,對于基本類型和String類型的常量,建議使用常量static final 修飾,因為final類型的常量會在靜態(tài)dex文件的域初始化部分,這時對基本數(shù)據(jù)類型和String類型常量的調(diào)用不會涉及類的初始化,而是直接調(diào)用字面量
- 3、避免內(nèi)部的get set方法的調(diào)用,get set的作用是對以外屏蔽具體的變量定義,從而達到更好的封裝性,如果在類的內(nèi)部調(diào)用get set的方法訪問變量的話,會降低訪問的速度,根據(jù)在安卓的官方的文檔,在沒有jit編譯器時,直接訪問變量的速度是調(diào)用get方法的3倍,在jit編譯器,直接訪問變量是調(diào)用get方法的7倍,當然使用了ProGuard的話,perGuard會對get set 進行內(nèi)聯(lián)的操作,從而達到直接訪問的效果
關(guān)于JIT:
- JIT是”Just In Time Compiler”的縮寫,就是”即時編譯技術(shù)”,與Dalvik虛擬機相關(guān),JIT是在2.2版本提出的,目的是為了提高Android的運行速度,一直存活到4.4版本,因為在4.4之后的ROM中,就不存在Dalvik虛擬機了。
- 編譯打包APK文件:1、Java編譯器將應用中所有Java文件編譯為class文件,2、dx工具將應用編譯輸出的類文件轉(zhuǎn)換為Dalvik字節(jié)碼,即dex文件
- Google在2.2版本添加了JIT編譯器,當App運行時,每當遇到一個新類,JIT編譯器就會對這個類進行編譯,經(jīng)過編譯后的代碼,會被優(yōu)化成相當精簡的原生型指令碼(即native code),這樣在下次執(zhí)行到相同邏輯的時候,速度就會更快。
- dex字節(jié)碼翻譯成本地機器碼是發(fā)生在應用程序的運行過程中的,并且應用程序每一次重新運行的時候,都要做重做這個翻譯工作,所以這個工作并不是一勞永逸,每次重新打開App,都需要JIT編譯,Dalvik虛擬機從Android一出生一直活到4.4版本,而JIT在Android剛發(fā)布的時候并不存在,在2.2之后才被添加到Dalvik中。
- AOT是”Ahead Of Time”的縮寫,指的就是ART(Anroid RunTime)這種運行方式。
- JIT是運行時編譯,這樣可以對執(zhí)行次數(shù)頻繁的dex代碼進行編譯和優(yōu)化,減少以后使用時的翻譯時間,雖然可以加快Dalvik運行速度,但是還是有弊病,那就是將dex翻譯為本地機器碼也要占用時間,所以Google在4.4之后推出了ART,用來替換Dalvik。
- ART的策略與Dalvik不同,在ART 環(huán)境中,應用在第一次安裝的時候,字節(jié)碼就會預先編譯成機器碼,使其成為真正的本地應用。之后打開App的時候,不需要額外的翻譯工作,直接使用本地機器碼運行,因此運行速度提高。
- 當然ART與Dalvik相比,還是有缺點的。
- ART需要應用程序在安裝時,就把程序代碼轉(zhuǎn)換成機器語言,所以這會消耗掉更多的存儲空間,但消耗掉空間的增幅通常不會超過應用代碼包大小的20%
- 由于有了一個轉(zhuǎn)碼的過程,所以應用安裝時間難免會延長
- 但是這些與更流暢的Android體驗相比而言,不值一提。
圖片優(yōu)化
四種圖片格式
-
JPEG
- 是一種廣泛使用的有損壓縮圖像標準格式,它不支持透明和多幀動畫,一般攝影的作品是JEPG格式的,通過控制壓縮比,可以調(diào)整圖片的大小
-
PNG
- 是一種無損壓縮的圖片格式,他支持完整的透明通道,從圖片處理的領(lǐng)域來講,JEPG只有RGB三個通道,而PNG有ARGB四個通道,因此PNG圖片占用空間一般比較大,會無形的增加app的大小,在做app瘦身時一般都要對PNG圖片進行梳理以減小其占用的體積
GIF
* 是一種古老的圖片的格式,誕生于1987年,隨著初代互聯(lián)網(wǎng)流行開來,他的特別是支持多幀動畫,表情圖,-
Webp
- google于2010年發(fā)布,支持有損和無損、支持完整的透明通道、也支持多幀動畫,目前主流的APP都已經(jīng)使用了Webp,淘寶,微信,即保證了圖片的大小和質(zhì)量
- 在安卓應用開發(fā)中能夠使用編解碼格式的只有三種 JEPG PNG WEBP
/**
* 在安卓應用開發(fā)中能夠使用編解碼格式的只有三種 JEPG PNG WEBP
*/
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2);//安卓4.0后開始支持
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
推薦幾種圖片處理網(wǎng)站
- 無損壓縮ImageOptin,在不犧牲圖片質(zhì)量的前提下,即減下來PNG圖片占用的空間,又提高了圖片的加載速度 https://imageoptim.com/api
智圖.png
有損壓縮ImageAlpha,圖片大小得到極大的縮小,如果需要使用的話,一定要ui設(shè)計師看能否使用 https://pngmini.com/
有損壓縮TinyPNG 比較知名的png壓縮的工具,也需要ui設(shè)計師看能夠使用不 https://tinypng.com/
PNG/JPEG 轉(zhuǎn)化為 wepb :智圖 :http://zhitu.isux.us/
如果ui設(shè)計師工作量不飽和的話,可以推薦, 盡量使用 .9.png 點9圖 小黑點表示 可拉伸區(qū)域,黑邊表示縱向顯示內(nèi)容的范圍
布局優(yōu)化:如果創(chuàng)建的層級結(jié)構(gòu)比較復雜,View樹嵌套的層次比較深,那么將會使得頁面的響應的時間變長,導致運行的時候越來越慢
- merge標簽(對安卓的事件傳遞要達到源碼級的熟悉才可以理解) 在某些場景下可以減少布局的層次,由于所有的Activity的根布局都是FrameLayout Window PhoneWindow DecorView 事件的傳遞,包括設(shè)置setContentView 等的方法---> 我會寫一篇文章獨立解釋安卓事件的源碼解析,會更加清楚的介紹這個類,(對安卓的事件傳遞要達到源碼級的熟悉才可以理解)todo<-----所以,當獨立的一個布局文件最外層是FrameLayout的時候,并且和這個布局不需要設(shè)置 background 或者 padding的時候,可以使用<merge>標簽來代替FrameLayout布局。另外一種的情況可以使用《merge》便簽的情況是當前布局作為另外一個布局的子布局
<include android:layout_height="50dp"
android:layout_width="match_parent"
layout="@layout/layout_include_merge"
/>
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:gravity="center"
android:text="merge 標簽 在某些場景下可以減少布局的層次,由于所有的"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</merge>
在安卓中經(jīng)常會使用到相同的布局,比如說title,最佳的實踐的方法就是把相同的布局抽取出來,獨立成一個xml文件,需要使用到的時候,就把這個布局include進來,不僅減少了代碼量,而且修改這個相同的布局,只需要修改一個地方即可.
ViewStub 是一種不可見的并且大小為0的試圖,它可以延遲到運行時才填充inflate 布局資源,當Viewstub設(shè)為可見或者是inflate的時候,就會填充布局資源,這個布局和普通的試圖就基本上沒有任何區(qū)別,比如說,加載網(wǎng)絡(luò)失敗,或者是一個比較消耗性能的功能,需要用戶去點擊才可以加載,參考我的開源的項目 WritingPen
注意事項:如果這個根布局是個View,比如說是個ImagView,那么找出來的id為null,得必須注意這一點
---------->2018.6.7修正這個說法,以前我說的是錯誤的,根本上的原因是ViewStub設(shè)置了 inflateid ,這才是更本身的原因,對不起!搞錯了,還是要看源碼
<ViewStub
android:padding="10dp"
android:background="@color/colorPrimary"
android:layout_gravity="center"
android:inflatedId="@+id/find_view_stub"
android:id="@+id/view_stub"
android:layout="@layout/view_stub_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="10dp"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="如果這個根布局是個View,比如說是個ImagView,那么找出來的id為null,得必須注意這一點"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!--如果這個根布局是個View,比如說是個ImagView,那么找出來的id為null,得必須注意這一點-->
<ImageView
android:layout_marginTop="20dp"
android:id="@+id/imageview"
android:padding="10dp"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
調(diào)用todo: 2018/5/4 為啥為null 原因是布局文件中根布局只有View,沒有ViewGroup,ViewStub.inflate() 的方法和 setVisibility 方法是差不多,因為 setVisibility方法會(看源碼)走這個inflate的方法
if (null!=mViewStub.getParent()){
/*
android:inflatedId 的值是Java代碼中調(diào)用ViewStub的 inflate()或者是serVisibility方法返回的Id,這個id就是被填充的View的Id
*/
/**
* ViewStub.inflate() 的方法和 setVisibility 方法是差不多,因為 setVisibility方法會(看源碼)走這個inflate的方法
*/
// View inflate = mViewStub.inflate();
mViewStub.setVisibility(View.VISIBLE);
//inflate--->android.support.v7.widget.AppCompatImageView{de7e3a2 V.ED..... ......I. 0,0-0,0 #7f07003e app:id/find_view_stub}
// System.out.println("shiming inflate--->"+inflate);
final View find_view_stub = findViewById(R.id.find_view_stub);
System.out.println("shiming ----"+find_view_stub);
View iamgeivew11 = find_view_stub.findViewById(R.id.imageview);
//himing ---- iamgeivew11null
// TODO: 2018/5/4 為啥為null 原因是布局文件中根布局只有View,沒有ViewGroup
System.out.println("shiming ---- iamgeivew11"+iamgeivew11);
}else{
Toast.makeText(LayoutOptimizationActivity.this,"已經(jīng)inflate了",Toast.LENGTH_LONG).show();
final View viewById = findViewById(R.id.find_view_stub);
View iamgeivew = findViewById(R.id.imageview);
//已經(jīng)inflate了android.support.v7.widget.AppCompatImageView{4637833 V.ED..... ........ 348,294-732,678 #7f07003e app:id/find_view_stub}
System.out.println("shiming l----已經(jīng)inflate了"+viewById);//
System.out.println("shiming l----已經(jīng)inflate了iamgeivew"+iamgeivew);//已經(jīng)inflate了iamgeivew==null
View iamgeivew11 = viewById.findViewById(R.id.imageview);
//已經(jīng)inflate了 iamgeivew11null
System.out.println("shiming l----已經(jīng)inflate了 iamgeivew11"+iamgeivew11);
}
}
- 盡量使用CompoundDrawable,如果存在相鄰的ImageView和TextView 的話
<LinearLayout
android:layout_width="match_parent"
android:layout_height="150dp">
<TextView
android:text="我是文字"
android:drawableBottom="@mipmap/ic_launcher_round"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:text="我是title2"
android:drawableEnd="@mipmap/ic_launcher_round"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableRight="@mipmap/ic_launcher_round" />
<TextView
android:text="我是文字33"
android:drawableLeft="@mipmap/ic_launcher_round"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@mipmap/ic_launcher_round" />
<TextView
android:drawableTop="@mipmap/ic_launcher_round"
android:text="我是文字3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

- 使用Lint 檢查代碼,和布局是否可以存在優(yōu)化的地方,我會寫個簡單的經(jīng)常遇見過的問題,同時完成一篇文檔,加以說明,地址Lint的使用(安卓性能提升必備掌握的工具)
網(wǎng)絡(luò)優(yōu)化. 移動端對額App幾乎都是聯(lián)網(wǎng)的,網(wǎng)絡(luò)延遲等會對App的性能產(chǎn)生較大的影響,網(wǎng)絡(luò)優(yōu)化可以節(jié)約網(wǎng)絡(luò)流量和電量
- 2018年7月2日增加上網(wǎng)過程的說明流程(最近面試別人,發(fā)現(xiàn)好多同學這個都說的不太明白,特此說明下):對于普通的上網(wǎng),系統(tǒng)是這樣做的:瀏覽器本身就是一個客戶端,當你輸入URL的時候,首先瀏覽器會去請求DNS服務(wù)器,通過DNS獲取相應域名的對應的Ip地址,通過IP地址找到對應Ip對應的服務(wù)器,要求建立TCP連接,等瀏覽器發(fā)送完HTTP Request包后,服務(wù)器接受到請求包之后才開始處理請求包,服務(wù)器調(diào)用自身服務(wù),返回Http Response (響應包):客戶端收到來自服務(wù)器的響應后開始渲染這個Response包里的主體(body),等收到全部的內(nèi)容隨后斷開與該服務(wù)器之間的TCP連接。具體的文章在這里Web工作的方式

- DNS域名的系統(tǒng),主要的功能根據(jù)應用請求所用的域名URL去網(wǎng)絡(luò)上面映射表中查相對應的IP地址,這個過程有可能會消耗上百毫秒,而且可能存在著DNS劫持的危險,可以替換為Ip直接連接的方式來代替域名訪問的方法,從而達到更快的網(wǎng)絡(luò)請求,但是使用Ip地址不夠靈活,當后臺變換了Ip地址的話,會出現(xiàn)訪問不了,前段的App需要發(fā)包,解決方法是增加Ip地址動態(tài)更新的能力,或者是在IP地址訪問失敗了,切換到域名的訪問.
Demo--->ping 一個地址,不正確的話,切換到備用的地址
boolean ping = ping("wwww.baidu.com");
/**
* 測試主域名是否可用
*
* @param ip
* @return
*/
private final int PING_TIME_OUT = 1000; // ping 超時時間
private boolean ping(String ip) {
try {
Integer status = executeCommandIp( ip, PING_TIME_OUT );
if ( status != null && status == 0 ) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return false;
}
/**
* 執(zhí)行域名是否可通
* @param command
* @param timeout
* @return
* @throws IOException
* @throws InterruptedException
* @throws TimeoutException
*/
private int executeCommandIp( final String command, final long timeout )
throws IOException, InterruptedException, TimeoutException {
Process process = Runtime.getRuntime().exec(
"ping -c 1 -w 100 " + command);
mWorker = new PingWorker(process);
mWorker.start();
try {
mWorker.join(timeout);
if (mWorker.exit != null) {
return mWorker.exit;
} else {
//throw new TimeoutException();
return -1;
}
} catch (InterruptedException ex) {
mWorker.interrupt();
Thread.currentThread().interrupt();
throw ex;
} finally {
process.destroy();
}
}
PingWorker 類
class PingWorker extends Thread {
private final Process process;
private Integer exit;
private String ip;
public PingWorker(Process process) {
this.process = process;
}
@Override
public void run() {
try {
exit = process.waitFor();
if (exit == 0) {
BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream()));
String str = new String();
StringBuffer ipInfo = new StringBuffer();
//讀出所有信息并顯示
while((str=buf.readLine())!=null) {
ipInfo.append(str);
}
/*
PING sni1st.dtwscache.ourwebcdn.com (14.215.228.4) 56(84) bytes of data.64 bytes from 14.215.228.4: icmp_seq=1 ttl=57 time=16.6 ms--- sni1st.dtwscache.ourwebcdn.com ping statistics ---1 packets transmitted, 1 received, 0% packet loss, time 0msrtt min/avg/max/mdev = 16.656/16.656/16.656/0.000 ms
*/
System.out.println("shiming ipInfo----->"+ipInfo);
Pattern mPattern = Pattern.compile("\\((.*?)\\)");
Matcher matcher = mPattern.matcher(ipInfo.toString());
if ( matcher.find() ) {
ip = matcher.group( 1 );
}
}
else {
ip = " process.waitFor()==="+exit;
}
}
catch (IOException e) {
e.printStackTrace();
ip="java.io.IOException: Stream closed";
return;
}
catch (InterruptedException e) {
ip="java.io.InterruptedException: Stream closed";
return;
}
}
}
- 合并網(wǎng)絡(luò)請求,一次完整的Http請求,首先進行的是DNS查找,通過TCP三次握手,從而建立連接,如果是https請求的話,還要經(jīng)過TLS握手成功后才可以進行連接,對于網(wǎng)絡(luò)請求,減少接口,能夠合并的網(wǎng)絡(luò)請求就盡量合并
- SSL(Secure Sockets Layer 安全套接層),及其繼任者傳輸層安全(Transport Layer Security,TLS)是為網(wǎng)絡(luò)通信提供安全及數(shù)據(jù)完整性的一種安全協(xié)議。TLS與SSL在傳輸層對網(wǎng)絡(luò)連接進行加密。
HTTPS和HTTP的區(qū)別主要為以下四點:
- 一、https協(xié)議需要到ca申請證書,一般免費證書很少,需要交費。
- 二、http是超文本傳輸協(xié)議,信息是明文傳輸,https 則是具有安全性的ssl加密傳輸協(xié)議。
- 三、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。
- 四、http的連接很簡單,是無狀態(tài)的;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進行加密傳輸、身份認證的網(wǎng)絡(luò)協(xié)議,比http協(xié)議安全。
預先獲取數(shù)據(jù)能夠?qū)⒕W(wǎng)絡(luò)請求集中在一次,這樣其他時間段手機就可以切換到空閑的時間,從而避免經(jīng)常性的喚醒,從而節(jié)約用電
避免輪詢:如果說每個一段時間需要向服務(wù)器發(fā)起主動的網(wǎng)絡(luò)請求,其實不建議在app端做這樣的操作,可以使用推送,如果說在不得已的情況下,也要避免使用Thread.sleep()函數(shù)來循環(huán)等待,建議使用系統(tǒng)的AlarmManager來實現(xiàn)定時輪詢,AlarmManager 可以保證在系統(tǒng)休眠的時候,CPU也可以得到休息,在下一次需要發(fā)起網(wǎng)絡(luò)球球的時候才喚醒
盡量避免網(wǎng)絡(luò)請求失敗時候,無限制的循環(huán)重試連接,在我第一篇簡書博客有寫過一個網(wǎng)絡(luò)加載的框架 :http://www.itdecent.cn/p/141ee58eb143 中有提到過
//基于Rxjava 和 RxAndroid Retorfit
o.subscribeOn(Schedulers.io())
.retryWhen(new RetryWhenHandler(1, 5))
.doOnSubscribe(new Action0() {
@Override
public void call() {
s.onBegin();
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s);
- 離線緩存,對于圖片或者文件,內(nèi)存緩存+磁盤緩存+網(wǎng)絡(luò)緩存,一般我們本地需要做的是二級緩存,當緩存中存在圖片或者是文件,直接從緩存中讀取,不會走網(wǎng)絡(luò),下載圖片,在Android中使用LruCache實現(xiàn)內(nèi)存緩存,DiskLruCache實現(xiàn)本地緩存
/**
* 圖片緩存的核心類
*/
private LruCache<String, Bitmap> mLruCache;
// 緩存大小
private static final int CACHE_MAX_SIZE = 1024;
/**
* LRU(Least recently used,最近最少使用)算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問過,那么將來被訪問的幾率也更高”。
*/
private void lruCacheDemo() {
// 獲取應用程序最大可用內(nèi)存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//設(shè)置LruCache緩存的大小,一般為當前進程可用容量的1/8。
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
//重寫sizeOf方法,計算出要緩存的每張圖片的大小
//這個方法要特別注意,跟我們實例化 LruCache 的 maxSize 要呼應,怎么做到呼應呢,比如 maxSize 的大小為緩存的個數(shù),這里就是 return 1就 ok,如果是內(nèi)存的大小,如果5M,這個就不能是個數(shù) 了,這是應該是每個緩存 value 的 size 大小,如果是 Bitmap,這應該是 bitmap.getByteCount();
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
////這里用戶可以重寫它,實現(xiàn)數(shù)據(jù)和內(nèi)存回收操作
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != newValue) {
oldValue.recycle();
}
}
};
}
/**
* 從LruCache中獲取一張圖片,如果不存在就返回null。
*/
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
/**
* 往LruCache中添加一張圖片
*
* @param key
* @param bitmap
*/
private void addBitmapToLruCache(String key, Bitmap bitmap) {
if (getBitmapFromLruCache(key) == null) {
if (bitmap != null)
mLruCache.put(key, bitmap);
}
}
- 壓縮數(shù)據(jù)的大?。嚎梢詫Πl(fā)送服務(wù)端數(shù)據(jù)進行g(shù)zip壓縮,同時可以使用更優(yōu)的數(shù)據(jù)傳輸格式,例如二進制的代替Json格式,這個比較牛逼,估計運用的很少,使用webp格式代替圖片格式
- 不同的網(wǎng)絡(luò)環(huán)境使用不同的超時策略,常見的網(wǎng)絡(luò)格式有 2g、3g、4g、wifi,實時的更新當前的網(wǎng)絡(luò)狀態(tài),通過監(jiān)聽來獲取最新的網(wǎng)絡(luò)類型,并動態(tài)調(diào)整網(wǎng)絡(luò)超時的時間
private void netWorkDemo() {
TextView netWork = findViewById(R.id.net_work);
boolean networkConnected = NetworkUtils.isNetworkConnected(this);
int networkType = NetworkUtils.getNetworkType(this);
System.out.println("shiming 是否聯(lián)網(wǎng)了"+networkConnected);
switch (networkType){
case TYPE_UNKNOWN:
System.out.println("shiming 聯(lián)網(wǎng)的類型---無網(wǎng)絡(luò)連接");
netWork.setText("是否聯(lián)網(wǎng)了---》"+networkConnected+" 聯(lián)網(wǎng)的類型---無網(wǎng)絡(luò)連接");
break;
case TYPE_2G:
System.out.println("shiming 聯(lián)網(wǎng)的類型---2G");
netWork.setText("是否聯(lián)網(wǎng)了---》"+networkConnected+" 聯(lián)網(wǎng)的類型---2G");
break;
case TYPE_3G:
System.out.println("shiming 聯(lián)網(wǎng)的類型---TYPE_3G");
netWork.setText("是否聯(lián)網(wǎng)了---》"+networkConnected+" 聯(lián)網(wǎng)的類型---TYPE_3G");
break;
case TYPE_4G:
System.out.println("shiming 聯(lián)網(wǎng)的類型---TYPE_4G");
netWork.setText("是否聯(lián)網(wǎng)了---》"+networkConnected+" 聯(lián)網(wǎng)的類型---TYPE_4G");
break;
case TYPE_WIFI:
System.out.println("shiming 聯(lián)網(wǎng)的類型---TYPE_WIFI");
netWork.setText("是否聯(lián)網(wǎng)了---》"+networkConnected+" 聯(lián)網(wǎng)的類型---TYPE_WIFI");
break;
}
}
NetworkUtils 類
package com.shiming.performanceoptimization.network_optimization;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
/**
* author: Created by shiming on 2018/4/28 10:52
* mailbox:lamshiming@sina.com
* des:網(wǎng)絡(luò)連接工具類
*/
public class NetworkUtils {
private static final String SUBTYPE_TD_SCDMA = "SCDMA";
private static final String SUBTYPE_WCDMA = "WCDMA";
private static final String SUBTYPE_CDMA2000 = "CDMA2000";
/**
* 判斷是否已連接到網(wǎng)絡(luò).
*
* @param context Context
* @return 是否已連接到網(wǎng)絡(luò)
*/
public static boolean isNetworkConnected(Context context) {
ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context
.CONNECTIVITY_SERVICE);
if (connectivity != null) {
NetworkInfo info = connectivity.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
if (info.getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
return false;
}
/**
* 獲取當前網(wǎng)絡(luò)類型
*
* @param context Context
* @return 當前網(wǎng)絡(luò)類型(Unknown, 2G, 3G, 4G, WIFI)
*/
public static int getNetworkType(Context context) {
NetworkInfo info = ((ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (info != null && info.isConnected()) {
if (info.getType() == ConnectivityManager.TYPE_WIFI) {
return NetworkType.TYPE_WIFI;
} else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
switch (info.getSubtype()) {
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_1xRTT:
case TelephonyManager.NETWORK_TYPE_IDEN: //api<8 : replace by 11
return NetworkType.TYPE_2G;
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_EVDO_B: //api<9 : replace by 14
case TelephonyManager.NETWORK_TYPE_EHRPD: //api<11 : replace by 12
case TelephonyManager.NETWORK_TYPE_HSPAP: //api<13 : replace by 15
return NetworkType.TYPE_3G;
case TelephonyManager.NETWORK_TYPE_LTE: //api<11 : replace by 13
return NetworkType.TYPE_4G;
default:
// http://baike.baidu.com/item/TD-SCDMA 中國移動 聯(lián)通 電信 三種3G制式
String subtypeName = info.getSubtypeName();
if (SUBTYPE_TD_SCDMA.equalsIgnoreCase(subtypeName) ||
SUBTYPE_WCDMA.equalsIgnoreCase(subtypeName) ||
SUBTYPE_CDMA2000.equalsIgnoreCase(subtypeName)) {
return NetworkType.TYPE_3G;
} else {
return NetworkType.TYPE_UNKNOWN;
}
}
}
}
return NetworkType.TYPE_UNKNOWN;
}
}
NetworkType類
package com.shiming.performanceoptimization.network_optimization;
/**
* author: Created by shiming on 2018/4/28 10:52
* mailbox:lamshiming@sina.com
* des:網(wǎng)絡(luò)連接類型常量
*/
public class NetworkType {
/**
* 無網(wǎng)絡(luò)連接
*/
public static final int TYPE_UNKNOWN = -1;
/**
* 2G
*/
public static final int TYPE_2G = 0;
/**
* 3G
*/
public static final int TYPE_3G = 1;
/**
* 4G
*/
public static final int TYPE_4G = 2;
/**
* WIFI
*/
public static final int TYPE_WIFI = 3;
}
- CDN的全稱是Content Delivery Network,即內(nèi)容分發(fā)網(wǎng)絡(luò)。其基本思路是盡可能避開互聯(lián)網(wǎng)上有可能影響數(shù)據(jù)傳輸速度和穩(wěn)定性的瓶頸和環(huán)節(jié),使內(nèi)容傳輸?shù)母?、更穩(wěn)定。通過在網(wǎng)絡(luò)各處放置節(jié)點服務(wù)器所構(gòu)成的在現(xiàn)有的互聯(lián)網(wǎng)基礎(chǔ)之上的一層智能虛擬網(wǎng)絡(luò),CDN系統(tǒng)能夠?qū)崟r地根據(jù)網(wǎng)絡(luò)流量和各節(jié)點的連接、負載狀況以及到用戶的距離和響應時間等綜合信息將用戶的請求重新導向離用戶最近的服務(wù)節(jié)點上。其目的是使用戶可就近取得所需內(nèi)容,解決 Internet網(wǎng)絡(luò)擁擠的狀況,提高用戶訪問網(wǎng)站的響應速度。
電量優(yōu)化
1、(BroadCastReceiver)為了減少應用耗損的電量,我們在代碼中盡量避免使用無用的操作代碼,當應用退到后臺了,一切頁面的刷新都是沒有意義的,并且浪費電,比如有個監(jiān)聽網(wǎng)絡(luò)狀態(tài)的廣播并執(zhí)行一些動作,彈窗或者是Toast,那么app需要在后臺的時候,禁用掉這個功能,
2、數(shù)據(jù)傳輸 藍牙傳輸,Wi-Fi傳輸 移動網(wǎng)絡(luò)傳輸 后臺數(shù)據(jù)的管理:根據(jù)業(yè)務(wù)需求,接口盡量避免無效數(shù)據(jù)的傳輸 數(shù)據(jù)傳輸?shù)念l度問題:通過經(jīng)驗值或者是數(shù)據(jù)統(tǒng)計的方法確定好數(shù)據(jù)傳輸?shù)念l度,避免冗余重復的數(shù)據(jù)傳輸,數(shù)據(jù)傳輸過程中要壓縮數(shù)據(jù)的大小,合并網(wǎng)絡(luò)請求,避免輪詢
3、位置服務(wù) 正確的使用位置復位,是應用耗電的一個關(guān)鍵
-
需要注意以下的三點:
1、有沒有及時注銷位置監(jiān)聽器:和廣播差不多
-
2、位置更新監(jiān)聽頻率的設(shè)定;更加業(yè)務(wù)需求設(shè)置一個合理的更新頻率值,
- minTime:用來指定間更新通知的最小的時間間隔,單位是毫秒,看日志這里是1s更新的
- minDistance:用來指定位置更新通知的最小的距離,單位是米
-
3、Android提供了三種定位
- GPS定位,通過GPS實現(xiàn)定位,精度最高,通常在10米(火星坐標),但是GPS定位在時間和電量上消耗也是最高的
- 網(wǎng)絡(luò)定位,通過移動通信的基站信號差異來計算出手機所在的位置,精度比GPS差好多
- 被動定位,最省電的定位服務(wù),如果使用被動定位服務(wù)。說明它想知道位置更新信息但有不主動獲取,等待手機中其他應用或者是服務(wù)或者是系統(tǒng)組件發(fā)出定位請求,并和這些組件的監(jiān)聽器一起接收位置的信息,實際的開發(fā)中,一般使用的是第三方的地圖,高德,騰訊,百度,他們做了很好的封裝,同時在地圖上的表現(xiàn)上更加的優(yōu)化
/**
* //設(shè)置定位精確度 Criteria.ACCURACY_COARSE比較粗略,Criteria.ACCURACY_FINE則比較精細
* criteria.setAccuracy(Criteria.ACCURACY_FINE);
* //設(shè)置是否要求速度
* criteria.setSpeedRequired(false);
* // 設(shè)置是否允許運營商收費
* criteria.setCostAllowed(false);
* //設(shè)置是否需要方位信息
* criteria.setBearingRequired(false);
* //設(shè)置是否需要海拔信息
* criteria.setAltitudeRequired(false);
* // 設(shè)置對電源的需求
* criteria.setPowerRequirement(Criteria.POWER_LOW);
*/
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(true);
criteria.setPowerRequirement(Criteria.POWER_LOW);
String serviceName = Context.LOCATION_SERVICE;
mLocationManager = (LocationManager) getSystemService(serviceName);
// locationManager.setTestProviderEnabled("gps", true);
// TODO: 2018/5/3 IllegalArgumentException 'Provider "gps" unknown" https://www.cnblogs.com/ok-lanyan/archive/2011/10/12/2208378.html
mLocationManager.addTestProvider(LocationManager.GPS_PROVIDER,
"requiresNetwork" == "", "requiresSatellite" == "", "requiresCell" == "", "hasMonetaryCost" == "",
"supportsAltitude" == "", "supportsSpeed" == "",
"supportsBearing" == "", android.location.Criteria.POWER_LOW,
android.location.Criteria.ACCURACY_FINE);
mProvider = mLocationManager.getBestProvider(criteria, true);
//獲取緯度
//獲取經(jīng)度
mLlistener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// thread is not runable, msg ignore, state:TIMED_WAITING, 這里的線程有可能ANR
if (location != null) {
double lat = location.getLatitude();//獲取緯度
double lng = location.getLongitude();//獲取經(jīng)度
System.out.println("shiming lat+" + lat);
System.out.println("shiming lng+" + lng);
String name = Thread.currentThread().getName();
mCount++;
System.out.println("當前線程的位置name---"+name+"i==="+mCount);
mTv_location.setText("位置信息是2s變動的,可以設(shè)置,我是第"+mCount+"次變動的--->"+"\n\r"+"lat===="+lat+" lng----->"+lng);
}
if (mLocationManager!=null) {
mLocationManager.removeUpdates(this);
if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 2000, 1000, mLlistener);
}else {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 1000, mLlistener);
}
// TODO: 2018/5/3 這里在報錯了,我把他注釋掉
// mLocationManager.setTestProviderEnabled(mProvider, false);// java.lang.IllegalArgumentException: Provider "network" unknown
}
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status,
Bundle extras) {
}
};
/**
* minTime:用來指定間更新通知的最小的時間間隔,單位是毫秒,看日志這里是1s更新的
* minDistance:用來指定位置更新通知的最小的距離,單位是米
*/
mLocationManager.requestLocationUpdates(mProvider, 2000, (float) 1000.0, mLlistener);
在OnDestroy 變量手動值為null,我在測試過程中,只有在值為null的時候這個位置監(jiān)聽才會停止,有興趣的小伙伴,可以好好看看值為null,底層會做什么操作
/**
* 記得需要銷毀這個監(jiān)聽,
* todo 如果不手動置為null的話,其實您可以通過日記發(fā)現(xiàn),這個監(jiān)聽還是一直在走的,所以說這里手動值為null的好處
*/
protected void onDestroy() {
if (mAm!=null){
mAm.cancel(mPi);
mAm=null;//變?yōu)閚ull
}
if (null!=mTag){
mTag.release();
//釋放喚醒鎖鎖
mTag=null;
}
mLocationManager.removeUpdates(mLlistener);
if (mLocationManager != null) {
mLocationManager.removeUpdates(mLlistener);
mLocationManager = null;//不用分配空間
}
if (mLlistener != null) {
mLlistener = null;
}
// mLocationManager.setTestProviderEnabled(mProvider, false);
super.onDestroy();
}
- WakeLock 是為了保持設(shè)備的喚醒狀態(tài)的API,組織用戶長時間不用,仍然需要組織設(shè)備進入休眠的狀態(tài),比如用戶在看電影的時候。使用wakelock 時,需要及時的釋放鎖,比如播放視屏的時候WakeLock保持屏幕的常亮,在暫停的時候就應該釋放鎖,而不是等到停止播放才釋放。
@SuppressLint("WakelockTimeout")
private void wakeLockDemo() {
// PowerManager.PARTIAL_WAKE_LOCK;//保持CPU正常運轉(zhuǎn),但屏幕和鍵盤燈有可能是關(guān)閉的
// PowerManager.SCREEN_DIM_WAKE_LOCK://保持CPU正常運轉(zhuǎn),允許屏幕點亮但可能是置灰的,鍵盤燈可能是關(guān)閉的
// PowerManager.SCREEN_BRIGHT_WAKE_LOCK;//保持CPU正常的運轉(zhuǎn),允許屏幕高亮顯示,鍵盤燈可能是關(guān)閉的
// PowerManager.FULL_WAKE_LOCK;//保持CPU正常運轉(zhuǎn),保持屏幕高亮顯示,鍵盤燈也保持連讀
// PowerManager.ACQUIRE_CAUSES_WAKEUP;//強制屏幕和鍵盤燈亮起,這種鎖針對必須通知用戶的操作
// PowerManager.ON_AFTER_RELEASE;//當WakeLock被釋放了,繼續(xù)保持屏幕和鍵盤燈開啟一定的時間
PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
/**
* case PARTIAL_WAKE_LOCK:
* case SCREEN_DIM_WAKE_LOCK:
* case SCREEN_BRIGHT_WAKE_LOCK:
* case FULL_WAKE_LOCK:
* case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
* case DOZE_WAKE_LOCK:
* case DRAW_WAKE_LOCK:
*/
mTag = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag");
if (null!= mTag){
mTag.acquire();
}
}
- AlarmManager 也是比較耗電的,通常情況下需要保證兩次喚醒操作的時間間隔不要太短了,在不需要使用喚醒功能的情況下,盡早的取消喚醒功能,否則應用會一直消耗電量 AlarmManager 是SDK提供的一個喚醒的APi,是系統(tǒng)級別的服務(wù),可以在特定的時刻廣播一個指定的Intent,這個pendingIntent可以用來啟動Activity、Service、BroadcastReceiver, app,在后臺也會啟動
private void alarmManager() {
//創(chuàng)建Intent對象,action為ELITOR_CLOCK,附加信息為字符串“你該打醬油了”
Intent intent = new Intent("action");
intent.putExtra("msg","重啟---App ---Le -- 回到前臺");
// intent.setClass(ElectricQuantityOptimizationActivity.this,MainActivity.class);
//定義一個PendingIntent對象,PendingIntent.getBroadcast包含了sendBroadcast的動作。
//也就是發(fā)送了action 為"action"的intent
mPi = PendingIntent.getBroadcast(this,0,intent,0);
//AlarmManager對象,注意這里并不是new一個對象,Alarmmanager為系統(tǒng)級服務(wù)
mAm = (AlarmManager)getSystemService(ALARM_SERVICE);
//設(shè)置鬧鐘從當前時間開始,每隔5s執(zhí)行一次PendingIntent對象pi,注意第一個參數(shù)與第二個參數(shù)的關(guān)系
// 5秒后通過PendingIntent pi對象發(fā)送廣播
assert mAm != null;
/**
* 頻繁的報警對電池壽命不利。至于API 22,警報管理器將覆蓋近期和高頻報警請求,
* 在未來至少延遲5秒的警報,并確保重復間隔至少為60秒,如果真的需要間隔很短的話,官方建議使用handler
* 該方法用于設(shè)置重復鬧鐘,第一個參數(shù)表示鬧鐘類型,第二個參數(shù)表示鬧鐘首次執(zhí)行時間,
* 第三個參數(shù)表示鬧鐘兩次執(zhí)行的間隔時間,第三個參數(shù)表示鬧鐘響應動作。
*/
mAm.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),1000, mPi);
//該方法用于設(shè)置一次性鬧鐘,第一個參數(shù)表示鬧鐘類型,第二個參數(shù)表示鬧鐘執(zhí)行時間,第三個參數(shù)表示鬧鐘響應動作。
//am.set(AlarmManager.RTC_WAKEUP,100000,pi);
/**
* (1)int type: 鬧鐘的類型,常用的有5個值:AlarmManager.ELAPSED_REALTIME、 AlarmManager.ELAPSED_REALTIME_WAKEUP、AlarmManager.RTC、 AlarmManager.RTC_WAKEUP、AlarmManager.POWER_OFF_WAKEUP。AlarmManager.ELAPSED_REALTIME表示鬧鐘在手機睡眠狀態(tài)下不可用,該狀態(tài)下鬧鐘使用相對時間(相對于系統(tǒng)啟動開始),狀態(tài)值為3;
* AlarmManager.ELAPSED_REALTIME_WAKEUP表示鬧鐘在睡眠狀態(tài)下會喚醒系統(tǒng)并執(zhí)行提示功能,該狀態(tài)下鬧鐘也使用相對時間,狀態(tài)值為2;
*
* AlarmManager.RTC表示鬧鐘在睡眠狀態(tài)下不可用,該狀態(tài)下鬧鐘使用絕對時間,即當前系統(tǒng)時間,狀態(tài)值為1;
*
* AlarmManager.RTC_WAKEUP表示鬧鐘在睡眠狀態(tài)下會喚醒系統(tǒng)并執(zhí)行提示功能,該狀態(tài)下鬧鐘使用絕對時間,狀態(tài)值為0;
*
* AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機關(guān)機狀態(tài)下也能正常進行提示功能,所以是5個狀態(tài)中用的最多的狀態(tài)之一,該狀態(tài)下鬧鐘也是用絕對時間,狀態(tài)值為4;不過本狀態(tài)好像受SDK版本影響,某些版本并不支持;
*/
//該方法也用于設(shè)置重復鬧鐘,與第二個方法相似,不過其兩個鬧鐘執(zhí)行的間隔時間不是固定的而已。
//基本上相似,只不過這個方法優(yōu)化了很多,省電
// am.setInexactRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),1000,pi);
}
/**
* author: Created by shiming on 2018/5/3 14:28
* mailbox:lamshiming@sina.com
*/
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
System.out.println("shiming " + msg) ;
Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();
// ElectricQuantityOptimizationActivity context1 = (ElectricQuantityOptimizationActivity) context;
// context1.startActivity(intent);
}
}
提供一個Demo,當app崩潰了,通過AlarmManager來重啟App的功能
/**
* author: Created by shiming on 2018/5/3 14:28
* mailbox:lamshiming@sina.com
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
public static CrashHandler mAppCrashHandler;
private Thread.UncaughtExceptionHandler mDefaultHandler;
private MyApplication mAppContext;
public void initCrashHandler(MyApplication application) {
this.mAppContext = application;
// 獲取系統(tǒng)默認的UncaughtException處理器
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
public static CrashHandler getInstance() {
if (mAppCrashHandler == null) {
mAppCrashHandler = new CrashHandler();
}
return mAppCrashHandler;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
ex.printStackTrace();
AlarmManager mgr = (AlarmManager) mAppContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(mAppContext, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("crash", true);
System.out.println("shiming -----》重啟應用了哦");
PendingIntent restartIntent = PendingIntent.getActivity(mAppContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 5000, restartIntent); // 1秒鐘后重啟應用
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
System.gc();
}
}
- 程序會重新啟動,如果點擊電量優(yōu)化,App崩潰了,請給與全部權(quán)限,還要在開發(fā)者模式里面給與位置信息模擬的設(shè)置,如果崩潰了, 你也可以發(fā)現(xiàn)app會自動的重新啟動,這是AlarmManager的應用,注意看MyApplication里面的代碼,tks
/**
* author: Created by shiming on 2018/5/3 14:48
* mailbox:lamshiming@sina.com
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
/**
* 程序會重新啟動,如果點擊電量優(yōu)化,App崩潰了,請給與全部權(quán)限,
* 還要在開發(fā)者模式里面給與位置信息模擬的設(shè)置,如果崩潰了,
* 你也可以發(fā)現(xiàn)app會自動的重新啟動,這是AlarmManager的應用,注意看MyApplication里面的代碼,tks
*/
CrashHandler.getInstance().initCrashHandler(this);
}
}
