日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

深度理解Android InstantRun原理以及源码分析

發布時間:2025/3/15 Android 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深度理解Android InstantRun原理以及源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

@Author 莫川

Instant Run官方介紹

簡單介紹一下Instant Run,它是Android Studio2.0以后新增的一個運行機制,能夠顯著減少你第二次及以后的構建和部署時間。簡單通俗的解釋就是,當你在Android Studio中改了你的代碼,Instant Run可以很快的讓你看到你修改的效果。而在沒有Instant Run之前,你的一個小小的修改,都肯能需要幾十秒甚至更長的等待才能看到修改后的效果。

傳統的代碼修改及編譯部署流程


構建整個apk → 部署app → app重啟 → 重啟Activity
而Instant Run則需要更少的時間。

Instant Run編譯和部署流程


只構建修改的部分 → 部署修改的dex或資源 → 熱部署,溫部署,冷部署

熱部署

Incremental code changes are applied and reflected in the app without needing to relaunch the app or even restart the current Activity. Can be used for most simple changes within method implementations.?
方法內的簡單修改,無需重啟app和Activity

溫部署

The Activity needs to be restarted before changes can be seen and used. Typically required for changes to resources.?
app無需重啟,但是activity需要重啟,比如資源的修改。

冷部署

The app is restarted (but still not reinstalled). Required for any structural changes such as to inheritance or method signatures.?
app需要重啟,比如繼承關系的改變或方法的簽名變化等。

上述說這么多概念,估計大家對Instant Run應該有了大體的認知了。那么它的實現原理是什么呢?其實,在沒有看案例之前,我基本上可以猜測到Instant Run的思路,基于目前比較火的插件化框架,是比較容易理解Instant Run的。但Instant Run畢竟是Google官方的工具,具有很好的借鑒意義。

Demo案例

新建一個簡單的android studio項目,新建自己的MyApplication,在AndroidManifest文件中設置:


首先,我們先反編譯一下APK的構成:?
使用的工具:d2j-dex2jar 和jd-gui

里面有2個dex文件和一個instant-run.zip文件。首先分別看一下兩個dex文件的源碼:
classes.dex的反編譯之后的源碼:

里面只有一個AppInfo,保存了app的基本信息,主要包含了包名和applicationClass。
classes2.dex反編譯之后的源碼:

我們赫然發現,兩個dex中竟然沒有一句我們自己寫的代碼??那么代碼在哪里呢?你可能猜到,app真正的業務dex在instant-run.zip中。解壓instant-run.zip之后,如下圖所示:

反編譯之后,我們會發現,我們真正的業務代碼都在這里。
另外,我們再decode看一下AndroidManifest文件

//TODO?
我們發現,我們的application也被替換了,替換成了com.android.tools.fd.runtime.BootstrapApplication

看到這里,那么大體的思路,可以猜到:?
1.Instant-Run代碼作為一個宿主程序,將app作為資源dex加載起來,和插件化一個思路?
2.那么InstantRun是怎么把業務代碼運行起來的呢?

InstantRun啟動app

首先BootstrapApplication分析,按照執行順序,依次分析attachBaseContext和onCreate方法。

1.attachBaseContext方法

...protected void attachBaseContext(Context context) {if (!AppInfo.usingApkSplits) {String apkFile = context.getApplicationInfo().sourceDir;long apkModified = apkFile != null ? new File(apkFile).lastModified() : 0L;createResources(apkModified);setupClassLoaders(context, context.getCacheDir().getPath(),apkModified);}createRealApplication();super.attachBaseContext(context);if (this.realApplication != null) {try {Method attachBaseContext = ContextWrapper.class.getDeclaredMethod("attachBaseContext",new Class[] { Context.class });attachBaseContext.setAccessible(true);attachBaseContext.invoke(this.realApplication,new Object[] { context });} catch (Exception e) {throw new IllegalStateException(e);}}}...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

我們依次需要關注的方法有:?
createResources → setupClassLoaders → createRealApplication → 調用realApplication的attachBaseContext方法

1.1.createResources

首先看createResources方法:

private void createResources(long apkModified) {FileManager.checkInbox();File file = FileManager.getExternalResourceFile();this.externalResourcePath = (file != null ? file.getPath() : null);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Resource override is "+ this.externalResourcePath);}if (file != null) {try {long resourceModified = file.lastModified();if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Resource patch last modified: "+ resourceModified);Log.v("InstantRun", "APK last modified: " + apkModified+ " "+ (apkModified > resourceModified ? ">" : "<")+ " resource patch");}if ((apkModified == 0L) || (resourceModified <= apkModified)) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Ignoring resource file, older than APK");}this.externalResourcePath = null;}} catch (Throwable t) {Log.e("InstantRun", "Failed to check patch timestamps", t);}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

該方法主要是判斷資源resource.ap_是否改變,然后保存resource.ap_的路徑到externalResourcePath中

1.2.setupClassLoaders
private static void setupClassLoaders(Context context, String codeCacheDir,long apkModified) {List<String> dexList = FileManager.getDexList(context, apkModified);Class<Server> server = Server.class;Class<MonkeyPatcher> patcher = MonkeyPatcher.class;if (!dexList.isEmpty()) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Bootstrapping class loader with dex list "+ join('\n', dexList));}ClassLoader classLoader = BootstrapApplication.class.getClassLoader();String nativeLibraryPath;try {nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath", new Class[0]).invoke(classLoader, new Object[0]);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Native library path: "+ nativeLibraryPath);}} catch (Throwable t) {Log.e("InstantRun", "Failed to determine native library path "+ t.getMessage());nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();}IncrementalClassLoader.inject(classLoader, nativeLibraryPath,codeCacheDir, dexList);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

繼續看IncrementalClassLoader.inject方法:?
IncrementalClassLoader的源碼如下:

public class IncrementalClassLoader extends ClassLoader {public static final boolean DEBUG_CLASS_LOADING = false;private final DelegateClassLoader delegateClassLoader;public IncrementalClassLoader(ClassLoader original,String nativeLibraryPath, String codeCacheDir, List<String> dexes) {super(original.getParent());this.delegateClassLoader = createDelegateClassLoader(nativeLibraryPath,codeCacheDir, dexes, original);}public Class<?> findClass(String className) throws ClassNotFoundException {try {return this.delegateClassLoader.findClass(className);} catch (ClassNotFoundException e) {throw e;}}private static class DelegateClassLoader extends BaseDexClassLoader {private DelegateClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {super(dexPath, optimizedDirectory, libraryPath, parent);}public Class<?> findClass(String name) throws ClassNotFoundException {try {return super.findClass(name);} catch (ClassNotFoundException e) {throw e;}}}private static DelegateClassLoader createDelegateClassLoader(String nativeLibraryPath, String codeCacheDir, List<String> dexes,ClassLoader original) {String pathBuilder = createDexPath(dexes);return new DelegateClassLoader(pathBuilder, new File(codeCacheDir),nativeLibraryPath, original);}private static String createDexPath(List<String> dexes) {StringBuilder pathBuilder = new StringBuilder();boolean first = true;for (String dex : dexes) {if (first) {first = false;} else {pathBuilder.append(File.pathSeparator);}pathBuilder.append(dex);}if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Incremental dex path is "+ BootstrapApplication.join('\n', dexes));}return pathBuilder.toString();}private static void setParent(ClassLoader classLoader, ClassLoader newParent) {try {Field parent = ClassLoader.class.getDeclaredField("parent");parent.setAccessible(true);parent.set(classLoader, newParent);} catch (IllegalArgumentException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (NoSuchFieldException e) {throw new RuntimeException(e);}}public static ClassLoader inject(ClassLoader classLoader,String nativeLibraryPath, String codeCacheDir, List<String> dexes) {IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes);setParent(classLoader, incrementalClassLoader);return incrementalClassLoader;}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

inject方法是用來設置classloader的父子順序的,使用IncrementalClassLoader來加載dex。由于ClassLoader的雙親委托模式,也就是委托父類加載類,父類中找不到再在本ClassLoader中查找。?
調用之后的效果如下圖所示:

我們可以在MyApplication中,用代碼驗證一下

@Overridepublic void onCreate() {super.onCreate();try{Log.d(TAG,"###onCreate in myApplication");String classLoaderName = getClassLoader().getClass().getName();Log.d(TAG,"###onCreate in myApplication classLoaderName = "+classLoaderName);String parentClassLoaderName = getClassLoader().getParent().getClass().getName();Log.d(TAG,"###onCreate in myApplication parentClassLoaderName = "+parentClassLoaderName);String pParentClassLoaderName = getClassLoader().getParent().getParent().getClass().getName();Log.d(TAG,"###onCreate in myApplication pParentClassLoaderName = "+pParentClassLoaderName);}catch (Exception e){e.printStackTrace();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

運行結果:

... 06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader 06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader 06-30 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader
  • 1
  • 2
  • 3
  • 4

由此,我們已經知道了,當前PathClassLoader委托IncrementalClassLoader加載dex。繼續回到BootstrapApplication的attachBaseContext方法繼續分析。

1.3.createRealApplication
private void createRealApplication() {if (AppInfo.applicationClass != null) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","About to create real application of class name = "+ AppInfo.applicationClass);}try {Class<? extends Application> realClass = (Class<? extends Application>) Class.forName(AppInfo.applicationClass);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Created delegate app class successfully : "+ realClass + " with class loader "+ realClass.getClassLoader());}Constructor<? extends Application> constructor = realClass.getConstructor(new Class[0]);this.realApplication = ((Application) constructor.newInstance(new Object[0]));if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Created real app instance successfully :"+ this.realApplication);}} catch (Exception e) {throw new IllegalStateException(e);}} else {this.realApplication = new Application();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

該方法就是用classes.dex中的AppInfo類的applicationClass常量中保存的app真實的application。由上面的反編譯截圖可以知道,demo中的applicationClass就是mobctrl.net.testinstantrun.MyApplication。通過反射的方式,創建真是的realApplication。

1.4.調用realApplication的attachBaseContext方法

代理realApplication的生命周期,通過反射調用realApplication的attachBaseContext方法,以當前的Context為參數。
attachBaseContext方法執行結束之后,我們繼續往下看,到BootstrapApplication的onCreate方法

2.onCreate

源碼如下:

public void onCreate() {if (!AppInfo.usingApkSplits) {MonkeyPatcher.monkeyPatchApplication(this, this,this.realApplication, this.externalResourcePath);MonkeyPatcher.monkeyPatchExistingResources(this,this.externalResourcePath, null);} else {MonkeyPatcher.monkeyPatchApplication(this, this,this.realApplication, null);}super.onCreate();if (AppInfo.applicationId != null) {try {boolean foundPackage = false;int pid = Process.myPid();ActivityManager manager = (ActivityManager) getSystemService("activity");List<ActivityManager.RunningAppProcessInfo> processes = manager.getRunningAppProcesses();boolean startServer = false;if ((processes != null) && (processes.size() > 1)) {for (ActivityManager.RunningAppProcessInfo processInfo : processes) {if (AppInfo.applicationId.equals(processInfo.processName)) {foundPackage = true;if (processInfo.pid == pid) {startServer = true;break;}}}if ((!startServer) && (!foundPackage)) {startServer = true;if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Multiprocess but didn't find process with package: starting server anyway");}}} else {startServer = true;}if (startServer) {Server.create(AppInfo.applicationId, this);}} catch (Throwable t) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Failed during multi process check", t);}Server.create(AppInfo.applicationId, this);}}if (this.realApplication != null) {this.realApplication.onCreate();}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

我們依次需要關注的方法有:?
monkeyPatchApplication → monkeyPatchExistingResources → Server啟動 → 調用realApplication的onCreate方法

2.1 monkeyPatchApplication

該方法的目的可以總結為:替換所有當前app的application為realApplication。

public static void monkeyPatchApplication(Context context,Application bootstrap, Application realApplication,String externalResourceFile) {try {Class<?> activityThread = Class.forName("android.app.ActivityThread");Object currentActivityThread = getActivityThread(context,activityThread);Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");mInitialApplication.setAccessible(true);Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);if ((realApplication != null) && (initialApplication == bootstrap)) {mInitialApplication.set(currentActivityThread, realApplication);}if (realApplication != null) {Field mAllApplications = activityThread.getDeclaredField("mAllApplications");mAllApplications.setAccessible(true);List<Application> allApplications = (List<Application>) mAllApplications.get(currentActivityThread);for (int i = 0; i < allApplications.size(); i++) {if (allApplications.get(i) == bootstrap) {allApplications.set(i, realApplication);}}}Class<?> loadedApkClass;try {loadedApkClass = Class.forName("android.app.LoadedApk");} catch (ClassNotFoundException e) {loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");}Field mApplication = loadedApkClass.getDeclaredField("mApplication");mApplication.setAccessible(true);Field mResDir = loadedApkClass.getDeclaredField("mResDir");mResDir.setAccessible(true);Field mLoadedApk = null;try {mLoadedApk = Application.class.getDeclaredField("mLoadedApk");} catch (NoSuchFieldException e) {}for (String fieldName : new String[] { "mPackages","mResourcePackages" }) {Field field = activityThread.getDeclaredField(fieldName);field.setAccessible(true);Object value = field.get(currentActivityThread);for (Map.Entry<String, WeakReference<?>> entry : ((Map<String, WeakReference<?>>) value).entrySet()) {Object loadedApk = ((WeakReference) entry.getValue()).get();if (loadedApk != null) {if (mApplication.get(loadedApk) == bootstrap) {if (realApplication != null) {mApplication.set(loadedApk, realApplication);}if (externalResourceFile != null) {mResDir.set(loadedApk, externalResourceFile);}if ((realApplication != null)&& (mLoadedApk != null)) {mLoadedApk.set(realApplication, loadedApk);}}}}}} catch (Throwable e) {throw new IllegalStateException(e);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

具體做的事情可以總結為:

1.替換ActivityThread的mInitialApplication為realApplication
2.替換mAllApplications 中所有的Application為realApplication
3.替換ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application為realApplication。

2.2 monkeyPatchExistingResources

替換所有當前app的mAssets為newAssetManager。

public static void monkeyPatchExistingResources(Context context,String externalResourceFile, Collection<Activity> activities) {if (externalResourceFile == null) {return;}try {AssetManager newAssetManager = (AssetManager) AssetManager.class.getConstructor(new Class[0]).newInstance(new Object[0]);Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", new Class[] { String.class });mAddAssetPath.setAccessible(true);if (((Integer) mAddAssetPath.invoke(newAssetManager,new Object[] { externalResourceFile })).intValue() == 0) {throw new IllegalStateException("Could not create new AssetManager");}Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]);mEnsureStringBlocks.setAccessible(true);mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);if (activities != null) {for (Activity activity : activities) {Resources resources = activity.getResources();try {Field mAssets = Resources.class.getDeclaredField("mAssets");mAssets.setAccessible(true);mAssets.set(resources, newAssetManager);} catch (Throwable ignore) {Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");mResourcesImpl.setAccessible(true);Object resourceImpl = mResourcesImpl.get(resources);Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");implAssets.setAccessible(true);implAssets.set(resourceImpl, newAssetManager);}Resources.Theme theme = activity.getTheme();try {try {Field ma = Resources.Theme.class.getDeclaredField("mAssets");ma.setAccessible(true);ma.set(theme, newAssetManager);} catch (NoSuchFieldException ignore) {Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");themeField.setAccessible(true);Object impl = themeField.get(theme);Field ma = impl.getClass().getDeclaredField("mAssets");ma.setAccessible(true);ma.set(impl, newAssetManager);}Field mt = ContextThemeWrapper.class.getDeclaredField("mTheme");mt.setAccessible(true);mt.set(activity, null);Method mtm = ContextThemeWrapper.class.getDeclaredMethod("initializeTheme",new Class[0]);mtm.setAccessible(true);mtm.invoke(activity, new Object[0]);Method mCreateTheme = AssetManager.class.getDeclaredMethod("createTheme", new Class[0]);mCreateTheme.setAccessible(true);Object internalTheme = mCreateTheme.invoke(newAssetManager, new Object[0]);Field mTheme = Resources.Theme.class.getDeclaredField("mTheme");mTheme.setAccessible(true);mTheme.set(theme, internalTheme);} catch (Throwable e) {Log.e("InstantRun","Failed to update existing theme for activity "+ activity, e);}pruneResourceCaches(resources);}}Collection<WeakReference<Resources>> references;if (Build.VERSION.SDK_INT >= 19) {Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance", new Class[0]);mGetInstance.setAccessible(true);Object resourcesManager = mGetInstance.invoke(null,new Object[0]);try {Field fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");fMActiveResources.setAccessible(true);ArrayMap<?, WeakReference<Resources>> arrayMap = (ArrayMap) fMActiveResources.get(resourcesManager);references = arrayMap.values();} catch (NoSuchFieldException ignore) {Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");mResourceReferences.setAccessible(true);references = (Collection) mResourceReferences.get(resourcesManager);}} else {Class<?> activityThread = Class.forName("android.app.ActivityThread");Field fMActiveResources = activityThread.getDeclaredField("mActiveResources");fMActiveResources.setAccessible(true);Object thread = getActivityThread(context, activityThread);HashMap<?, WeakReference<Resources>> map = (HashMap) fMActiveResources.get(thread);references = map.values();}for (WeakReference<Resources> wr : references) {Resources resources = (Resources) wr.get();if (resources != null) {try {Field mAssets = Resources.class.getDeclaredField("mAssets");mAssets.setAccessible(true);mAssets.set(resources, newAssetManager);} catch (Throwable ignore) {Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");mResourcesImpl.setAccessible(true);Object resourceImpl = mResourcesImpl.get(resources);Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");implAssets.setAccessible(true);implAssets.set(resourceImpl, newAssetManager);}resources.updateConfiguration(resources.getConfiguration(),resources.getDisplayMetrics());}}} catch (Throwable e) {throw new IllegalStateException(e);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147

改方法的目的總結為:
1.如果resource.ap_文件有改變,那么新建一個AssetManager對象newAssetManager,然后用newAssetManager對象替換所有當前Resource、Resource.Theme的mAssets成員變量。?
2.如果當前的已經有Activity啟動了,還需要替換所有Activity中mAssets成員變量

2.3 Server啟動

判斷Server是否已經啟動,如果沒有啟動,則啟動Server

2.4 調用realApplication的onCreate方法

和1.4的目的一樣,代理realApplication的生命周期。
至此,我們的app就啟動起來了。下一步就要分析,Server啟動之后,到底是如何進行熱部署、溫部署和冷部署了。

3.Server負責的熱部署、溫部署和冷部署

首先重點關注一下Server的內部類SocketServerReplyThread

3.1 SocketServerReplyThread

...private class SocketServerReplyThread extends Thread {private final LocalSocket mSocket;SocketServerReplyThread(LocalSocket socket) {this.mSocket = socket;}public void run() {try {DataInputStream input = new DataInputStream(this.mSocket.getInputStream());DataOutputStream output = new DataOutputStream(this.mSocket.getOutputStream());try {handle(input, output);} finally {try {input.close();} catch (IOException ignore) {}try {output.close();} catch (IOException ignore) {}}return;} catch (IOException e) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Fatal error receiving messages", e);}}}private void handle(DataInputStream input, DataOutputStream output)throws IOException {long magic = input.readLong();if (magic != 890269988L) {Log.w("InstantRun","Unrecognized header format " + Long.toHexString(magic));return;}int version = input.readInt();output.writeInt(4);if (version != 4) {Log.w("InstantRun","Mismatched protocol versions; app is using version 4 and tool is using version "+ version);} else {int message;for (;;) {message = input.readInt();switch (message) {case 7:if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Received EOF from the IDE");}return;case 2:boolean active = Restarter.getForegroundActivity(Server.this.mApplication) != null;output.writeBoolean(active);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Received Ping message from the IDE; returned active = "+ active);}break;case 3:String path = input.readUTF();long size = FileManager.getFileSize(path);output.writeLong(size);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Received path-exists(" + path+ ") from the " + "IDE; returned size="+ size);}break;case 4:long begin = System.currentTimeMillis();path = input.readUTF();byte[] checksum = FileManager.getCheckSum(path);if (checksum != null) {output.writeInt(checksum.length);output.write(checksum);if (Log.isLoggable("InstantRun", 2)) {long end = System.currentTimeMillis();String hash = new BigInteger(1, checksum).toString(16);Log.v("InstantRun", "Received checksum(" + path+ ") from the " + "IDE: took "+ (end - begin) + "ms to compute "+ hash);}} else {output.writeInt(0);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Received checksum(" + path+ ") from the "+ "IDE: returning <null>");}}break;case 5:if (!authenticate(input)) {return;}Activity activity = Restarter.getForegroundActivity(Server.this.mApplication);if (activity != null) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Restarting activity per user request");}Restarter.restartActivityOnUiThread(activity);}break;case 1:if (!authenticate(input)) {return;}List<ApplicationPatch> changes = ApplicationPatch.read(input);if (changes != null) {boolean hasResources = Server.hasResources(changes);int updateMode = input.readInt();updateMode = Server.this.handlePatches(changes,hasResources, updateMode);boolean showToast = input.readBoolean();output.writeBoolean(true);Server.this.restart(updateMode, hasResources,showToast);}break;case 6:String text = input.readUTF();Activity foreground = Restarter.getForegroundActivity(Server.this.mApplication);if (foreground != null) {Restarter.showToast(foreground, text);} else if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Couldn't show toast (no activity) : "+ text);}break;}}}}...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160

socket開啟后,開始讀取數據,當讀到1時,獲取代碼變化的ApplicationPatch列表,然后調用handlePatches來處理代碼的變化。

3.2 handlePatches

源碼如下:

private int handlePatches(List<ApplicationPatch> changes,boolean hasResources, int updateMode) {if (hasResources) {FileManager.startUpdate();}for (ApplicationPatch change : changes) {String path = change.getPath();if (path.endsWith(".dex")) {handleColdSwapPatch(change);boolean canHotSwap = false;for (ApplicationPatch c : changes) {if (c.getPath().equals("classes.dex.3")) {canHotSwap = true;break;}}if (!canHotSwap) {updateMode = 3;}} else if (path.equals("classes.dex.3")) {updateMode = handleHotSwapPatch(updateMode, change);} else if (isResourcePath(path)) {updateMode = handleResourcePatch(updateMode, change, path);}}if (hasResources) {FileManager.finishUpdate(true);}return updateMode;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

1.如果后綴為“.dex”,冷部署處理handleColdSwapPatch
2.如果后綴為“classes.dex.3”,熱部署處理handleHotSwapPatch
3.其他情況,溫部署,處理資源handleResourcePatch

handleColdSwapPatch冷部署
private static void handleColdSwapPatch(ApplicationPatch patch) {if (patch.path.startsWith("slice-")) {File file = FileManager.writeDexShard(patch.getBytes(), patch.path);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Received dex shard " + file);}}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

把dex文件寫到私有目錄,等待整個app重啟,重啟之后,使用前面提到的IncrementalClassLoader加載dex即可。

handleHotSwapPatch熱部署
private int handleHotSwapPatch(int updateMode, ApplicationPatch patch) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Received incremental code patch");}try {String dexFile = FileManager.writeTempDexFile(patch.getBytes());if (dexFile == null) {Log.e("InstantRun", "No file to write the code to");return updateMode;}if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Reading live code from " + dexFile);}String nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();DexClassLoader dexClassLoader = new DexClassLoader(dexFile,this.mApplication.getCacheDir().getPath(),nativeLibraryPath, getClass().getClassLoader());Class<?> aClass = Class.forName("com.android.tools.fd.runtime.AppPatchesLoaderImpl", true,dexClassLoader);try {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Got the patcher class " + aClass);}PatchesLoader loader = (PatchesLoader) aClass.newInstance();if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Got the patcher instance " + loader);}String[] getPatchedClasses = (String[]) aClass.getDeclaredMethod("getPatchedClasses", new Class[0]).invoke(loader, new Object[0]);if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Got the list of classes ");for (String getPatchedClass : getPatchedClasses) {Log.v("InstantRun", "class " + getPatchedClass);}}if (!loader.load()) {updateMode = 3;}} catch (Exception e) {Log.e("InstantRun", "Couldn't apply code changes", e);e.printStackTrace();updateMode = 3;}} catch (Throwable e) {Log.e("InstantRun", "Couldn't apply code changes", e);updateMode = 3;}return updateMode;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

將patch的dex文件寫入到臨時目錄,然后使用DexClassLoader去加載dex。然后反射調用AppPatchesLoaderImpl類的load方法,需要說明的是,AppPatchesLoaderImpl繼承自抽象類AbstractPatchesLoaderImpl,并實現了抽象方法:getPatchedClasses
如下是AbstractPatchesLoaderImpl抽象類的源碼,注意看load方法:

public abstract class AbstractPatchesLoaderImpl implements PatchesLoader {public abstract String[] getPatchedClasses();public boolean load() {try {for (String className : getPatchedClasses()) {ClassLoader cl = getClass().getClassLoader();Class<?> aClass = cl.loadClass(className + "$override");Object o = aClass.newInstance();Class<?> originalClass = cl.loadClass(className);Field changeField = originalClass.getDeclaredField("$change");changeField.setAccessible(true);Object previous = changeField.get(null);if (previous != null) {Field isObsolete = previous.getClass().getDeclaredField("$obsolete");if (isObsolete != null) {isObsolete.set(null, Boolean.valueOf(true));}}changeField.set(null, o);if ((Log.logging != null)&& (Log.logging.isLoggable(Level.FINE))) {Log.logging.log(Level.FINE, String.format("patched %s",new Object[] { className }));}}} catch (Exception e) {if (Log.logging != null) {Log.logging.log(Level.SEVERE, String.format("Exception while patching %s",new Object[] { "foo.bar" }), e);}return false;}return true;}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

由此,我們大概理清楚了InstantRun熱部署的原理:

1

在第一次構建apk時,在每一個類中注入了一個$change的成員變量,它實現了IncrementalChange接口,并在每一個方法中,插入了一段類似的邏輯。

IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {localIncrementalChange.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[] { this,... });return;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

就是當$change不為空的時候,執行IncrementalChange中的方法。?
比如:?
demo的MainActivity源代碼

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);setSupportActionBar(toolbar);FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);fab.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show();}});}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.menu_main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

編譯之后的代碼為:(反編譯)

public class MainActivity extends AppCompatActivity {public MainActivity() {}MainActivity(Object[] paramArrayOfObject,InstantReloadException paramInstantReloadException) {}public void onCreate(Bundle paramBundle) {IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {localIncrementalChange.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[] { this,paramBundle });return;}super.onCreate(paramBundle);setContentView(2130968601);setSupportActionBar((Toolbar) findViewById(2131492969));((FloatingActionButton) findViewById(2131492970)).setOnClickListener(new View.OnClickListener() {public void onClick(View paramAnonymousView) {IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {localIncrementalChange.access$dispatch("onClick.(Landroid/view/View;)V",new Object[] { this, paramAnonymousView });return;}Snackbar.make(paramAnonymousView,"Replace with your own action", 0).setAction("Action", null).show();}});}public boolean onCreateOptionsMenu(Menu paramMenu) {IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {return ((Boolean) localIncrementalChange.access$dispatch("onCreateOptionsMenu.(Landroid/view/Menu;)Z", new Object[] {this, paramMenu })).booleanValue();}getMenuInflater().inflate(2131558400, paramMenu);return true;}public boolean onOptionsItemSelected(MenuItem paramMenuItem) {boolean bool = true;IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {bool = ((Boolean) localIncrementalChange.access$dispatch("onOptionsItemSelected.(Landroid/view/MenuItem;)Z",new Object[] { this, paramMenuItem })).booleanValue();}while (paramMenuItem.getItemId() == 2131492993) {return bool;}return super.onOptionsItemSelected(paramMenuItem);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

可以看到,每個方法前,都注入了這段邏輯。

2

當我們修改代碼中方法的實現之后,點擊InstantRun,它會生成對應的patch文件來記錄你修改的內容。patch文件中的替換類是在所修改類名的后面追加overrideIncrementalChange,MainActivity../build/intermediates/transforms/instantRun/debug/folders/4000/5.MainActivityoverride,并實現IncrementalChange接口。比如,以MainActivity為例在目錄../build/intermediates/transforms/instantRun/debug/folders/4000/5下查找到.生成了MainActivityoverride類。

public class MainActivity$override implements IncrementalChange {public MainActivity$override() {}public static Object init$args(Object[] var0) {Object[] var1 = new Object[]{"android/support/v7/app/AppCompatActivity.()V"};return var1;}public static void init$body(MainActivity $this) {}public static void onCreate(MainActivity $this, Bundle savedInstanceState) {Object[] var2 = new Object[]{savedInstanceState};MainActivity.access$super($this, "onCreate.(Landroid/os/Bundle;)V", var2);$this.setContentView(2130968601);Toolbar toolbar = (Toolbar)$this.findViewById(2131492969);$this.setSupportActionBar(toolbar);FloatingActionButton fab = (FloatingActionButton)$this.findViewById(2131492970);Object[] var5 = new Object[]{$this};Class[] var10002 = new Class[]{MainActivity.class};String var10003 = "<init>";fab.setOnClickListener((1)((1)AndroidInstantRuntime.newForClass(var5, var10002, 1.class)));AndroidInstantRuntime.setPrivateField($this, (TextView)$this.findViewById(2131492971), MainActivity.class, "textView");((TextView)AndroidInstantRuntime.getPrivateField($this, MainActivity.class, "textView")).setText("myHello");}public static boolean onCreateOptionsMenu(MainActivity $this, Menu menu) {$this.getMenuInflater().inflate(2131558400, menu);return true;}public static boolean onOptionsItemSelected(MainActivity $this, MenuItem item) {int id = item.getItemId();if(id == 2131492993) {return true;} else {Object[] var3 = new Object[]{item};return ((Boolean)MainActivity.access$super($this, "onOptionsItemSelected.(Landroid/view/MenuItem;)Z", var3)).booleanValue();}}public Object access$dispatch(String var1, Object... var2) {switch(var1.hashCode()) {case -1635453101:return new Boolean(onCreateOptionsMenu((MainActivity)var2[0], (Menu)var2[1]));case -1630101479:return init$args((Object[])var2[0]);case -641568046:onCreate((MainActivity)var2[0], (Bundle)var2[1]);return null;case -604658433:init$body((MainActivity)var2[0]);return null;case 1893326613:return new Boolean(onOptionsItemSelected((MainActivity)var2[0], (MenuItem)var2[1]));default:throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var1, Integer.valueOf(var1.hashCode()), "mobctrl/net/testinstantrun/MainActivity"}));}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
3

生成AppPatchesLoaderImpl類,繼承自AbstractPatchesLoaderImpl,并實現getPatchedClasses方法,來記錄哪些類被修改了。
比如,仍然在目錄../build/intermediates/transforms/instantRun/debug/folders/4000/5下查找AppPatchesLoaderImpl.class

public class AppPatchesLoaderImpl extends AbstractPatchesLoaderImpl {public AppPatchesLoaderImpl() {}public String[] getPatchedClasses() {return new String[]{"android.support.design.R$id", "mobctrl.net.testinstantrun.MainActivity$1", "mobctrl.net.testinstantrun.R$id", "mobctrl.net.testinstantrun.MainActivity", "android.support.v7.appcompat.R$id"};}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
4

調用load方法之后,根據getPatchedClasses返回的修改過的類的列表,去加載對應的overrideoverride類,然后把原有類的change設置為對應的實現了IncrementalChange接口的$override類。?

然后等待restart之后生效

handleResourcePatch
private static int handleResourcePatch(int updateMode,ApplicationPatch patch, String path) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Received resource changes (" + path + ")");}FileManager.writeAaptResources(path, patch.getBytes());updateMode = Math.max(updateMode, 2);return updateMode;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

將資源的patch寫入到私有目錄,等到restart之后生效.

restart

根據不同的InstantRun的updateMode模式,進行重啟,使上述的3中部署模式生效!

private void restart(int updateMode, boolean incrementalResources,boolean toast) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Finished loading changes; update mode ="+ updateMode);}if ((updateMode == 0) || (updateMode == 1)) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Applying incremental code without restart");}if (toast) {Activity foreground = Restarter.getForegroundActivity(this.mApplication);if (foreground != null) {Restarter.showToast(foreground,"Applied code changes without activity restart");} else if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Couldn't show toast: no activity found");}}return;}List<Activity> activities = Restarter.getActivities(this.mApplication,false);if ((incrementalResources) && (updateMode == 2)) {File file = FileManager.getExternalResourceFile();if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "About to update resource file=" + file+ ", activities=" + activities);}if (file != null) {String resources = file.getPath();MonkeyPatcher.monkeyPatchApplication(this.mApplication, null,null, resources);MonkeyPatcher.monkeyPatchExistingResources(this.mApplication,resources, activities);} else {Log.e("InstantRun", "No resource file found to apply");updateMode = 3;}}Activity activity = Restarter.getForegroundActivity(this.mApplication);if (updateMode == 2) {if (activity != null) {if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Restarting activity only!");}boolean handledRestart = false;try {Method method = activity.getClass().getMethod("onHandleCodeChange", new Class[] { Long.TYPE });Object result = method.invoke(activity,new Object[] { Long.valueOf(0L) });if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun", "Activity " + activity+ " provided manual restart method; return "+ result);}if (Boolean.TRUE.equals(result)) {handledRestart = true;if (toast) {Restarter.showToast(activity, "Applied changes");}}} catch (Throwable ignore) {}if (!handledRestart) {if (toast) {Restarter.showToast(activity,"Applied changes, restarted activity");}Restarter.restartActivityOnUiThread(activity);}return;}if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","No activity found, falling through to do a full app restart");}updateMode = 3;}if (updateMode != 3) {if (Log.isLoggable("InstantRun", 6)) {Log.e("InstantRun", "Unexpected update mode: " + updateMode);}return;}if (Log.isLoggable("InstantRun", 2)) {Log.v("InstantRun","Waiting for app to be killed and restarted by the IDE...");}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

總體總結

總結起來,做了一下幾件事:

第一次編譯apk:

1.把Instant-Run.jar和instant-Run-bootstrap.jar打包到主dex中
2.替換AndroidManifest.xml中的application配置
3.使用asm工具,在每個類中添加$change,在每個方法前加邏輯
4.把源代碼編譯成dex,然后存放到壓縮包instant-run.zip中

app運行期:

1.獲取更改后資源resource.ap_的路徑
2.設置ClassLoader。setupClassLoader:
使用IncrementalClassLoader加載apk的代碼,將原有的BootClassLoader → PathClassLoader改為BootClassLoader → IncrementalClassLoader → PathClassLoader繼承關系。
3.createRealApplication:
創建apk真實的application
4.monkeyPatchApplication
反射替換ActivityThread中的各種Application成員變量
5.monkeyPatchExistingResource
反射替換所有存在的AssetManager對象
6.調用realApplication的onCreate方法
7.啟動Server,Socket接收patch列表

有代碼修改時

1.生成對應的$override類
2.生成AppPatchesLoaderImpl類,記錄修改的類列表
3.打包成patch,通過socket傳遞給app
4.app的server接收到patch之后,分別按照handleColdSwapPatch、handleHotSwapPatch、handleResourcePatch等待對patch進行處理
5.restart使patch生效

Instant Run的借鑒意義

Android插件化框架改進

Android熱修復方案

app加殼

<未完待續>

InstantRun源碼

我自己通過jd-gui反編譯獲取的,可以參考:
https://github.com/nuptboyzhb/AndroidInstantRun?

參考博文

[1].Instant Run: How Does it Work?!
[2].Instant Run工作原理及用法
[3].Instant Run: An Android Tool Time Deep Dive

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/NUPTboyZHB/article/details/51828701

總結

以上是生活随笔為你收集整理的深度理解Android InstantRun原理以及源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

午夜999| 最新日韩视频在线观看 | 中文字幕欧美激情 | 欧美性另类 | 午夜国产一区二区三区四区 | 手机在线观看国产精品 | 免费看污污视频的网站 | 日韩成人中文字幕 | 日本午夜在线亚洲.国产 | 日韩精品一区二区三区不卡 | 成年人视频在线免费 | 四季av综合网站 | 午夜视频在线观看网站 | 在线国产中文字幕 | 中文字幕av网站 | 91精品影视 | 九九免费精品视频在线观看 | 在线观看视频一区二区三区 | 久久精品一区二区三 | 2021av在线 | 色偷偷88888欧美精品久久久 | 国产91在线免费视频 | 欧美日韩在线视频一区 | 亚洲97在线 | 日本韩国在线不卡 | 国产小视频免费在线观看 | 成人久久久电影 | 久久超级碰 | 免费观看mv大片高清 | 一区二区三区高清不卡 | 男女靠逼app| 中文字幕在线网址 | 国产精品久久久久久吹潮天美传媒 | 日韩亚洲在线 | 在线亚洲精品 | 国产精品自产拍在线观看蜜 | 日韩精品一区二区三区不卡 | 国产裸体视频bbbbb | 国产专区视频 | 亚洲国产一二三 | 色视频 在线 | 高潮久久久久久 | 日本视频不卡 | 成人香蕉视频 | 日本女人的性生活视频 | 日韩成人免费在线观看 | 蜜臀av一区二区 | 99久久久国产精品 | 在线免费观看视频一区 | 亚洲美女免费精品视频在线观看 | 97操操操 | 欧美 亚洲 另类 激情 另类 | 91色视频| 午夜体验区| 久久激情视频免费观看 | 中文字幕精品一区久久久久 | 欧美日韩在线视频一区二区 | 天天操夜操 | 精品在线播放视频 | 久久视频在线 | 丁香资源影视免费观看 | 日日干天天爽 | 97电影在线 | 很黄很色很污的网站 | 日韩高清久久 | av免费观看网站 | 国产在线不卡 | 91人人网 | 久久午夜色播影院免费高清 | 免费a视频在线观看 | 国产在线精品福利 | 96精品在线 | 日日夜夜免费精品视频 | 在线黄色免费av | 国产成人精品一区二区在线观看 | 国产丝袜美腿在线 | 操夜夜操 | 国产精品第2页 | 天天操 夜夜操 | 最新的av网站 | www五月 | 国产精品久久99综合免费观看尤物 | 日韩亚洲在线观看 | 9草在线 | 成人性生交视频 | 亚洲一区二区三区四区在线视频 | 日韩综合一区二区 | 成年人视频在线免费 | 亚洲精品视频国产 | 国产福利中文字幕 | 99这里精品 | 97视频在线观看成人 | 免费在线观看av网址 | 91av原创 | 99热官网| 永久黄网站色视频免费观看w | 欧美日韩在线观看不卡 | 又黄又爽又刺激视频 | 久久国产精品影片 | 国产麻豆剧果冻传媒视频播放量 | 99热99re6国产在线播放 | 久久性生活片 | 国产精品久久久久久久久久东京 | 91豆麻精品91久久久久久 | 手机看片中文字幕 | 一区二区三区在线免费观看视频 | 国产高清在线观看av | 国产99re| 91精品视频免费观看 | av中文字幕在线看 | 一区av在线播放 | 黄a网| 波多野结衣综合网 | 99热免费在线 | 久草热视频 | 天天激情综合网 | 国产91区| 亚洲精品美女在线 | av免费看看| 亚洲国内精品视频 | 99精品乱码国产在线观看 | 中文字幕在线视频网站 | 日本黄色大片免费看 | 日韩a免费 | 亚洲精品乱码久久久久久写真 | 日韩视频免费观看高清完整版在线 | 国产精品1区2区在线观看 | 国产精品99久久久久久大便 | 亚洲精品资源在线观看 | 青青草久草在线 | 亚洲成人av在线电影 | 国产精品一区二区三区免费视频 | 91av在线看| 一区二区精品在线 | 日韩中文字幕a | 精品国产乱码久久久久久1区二区 | 2019av在线视频| 五月激情久久久 | 日韩中文在线视频 | 992tv成人免费看片 | 国产涩图 | 国产亚洲精品久久久久动 | 97超级碰碰碰碰久久久久 | 啪啪凸凸 | 日韩精品视频在线免费观看 | 免费观看全黄做爰大片国产 | 精品国产aⅴ麻豆 | 欧美影片 | 亚洲综合视频网 | 国产精品成人久久久久久久 | 黄色在线观看免费网站 | 最新婷婷色 | 国产一及片 | 日韩性片 | 亚洲一区二区高潮无套美女 | 五月婷亚洲 | 久久久久久久网站 | av成人免费在线看 | 亚洲精品久久久蜜桃直播 | 欧美一区成人 | www.狠狠操.com| 91在线中文 | 国产精品免费在线视频 | 在线看片日韩 | 麻豆影视在线观看 | 黄色激情网址 | 亚洲专区在线播放 | 天天爱天天射 | 国产精品粉嫩 | 亚洲一区二区三区四区在线视频 | 中文字幕视频一区二区 | 久久综合久久综合这里只有精品 | 亚洲国产精品电影 | 国产韩国精品一区二区三区 | 色福利网站 | 美女精品网站 | 中文字幕在线免费看线人 | 日韩av成人 | 久草国产视频 | 美州a亚洲一视本频v色道 | 97在线免费| 99爱爱| 黄色片软件网站 | 久久久久久久网 | 色噜噜狠狠狠狠色综合久不 | 国产成人免费精品 | 最近的中文字幕大全免费版 | 久久视频这里只有精品 | 精品国产一区二区三区久久久久久 | 超碰在线9| 成年人视频免费在线 | 在线观看91视频 | av天天澡天天爽天天av | 六月丁香在线观看 | 亚洲精品视频网站在线观看 | 亚洲国产精品久久久久久 | 欧美美女视频在线观看 | 在线亚洲成人 | 亚洲 欧洲 国产 精品 | 夜夜视频欧洲 | 精品在线99 | 最新精品视频在线 | 久久久久亚洲国产 | 久久国产精品免费一区二区三区 | 免费黄色特级片 | 综合国产在线 | 国产91全国探花系列在线播放 | 国产精品久久久一区二区三区网站 | 亚洲美女在线国产 | 色亚洲激情 | 91热视频 | 欧美日在线观看 | 操老逼免费视频 | 香蕉视频色 | 久久久久看片 | 在线成人一区二区 | 91精品伦理 | 亚洲精品视频在线观看网站 | 91亚洲精| av女优中文字幕在线观看 | 缴情综合网五月天 | 国产精品69av| 亚洲欧美日韩精品一区二区 | 波多野结衣久久资源 | 国产女人40精品一区毛片视频 | 不卡的av电影 | 日本精品久久久久久 | 久久久精品在线观看 | 丁香激情视频 | 国产黄色在线网站 | 久久成人高清视频 | 亚洲精品久久激情国产片 | 色黄视频免费观看 | 国产黄色免费观看 | 日韩偷拍精品 | 成人久久精品视频 | 国产91精品高清一区二区三区 | 91成人蝌蚪 | 精品国产伦一区二区三区观看方式 | 免费看一级黄色大全 | 国产精品99久久久 | 国产精品1区2区在线观看 | 日韩影视大全 | 精品久久久久久亚洲综合网站 | 成人在线网站观看 | 在线观看成人国产 | 毛片二区 | 久久国产电影院 | 国产精品久久久一区二区三区网站 | 五月亚洲综合 | 日韩在线观看你懂得 | 国产精品涩涩屋www在线观看 | 免费福利片 | 人人爽人人av | 国产精品黑丝在线观看 | 日本久久综合视频 | 国产成人精品一区二区三区网站观看 | 8x成人免费视频 | 日韩精品一区二区不卡 | 视频在线观看91 | 激情狠狠干 | 又黄又刺激的视频 | 手机看片福利 | 日本黄色a级大片 | 免费网站看v片在线a | 国产视频观看 | 色综合激情久久 | 96视频在线 | 午夜精品视频在线 | 国产理论影院 | 国产网站在线免费观看 | 日本99精品 | 在线三级播放 | 中文字幕在线观看视频网站 | 在线观看亚洲精品视频 | 天天做天天爱天天爽综合网 | 欧美性黄网官网 | av+在线播放在线播放 | 三级黄免费看 | 日韩免费观看视频 | av在线网站观看 | 成人久久免费 | 国产在线免费 | 豆豆色资源网xfplay | 三级黄色大片在线观看 | 伊人久久精品久久亚洲一区 | 在线观看日韩精品视频 | 草久久久 | 天天操天天色天天 | av在线最新 | 亚洲综合视频在线播放 | 色婷婷综合在线 | 天天干夜夜夜操天 | 午夜精品久久久久久久99 | 97超碰人人模人人人爽人人爱 | 成年人在线| 91禁在线观看 | 91污视频在线 | 国产伦理一区二区 | 日本中文字幕在线一区 | 操操操人人 | 涩五月婷婷 | 国产精品久久久久久久电影 | 国产精品99久久久久久久久久久久 | 最新超碰在线 | 激情综合一区 | 亚洲精品国偷自产在线91正片 | 日本99干网 | 欧美一区三区四区 | 免费日韩一区二区三区 | 婷婷综合五月天 | 亚洲精区二区三区四区麻豆 | 国产成人一区二 | 久久精品视频99 | 国产人成在线视频 | 在线观看免费成人 | 成人一级视频在线观看 | 国产精品第一页在线观看 | 久久久91精品国产 | 日韩欧美精品在线观看视频 | 日韩毛片在线一区二区毛片 | 九色琪琪久久综合网天天 | 中文资源在线官网 | 成年人在线观看视频免费 | 91精品国产92久久久久 | 成人av免费在线看 | 日韩精品久久一区二区三区 | 久久精品国产美女 | 色婷婷欧美 | 国产高清在线免费视频 | 日韩一区二区免费视频 | 91视频免费网址 | 九九九九九精品 | 999热线在线观看 | 一色屋精品视频在线观看 | 欧美国产高清 | 久久久天堂 | 色资源网免费观看视频 | 四虎影视成人永久免费观看视频 | 久久婷婷国产色一区二区三区 | 国语自产偷拍精品视频偷 | 日韩精品一区二区三区三炮视频 | 日韩在线观看视频网站 | 久久久久久久影院 | 女人18毛片a级毛片一区二区 | 96国产精品| 黄色a视频 | 欧美激情视频一区二区三区 | 国产精品久久久久久久久久妇女 | 久久久国产日韩 | 亚洲成人资源在线观看 | 瑞典xxxx性hd极品 | 九九在线国产视频 | 久久久久久久久久久久av | 精品999 | 91精品久久久久 | 欧美激情精品久久久久久 | 天天草视频 | 亚洲精品国产精品国自产观看 | 天天天天干 | 日韩精品 在线视频 | 91精品视频在线免费观看 | 美女网站在线播放 | 在线观看麻豆av | 亚洲精品乱码久久久久久蜜桃不爽 | www色com| 国产午夜麻豆影院在线观看 | 99精品免费视频 | 高清不卡毛片 | 丁香花在线视频观看免费 | 三级毛片视频 | 国产精品麻豆欧美日韩ww | 不卡av在线免费观看 | 欧美激情精品久久久久久免费 | 亚洲成人一二三 | 黄色网在线免费观看 | 99成人免费视频 | 中文字幕日韩一区二区三区不卡 | 亚洲精品va | 成人国产精品一区二区 | 美女视频网站久久 | www.av小说| 成人在线免费看 | 国产成人三级三级三级97 | 国产成人精品av | 国产夫妻av在线 | 免费观看黄色12片一级视频 | 美女免费视频观看网站 | 97品白浆高清久久久久久 | 9免费视频| 日韩av手机在线看 | 亚州中文av | 国产私拍在线 | 久久成 | 日韩 在线a| 天天操 夜夜操 | 亚洲一区久久 | 在线小视频 | 国产一级免费片 | 五月婷婷毛片 | 久久久电影网站 | 欧美少妇18p | 久久国产精品视频观看 | 中文字幕视频观看 | 日韩精品中文字幕久久臀 | 国产在线不卡一区 | 国产免费区 | 中文字幕中文字幕在线中文字幕三区 | 一区二区三区在线视频111 | 日韩啪视频 | 五月综合网站 | 色综合a | 黄色www在线观看 | 国产偷国产偷亚洲清高 | 国产成人av电影在线 | 91成版人在线观看入口 | 色欧美成人精品a∨在线观看 | 亚洲成人午夜av | av在线免费观看黄 | 久久在线影院 | 在线你懂的视频 | 日韩av片在线 | 国产日韩精品在线观看 | 91麻豆精品国产91久久久无需广告 | 高清精品视频 | 成年人在线看片 | 99视频精品| 91麻豆精品国产91久久久无限制版 | 久久人人爽人人爽人人片av软件 | 亚洲精品欧美专区 | 日韩av成人免费看 | 国产精品18久久久久久久久 | 国产精品久久久久高潮 | 欧美特一级片 | av超碰在线 | av电影一区二区三区 | 深爱激情五月综合 | 免费观看www7722午夜电影 | 久久九九九九 | 国产黄影院色大全免费 | 一区二区三区四区五区在线 | 国产午夜精品久久久久久久久久 | 国产最新精品视频 | 免费看在线看www777 | 久久福利在线 | 久久好看免费视频 | 国产1区在线观看 | 日韩免费看视频 | 99久在线精品99re8热视频 | 日韩经典一区二区三区 | 激情五月激情综合网 | www.久久色 | 亚洲欧洲视频 | 91porny九色91啦中文 | www五月婷婷| 精品美女久久久久久免费 | 亚洲精品网页 | av中文电影 | 免费av片在线| 中文字幕av全部资源www中文字幕在线观看 | 一级片色播影院 | 久久五月网 | 人人狠狠综合久久亚洲 | www.五月天激情 | wwwwww国产 | 97碰碰精品嫩模在线播放 | 久久精品一区二区三区四区 | 伊人午夜| 亚洲精品视频在线观看免费视频 | 欧美精品一二三 | 精品国产1区 | 久久久九色精品国产一区二区三区 | 午夜精品视频福利 | 欧美一级特黄高清视频 | 久久久91精品国产一区二区精品 | 日日干日日 | 日韩网站免费观看 | 国产色婷婷精品综合在线手机播放 | 999ZYZ玖玖资源站永久 | 欧美日韩不卡一区 | 国产成人精品日本亚洲999 | 久久精品79国产精品 | 国产美女永久免费 | 日韩在线精品一区 | 99视频免费 | 99久久超碰中文字幕伊人 | 在线播放国产一区二区三区 | 91精品国产欧美一区二区成人 | 97精品欧美91久久久久久 | 欧美日韩精品综合 | 好看av在线 | 国产精品色 | 亚洲国产精品久久久久 | 久爱精品在线 | 99在线视频观看 | 最近日本中文字幕 | 久久久久久国产精品久久 | 国产免费xvideos视频入口 | aaa毛片视频 | 成年人免费观看国产 | 欧美坐爱视频 | 久久久久女人精品毛片九一 | 国产精品亚洲成人 | 国产69精品久久久久久久久久 | 亚洲日本韩国一区二区 | 久草视频免费在线观看 | 91精品999 | 天天色综合1| 色999精品 | 国产永久免费高清在线观看视频 | 91中文字幕永久在线 | 国内视频一区二区 | 欧女人精69xxxxxx | 久久综合狠狠综合久久狠狠色综合 | 欧美一区二区三区在线看 | 西西www4444大胆在线 | 中文字幕在线影院 | 日韩av黄 | 久草在线手机视频 | 中文一区二区三区在线观看 | 在线观看电影av | 成人午夜影视 | 成片视频免费观看 | 一区 在线 影院 | 久久久污| 亚洲欧洲国产日韩精品 | 亚洲草视频| 成年人电影免费在线观看 | 欧美另类v| 四虎小视频 | www.在线观看视频 | 青草视频免费观看 | www.夜夜操.com | 国产小视频在线免费观看视频 | 草久在线| 伊人网综合在线观看 | 中文字幕精品三级久久久 | 美女搞黄国产视频网站 | 色香天天| 久久久久久久国产精品视频 | 日韩欧美xxx | 精品国产乱码 | ,久久福利影视 | 日韩免费福利 | 国产精品久久久久久久久久尿 | 西西www4444大胆在线 | 亚洲免费视频在线观看 | 免费看久久久 | 欧美日韩亚洲第一 | 91成人在线观看高潮 | 久久天天综合网 | 91桃色在线观看视频 | 色综合在 | 国产区免费 | 日韩一级片网址 | 久草色在线观看 | 日韩小视频 | 成年人国产视频 | 91成人破解版 | www日日| 在线观看视频精品 | 婷婷深爱五月 | 99久久精品国产亚洲 | av成人动漫 | 91高清完整版在线观看 | 在线播放国产一区二区三区 | 狠狠狠狠狠狠狠干 | 久久99国产精品 | 狠狠色伊人亚洲综合网站野外 | 女人高潮特级毛片 | 日韩精品播放 | jizzjizzjizz亚洲| 中文字幕电影在线 | 国产中文 | 日韩成年视频 | 国产一区视频导航 | 日韩高清在线不卡 | 国产黄色精品在线观看 | 亚洲精品乱码久久久久 | av电影免费在线看 | 色婷婷免费视频 | 婷婷在线免费观看 | 三上悠亚一区二区在线观看 | 亚洲四虎在线 | 麻豆手机在线 | 9999亚洲| 天天天在线综合网 | 高清av影院 | 国产精品电影一区 | 久久成年人视频 | 极品国产91在线网站 | 麻豆传媒在线免费看 | 免费在线国产黄色 | 国产一级片网站 | 久草视频资源 | 99久久www免费 | 久久久久久久久久免费 | 91精品高清| 成人av电影免费在线播放 | 天天干天天干天天射 | 久久国产欧美日韩 | 亚洲精品88欧美一区二区 | 亚洲一级黄色 | 国产精品一区二区三区视频免费 | 激情 一区二区 | 中文字幕在线成人 | 久草在线资源观看 | av免费看在线 | 国产精品麻豆欧美日韩ww | 香蕉手机在线 | 国产在线一线 | 99久久99久久精品 | 日日草av | 日韩av一区二区在线播放 | 在线观看91 | 丁香婷婷基地 | 天天综合狠狠精品 | 精品日韩在线 | 综合色中文 | 97av视频| 欧美成年黄网站色视频 | 日韩激情视频在线观看 | 在线观看亚洲精品 | 高清视频一区 | 亚洲精品在线免费播放 | 久草在线资源网 | 97精品电影院 | 视频二区 | 99久久精品国产一区二区三区 | h久久| 欧美久久久久久久久久久 | 国产一区二区三区四区大秀 | 国产91在线 | 美洲 | 97在线看 | 日韩精品一区二区三区在线视频 | 国产做a爱一级久久 | 丁香婷婷久久久综合精品国产 | 中文字幕一区二区三区四区在线视频 | 91丨porny丨九色| 亚州av一区 | 深夜福利视频在线观看 | 久久精品电影 | 国产在线观看污片 | 日本中文字幕在线一区 | 亚洲三级在线 | 亚洲黑丝少妇 | 国产成人一区二区精品非洲 | 99久久www免费| 久草在线播放视频 | 亚洲成人精品在线观看 | 99色在线观看视频 | 免费观看性生交 | 亚洲一区尤物 | 伊人黄色网 | 日韩在线免费观看视频 | 国产一级黄 | 亚洲视频在线观看 | 在线观看视频免费大全 | 91亚洲在线观看 | av片中文字幕 | 国内久久视频 | 日日躁夜夜躁xxxxaaaa | 夜色资源站wwwcom | 嫩草伊人久久精品少妇av | 久久精品欧美 | 亚洲午夜精品在线观看 | 日韩视频www | 黄色片毛片 | 在线av资源| 日本一区二区免费在线观看 | 久影院 | 日韩激情视频在线观看 | 五月天婷婷免费视频 | 国产999视频 | 国产精品久久久视频 | 91麻豆精品久久久久久 | 亚洲成a人片在线观看网站口工 | 伊人永久在线 | 国产一线天在线观看 | 免费看片在线观看 | 欧美人体xx | 日本韩国欧美在线观看 | 国产在线视频一区二区 | 国产一级二级在线播放 | 日韩综合在线观看 | 成人免费视频免费观看 | 日韩在线电影一区二区 | 成人中文字幕在线观看 | 久久精品国产成人精品 | 黄色在线成人 | 成人网页在线免费观看 | 国产精品婷婷午夜在线观看 | 狠狠夜夜 | 国产又粗又硬又长又爽的视频 | 日韩大陆欧美高清视频区 | 在线v| 中文字幕免费 | 狠狠干综合 | 国产成人在线免费观看 | 久久99视频免费观看 | 国产手机视频 | av性网站| 丁香 婷婷 激情 | 国产成人a亚洲精品 | 国产亚洲精品久久久久久久久久 | 久久成人一区二区 | 日韩亚洲在线视频 | 丝袜制服综合网 | 日韩中字在线观看 | 欧美日韩电影在线播放 | 天天综合网 天天综合色 | 成人一级黄色片 | 久久久久久蜜av免费网站 | 日本少妇久久久 | 日韩一区二区三区观看 | av女优中文字幕在线观看 | 国产精品亚洲a | www91在线| 国产精品欧美激情在线观看 | 国产福利av在线 | 中文字幕一区二区在线播放 | 久久国产免| 特级xxxxx欧美 | 香蕉在线视频播放网站 | 亚洲一级特黄 | 成人亚洲欧美 | 免费在线 | 永久免费av在线播放 | 国产午夜视频在线观看 | 成人黄色大片在线免费观看 | 免费看的黄色 | 99精品视频在线观看免费 | 成人免费在线电影 | 午夜 免费 | 国产视频1区2区3区 久久夜视频 | 欧洲精品在线视频 | 69精品久久久 | 深爱五月网 | 三上悠亚一区二区在线观看 | 国产高清精 | 精品视频999 | 欧美激情精品久久久久久变态 | 超碰在线中文字幕 | 久久一视频 | 亚洲a在线观看 | 亚洲极色 | 五月婷婷六月丁香激情 | 81国产精品久久久久久久久久 | 美女精品国产 | 91传媒视频在线观看 | 色婷婷视频在线观看 | 一区二区三区中文字幕在线观看 | 国产精品免费不卡 | 成人在线视频免费观看 | aaa日本高清在线播放免费观看 | 国产精品精品 | 波多野结衣精品 | 色婷婷成人 | 久久国产精品久久精品国产演员表 | 日韩高清在线一区 | 色91在线| 国产一级做a爱片久久毛片a | 日韩 精品 一区 国产 麻豆 | 久久免费美女视频 | 中国一区二区视频 | 91完整版在线观看 | 丝袜美腿在线 | 国产精品综合在线观看 | 激情网五月婷婷 | 天天爽夜夜爽精品视频婷婷 | 色a资源在线 | 国产在线欧美在线 | 一区二区欧美日韩 | 久久综合影视 | 日韩av一卡二卡三卡 | 99视频在线免费观看 | 99亚洲精品视频 | 日韩中文字幕a | 国产精品福利在线播放 | 久久成视频 | 婷婷在线综合 | а天堂中文最新一区二区三区 | 国产亚州av | 中文字幕在线观看一区二区三区 | 美女视频a美女大全免费下载蜜臀 | 亚洲第一香蕉视频 | 少妇性bbb搡bbb爽爽爽欧美 | 日韩电影在线一区二区 | 天天操天 | 亚洲精品国产自产拍在线观看 | 中文av网 | 精品视频资源站 | av无限看 | 五月激情亚洲 | 日韩精品资源 | 国产精品久久久久久久久毛片 | 一区二区欧美日韩 | 精品国产aⅴ一区二区三区 在线直播av | 五月导航 | 婷婷六月久久 | 色欲综合视频天天天 | 色婷婷一区 | 久久九九影视网 | 久久人人爽av | 麻豆国产精品视频 | 国产伦理久久精品久久久久_ | 日韩高清成人在线 | 国产丝袜| 久久综合狠狠综合久久激情 | av在线播放一区二区三区 | 亚洲九九九在线观看 | 国产一区在线免费观看视频 | 国产高清在线精品 | 综合婷婷 | 五月天婷婷在线播放 | 精品国产精品久久一区免费式 | 国产麻豆精品免费视频 | 九九热精品视频在线播放 | 国产二区精品 | 狠狠躁夜夜躁人人爽超碰91 | 久久人人射 | 五月综合久久 | 99草在线视频 | 亚洲国产精品500在线观看 | 久久成人国产精品一区二区 | 操操操综合| 91禁在线观看 | 日韩在线观看高清 | 欧美激情视频一区二区三区免费 | 欧美俄罗斯性视频 | 日韩视频图片 | 欧美久久久久久久久久久 | 成人在线观看影院 | 成人9ⅰ免费影视网站 | 日韩欧美一区视频 | 免费在线观看一级片 | 亚洲粉嫩av | 欧美久久久影院 | 成人黄色中文字幕 | 狠狠色丁香婷婷综合久小说久 | 在线免费观看黄色av | 亚洲在线视频免费 | 日本动漫做毛片一区二区 | 日日爱夜夜爱 | 国产乱视频 | 国产精品99久久久久人中文网介绍 | 精壮的侍卫呻吟h | 黄色国产大片 | 福利一区在线视频 | 国内偷拍精品视频 | av在线免费播放 | 国产精品久久久久久模特 | 91色一区二区三区 | 中文字幕视频免费观看 | 成人高清在线观看 | 精品资源在线 | a黄色一级 | 深爱激情站 | 国产精品免费大片视频 | 久操视频在线 | 999国产在线 | 香蕉久草 | av免费网页| 三级av在线 | 久久久久久在线观看 | 国产精品久久久久久久久软件 | 精品国产一区二区三区久久久蜜月 | av在线播放网址 | 精品久久久国产 | www黄com | 亚洲成色777777在线观看影院 | av片中文字幕 | 91一区二区三区在线观看 | 最新中文字幕视频 | 激情深爱| 国产成人精品久久二区二区 | 五月天久久 | 国产在线久草 | 精品亚洲视频在线 | 欧美激情视频一二三区 | 国产色视频| 在线视频18在线视频4k | 久久综合九色综合97婷婷女人 | 亚洲日本韩国一区二区 | 久久精品国产一区二区三区 | 国产综合小视频 | 久草免费在线视频观看 | 五月激情片 | 久久久精品免费看 | 在线激情小视频 | 国内久久精品 | 久久国产精品免费视频 | 人人爽人人插 | 99成人免费视频 | 日韩视频一区二区三区 | 91手机视频在线 | 欧美最猛性xxx | 99精品视频免费观看视频 | 热久精品 | 国产精品久久久av | av亚洲产国偷v产偷v自拍小说 | 国产精品毛片久久久久久久久久99999999 | 亚洲精品在线免费看 | 亚洲涩综合 | 亚洲五月婷 | 亚洲精品久久久久久中文传媒 | 成年人免费av网站 | 人人射人人爱 | 免费av网址大全 | 久久99精品久久久久久清纯直播 | 日韩av中文字幕在线免费观看 | 一级黄色大片 | 国产成人在线一区 | 成人亚洲综合 | av+在线播放在线播放 | 狠狠躁天天躁 | 视频在线观看入口黄最新永久免费国产 | 特级黄色电影 | 99热超碰在线| 6080yy精品一区二区三区 | 亚洲综合视频在线 | 亚洲第二色 | 九九亚洲精品 | 国产资源网| 日韩高清一二区 | 黄色的网站免费看 | 九九av| 黄色影院在线播放 | 国产精品不卡在线 | 免费h精品视频在线播放 | 久久精品欧美日韩精品 | 7777精品伊人久久久大香线蕉 | 久久免费观看视频 | 亚洲天堂网在线观看视频 | 亚洲精品www久久久久久 | 国产精品6999成人免费视频 | 91精品视频网站 | 国产日韩精品欧美 | 人人舔人人射 | 一区二区三区四区精品 | 国产亚洲高清视频 | 久久论理 | 亚一亚二国产专区 | 97成人资源站| 国产精品毛片 | 日韩欧美69 | 毛片99 | 日韩高清不卡在线 | 五月激情丁香婷婷 | 久久久国产一区二区三区四区小说 | 亚洲国产综合在线 | 97超碰超碰久久福利超碰 | 少妇超碰在线 | 手机成人免费视频 | 六月丁香婷婷网 | 亚洲人人av | 欧美日韩国产二区 | 国产成人久久 | 99精品久久久久久久 | 97超碰站 | 欧美一性一交一乱 | 国产精品大片免费观看 | 最新在线你懂的 | 国产精品爽爽久久久久久蜜臀 | 国产一区二区三精品久久久无广告 | 色中色亚洲 | 久久综合亚洲鲁鲁五月久久 | 久久99国产综合精品 | 中文超碰字幕 | 久久av影视| 久久99国产精品免费 | 九九九九九国产 | 日日干美女 | 六月婷婷久香在线视频 | 一区二区欧美激情 | 在线观看视频97 | 久久久网页 | 五月天丁香综合 | 亚洲韩国一区二区三区 | 国产精品一区二区免费 | 日本资源中文字幕在线 | 久久国产精品久久精品 | 久热免费在线观看 | 亚洲精品乱码久久久久久蜜桃欧美 | 久久99久久99精品免费看小说 | 日日操天天操狠狠操 | 日韩午夜在线 | 精品视频专区 | 草免费视频 | 日日夜夜操操操操 | 日韩三级不卡 | 成人在线黄色 | 日韩一区精品 | 亚洲视频网站在线观看 | 日韩av网站在线播放 | 日韩精品免费专区 | 91亚洲在线观看 | 国产在线观看二区 |