I did not expect that the writing of the exercise content was recognized by the teacher, it seems that I have to practice more seriously. Today we’re going to practice 22,27, ASM 3.

Chapter22

Try using the Facebook Redex library to optimize our installation package.

The preparatory work

First, download Redex:

git clone https://github.com/facebook/redex.git
cd redex
Copy the code

Next install:

autoreconf -ivf && ./configure && make -j4
sudo make install
Copy the code

Install here when I execute, report the following error:



It’s just not installedboostSo execute the following command to install it.

brew install boost jsoncpp
Copy the code

After boost installation is complete, the redex installation wait continues for more than 10 minutes…

The next step is to compile our Sample and get our installation package (information below).

You can see that there are three dex files with an APK size of 13.7 MB.

Optimization using the redex command

To better understand the flow, you can output redex logs

export TRACE=2
Copy the code

The method to remove debuginfo needs to be executed in the project root directory:

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.apkCopy the code

The long command above can actually unpack several parts:

  • –sign Indicates the signature information

  • -s (keystore) Path of the signature file

  • -a (keyAlias) Specifies the alias of the signature

  • -p (keypass) Specifies the signature password

  • -c Specifies the path of the redex configuration file

  • -p ProGuard rule file path

  • -o Indicates the path of the output file

  • The last thing to deal with is the path to the APK file

When using it, I encountered the following problem:



I can’t find it hereZipalign, so we need to configure the android SDK root directory path, add in front of the original command:

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

The results are as follows:



The actual effect is 14.21M for the original debug package and 12.91m after removing the debuginfo method. The effect is still good.What is removed is some debugging information and stack line numbers.



But the teacher is in Sample’sproguard-rules.proAdd the-keepattributes SourceFile,LineNumberTableThe line number information is preserved.

Therefore, after the package is installed, you can still see the line number of stack information on the home page.

Dex method of resubcontracting

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
Copy the code

It is the same as the previous command, except that -c uses the configuration file interdex.config.

Output information:



The effect is that the original debug package contains 14.21m and three dex, and the optimized package contains 13.34m and two dex.

According to the teacher,If your application has more than four Dex, this volume optimization is at least 10%.Looks like the effect is still pretty good. As for other issues, such as using in a Windows environmentredex, can be seenredextheUsing document.

Chapter27

Use AspectJ to achieve the example of piling

The effect is the same as Chapter07, except Chapter07 is implemented using ASM, this time in AspectJ. ASM and AspectJ are Java bytecode processing frameworks. AspectJ is much simpler to use, and the same functionality is implemented in the following code. But ASM is more efficient and flexible than AspectJ.

AspectJ implementation code:

@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(); }}Copy the code

Here’s what the above code means:

  • @Aspect: At compile time AspectJ looks for classes annotated by @Aspect and then executes our AOP implementation.

  • @before: can be simply understood as Before method execution.

  • @after: can be simply interpreted as After the method is executed.

  • Execution: Method execution.

  • * * * (..) : The first asterisk represents any return type, the second asterisk represents any class, and the third asterisk represents any method. Asterisks and parentheses can be replaced with concrete values, such as String testClass.test (String)

Knowing the meaning of the relevant annotations, the code of the implementation means that all methods insert the corresponding specified actions before and after execution.

The effect comparison is as follows:





Down implement toMainActivitytheonResumeMethods to increasetry catch.

@ Aspect public class TryCatchAspect {the @pointcut (" execution (* com. Sample. Systrace. MainActivity. OnResume () "), / / < - specify the classes and methods  public void methodTryCatch() { } @Around("methodTryCatch()") public void aroundTryJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { // try catch try { joinPoint.proceed(); } catch (Exception e) {e.printStackTrace(); }}}Copy the code

There are two new notes:

  • @around: Replaces the previous code, which can be called using joinPoint.proceed().

  • @pointcut: Specifies a Pointcut.

The implementation is to specify a pointcut that wraps a layer of try catch with the idea of replacing the original method.

The effect comparison is as follows:

Of course, AspectJ has many other uses. The AspectJ Programming Guide is included in Sample to help you learn AspectJ in detail.

Chapter-ASM

Sample uses ASM to implement statistical method time-consuming and replace all new threads in the project

  • The apply plugin for ASMSample build.gradle should be added first: Classpath (“com.geektime.asm:asm-gradle-plugin:1.0”) {changing = true}

  • Run gradle task “: the asm – gradle – plugin: buildAndPublishToLocalMaven” compiled plugin plug-in, compiled plug-in locally. M2 \ repository directory.

  • Open the contents of the first step to run.

The general process of implementation: first use Transform to traverse all files, through ASM visitMethod to traverse all methods, and finally through AdviceAdapter to achieve the final modification of bytecode. Specific implementation can see code and practice Sample run | ASM pile strengthening practice.

Effect comparison:

Here are two exercises:

  1. Add to a methodtry catch

So I’m going to try catch the mm method of the MainActivity. The implementation is very simple, directly modify ASMCode TraceMethodAdapter.

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); }}}Copy the code

VisitTryCatchBlock method: the first three parameters are the Label instance, where one and two are the range of the try block, three are the start of the catch block, and the fourth parameter is the exception type. Other methods and parameters are not detailed, refer to the ASM documentation for details.

Implementation is similar to AspectJ, inserting our code at the beginning and end of method execution.

I will not screenshot the effect, the code is as follows:

public void mm() { try { A a = new A(new B(2)); } catch (Exception e) { e.printStackTrace(); }}Copy the code
  1. See who gets it in the codeIMEI

This one is easier, just find out who uses the getDeviceId method of TelephonyManager. (The answer is in 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); }}}Copy the code

The output after Build is as follows:


In general, ASM is more difficult to get started than AspectJ, requiring an understanding of compiled bytecode, and the functionality used here is just the tip of the iceberg. ASM Bytecode Outline plug-in recommended by class representative Peng Fei is a good helper! Finally, I also uploaded the code I practiced to Github, which also includes a Chinese ASM document, if you are interested, you can download it and have a look.

reference

  • Practice Sample run | ASM pile strengthening practice

  • ASM document