Android性能優化全面攻略

一 :安裝包性能壓縮

一個字:刪!!刪不了就儘可能小。java

1.圖片壓縮

圖片:apk裏面的資源圖片 壓縮圖片 svg圖片:一些圖片的描述,犧牲CPU的計算能力的,節省空間。 使用的原則:簡單的圖標。 webp:谷歌如今很是提倡的使用。保存圖片比較小。 VP8派生而來的。webp的無損壓縮比PNG文件小45%左右,即便PNG進過其餘的壓縮工具壓縮後, 任然能夠減少到PNG的28%。android

Facebook在用、騰訊、淘寶。 缺點:加載相比於PNG要慢不少。 可是配置比較高。 工具:isparta.github.io/git

2.插件化 資源動態加載

好比:emoji表情、換膚 動態下載的資源。 一些模塊的插件化動態添加。github

3.Lint工具 建議優化的點

1)檢測沒有用的佈局 刪除 2)未使用到的資源 好比 圖片 ---刪除 3)建議String.xml有一些沒有用到的字符。web

4.極限壓縮

7zZip工具的使用。算法

5.Proguard 混淆。

讓apk變小。爲何? 1)能夠刪除註釋和不用的代碼。 2)將java文件名改爲短名a.java,b.java 3)方法名等 CommonUtil.getDisplayMetrix();--》a.a() 4) shrinkResource 去除無用資源shell

在常規的安裝包的優化以外繼續壓縮---資源文件再壓

系統編譯完成apk文件之後: 映射關係:res/drawable/ic_launcher.png ----- > 0x7f020000數據庫

再作「混淆」:要實現將res/drawable/ic_launcher.png圖片改爲a.png 將drawable,String,layout的名字繼續縮減 好比:R.string.description--->R.string.a res/drawable/ic_launcher.png圖片改爲a.pngexpress

還能夠更加誇張 res/drawable--->r/d res/value-->r/v res/drawable/ic_launcher.png圖片改爲r/d/a.pngapache

讀取resources.arsc二進制文件,而後修改某一段一段的字節。 有一段叫作:res/drawable/ic_launcher.png 在本身數組當中的第800位-810位 將這一段第800位-810位替換成改爲r/d/a.png 的字節碼。

args參數: demo.apk -config config.xml -7zip 7za.exe -out path/dirName -mapping your-project/path/mapping.txt

騰訊開源工具:AndResGuard 即利用此原理。

github.com/shwenzhang/…

二: 圖片 bitmap 壓縮

1.質量壓縮 像素大小並不變化

原理:經過算法扣掉了圖片中一些相近的像素,達到下降質量的沒得 只能下降File的大小,不能下降內存中的bitmap,bitmap是按照width*high來計算的的 。 只能將圖片壓縮後保存到本地,或者上傳服務器。 Bitmap.compress(CompressFormat format, int quality, OutputStream stream)

2.尺寸壓縮

減小單位尺寸上的像素值,真正意義上下降像素。 緩存縮略圖,頭像等。 int rotatio = 4;// 壓縮倍數 Rect rect = new Rect(0,0,bitmap.getWidth() / rotatio,bitmap.getHeight()/rotatio); Canvas canvas = new Canvas(); canvas.save(); canvas.drawBitmap(bitmap,null,rect,null); canvas.restore();

3.採樣率壓縮

insampleSize = 2

3.1//鄰近壓縮 簡單粗暴,容易失真 直接將鄰近點的像素看成壓縮後的像素。

Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png",options); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; options.inSampleSize = 8;

3.2//雙線性採樣率壓縮 將附近像素計算權重後肯定壓縮後的像素

Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png"); Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);

4終極壓縮

將SKIA引擎增長哈夫曼算法 利用libjpeg.so實現 --》微信apk也有了libwechatjpeg.so庫 具體JNI實現

/*
 * Copyright 2014 http://Bither.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bitherlibjni.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>

//統一編譯方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h" /* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h" /* for version message */
#include "jpeg/android/config.h"
}


#define LOG_TAG "jni"
#define LOGW(...) __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#define true 1
#define false 0

typedef uint8_t BYTE;

char *error;
struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  (*cinfo->err->output_message) (cinfo);
  error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
  LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
 // LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
//  LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
//  LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
  longjmp(myerr->setjmp_buffer, 1);
}

int generateJPEG(BYTE* data, int w, int h, int quality,
		const char* outfilename, jboolean optimize) {

	//jpeg的結構體,保存的好比寬、高、位深、圖片格式等信息,至關於java的類
	struct jpeg_compress_struct jcs;

	//當讀完整個文件的時候就會回調my_error_exit這個退出方法。setjmp是一個系統級函數,是一個回調。
	struct my_error_mgr jem;
	jcs.err = jpeg_std_error(&jem.pub);
	jem.pub.error_exit = my_error_exit;
	if (setjmp(jem.setjmp_buffer)) {
		return 0;
	}

	//初始化jsc結構體
	jpeg_create_compress(&jcs);
	//打開輸出文件 wb:可寫byte
	FILE* f = fopen(outfilename, "wb");
	if (f == NULL) {
		return 0;
	}
	//設置結構體的文件路徑
	jpeg_stdio_dest(&jcs, f);
	jcs.image_width = w;//設置寬高
	jcs.image_height = h;
//	if (optimize) {
//		LOGI("optimize==ture");
//	} else {
//		LOGI("optimize==false");
//	}

	//看源碼註釋,設置哈夫曼編碼:/* TRUE=arithmetic coding, FALSE=Huffman */
	jcs.arith_code = false;
	int nComponent = 3;
	/* 顏色的組成 rgb,三個 # of color components in input image */
	jcs.input_components = nComponent;
	//設置結構體的顏色空間爲rgb
	jcs.in_color_space = JCS_RGB;
//	if (nComponent == 1)
//		jcs.in_color_space = JCS_GRAYSCALE;
//	else
//		jcs.in_color_space = JCS_RGB;

	//所有設置默認參數/* Default parameter setup for compression */
	jpeg_set_defaults(&jcs);
	//是否採用哈弗曼表數據計算 品質相差5-10倍
	jcs.optimize_coding = optimize;
	//設置質量
	jpeg_set_quality(&jcs, quality, true);
	//開始壓縮,(是否寫入所有像素)
	jpeg_start_compress(&jcs, TRUE);

	JSAMPROW row_pointer[1];
	int row_stride;
	//一行的rgb數量
	row_stride = jcs.image_width * nComponent;
	//一行一行遍歷
	while (jcs.next_scanline < jcs.image_height) {
		//獲得一行的首地址
		row_pointer[0] = &data[jcs.next_scanline * row_stride];

		//此方法會將jcs.next_scanline加1
		jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:寫入的行數
	}
	jpeg_finish_compress(&jcs);//結束
	jpeg_destroy_compress(&jcs);//銷燬 回收內存
	fclose(f);//關閉文件

	return 1;
}

/**
 * byte數組轉C的字符串
 */
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
	char* rtn = NULL;
	jsize alen = env->GetArrayLength( barr);
	jbyte* ba = env->GetByteArrayElements( barr, 0);
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1);
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	env->ReleaseByteArrayElements( barr, ba, 0);
	return rtn;
}

jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
		jclass thiz, jobject bitmapcolor, int w, int h, int quality,
		jbyteArray fileNameStr, jboolean optimize) {
	BYTE *pixelscolor;
	//1.將bitmap裏面的全部像素信息讀取出來,並轉換成RGB數據,保存到二維byte數組裏面
	//處理bitmap圖形信息方法1 鎖定畫布
	AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);

	//2.解析每個像素點裏面的rgb值(去掉alpha值),保存到一維數組data裏面
	BYTE *data;
	BYTE r,g,b;
	data = (BYTE*)malloc(w*h*3);//每個像素都有三個信息RGB
	BYTE *tmpdata;
	tmpdata = data;//臨時保存data的首地址
	int i=0,j=0;
	int color;
	for (i = 0; i < h; ++i) {
		for (j = 0; j < w; ++j) {
			//解決掉alpha
			//獲取二維數組的每個像素信息(四個部分a/r/g/b)的首地址
			color = *((int *)pixelscolor);//經過地址取值
			//0~255:
//			a = ((color & 0xFF000000) >> 24);
			r = ((color & 0x00FF0000) >> 16);
			g = ((color & 0x0000FF00) >> 8);
			b = ((color & 0x000000FF));
			//改值!!!----保存到data數據裏面
			*data = b;
			*(data+1) = g;
			*(data+2) = r;
			data = data + 3;
			//一個像素包括argb四個值,每+4就是取下一個像素點
			pixelscolor += 4;
		}
	}
	//處理bitmap圖形信息方法2 解鎖
	AndroidBitmap_unlockPixels(env,bitmapcolor);
	char* fileName = jstrinTostring(env,fileNameStr);
	//調用libjpeg核心方法實現壓縮
	int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
	if(resultCode ==0){
		jstring result = env->NewStringUTF("-1");
		return result;
	}
	return env->NewStringUTF("1");
}

複製代碼

FFmpeg中有雙立方採樣壓縮跟三線性採樣壓縮,可是未引用FFmpeg的android項目沒法使用,故採用上面這種方式。

三:Splash啓動優化

提高應用的啓動速度 和 splash頁面的設計

1.啓動分爲兩種方式:

1)冷啓動:當直接從桌面上直接啓動,同時後臺沒有該進程的緩存,這個時候系統就須要
從新建立一個新的進程而且分配各類資源。
2)熱啓動:該app後臺有該進程的緩存,這時候啓動的進程就屬於熱啓動。

熱啓動不須要從新分配進程,也不會Application了,直接走的就是app的入口Activity,這樣就速度快不少
複製代碼

2.如何測量一個應用的啓動時間

使用命令行來啓動app,同時進行時間測量。單位:毫秒
adb shell am start -W [PackageName]/[PackageName.MainActivity]
adb shell am start -W project.study.koller.mystudyproject.app/project.study.koller.mystudyproject.app.MainActivity


ThisTime: 165 指當前指定的MainActivity的啓動時間
TotalTime: 165 整個應用的啓動時間,Application+Activity的使用的時間。
WaitTime: 175 包括系統的影響時間---比較上面大。
複製代碼

3.應用啓動的流程

Application從構造方法開始--->attachBaseContext()--->onCreate()
Activity構造方法--->onCreate()--->設置顯示界面佈局,設置主題、背景等等屬性
--->onStart()--->onResume()--->顯示裏面的view(測量、佈局、繪製,顯示到界面上)

時間花在哪裏了?
複製代碼

4.減小應用的啓動時間的耗時

1)、不要在Application的構造方法、attachBaseContext()、onCreate()裏面進行初始化耗時操做。
2)、MainActivity,因爲用戶只關心最後的顯示的這一幀,對咱們的佈局的層次要求要減小,自定義控件的話測量、佈局、繪製的時間。
	不要在onCreate、onStart、onResume當中作耗時操做。
3)、對於SharedPreference的初始化。
	由於他初始化的時候是須要將數據所有讀取出來放到內存當中。
	優化1:能夠儘量減小sp文件數量(IO須要時間);2.像這樣的初始化最好放到線程裏面;3.大的數據緩存到數據庫裏面。
複製代碼

app啓動的耗時主要是在:Application初始化 + MainActivity的界面加載繪製時間。

因爲MainActivity的業務和佈局複雜度很是高,甚至該界面必需要有一些初始化的數據才能顯示。 那麼這個時候MainActivity就可能半天都出不來,這就給用戶感受app太卡了。

咱們要作的就是給用戶趕忙利落的體驗。點擊app就立馬彈出咱們的界面。 因而乎想到使用SplashActivity--很是簡單的一個歡迎頁面上面都不幹就只顯示一個圖片。

可是SplashActivity啓動以後,仍是須要跳到MainActivity。MainActivity仍是須要從頭開始加載佈局和數據。 想到SplashActivity裏面能夠去作一些MainActivity的數據的預加載。而後須要經過意圖傳到MainActivity。

可不能夠再作一些更好的優化呢? 耗時的問題:Application+Activity的啓動及資源加載時間;預加載的數據花的時間。

若是咱們能讓這兩個時間重疊在一個時間段內併發地作這兩個事情就省時間了。

5 解決:

將SplashActivity和MainActivity合爲一個。

一進來仍是現實的MainActivity,SplashActivity能夠變成一個SplashFragment,而後放一個FrameLayout做爲根佈局直接現實SplashFragment界面。
SplashFragment裏面很是之簡單,就是現實一個圖片,啓動很是快。
當SplashFragment顯示完畢後再將它remove。同時在splash的2S的友好時間內進行網絡數據緩存。
這個時候咱們纔看到MainActivity,就沒必要再去等待網絡數據返回了。

問題:SplashView和ContentView加載放到一塊兒來作了 ,這可能會影響應用的啓動時間。
解決:可使用ViewStub延遲加載MainActivity當中的View來達到減輕這個影響。
複製代碼

viewStub的設計就是爲了防止MainActivity的啓動加載資源太耗時了。延遲進行加載,不影響啓動,用戶友好。 可是viewStub加載也須要時間。等到主界面出來之後。 viewStub.inflate(xxxx);

6.如何設計延遲加載DelayLoad

第一時間想到的就是在onCreate裏面調用handler.postDelayed()方法。
問題:這個延遲時間如何控制?
不一樣的機器啓動速度不同。這個時間如何控制?
假設,須要在splash作一個動畫--2S

須要達到的效果:應用已經啓動並加載完成,界面已經顯示出來了,而後咱們再去作其餘的事情。
複製代碼

若是咱們這樣:

mHandler.postDelayed(new Runnable() {
			@Override
			public void run() {
				mProgressBar.setVisibility(View.GONE);
				iv.setVisibility(View.VISIBLE);
			}
		}, 2500);
複製代碼

是無法作到等應用已經啓動並加載完成,界面已經顯示出來了,而後咱們再去作其餘的事情。2500ms並不能適配全部手機(CPU,內存大小,都不同)

問題:何時應用已經啓動並加載完成,界面已經顯示出來了。 onResume執行完了以後才顯示完畢。 利用getWindow().getDecorView().post(Runable)便可

public class MainActivity extends FragmentActivity {

	private Handler mHandler = new Handler();
	private SplashFragment splashFragment;
	private ViewStub viewStub;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		splashFragment = new SplashFragment();
		FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
		transaction.replace(R.id.frame, splashFragment);
		transaction.commit();
		
//		mHandler.postDelayed(new Runnable() {
//			@Override
//			public void run() {
//				mProgressBar.setVisibility(View.GONE);
//				iv.setVisibility(View.VISIBLE);
//			}
//		}, 2500);
		
		viewStub = (ViewStub)findViewById(R.id.content_viewstub);
		//1.判斷當窗體加載完畢的時候,立馬再加載真正的佈局進來
		getWindow().getDecorView().post(new Runnable() {
			
			@Override
			public void run() {
				// 開啓延遲加載
				mHandler.post(new Runnable() {
					
					@Override
					public void run() {
						//將viewstub加載進來
						viewStub.inflate();
					}
				} );
			}
		});
		
		
		//2.判斷當窗體加載完畢的時候執行,延遲一段時間作動畫。
		getWindow().getDecorView().post(new Runnable() {
			
			@Override
			public void run() {
				// 開啓延遲加載,也能夠不用延遲能夠立馬執行(我這裏延遲是爲了實現fragment裏面的動畫效果的耗時)
				mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);
//				mHandler.post(new DelayRunnable());
				
			}
		});
		//3.同時進行異步加載數據
		
		
	}
	
	@Override
	protected void onResume() {
		// TODO Auto-generated method stub
		super.onResume();
	}
	
	static class DelayRunnable implements Runnable{
		private WeakReference<Context> contextRef;
		private WeakReference<SplashFragment> fragmentRef;
		
		public DelayRunnable(Context context, SplashFragment f) {
			contextRef = new WeakReference<Context>(context);
			fragmentRef = new WeakReference<SplashFragment>(f);
		}

		@Override
		public void run() {
			// 移除fragment
			if(contextRef!=null){
				SplashFragment splashFragment = fragmentRef.get();
				if(splashFragment==null){
					return;
				}
				FragmentActivity activity = (FragmentActivity) contextRef.get();
				FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
				transaction.remove(splashFragment);
				transaction.commit();
				
			}
		}
		
	}

}
複製代碼

四: 進程保活

Service

service:是一個後臺服務,專門用來處理常駐後臺的工做的組件。

即時通信:service來作常駐後臺的心跳傳輸。 1.良民:核心服務儘量地輕!!! 不少人喜歡把全部的後臺操做都集中在一個service裏面。 爲核心服務專門作一個進程,跟其餘的全部後臺操做隔離。 樹大招風,核心服務千萬要輕。

1、優先級

進程的重要性優先級:(越日後的就越容易被系統殺死) 1.前臺進程;Foreground process 1)用戶正在交互的Activity(onResume()) 2)當某個Service綁定正在交互的Activity。 3)被主動調用爲前臺Service(startForeground()) 4)組件正在執行生命週期的回調(onCreate()/onStart()/onDestroy()) 5)BroadcastReceiver 正在執行onReceive();

2.可見進程;Visible process 1)咱們的Activity處在onPause()(沒有進入onStop()) 2)綁定到前臺Activity的Service。

3.服務進程;Service process 簡單的startService()啓動。 4.後臺進程;Background process 對用戶沒有直接影響的進程----Activity出於onStop()的時候。 android:process=":xxx" 5.空進程; Empty process 不含有任何的活動的組件。(android設計的,爲了第二次啓動更快,採起的一個權衡)

2、如何提高進程的優先級(儘可能作到不輕易被系統殺死)

1.QQ採起在鎖屏的時候啓動一個1個像素的Activity,當用戶解鎖之後將這個Activity結束掉(順便同時把本身的核心服務再開啓一次)。被用戶發現了就很差了。

故事:小米撕逼。 背景:當手機鎖屏的時候什麼都乾死了,爲了省電。 鎖屏界面在上面蓋住了。 監聽鎖屏廣播,鎖了---啓動這個Activity。 監聽鎖屏的, 開啓---結束掉這個Activity。 要監聽鎖屏的廣播---動態註冊。 ScreenListener.begin(new xxxListener onScreenOff() );

被系統沒法殺死的進程。

2.app運營商和手機廠商可能有合做關係---白名單。

3.雙進程守護---能夠防止單個進程殺死,同時能夠防止第三方的360清理掉。

一個進程被殺死,另一個進程又被他啓動。相互監聽啓動。

A<--->B
殺進程是一個一個殺的。本質是和殺進程時間賽跑。
複製代碼

4.JobScheduler

把任務加到系統調度隊列中,當到達任務窗口期的時候就會執行,咱們能夠在這個任務裏面啓動咱們的進程。 這樣能夠作到將近殺不死的進程。

5.監聽QQ,微信,系統應用,友盟,小米推送等等的廣播,而後把本身啓動了。

6.利用帳號同步機制喚醒咱們的進程

AccountManager

7.NDK來解決,Native進程來實現雙進程守護。

數據傳輸效率優化-flatBuffer

1、數據的序列化和反序列化

服務器對象Object----流--->客戶端Object對象

序列化: Serializable/Parcelable

時間:1ms * 10 * 50 * 20 = 10000ms 性能:內存的浪費和CPU計算時間的佔用。

json/xml json序列化的工具GSON/fastjson

FlatBuffer:基於二進制的文件。 json:基於字符串的

五:插件化-VIrtualAPK 加載邏輯

VirtualAPK加載邏輯 1 宿主APPlication,新建PluginManager的Instance 2 PluginManager構造函數中Hook了Instrumention,Handler,AMS,新建ComponentHanlder 3 MainActiviy 加摘APK,PluginManager.LoadPlugin(apk) 4 建立LoadedPlugin 加載APK,緩存四大組件及Instrumention 5 嘗試啓動插件apk的application. 利用Instrumention.newApplication(DexClassloader,applicatoinClassName,activity的context); 6 給application註冊activitylifecycle的回調監聽 7 點擊啓動按鈕,攔截execStartActivity方法,將目標Acitivity(即插件Acitivity)替換爲佔位Activity,即替換Intent. 8 攔截newActivity方法,先嚐試用當前的classLoader加載參數class,若是拋出ClassNotFoundException即爲插件Activity(由於佔位Activity並無實體,僅在Manifest中註冊),還原目標Intent,調用newActivity啓動,並利用反射獲取資源文件夾,將其賦值給插件Activity。

9 插件化出現android.view.inflateException是由於資源沒有被加載進來,因此報錯,資源找不到因此報錯。用純代碼方式佈局,無問題.

10 在小米9上(android9.0 MiUi10.2) VitrualAPK啓動demo也有問題。

六:熱修復

1 將bug的類修改完畢 2 利用dx工具(androidSdk自帶)將其打包爲dex文件 3 將dex文件下載到手機 4 利用DexClassLoader跟PathClassLoader的共同父類BaseDexLoader裏面的DexPathList的dexElements數組,將其插入到數組最前面便可。

class BaseDexClassLoader{
	DexPathList pathList;
}
class DexPathList{
	Element[] dexElements;
}
複製代碼

1.找到MyTestClass.class project_name\app\build\intermediates\bin\MyTestClass.class 2.配置dx.bat的環境變量 Android\sdk\build-tools\23.0.3\dx.bat 3.命令 dx --dex --output=D:\Users\adminstor\Desktop\dex\classes2.dex D:\Users\adminstor\Desktop\dex 命令解釋: --output=D:\Users\adminstor\Desktop\dex\classes2.dex 指定輸出路徑 D:\Users\adminstor\Desktop\dex 最後指定去打包哪一個目錄下面的class字節文件(注意要包括全路徑的文件夾,也能夠有多個class)

而後利用反射將其加載進app,等到下次啓動便可。

public class FixDexUtils {
	private static HashSet<File> loadedDex = new HashSet<File>();
	
	static{
		loadedDex.clear();
	}

	public static void loadFixedDex(Context context){
		if(context == null){
			return ;
		}
		//遍歷全部的修復的dex
		File fileDir = context.getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
		File[] listFiles = fileDir.listFiles();
		for(File file:listFiles){
			if(file.getName().startsWith("classes")&&file.getName().endsWith(".dex")){
				loadedDex.add(file);//存入集合
			}
		}
		//dex合併以前的dex
		doDexInject(context,fileDir,loadedDex);
	}

	private static void setField(Object obj,Class<?> cl, String field, Object value) throws Exception {
		Field localField = cl.getDeclaredField(field);
		localField.setAccessible(true);
		localField.set(obj,value);
	}

	private static void doDexInject(final Context appContext, File filesDir,HashSet<File> loadedDex) {
		String optimizeDir = filesDir.getAbsolutePath()+File.separator+"opt_dex";
		File fopt = new File(optimizeDir);
		if(!fopt.exists()){
			fopt.mkdirs();
		}
		//1.加載應用程序的dex
		try {
			PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();

			for (File dex : loadedDex) {
				//2.加載指定的修復的dex文件。
				DexClassLoader classLoader = new DexClassLoader(
						dex.getAbsolutePath(),//String dexPath,
						fopt.getAbsolutePath(),//String optimizedDirectory,
						null,//String libraryPath,
						pathLoader//ClassLoader parent
				);
				//3.合併
				Object dexObj = getPathList(classLoader);
				Object pathObj = getPathList(pathLoader);
				Object mDexElementsList = getDexElements(dexObj);
				Object pathDexElementsList = getDexElements(pathObj);
				//合併完成
				Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
				//重寫給PathList裏面的lement[] dexElements;賦值
				Object pathList = getPathList(pathLoader);
				setField(pathList,pathList.getClass(),"dexElements",dexElements);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
    }

	private static Object getField(Object obj, Class<?> cl, String field)
			throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
		Field localField = cl.getDeclaredField(field);
		localField.setAccessible(true);
		return localField.get(obj);
	}
	private static Object getPathList(Object baseDexClassLoader) throws Exception {
			return getField(baseDexClassLoader,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
	}

	private static Object getDexElements(Object obj) throws Exception {
			return getField(obj,obj.getClass(),"dexElements");
	}

	/**
	 * 兩個數組合並
	 * @param arrayLhs
	 * @param arrayRhs
     * @return
     */
	private static Object combineArray(Object arrayLhs, Object arrayRhs) {
		Class<?> localClass = arrayLhs.getClass().getComponentType();
		int i = Array.getLength(arrayLhs);
		int j = i + Array.getLength(arrayRhs);
		Object result = Array.newInstance(localClass, j);
		for (int k = 0; k < j; ++k) {
			if (k < i) {
				Array.set(result, k, Array.get(arrayLhs, k));
			} else {
				Array.set(result, k, Array.get(arrayRhs, k - i));
			}
		}
		return result;
	}
}
複製代碼
相關文章
相關標籤/搜索