[Android 学习笔记] instant-run 源码学习 ( 2 )

新建一个项目, 实现简单的 MainActivity , 打开 Android Studio 的 instant run 功能

如果调试设备是小米手机, 需要配置一下
Android Studio 2.3 在小米手机中 调试安装Apk失败

package org.demo.example;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                Log.d("MainActivity", "number=0");
            }
        });
    }
}

instant run 编译后的代码, 可以看到每个方法中都有代理判断

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.demo.example;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.android.tools.fd.runtime.IncrementalChange;
import com.android.tools.fd.runtime.InstantReloadException;

public class MainActivity extends Activity {
    public static final long serialVersionUID = -8981860069525532301L;

    public MainActivity() {  // 对构造函数的特殊处理
        IncrementalChange var1 = $change;
        if(var1 != null) {
            Object[] var10001 = (Object[])var1.access$dispatch("init$args.([Lorg/demo/example/MainActivity;[Ljava/lang/Object;)Ljava/lang/Object;", new Object[]{null, new Object[0]}); // 调用代理对象的 init$args 方法
            Object[] var2 = (Object[])var10001[0];
            this(var10001, (InstantReloadException)null);
            var2[0] = this;
            var1.access$dispatch("init$body.(Lorg/demo/example/MainActivity;[Ljava/lang/Object;)V", var2);
        } else {
            super();
        }
    }

    public void onCreate(Bundle savedInstanceState) {
        IncrementalChange var2 = $change;
        if(var2 != null) {
            var2.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[]{this, savedInstanceState});
        } else {
            super.onCreate(savedInstanceState);
            this.setContentView(2130903040);
            Button button = (Button)this.findViewById(2131099648);
            button.setOnClickListener(new OnClickListener() {
                public static final long serialVersionUID = -6016583034602689826L;

                public {
                    IncrementalChange var2 = $change;
                    if(var2 != null) {
                        Object[] var10001 = (Object[])var2.access$dispatch("init$args.([Lorg/demo/example/MainActivity$1;Lorg/demo/example/MainActivity;[Ljava/lang/Object;)Ljava/lang/Object;", new Object[]{null, MainActivity.this, new Object[0]});
                        Object[] var3 = (Object[])var10001[0];
                        var3[0] = this;
                        var2.access$dispatch("init$body.(Lorg/demo/example/MainActivity$1;Lorg/demo/example/MainActivity;[Ljava/lang/Object;)V", var3);
                    }
                }

                public void onClick(View v) {
                    IncrementalChange var2 = $change;
                    if(var2 != null) {
                        var2.access$dispatch("onClick.(Landroid/view/View;)V", new Object[]{this, v});
                    } else {
                        Log.d("MainActivity", "number=0");
                    }
                }

                {
                    String var3 = (String)((Object[])MainActivity.this)[1];
                    switch(var3.hashCode()) {
                    case -1968665286:
                        return;
                    case -85614767:
                        return;
                    default:
                        throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var3, Integer.valueOf(var3.hashCode()), "org/demo/example/MainActivity$1"}));
                    }
                }
            });
        }
    }

    MainActivity(Object[] var1, InstantReloadException var2) {
        String var3 = (String)var1[1];
        switch(var3.hashCode()) {
        case -1230767868:
            super();
            return;
        case -807788715:
            this();
            return;
        default:
            throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var3, Integer.valueOf(var3.hashCode()), "org/demo/example/MainActivity"}));
        }
    }
}

修改一下MainAcitivity 代码, 使日志打印 number=1

 button.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                Log.d("MainActivity", "number=1");
            }
        });

点击 instant run 编译出 patch 包并运行到测试设备, 可以看到[Project]\app\build\intermediates\transforms\instantRun\debug\folders 中有生成新文件:

TIM截图20170727162452.png

其中目录1下面是第一次编译出的 class 文件, 目录 4000 中时 Patch 包中的 class 文件;

MainActivity$1$override 为 MainActivity 类的补丁类, 都是静态方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.demo.example;

import android.util.Log;
import android.view.View;
import com.android.tools.fd.runtime.IncrementalChange;
import com.android.tools.fd.runtime.InstantReloadException;
import org.demo.example.MainActivity;
import org.demo.example.MainActivity.1;

public class MainActivity$1$override implements IncrementalChange {
    public MainActivity$1$override() {
    }

    public static Object init$args(1[] var0, MainActivity this$0, Object[] var2) {
        Object[] var3 = new Object[]{new Object[]{var0, this$0, new Object[0]}, "java/lang/Object.()V"};
        return var3;
    }

    public static void init$body(1 $this, MainActivity this$0, Object[] var2) {
    }

    public static void onClick(1 $this, View v) {
        Log.d("MainActivity", "number=1"); // 新的实现
    }

    public Object access$dispatch(String var1, Object... var2) { // 用于方法分发
        switch(var1.hashCode()) {
        case -1912803358:
            onClick((1)var2[0], (View)var2[1]);
            return null;
        case -61430165:
            return init$args((1[])var2[0], (MainActivity)var2[1], (Object[])var2[2]);
        case 1978365703:
            init$body((1)var2[0], (MainActivity)var2[1], (Object[])var2[2]);
            return null;
        default:
            throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var1, Integer.valueOf(var1.hashCode()), "org/demo/example/MainActivity$1"}));
        }
    }
}

目录 4000 中还有一个 AppPatchesLoaderImpl 类, 包含了补丁类信息, 这个类在加载到调试设备上后会被通过反射实例化并调用其 getPatchedClasses 方法来获取补丁类信息

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.android.tools.fd.runtime;

import com.android.tools.fd.runtime.AbstractPatchesLoaderImpl;

public class AppPatchesLoaderImpl extends AbstractPatchesLoaderImpl {
    public static final long BUILD_ID = 1501127139210L;

    public AppPatchesLoaderImpl() {
    }

    public String[] getPatchedClasses() {
        return new String[]{"org.demo.example.MainActivity$1"};
    }
}

反编译已经生成的 APK 发现使用 instant run 第一次编译后, 在AndroidManifest.xml 中插入了一个 ContentProvider , 这个 ContentProvider 已经被打包到 APK 中的 classes.dex 了, APP 启动后 ContentProvider 的 onCreate 方法被调用:


image.png
image.png

该 ContentProvider 源码位于 [Project]\app\build\intermediates\incremental-runtime-classes\debug\ 目录下,

image.png

该 ContentProvider 实现非常简单, 在 onCreate 中判断是否运行在主进程中, 如果是就启动一个 SocketServer 用于接收补丁包数据, 主要逻辑都在 Server 类中, Server 通过 socket 接收到补丁包数据后通过约定的数据格式从 socket 的输入流中解析出 ApplicationPatch 对象数据

  public static List<ApplicationPatch> read(DataInputStream input)
    throws IOException
  {
    int changeCount = input.readInt();
    if ((Log.logging != null) && (Log.logging.isLoggable(Level.FINE))) {
      Log.logging.log(Level.FINE, "Receiving " + changeCount + " changes");
    }
    List<ApplicationPatch> changes = new ArrayList(changeCount);
    for (int i = 0; i < changeCount; i++)
    {
      String path = input.readUTF();
      int size = input.readInt();
      byte[] bytes = new byte[size];
      input.readFully(bytes);
      changes.add(new ApplicationPatch(path, bytes));
    }
    return changes;
  }

然后调用 handlePatches 方法, 根据 path 判断是 Hot Swap 或 Cold Swap:

 private int handlePatches(List<ApplicationPatch> changes, boolean hasResources, int updateMode)
  {
    if (hasResources) {
      FileManager.startUpdate();
    }
    for (ApplicationPatch change : changes)
    {
      String path = change.getPath();
      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;
  }

  private static boolean isResourcePath(String path)
  {
    return (path.equals("resources.ap_")) || (path.startsWith("res/"));
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容