數據集合的使用:建議最佳的作法是可能使用ArrayList做爲首選,只要你須要使用額外的功能的時候,或者當程序性能因爲常常從表的中間進行插入和刪除而變差的時候,纔會去選擇LinkedList。HashMap性能上於HashTable至關,由於HashMap和HashTable在底層的存儲和查找機制是同樣的,可是TreeMap一般比HashMap要慢。HashSet整體上的性能比TreeSet好,特別實在添加和查詢元素的時候,而這兩個操做也是最重要的操做。TreeSet存在的惟一的緣由是它能夠維持元素的排序的狀態,因此當須要一個排好序的Set,才使用TreeSet。由於其內部的結果歐支持排序,而且由於迭代是咱們更有可能執行的操做,因此,用TreeSet迭代一般比用HashSet要快。html
/**
* 建議最佳的作法是可能使用ArrayList做爲首選,只要你須要使用額外的功能的時候,或者當程序性能因爲常常從表的中間進行
* 插入和刪除而變差的時候,纔會去選擇LinkedList
*/
//數據結構的選擇,對於ArrayList,插入的操做特別高昂,而且其代價將隨着列表的尺寸的增長而增長
ArrayList list=new ArrayList();
//須要執行大量的隨機的訪問,這個不是一個好的選擇,若是是使用迭代器在列表中插入新的數據,使用這個,比較低廉(插入和移除的代價比較低廉)
LinkedList linkedList=new LinkedList();
//HashMap性能上於HashTable至關,由於HashMap和HashTable在底層的存儲和查找機制是同樣的,可是TreeMap一般比HashMap要慢
HashMap<String,String> hashMap=new HashMap<>();
/**
* HashSet整體上的性能比TreeSet好,特別實在添加和查詢元素的時候,而這兩個操做也是最重要的操做。TreeSet存在的惟一的緣由是它
* 能夠維持元素的排序的狀態,因此當須要一個排好序的Set,才使用TreeSet。由於其內部的結果歐支持排序,而且由於迭代是咱們更有可能
* 執行的操做,因此,用TreeSet迭代一般比用HashSet要快
*/
HashSet<String> hashSet=new HashSet<>();
TreeSet<String> treeSet=new TreeSet<>();
複製代碼
SparseArray是Android特有的稀疏數組的實現,他是Integer和Object的爲例進行的一個映射用於代替 HsahMap<Integer,>,提升性能。java
內部核心的實現(二分查找)android
/**
* 二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。
* 可是,折半查找要求線性表必須採用順序存儲結構,並且表中元素按關鍵字有序排列。
* @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) {
/**
* >>>與>>惟一的不一樣是它不管原來的最左邊是什麼數,通通都用0填充。
* —好比你的例子,byte是8位的,-1表示爲byte型是11111111(補碼錶示法)
* b>>>4就是無符號右移4位,即00001111,這樣結果就是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
}
}
//按位取反(~)運算符 ,沒有找到,這個數據
return ~lo; // value not present
}
複製代碼
SpareArray 家族有如下的四類git
//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中的設計模式:原型模式:這裏有使用到了的,原型模式內存中複製數據的,不會調用到類的構造的方法,並且訪問的權限對原型模式無效es6
SparseArray<String> clone = sparseArray.clone();
複製代碼
Handler正確的使用姿式(☺☺☺) 下面的代碼是不少人都會這樣寫,這樣會形成內存泄漏 緣由:Handler是和Looper以及MessageQueue一塊兒工做的,在安卓中,一個 應用啓動了,系統會默認建立一個主線程服務的Looper對象 ,該Looper對象處理主線程的全部的Message消息,他的生命週期貫穿整個應用。在主線程中使用的Handler的都會默認的綁定到這個looper的對象,咋主線程中建立handler的時候,它會當即關聯主線程Looper對象的MessageQueue,這時發送到的MessageQueue 中的Message對象都會持有這個Handler的對象的引用,這樣Looper處理消息時Handler的handlerMessage的方法,所以,若是Message尚未處理完成,那麼handler的對象不會當即被垃圾回收github
/*-------------old ide 已經告訴咱們這裏可能內存泄露-------------------*/
@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 用戶即便退出了應用的話,這裏也是會執行的 ,經過日記的觀察
// //這裏有可能用戶退出了Activity
// System.out.println("shiming mHandler --todo");
// }
// },5000);
複製代碼
如何避免,有兩點的能夠嘗試web
InnerHandler innerHandler = new InnerHandler(this);
innerHandler.postDelayed(new Runnable() {
@Override
public void run() {
//這裏這要 退出了 就不會執行了
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
/**
*若是建立單利必須須要使用到context對象
*/
//這樣不會內存泄露,不用改動單利類中代碼
SingleInstance.getSingleInstance(getApplication().getApplicationContext());
複製代碼
class SingleInstance {
private static SingleInstance sSingleInstance;
private final Context mContext;
private SingleInstance(Context context){
mContext = context;
}
// 由於每次調用實例都須要判斷同步鎖,不少項目包括不少人都是用這種的
// 雙重判斷校驗的方法,這種的方法看似很完美的解決了效率的問題,可是它
// 在併發量很少,安全性不過高的狀況下能完美的運行,可是,
// 在jvm編譯的過程當中會出現指令重排的優化過程,這就會致使singleton實際上
// 沒有被初始化,就分配了內存空間,也就是說singleton!=null可是又沒有被初始化,
// 這就會致使返回的singletonthird返回的是不完整的
public static SingleInstance getSingleInstance(Context context){
if (sSingleInstance==null){
synchronized (SingleInstance.class){
if (sSingleInstance==null) {
// TODO: 2018/4/28 注意外面傳入的conext對象是否,是哪一個
sSingleInstance= new SingleInstance(context);
//第二種是改動代碼,使用application 中的context變量
sSingleInstance= new SingleInstance(context.getApplicationContext());
}
}
}
return sSingleInstance;
}
}
複製代碼
* 和java同樣,Android也是基於垃圾回收(GC)機制實現內存的自動的回收,垃圾回收的算法「標記-清除(Mark-Sweep)」
* 「標記壓縮(Mark-Compact)「複製算法(Copying)以及引用計數算法(Reference-Counting),安卓的虛擬機(Dalvik仍是Art),
* 都是使用標記清除算法」
*
mTextView1.setText(des1);
/**
* 在Android中,內存泄露是指再也不使用的對象依然佔有內存,或者是他們佔用的內存沒有獲得釋放,
* 從而致使內存空間不斷的減小,因爲可用的空間比較少,發生內存泄露會使得內存更加的緊張,
* 甚至最終因爲內存耗盡而發生的OOM,致使應用的崩潰
*/
mTextView2.setText(des2);
複製代碼
String des3="強引用:Java中裏面最普遍的使用的一種,也是對象默認的引用類型,若是又一個對象具備強引用,那麼垃圾回收器是不會對它進行回收操做的,當內存的空間不足的時候,Java虛擬機將會拋出OutOfMemoryError錯誤,這時應用將會被終止運行";
mTextView3.setText(des3);
String des4="軟引用:一個對象若是隻有一個軟引用,那麼當內存空間充足是,垃圾回收器不會對他進行回收操做,只有當內存空間不足的時候,這個對象纔會被回收,軟引用能夠用來實現內存敏感的高速緩存,若是配合引用隊列(ReferenceQueue使用,當軟引用指向對象被垃圾回收器回收後,java會把這個軟引用加入到與之關聯的引用隊列中)";
Object obj=new Object();
SoftReference<Object> sr = new SoftReference<>(obj);//這裏使用了軟引用...
/*
*在這個期間,有可能會出現內存不足的狀況發生,那麼GC就會直接把全部的軟引用所有清除..並釋放內存空間
*若是內存空間足夠的話,那麼就GC就不會進行工做...
*GC的工做取決於內存的大小,以及其內部的算法,,,,
*/
if(sr!=null){
//若是軟引用還存在,那麼直接就能夠獲取這個對象的相關數據...這樣就實現了cache...
obj = sr.get();
}else{
//若是已經不存在,表示GC已經將其回收,咱們須要從新實例化對象,獲取數據信息...
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="虛引用:和軟引用和弱引用不一樣,虛引用並不會對所指向的對象生命週期產生任何影響,也就是對象仍是會按照它原來的方式別垃圾回收期回收,虛引用本質上只是有一個標記做用,主要用來跟蹤對象被垃圾回收的活動,虛引用必須和引用隊列配合使用,當對象被垃圾回收時,若是存在虛引用,那麼Java虛擬機會將這個虛引用加入到與之關聯的引用隊列中";
mTextView6.setText(des6);
/**
* 若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。
* 虛引用主要用來跟蹤對象被垃圾回收器回收的活動
*/
// TODO: 2018/5/2 程序能夠經過判斷引用隊列中是否已經加入了虛引用,
// 來了解被引用的對象是否將要被垃圾回收。若是程序發現某個虛引用已經被加入到引用隊列,
// 那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference<Object>(obj1, queue);
String des7="引用隊列:ReferenceQueue通常是做爲WeakReference SoftReference 的構造的函數參數傳入的,在WeakReference 或者是 softReference 的指向的對象被垃圾回收後,ReferenceQueue就是用來保存這個已經被回收的Reference";
mTextView7.setText(des7);
複製代碼
/**
* list中使用大量的bitmap,這種狀況的話,我本身感受使用的比較少
*/
public class MemoryCache {
//將HashMap封裝成一個線程安全的集合,而且使用軟引用的方式防止OOM(內存不足)...
//因爲在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內容的存儲
private ReferenceQueue<Employee> q;// 垃圾Reference的隊列
// 繼承SoftReference,使得每個實例都具備可識別的標識。
// 而且該標識與其在HashMap內的key相同。
public class EmployeeRef extends SoftReference<Employee> {
private String _key = "";
public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
super(em, q);
_key = em.getID();
}
}
// 構建一個緩存器實例
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);
}
// 依據所指定的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,從新構建一個實例,
// 並保存對這個新建實例的軟引用
if (em == null) {
em = new Employee(ID);
System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);
this.cacheEmployee(em);
}
return em;
}
// 清除那些所軟引用的Employee對象已經被回收的EmployeeRef對象
private void cleanCache() {
EmployeeRef ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
employeeRefs.remove(ref._key);
}
}
// 清除Cache內的所有內容
public void clearCache() {
cleanCache();
employeeRefs.clear();
//告訴垃圾收集器打算進行垃圾收集,而垃圾收集器進不進行收集是不肯定的
System.gc();
//強制調用已經失去引用的對象的finalize方法
System.runFinalization();
}
/**
* 當垃圾收集器認爲沒有指向對象實例的引用時,會在銷燬該對象以前調用finalize()方法。
* 該方法最多見的做用是確保釋放實例佔用的所有資源。java並不保證定時爲對象實例調用該方法,
* 甚至不保證方法會被調用,因此該方法不該該用於正常內存處理。
* @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;// 該僱員聯繫電話
private int salary;// 該僱員薪資
private String origin;// 該僱員信息的來源
// 構造方法
public Employee(String id) {
this.id = id;
getDataFromlnfoCenter();
}
// 到數據庫中取得僱員信息
private void getDataFromlnfoCenter() {
// 和數據庫創建鏈接井查詢該僱員的信息,將查詢結果賦值
// 給name,department,plone,salary等變量
// 同時將origin賦值爲"From DataBase"
}
public String getID() {
return id;
}
}
複製代碼
其餘須要注意到的地方:數據庫
關於JIT:
四種圖片格式
JPEG
PNG
GIF * 是一種古老的圖片的格式,誕生於1987年,隨着初代互聯網流行開來,他的特別是支持多幀動畫,表情圖,
Webp
在安卓應用開發中可以使用編解碼格式的只有三種 JEPG PNG WEBP
/**
* 在安卓應用開發中可以使用編解碼格式的只有三種 JEPG PNG WEBP
*/
public enum CompressFormat {
JPEG (0),
PNG (1),
WEBP (2);//安卓4.0後開始支持
CompressFormat(int nativeInt) {
this.nativeInt = nativeInt;
}
final int nativeInt;
}
複製代碼
推薦幾種圖片處理網站
無損壓縮ImageOptin,在不犧牲圖片質量的前提下,即減下來PNG圖片佔用的空間,又提升了圖片的加載速度 imageoptim.com/api
有損壓縮ImageAlpha,圖片大小獲得極大的縮小,若是須要使用的話,必定要ui設計師看可否使用 pngmini.com/
有損壓縮TinyPNG 比較知名的png壓縮的工具,也須要ui設計師看可以使用不 tinypng.com/
PNG/JPEG 轉化爲 wepb :智圖 :zhitu.isux.us/
若是ui設計師工做量不飽和的話,能夠推薦, 儘可能使用 .9.png 點9圖 小黑點表示 可拉伸區域,黑邊表示縱向顯示內容的範圍
####佈局優化:若是建立的層級結構比較複雜,View樹嵌套的層次比較深,那麼將會使得頁面的響應的時間變長,致使運行的時候愈來愈慢
<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>
複製代碼
在安卓中常常會使用到相同的佈局,好比說title,最佳的實踐的方法就是把相同的佈局抽取出來,獨立成一個xml文件,須要使用到的時候,就把這個佈局include進來,不只減小了代碼量,並且修改這個相同的佈局,只須要修改一個地方便可.
ViewStub 是一種不可見的而且大小爲0的試圖,它能夠延遲到運行時才填充inflate 佈局資源,當Viewstub設爲可見或者是inflate的時候,就會填充佈局資源,這個佈局和普通的試圖就基本上沒有任何區別,好比說,加載網絡失敗,或者是一個比較消耗性能的功能,須要用戶去點擊才能夠加載,參考個人開源的項目 WritingPen
注意事項:若是這個根佈局是個View,好比說是個ImagView,那麼找出來的id爲null,得必須注意這一點 ###---------->2018.6.7修正這個說法,之前我說的是錯誤的,根本上的緣由是ViewStub設置了 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>
複製代碼
調用todo: 2018/5/4 為啥為null 緣由是佈局文件中根佈局只有View,沒有ViewGroup,ViewStub.inflate() 的方法和 setVisibility 方法是差很少,由於 setVisibility方法會(看源碼)走這個inflate的方法
if (null!=mViewStub.getParent()){
/*
android:inflatedId 的值是Java代碼中調用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,"已經inflate了",Toast.LENGTH_LONG).show();
final View viewById = findViewById(R.id.find_view_stub);
View iamgeivew = findViewById(R.id.imageview);
//已經inflate了android.support.v7.widget.AppCompatImageView{4637833 V.ED..... ........ 348,294-732,678 #7f07003e app:id/find_view_stub}
System.out.println("shiming l----已經inflate了"+viewById);//
System.out.println("shiming l----已經inflate了iamgeivew"+iamgeivew);//已經inflate了iamgeivew==null
View iamgeivew11 = viewById.findViewById(R.id.imageview);
//已經inflate了 iamgeivew11null
System.out.println("shiming l----已經inflate了 iamgeivew11"+iamgeivew11);
}
}
複製代碼
<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>
複製代碼
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;
}
/**
* 執行域名是否可通
* @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;
}
}
}
複製代碼
HTTPS和HTTP的區別主要爲如下四點:
1、https協議須要到ca申請證書,通常免費證書不多,須要交費。
2、http是超文本傳輸協議,信息是明文傳輸,https 則是具備安全性的ssl加密傳輸協議。
3、http和https使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是80,後者是443。
4、http的鏈接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
預先獲取數據可以將網絡請求集中在一次,這樣其餘時間段手機就能夠切換到空閒的時間,從而避免常常性的喚醒,從而節約用電
避免輪詢:若是說每一個一段時間須要向服務器發起主動的網絡請求,其實不建議在app端作這樣的操做,可使用推送,若是說在不得已的狀況下,也要避免使用Thread.sleep()函數來循環等待,建議使用系統的AlarmManager來實現定時輪詢,AlarmManager 能夠保證在系統休眠的時候,CPU也能夠獲得休息,在下一次須要發起網絡球球的時候才喚醒
儘可能避免網絡請求失敗時候,無限制的循環重試鏈接,在我第一篇簡書博客有寫過一個網絡加載的框架 :www.jianshu.com/p/141ee58eb… 中有提到過
//基於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);
複製代碼
/**
* 圖片緩存的核心類
*/
private LruCache<String, Bitmap> mLruCache;
// 緩存大小
private static final int CACHE_MAX_SIZE = 1024;
/**
* LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。
*/
private void lruCacheDemo() {
// 獲取應用程序最大可用內存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//設置LruCache緩存的大小,通常爲當前進程可用容量的1/8。
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
//重寫sizeOf方法,計算出要緩存的每張圖片的大小
//這個方法要特別注意,跟咱們實例化 LruCache 的 maxSize 要呼應,怎麼作到呼應呢,好比 maxSize 的大小爲緩存的個數,這裏就是 return 1就 ok,若是是內存的大小,若是5M,這個就不能是個數 了,這是應該是每一個緩存 value 的 size 大小,若是是 Bitmap,這應該是 bitmap.getByteCount();
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
////這裏用戶能夠重寫它,實現數據和內存回收操做
@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);
}
}
複製代碼
壓縮數據的大小:能夠對發送服務端數據進行gzip壓縮,同時可使用更優的數據傳輸格式,例如二進制的代替Json格式,這個比較牛逼,估計運用的不多,使用webp格式代替圖片格式
不一樣的網絡環境使用不一樣的超時策略,常見的網絡格式有 2g、3g、4g、wifi,實時的更新當前的網絡狀態,經過監聽來獲取最新的網絡類型,並動態調整網絡超時的時間
private void netWorkDemo() {
TextView netWork = findViewById(R.id.net_work);
boolean networkConnected = NetworkUtils.isNetworkConnected(this);
int networkType = NetworkUtils.getNetworkType(this);
System.out.println("shiming 是否聯網了"+networkConnected);
switch (networkType){
case TYPE_UNKNOWN:
System.out.println("shiming 聯網的類型---無網絡鏈接");
netWork.setText("是否聯網了---》"+networkConnected+" 聯網的類型---無網絡鏈接");
break;
case TYPE_2G:
System.out.println("shiming 聯網的類型---2G");
netWork.setText("是否聯網了---》"+networkConnected+" 聯網的類型---2G");
break;
case TYPE_3G:
System.out.println("shiming 聯網的類型---TYPE_3G");
netWork.setText("是否聯網了---》"+networkConnected+" 聯網的類型---TYPE_3G");
break;
case TYPE_4G:
System.out.println("shiming 聯網的類型---TYPE_4G");
netWork.setText("是否聯網了---》"+networkConnected+" 聯網的類型---TYPE_4G");
break;
case TYPE_WIFI:
System.out.println("shiming 聯網的類型---TYPE_WIFI");
netWork.setText("是否聯網了---》"+networkConnected+" 聯網的類型---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:網絡鏈接工具類
*/
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";
/**
* 判斷是否已鏈接到網絡.
*
* @param context Context
* @return 是否已鏈接到網絡
*/
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;
}
/**
* 獲取當前網絡類型
*
* @param context Context
* @return 當前網絡類型(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 中國移動 聯通 電信 三種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:網絡鏈接類型常量
*/
public class NetworkType {
/**
* 無網絡鏈接
*/
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;
}
複製代碼
####電量優化
一、(BroadCastReceiver)爲了減小應用耗損的電量,咱們在代碼中儘可能避免使用無用的操做代碼,當應用退到後臺了,一切頁面的刷新都是沒有意義的,而且浪費電,好比有個監聽網絡狀態的廣播並執行一些動做,彈窗或者是Toast,那麼app須要在後臺的時候,禁用掉這個功能,
二、數據傳輸 藍牙傳輸,Wi-Fi傳輸 移動網絡傳輸 後臺數據的管理:根據業務需求,接口儘可能避免無效數據的傳輸 數據傳輸的頻度問題:經過經驗值或者是數據統計的方法肯定好數據傳輸的頻度,避免冗餘重複的數據傳輸,數據傳輸過程當中要壓縮數據的大小,合併網絡請求,避免輪詢
三、位置服務 正確的使用位置復位,是應用耗電的一個關鍵
須要注意如下的三點:
一、有沒有及時註銷位置監聽器:和廣播差很少
二、位置更新監聽頻率的設定;更加業務需求設置一個合理的更新頻率值,
三、Android提供了三種定位
/**
* //設置定位精確度 Criteria.ACCURACY_COARSE比較粗略,Criteria.ACCURACY_FINE則比較精細
* criteria.setAccuracy(Criteria.ACCURACY_FINE);
* //設置是否要求速度
* criteria.setSpeedRequired(false);
* // 設置是否容許運營商收費
* criteria.setCostAllowed(false);
* //設置是否須要方位信息
* criteria.setBearingRequired(false);
* //設置是否須要海拔信息
* criteria.setAltitudeRequired(false);
* // 設置對電源的需求
* 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); //獲取緯度 //獲取經度 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();//獲取經度 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變更的,能夠設置,我是第"+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的時候這個位置監聽纔會中止,有興趣的小夥伴,能夠好好看看值爲null,底層會作什麼操做
/**
* 記得須要銷燬這個監聽,
* todo 若是不手動置爲null的話,其實您能夠經過日記發現,這個監聽仍是一直在走的,因此說這裏手動值爲null的好處
*/
protected void onDestroy() {
if (mAm!=null){
mAm.cancel(mPi);
mAm=null;//變爲null
}
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();
}
複製代碼
@SuppressLint("WakelockTimeout")
private void wakeLockDemo() {
// PowerManager.PARTIAL_WAKE_LOCK;//保持CPU正常運轉,但屏幕和鍵盤燈有多是關閉的
// PowerManager.SCREEN_DIM_WAKE_LOCK://保持CPU正常運轉,容許屏幕點亮但多是置灰的,鍵盤燈多是關閉的
// PowerManager.SCREEN_BRIGHT_WAKE_LOCK;//保持CPU正常的運轉,容許屏幕高亮顯示,鍵盤燈多是關閉的
// PowerManager.FULL_WAKE_LOCK;//保持CPU正常運轉,保持屏幕高亮顯示,鍵盤燈也保持連讀
// PowerManager.ACQUIRE_CAUSES_WAKEUP;//強制屏幕和鍵盤燈亮起,這種鎖針對必須通知用戶的操做
// PowerManager.ON_AFTER_RELEASE;//當WakeLock被釋放了,繼續保持屏幕和鍵盤燈開啓必定的時間
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();
}
}
複製代碼
private void alarmManager() {
//建立Intent對象,action爲ELITOR_CLOCK,附加信息爲字符串「你該打醬油了」
Intent intent = new Intent("action");
intent.putExtra("msg","重啓---App ---Le -- 回到前臺");
// intent.setClass(ElectricQuantityOptimizationActivity.this,MainActivity.class);
//定義一個PendingIntent對象,PendingIntent.getBroadcast包含了sendBroadcast的動做。
//也就是發送了action 爲"action"的intent
mPi = PendingIntent.getBroadcast(this,0,intent,0);
//AlarmManager對象,注意這裏並非new一個對象,Alarmmanager爲系統級服務
mAm = (AlarmManager)getSystemService(ALARM_SERVICE);
//設置鬧鐘從當前時間開始,每隔5s執行一次PendingIntent對象pi,注意第一個參數與第二個參數的關係
// 5秒後經過PendingIntent pi對象發送廣播
assert mAm != null;
/**
* 頻繁的報警對電池壽命不利。至於API 22,警報管理器將覆蓋近期和高頻報警請求,
* 在將來至少延遲5秒的警報,並確保重複間隔至少爲60秒,若是真的須要間隔很短的話,官方建議使用handler
* 該方法用於設置重複鬧鐘,第一個參數表示鬧鐘類型,第二個參數表示鬧鐘首次執行時間,
* 第三個參數表示鬧鐘兩次執行的間隔時間,第三個參數表示鬧鐘響應動做。
*/
mAm.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),1000, mPi);
//該方法用於設置一次性鬧鐘,第一個參數表示鬧鐘類型,第二個參數表示鬧鐘執行時間,第三個參數表示鬧鐘響應動做。
//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表示鬧鐘在手機睡眠狀態下不可用,該狀態下鬧鐘使用相對時間(相對於系統啓動開始),狀態值爲3;
* AlarmManager.ELAPSED_REALTIME_WAKEUP表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘也使用相對時間,狀態值爲2;
*
* AlarmManager.RTC表示鬧鐘在睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間,即當前系統時間,狀態值爲1;
*
* AlarmManager.RTC_WAKEUP表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘使用絕對時間,狀態值爲0;
*
* AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機關機狀態下也能正常進行提示功能,因此是5個狀態中用的最多的狀態之一,該狀態下鬧鐘也是用絕對時間,狀態值爲4;不過本狀態好像受SDK版本影響,某些版本並不支持;
*/
//該方法也用於設置重複鬧鐘,與第二個方法類似,不過其兩個鬧鐘執行的間隔時間不是固定的而已。
//基本上類似,只不過這個方法優化了不少,省電
// 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;
// 獲取系統默認的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();
}
}
複製代碼
/**
* 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();
/**
* 程序會從新啓動,若是點擊電量優化,App崩潰了,請給與所有權限,
* 還要在開發者模式裏面給與位置信息模擬的設置,若是崩潰了,
* 你也能夠發現app會自動的從新啓動,這是AlarmManager的應用,注意看MyApplication裏面的代碼,tks
*/
CrashHandler.getInstance().initCrashHandler(this);
}
}
複製代碼
###以上就是我的總結的基本,總結的不太全面,同時也不太詳細,若是能夠的話,還請給個小星星,表示鼓勵,謝謝了☺☺☺ ###git地址PerformanceOptimizationForAndroid