preface

Recently, I helped my colleague solve a thorny problem. The process of pit mining was quite interesting. Record it here. (PS: The main reason is that the project is relatively large, and we only have the development authority for part of the business code of the entire Android project. So some of the usual methods of problem solving are not available.)

The problem

Requirements: Web page H5 and native interaction, save base64 pictures.

Problem: Using the existing encapsulated Hybrid protocol, it was found in the final integration test that some mobile phones could not be saved successfully.

  • Debugging foundH5The original protocol format is used to invoke the new protocol, but the service side cannot be triggerednativeLog and breakpoints for the registered new protocol.
  1. H5 uses the original protocol format to call a new protocol that already exists onlinenativeCan end call success, this problem is eliminated;
  2. H5 removed the parameters in the new protocol and found that it could be callednativeThe new protocol is implemented. Guess that protocol parameters may cause the problem;
    1. The breakpoint is used to find where the protocol call is triggered, i.eH5andnativeWhere data is communicated. Found that the currentHybridThe protocol usesnativeThe copyonJsPromptMethod, interceptJavaScripttheprompt()Methods.
    2. Change the new protocol parameters back and call again. Breakpoints innativeThe copyonJsPromptIn the method, it is found that the transmitted data is truncated and the data fails to be parsed and forwarded to the next service test.

Trouble spots

The clone Prompt method used by the original Hybrid protocol in the project serves as the communication bridge, but now it encounters the problem of ** big flood (large data) ** obstacles.

Solution selection

  1. Let H5 change base64 image to HTTP image;

    The picture is originally drawn by H5, and the interactive experience is poor when the client downloads again after uploading.

  2. Our business side implements a set of Hybrid protocol.

  3. Have the infrastructure team modify the existing Hybrid protocol;

    Bugs discovered overnight will need to be closed beta tomorrow. It’s hard to make cross-departmental infrastructure changes in 24 hours.

Finally, we choose the second option, which is to implement a Hybrid protocol by ourselves.

Solution implementation

  1. getWebViewcalladdJavascriptInterfaceApproach toH5Environment AddJSObject.
  2. The development ofJSThe tool allows it to be called to the new protocol in the old protocol formatJSCommunication methods.
  3. Parse the data and throw it back to the originalonJsPromptMethod that handles data.

If we could change all the project code, there would be no difficulty in this solution. The difficulty is that we now only have the outermost shell of the H5 page for the Activity, and the wrapped WebView does not expose the methods we want. So the third step of the scheme is problematic.

We have two solutions to this problem:

  1. This time through injectionJSThe object’sHybridCommunication protocol and original projectHybridProtocol does two sets of logic;
  2. Through many timeshookBlack tech call to the original other classesdispatchMethods;

If that were all there would be no article.

Okay, after all this talk we’re finally getting down to business. I don’t know how many students are going to continue to watch.

We can take all the AAR files in the project, see if we can modify the source code to provide the API we want, and then fix the problem by upgrading the AAR version. Well, the focus of this article is on modifying the AAR class file.

Modify the AAR class file

Plan a

Delete the class you want to modify in the AAR and repackage it as a new AAR. The project relies on the new version of the AAR and then creates an identical class under the corresponding package for the project.

  1. Will the originalclassThe content of the file is decompiled and copied to the newly created class to run directly.
  2. Will the originalclassThe contents of the file are decompiled and copied to the newly created class. Last recompile generatedclassTo add to theAARRepackage to generate a newAAR.

If the class is confused, the scheme is basically invalid. Because a large number of packages and class names are the same in the contents of the decompilated class, there is no way to determine whether the class or package was used during the call during the recompilation.

For example, the following structure is often seen after confusion.

com.xx.a
com.xx.a.a
Copy the code

Instead of looking for class A under package A, write the following code saying there is no class A under class A.

a ma = new com.xx.a.a();
Copy the code

Scheme 2

As can be seen from the figure above, this scheme is faceted programming. We just need to add a method or two to a class to solve the problem of restricted access. Given the difficulty of the problem at hand, we chose Javassist. Because the Javassist source-level API is easier to use than the actual bytecode operations in ASM, you don’t need a deep understanding of the JVM specification to use it.

Javassist official documentation

Jar package download address: Github: JavAssist

// The method to be added
//public void executeJSCmd(String var1) {
// if (this.mActionDispatcher ! = null) {
// Message var2 = this.mActionHandler.obtainMessage(0);
// var2.obj = var1;
// this.mActionHandler.sendMessage(var2);
/ /}
/ /}
/ / need to manipulate the class name of the class: com. XXX. Android. Web. Webview. BaseWebChromeClient
public class Test {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool classPool = ClassPool.getDefault();
        // The class file must be placed in the compiled class file of the project, and the path is corresponding
        CtClass ctClass = classPool.get("com.xxx.android.web.webview.BaseWebChromeClient");
        CtMethod newmethod = CtNewMethod.make("public void executeJSCmd(String message) { if (this.mActionDispatcher != null) { android.os.Message msg = this.mActionHandler.obtainMessage(0); msg.obj = message; this.mActionHandler.sendMessage(msg); } }",ctClass); ctClass.addMethod(newmethod); ctClass.writeFile(); }}Copy the code

Note that, for example, the method we add involves other classes that need to write the full path android.os.Message, and the jar package associated with this class must also be added to the runtime environment (you can also put the class file in the class file directory compiled by the project). Otherwise, an error will be reported during execution.

Exception in thread "main" javassist.CannotCompileException: [source error] no such class: android.os.Message
	at javassist.CtNewMethod.make(CtNewMethod.java: 78).at javassist.CtNewMethod.make(CtNewMethod.java44) :at com.test.pattern.Test.main(Test.java13) :Copy the code

Pay attention to the point

When replacing or deleting a class in a JAR, it is best not to unzip it and then use the named package. I have one when I use the command to open a jar package on my Max computer. DS_Store file. The BetterZip compression & Decompression tool I use makes it very easy to add and remove classes from jar packages without decompressing them.

The article here is all about the end, if there are other need to exchange can leave a message oh ~! ~!