Android 开发高手课 课后练习(22,27,ASM)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://weilu.blog.csdn.net/article/details/88019688

没想到之前的写的练习内容得到了老师的认可,看来我要更加认真努力练习了。今天来练习22,27,ASM三课内容。

Chapter22

尝试使用facebook redex库来优化我们的安装包。

准备工作

首先是下载redex

git clone https://github.com/facebook/redex.git
cd redex

接着安装:

autoreconf -ivf && ./configure && make -j4
sudo make install

安装这里我执行时,报下图错误:
在这里插入图片描述
其实就是没有安装boost,所以执行下面的命令安装它。

brew install boost jsoncpp

安装boost 完成后,继续紧接着就是十几分钟的redex安装等待。。。

下来就是编译我们的Sample,得到我们的安装包(信息如下)。
在这里插入图片描述

可以看到有三个dex文件,apk大小为13.7M。

通过redex命令优化

为了可以更加清楚流程,可以输出redex的日志

export TRACE=2

去除debuginfo的方法,需要在项目根目录执行:

redex --sign -s ReDexSample/keystore/debug.keystore -a androiddebugkey -p android -c redex-test/stripdebuginfo.config -P ReDexSample/proguard-rules.pro  -o redex-test/strip_output.apk ReDexSample/build/outputs/apk/debug/ReDexSample-debug.apk

上面这段很长的命令,其实可以拆解几部分:

  • --sign 签名信息

  • -s (keystore) 签名文件路径

  • -a (keyalias) 签名的别名

  • -p (keypass) 签名的密码

  • -c 指定redex的配置文件路径

  • -P ProGuard规则文件路径

  • -o 输出的文件路径

  • 最后是要处理apk文件的路径

使用时,我遇到了下图的问题:
在这里插入图片描述
这里是找不到Zipalign,所以需要我们配置android sdk的根目录路径,添加在原命令前面:

ANDROID_SDK=/path/to/android/sdk redex [... arguments ...]

结果如下:
在这里插入图片描述
实际效果为原debug包为14.21M,去除debuginfo的方法后为12.91M,效果还是不错的。去除的部分就是一些调试信息及堆栈行号。
在这里插入图片描述
不过老师在Sample的proguard-rules.pro 中添加了-keepattributes SourceFile,LineNumberTable保留了行号信息。

所以处理后的包安装后进入首页,还是可以看到堆栈信息的行号的。

Dex重分包的方法

redex --sign -s ReDexSample/keystore/debug.keystore -a androiddebugkey -p android -c redex-test/interdex.config -P ReDexSample/proguard-rules.pro  -o redex-test/interdex_output.apk ReDexSample/build/outputs/apk/debug/ReDexSample-debug.apk

和之前的命令一样,只是-c 使用的配置文件为interdex.config

输出信息:
在这里插入图片描述
效果为原debug包为14.21M、3个dex,优化后为13.34M、2个dex。
在这里插入图片描述根据老师的介绍,如果你的应用有4个以上的Dex,这个体积优化至少有10%。 看来效果还是很棒棒的。至于其他问题,比如在Windows环境使用redex,可以参看redex使用文档

Chapter27

利用AspectJ实现插桩的例子

效果和Chapter07是一样的,只是Chapter07使用的是ASM方式实现的,这次是AspectJ实现。ASMAspectJ 都是Java字节码处理框架。相比较来说AspectJ 使用更加简单,同样的功能实现只需下面这点代码。但是ASMAspectJ 更加高效和灵活。

AspectJ实现代码:

@Aspect
public class TraceTagAspectj {

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    @Before("execution(* **(..))")
    public void before(JoinPoint joinPoint) {
        Trace.beginSection(joinPoint.getSignature().toString());
    }

    /**
     * hook method when it's called out.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    @After("execution(* **(..))")
    public void after() {
        Trace.endSection();
    }
}

简单介绍上面代码的意思:

  • @Aspect: 在编译时AspectJ 会查找被 @Aspect 注解的类,然后执行我们的AOP实现。

  • @Before : 可以简单理解为方法执行前。

  • @After :可以简单理解为方法执行后。

  • execution:方法执行。

  • * **(..) :第一个星号代表任意返回类型,第二个星号代表任意类,第三个代表任意方法,括号内为方法参数无限制。星号和括号内都是可以替换为具体值,比如 String TestClass.test(String)

知道了相关注解的含义,那么实现的代码含义就是 所有方法在执行前后插入相应指定操作

效果对比如下:
在这里插入图片描述
在这里插入图片描述
下来实现给MainActivityonResume 方法增加 try catch

@Aspect
public class TryCatchAspect {
    
    @Pointcut("execution(* com.sample.systrace.MainActivity.onResume())") // <- 指定类与方法
    public void methodTryCatch() {
    }

    @Around("methodTryCatch()")
    public void aroundTryJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
       
         // try catch
         try {
             joinPoint.proceed(); // <- 调用原方法
         } catch (Exception e) {
              e.printStackTrace();
         }
    }
}

上面用到了两个新注解:

  • @Around :用于替换以前的代码,使用joinPoint.proceed()可以调用原方法。

  • @Pointcut :指定一个切入点。

实现就是指定一个切入点,利用替换原方法的思路包裹一层 try catch

效果对比如下:
在这里插入图片描述在这里插入图片描述

当然AspectJ还有很多用法,Sample中包含有《AspectJ程序设计指南》,便于我们具体了解学习AspectJ

Chapter-ASM

Sample利用 ASM 实现了统计方法耗时和替换项目中所有的 new Thread

  • 运行项目首先要注掉 ASMSample build.gradleapply plugin: 'com.geektime.asm-plugin' 和根目录 build.gradleclasspath ("com.geektime.asm:asm-gradle-plugin:1.0") { changing = true }

  • 运行 gradle task ":asm-gradle-plugin:buildAndPublishToLocalMaven" 编译plugin插件,编译的插件在本地.m2\repository目录下。

  • 打开第一步注掉的内容就可以运行了。

实现的大致过程:先利用 Transform 遍历所有文件,在通过 ASMvisitMethod 遍历所有方法,最后通过 AdviceAdapter 实现最终的修改字节码。具体实现可以看代码和练习Sample跑起来 | ASM插桩强化练习

效果对比:
在这里插入图片描述

在这里插入图片描述

下来就是两个练习:

  1. 给某个方法增加 try catch

这里我就给 MainActivitymm 方法进行 try catch。实现很简单,直接修改 ASMCodeTraceMethodAdapter

public static class TraceMethodAdapter extends AdviceAdapter {

        private final String methodName;
        private final String className;
        private final Label tryStart = new Label();
        private final Label tryEnd = new Label();
        private final Label catchStart = new Label();
        private final Label catchEnd = new Label();

        protected TraceMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className) {
            super(api, mv, access, name, desc);
            this.className = className;
            this.methodName = name;
        }

        @Override
        protected void onMethodEnter() {
            if (className.equals("com/sample/asm/MainActivity") && methodName.equals("mm")) {
                mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, "java/lang/Exception");
                mv.visitLabel(tryStart);
            }
        }

        @Override
        protected void onMethodExit(int opcode) {
            if (className.equals("com/sample/asm/MainActivity") && methodName.equals("mm")) {
                mv.visitLabel(tryEnd);
                mv.visitJumpInsn(GOTO, catchEnd);
                mv.visitLabel(catchStart);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/RuntimeException", "printStackTrace", "()V", false);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitLabel(catchEnd);
            }
        }
    }

visitTryCatchBlock 方法:前三个参数均是Label 实例,其中一,二表示 try块的范围,三则是catch 块的开始位置,第四个参数是异常类型。其他的方法及参数就不细说了,具体参考 ASM文档

实现类似AspectJ,在方法执行开始及结束时插入我们的代码。

效果我就不截图了,代码如下:

	public void mm() {
        try {
            A a = new A(new B(2));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  1. 查看代码中谁获取了 IMEI

这个就更简单了,直接寻找谁使用了TelephonyManagergetDeviceId 方法。(sample中有答案)


public class IMEIMethodAdapter extends AdviceAdapter {

    private final String methodName;
    private final String className;

    protected IMEIMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className) {
        super(api, mv, access, name, desc);
        this.className = className;
        this.methodName = name;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
        super.visitMethodInsn(opcode, owner, name, desc, itf);

        if (owner.equals("android/telephony/TelephonyManager") && name.equals("getDeviceId") && desc.equals("()Ljava/lang/String;")) {
            Log.e("asmcode", "get imei className:%s, method:%s, name:%s", className, methodName, name);
        }
    }
}     

Build后输出如下:
在这里插入图片描述


总体来说ASM 的上手难度还是高于AspectJ,需要我们了解编译后的字节码,这里所使用的功能也只是冰山一角。课代表鹏飞同学推荐的ASM Bytecode Outline 插件是个好帮手!最后我将我练习的代码也上传到了Github,里面还包括一份中文版的 ASM 文档,有兴趣的可以下载看看。

参考

展开阅读全文

asm字节码操作框架的开发

02-10

我们都知道asm可以帮助我们生成Class文件以及去控制正在运行的java程序并修改代码rnrn但是我的用途很简单就是动态帮我生成javaBean即可,通过这两天的研究,成果是做出来的,但是结果有一点点问题,这个class无法运行,错误就是我new这个javaBean 就错误如下rnrn[code=Java]rnxception in thread "main" java.lang.NoClassDefFoundError: Edocumentrn at com.gwinsoft.gwinoa.service.flex.impl.FormDesignServiceImpl.main(FormDesignServiceImpl.java:961)rnCaused by: java.lang.ClassNotFoundException: Edocumentrn at java.net.URLClassLoader$1.run(URLClassLoader.java:202)rn at java.security.AccessController.doPrivileged(Native Method)rn at java.net.URLClassLoader.findClass(URLClassLoader.java:190)rn at java.lang.ClassLoader.loadClass(ClassLoader.java:307)rn at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)rn at java.lang.ClassLoader.loadClass(ClassLoader.java:248)rn ... 1 morern[/code]rnrn这是一个很初级的错误,可是不明白为什么出出现这个问题,我是在Eclipse里运行的,不会有classpath这类问题存在rnrnrn首先我贴上,生成的class字节码内容(通过asm插件Bytecode产生)rnrn[code=Java]rn// class version 49.0 (49)rn// access flags 33rn// declaration: com/gwinsoft/gwinoa/entity/dataform/Edocument extends com.gwinsoft.gwinoa.entity.IEntityrnpublic class Edocument rnrn // compiled from: Edocument.javarnrn // access flags 2rn private String titlernrn // access flags 2rn private Owners nigaorenrnrn // access flags 2rn private Owners nigaorenIdrnrn // access flags 2rn private Date nigaodaternrn // access flags 2rn private Department nigaodeprnrn // access flags 2rn private Department depIdrnrn // access flags 2rn private String contentrnrn // access flags 1rn public () : voidrn L0 (0)rn LINENUMBER 9 L0rn ALOAD 0: thisrn INVOKESPECIAL IEntity.() : voidrn RETURNrn L1 (4)rn LOCALVARIABLE this Edocument L0 L1 0rn MAXSTACK = 1rn MAXLOCALS = 1rnrn // access flags 8rn static () : voidrn GETSTATIC System.out : PrintStreamrn LDC "Edocument \u5df2\u7ecfload\u8fdb\u6765\u4e86"rn INVOKEVIRTUAL PrintStream.println(String) : voidrn RETURNrn MAXSTACK = 2rn MAXLOCALS = 0rnrn // access flags 1rn public getTitle() : Stringrn ALOAD 0rn GETFIELD Edocument.title : Stringrn ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setTitle(String) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.title : Stringrn RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn // access flags 1rn public getNigaoren() : Ownersrn ALOAD 0rn GETFIELD Edocument.nigaoren : Ownersrn ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setNigaoren(Owners) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.nigaoren : Ownersrn RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn // access flags 1rn public getNigaorenId() : Ownersrn ALOAD 0rn GETFIELD Edocument.nigaorenId : Ownersrn ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setNigaorenId(Owners) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.nigaorenId : Ownersrn RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn // access flags 1rn public getNigaodate() : Datern ALOAD 0rn GETFIELD Edocument.nigaodate : Datern ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setNigaodate(Date) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.nigaodate : Datern RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn // access flags 1rn public getNigaodep() : Departmentrn ALOAD 0rn GETFIELD Edocument.nigaodep : Departmentrn ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setNigaodep(Department) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.nigaodep : Departmentrn RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn // access flags 1rn public getDepId() : Departmentrn ALOAD 0rn GETFIELD Edocument.depId : Departmentrn ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setDepId(Department) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.depId : Departmentrn RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn // access flags 1rn public getContent() : Stringrn ALOAD 0rn GETFIELD Edocument.content : Stringrn ARETURNrn MAXSTACK = 2rn MAXLOCALS = 1rnrn // access flags 1rn public setContent(String) : voidrn ALOAD 0rn ALOAD 1rn PUTFIELD Edocument.content : Stringrn RETURNrn MAXSTACK = 3rn MAXLOCALS = 3rnrn[/code]rnrn另外在附上,通过Asm生成的Class文件,我将它反编译后得到的java代码,用于意思的对比,方便理解rnrn[code=Java]rnpackage com.gwinsoft.gwinoa.entity.dataform;rnrnimport com.gwinsoft.gwinoa.entity.IEntity;rnimport com.gwinsoft.gwinoa.entity.uum.Department;rnimport com.gwinsoft.gwinoa.entity.uum.Owners;rnimport java.io.PrintStream;rnimport java.util.Date;rnrnpublic class Edocument extends IEntityrnrn private String title;rn private Owners nigaoren;rn private Owners nigaorenId;rn private Date nigaodate;rn private Department nigaodep;rn private Department depId;rn private String content;rnrn staticrn rn System.out.println("Edocument 已经load进来了");rn rnrn public String getTitle()rn rn return this.title;rn rnrn public void setTitle(String paramString)rn rn this.title = paramString;rn rnrn public Owners getNigaoren()rn rn return this.nigaoren;rn rnrn public void setNigaoren(Owners paramOwners)rn rn this.nigaoren = paramOwners;rn rnrn public Owners getNigaorenId()rn rn return this.nigaorenId;rn rnrn public void setNigaorenId(Owners paramOwners)rn rn this.nigaorenId = paramOwners;rn rnrn public Date getNigaodate()rn rn return this.nigaodate;rn rnrn public void setNigaodate(Date paramDate)rn rn this.nigaodate = paramDate;rn rnrn public Department getNigaodep()rn rn return this.nigaodep;rn rnrn public void setNigaodep(Department paramDepartment)rn rn this.nigaodep = paramDepartment;rn rnrn public Department getDepId()rn rn return this.depId;rn rnrn public void setDepId(Department paramDepartment)rn rn this.depId = paramDepartment;rn rnrn public String getContent()rn rn return this.content;rn rnrn public void setContent(String paramString)rn rn this.content = paramString;rn rnrn[/code]rnrn各位看官,请帮助分析下,为什么会出现一开始的错误,是我的Asm写法问题吗? 我自己的分析是出在构造方法上,但是就是找不到问题在哪里,我的写法基本上也是从网上学来的。rn 论坛

没有更多推荐了,返回首页