摘要:六修改內(nèi)部類的方法這個(gè)的方法是對(duì)類型的數(shù)據(jù)進(jìn)行解析,我們判斷輸入的數(shù)據(jù)類型不是類型,就直接跳過(guò)解析,核心是在方法中插入方法。每一個(gè)類每一個(gè)內(nèi)部類每一個(gè)匿名內(nèi)部類,都會(huì)生成一個(gè)獨(dú)立的文件,如。
一、項(xiàng)目地址
項(xiàng)目地址:github-gson-plugin
二、ReaderTools解析/** * Created by tangfuling on 2018/10/23. */ public class ReaderTools { private static JsonSyntaxErrorListener mListener; public static void setListener(JsonSyntaxErrorListener listener) { mListener = listener; } /** * used for array、collection、map、object * skipValue when expected token error * * @param in input json reader * @param expectedToken expected token */ public static boolean checkJsonToken(JsonReader in, JsonToken expectedToken) { if (in == null || expectedToken == null) { return false; } JsonToken inToken = null; try { inToken = in.peek(); } catch (IOException e) { e.printStackTrace(); } if (inToken == expectedToken) { return true; } if (inToken != JsonToken.NULL) { String exception = "expected " + expectedToken + " but was " + inToken + " path " + in.getPath(); notifyJsonSyntaxError(exception); } skipValue(in); return false; } /** * used for basic data type, we only deal type Number and Boolean * skipValue when json parse error * * @param in input json reader * @param exception json parse exception */ public static void onJsonTokenParseException(JsonReader in, Exception exception) { if (in == null || exception == null) { return; } skipValue(in); notifyJsonSyntaxError(exception.getMessage()); } private static void skipValue(JsonReader in) { if (in == null) { return; } try { in.skipValue(); } catch (IOException e) { e.printStackTrace(); } } private static void notifyJsonSyntaxError(String exception) { if (mListener == null) { return; } String invokeStack = Log.getStackTraceString(new Exception("syntax error exception")); mListener.onJsonSyntaxError(exception, invokeStack); } public interface JsonSyntaxErrorListener { public void onJsonSyntaxError(String exception, String invokeStack); } }
1.對(duì)外暴露setListener()接口,用戶可以監(jiān)聽到Json解析異常。
2.checkJsonToken()方法,用于判斷輸入字段的數(shù)據(jù)類型是否與預(yù)期的數(shù)據(jù)類型一致,如果數(shù)據(jù)類型不一致,則跳過(guò)解析,同時(shí)通知listener解析失敗。該方法用于判斷array、collection、map、object是否合法。
3.onJsonTokenParseException()方法,會(huì)利用javassist對(duì)Gson拋出的Exception進(jìn)行捕獲,然后調(diào)用該方法,同時(shí)通知listener解析失敗。該方法用于判斷Integer、Boolean等基本數(shù)據(jù)類型。
1.ReaderTools.java的setListener()方法需要暴露給用戶使用,但Plugin僅僅是一個(gè)插件,無(wú)法將java語(yǔ)言的接口暴露出去給用戶使用,所以需要建立2個(gè)工程。
2.gson-plugin-sdk:主要包含ReaderTools.java,與用戶交互的類及方法需要在這個(gè)sdk中定義并實(shí)現(xiàn)。
3.gson-plugin:主要是侵入編譯流程,并修改Gson的字節(jié)碼,同時(shí)在特定的地方調(diào)用ReaderTools.java中的方法,如checkJsonToken()方法,onJsonTokenParseException()方法等。
4.這樣用戶接入需要引入兩個(gè)庫(kù),gson-plugin-sdk和gson-plugin。
5.為了方便用戶接入,可以在gson-plugin中幫助用戶引入gson-plugin-sdk,這樣用戶就只需要引入gson-plugin即可。
6.在gson-plugin中幫助用戶引入gson-plugin-sdk
project.dependencies.add("compile", "com.ke.gson.sdk:gson_sdk:1.3.0")
7.GsonPlugin為插件入口類,在此注冊(cè)自定義的GsonJarTransform
/** * Created by tangfuling on 2018/10/25. */ class GsonPlugin implements Plugin四、GsonJarTransform編譯流程{ @Override void apply(Project project) { //add dependencies project.dependencies.add("compile", "com.ke.gson.sdk:gson_sdk:1.3.0") //add transform project.android.registerTransform(new GsonJarTransform(project)) } }
@Override String getName() { return "GsonJarTransform" } @Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { //初始化ClassPool MyClassPool.resetClassPool(mProject, transformInvocation) //處理jar和file TransformOutputProvider outputProvider = transformInvocation.getOutputProvider() for (TransformInput input : transformInvocation.getInputs()) { for (JarInput jarInput : input.getJarInputs()) { // name must be unique,or throw exception "multiple dex files define" def jarName = jarInput.name if (jarName.endsWith(".jar")) { jarName = jarName.substring(0, jarName.length() - 4) } def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath()) //source file File file = InjectGsonJar.inject(jarInput.file, transformInvocation.context, mProject) if (file == null) { file = jarInput.file } //dest file File dest = outputProvider.getContentLocation(jarName + md5Name, jarInput.contentTypes, jarInput.scopes, Format.JAR) FileUtils.copyFile(file, dest) } for (DirectoryInput directoryInput : input.getDirectoryInputs()) { File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY) FileUtils.copyDirectory(directoryInput.file, dest) } } }
1.初始化ClassPool,javassist中用到的類都需要先加入ClassPath。
/** * Created by tangfuling on 2018/10/31. */ public class MyClassPool { private static ClassPool sClassPool public static ClassPool getClassPool() { return sClassPool } public static void resetClassPool(Project project, TransformInvocation transformInvocation) { // ClassPool.getDefault() 有可能被其他使用 Javassist 的插件污染(如 nuwa), // 導(dǎo)致ClassPool中出現(xiàn)重復(fù)的類,Javassist拋出異常,所以不能使用默認(rèn)的 sClassPool = new ClassPool() sClassPool.appendSystemPath() // bootClasspath 包括 android.jar 和 useLibrary 指定的library 的路徑(如 org.apache.http.legacy ) project.android.bootClasspath.each { sClassPool.appendClassPath(it.absolutePath) } // 其它c(diǎn)lass for (TransformInput input : transformInvocation.getInputs()) { for (JarInput jarInput : input.getJarInputs()) { sClassPool.appendClassPath(jarInput.file.getAbsolutePath()) } for (DirectoryInput directoryInput : input.getDirectoryInputs()) { sClassPool.appendClassPath(directoryInput.file.getAbsolutePath()) } } } }
2.transform處理過(guò)程
2.1.在編譯過(guò)程中,transform會(huì)對(duì)項(xiàng)目中所有依賴的jar文件和項(xiàng)目本身的class文件進(jìn)行處理,將處理結(jié)果交給下一個(gè)步驟,繼續(xù)處理。
2.2.如果不做任何處理,那么transform至少會(huì)做一件事情,將輸入的jar文件和class文件,拷貝到build/intermediates/transforms/GsonJarTransform目錄。
2.3.gson-plugin需要對(duì)gson.jar做處理。
File file = InjectGsonJar.inject(jarInput.file, transformInvocation.context, mProject)五、處理gson.jar包
/** * Created by tangfuling on 2018/10/25. */ class InjectGsonJar { public static File inject(File jarFile, Context context, Project project) throws NotFoundException { if (!jarFile.name.contains("gson")) { return null } println("GsonPlugin: inject gson jar start") //原始jar path String srcPath = jarFile.getAbsolutePath() //原始jar解壓后的tmpDir String tmpDirName = jarFile.name.substring(0, jarFile.name.length() - 4) String tmpDirPath = context.temporaryDir.getAbsolutePath() + File.separator + tmpDirName //目標(biāo)jar path String targetPath = context.temporaryDir.getAbsolutePath() + File.separator + jarFile.name //解壓 Decompression.uncompress(srcPath, tmpDirPath) //修改 InjectReflectiveTypeAdapterFactory.inject(tmpDirPath) InjectMapTypeAdapterFactory.inject(tmpDirPath) InjectArrayTypeAdapter.inject(tmpDirPath) InjectCollectionTypeAdapterFactory.inject(tmpDirPath) InjectTypeAdapters.inject(tmpDirPath) //重新壓縮 Compressor.compress(tmpDirPath, targetPath) //刪除臨時(shí)目錄 StrongFileUtil.deleteDirPath(tmpDirPath) println("GsonPlugin: inject gson jar success") //返回目標(biāo)jar File targetFile = new File(targetPath) if (targetFile.exists()) { return targetFile } return null } }
1.輸入的gson.jar位置:.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson
2.對(duì)輸入的jar包解壓到一個(gè)臨時(shí)目錄,并對(duì)解壓后的class文件進(jìn)行修改:build/tmp/transformClassesWithGsonJarTransformForDebug,會(huì)生成一個(gè)文件夾gson-2.8.5
3.將修改后的文件重新壓縮到當(dāng)前目錄:build/tmp/transformClassesWithGsonJarTransformForDebug,會(huì)重新生成一個(gè)jar包gson-2.8.5.jar
4.刪除步驟2中生成的文件夾gson-2.8.5
5.將tmp目錄下的gson-2.8.5.jar返回
6.transform會(huì)將tmp目錄下gson-2.8.5.jar拷貝到build/intermediates/transforms/GsonJarTransform目錄供下一個(gè)步驟使用。
1.這個(gè)Adapter.class的read()方法是對(duì)Object類型的數(shù)據(jù)進(jìn)行解析,我們判斷輸入的數(shù)據(jù)類型不是Object類型,就直接跳過(guò)解析,核心是在read()方法中插入ReaderTools.checkJsonToken()方法。
2.每一個(gè)類、每一個(gè)內(nèi)部類、每一個(gè)匿名內(nèi)部類,都會(huì)生成一個(gè)獨(dú)立的.class文件,如ReflectiveTypeAdapterFactory.class,ReflectiveTypeAdapterFactory$Adapter.class,ReflectiveTypeAdapterFactory$BoundField.class,ReflectiveTypeAdapterFactory$1.class。
3.遍歷文件夾找到對(duì)應(yīng)的class,通過(guò)javassist在read()方法前面插入判斷代碼。
/** * Created by tangfuling on 2018/10/30. */ public class InjectReflectiveTypeAdapterFactory { public static void inject(String dirPath) { ClassPool classPool = MyClassPool.getClassPool() File dir = new File(dirPath) if (dir.isDirectory()) { dir.eachFileRecurse { File file -> if ("ReflectiveTypeAdapterFactory.class".equals(file.name)) { CtClass ctClass = classPool.getCtClass("com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter") CtMethod ctMethod = ctClass.getDeclaredMethod("read") ctMethod.insertBefore(" if (!com.ke.gson.sdk.ReaderTools.checkJsonToken($1, com.google.gson.stream.JsonToken.BEGIN_OBJECT)) { " + " return null; " + " }") ctClass.writeFile(dirPath) ctClass.detach() println("GsonPlugin: inject ReflectiveTypeAdapterFactory success") } } } } }七、字節(jié)碼加 try-catch
1.TypeAdapters.class處理基本數(shù)據(jù)類型,每個(gè)基本數(shù)據(jù)類型都對(duì)應(yīng)一個(gè)匿名內(nèi)部類
public static final TypeAdapterBOOLEAN = new TypeAdapter () { public Boolean read(JsonReader in) throws IOException { if(in.peek() == JsonToken.NULL) { in.nextNull(); return null; } else { return in.peek() == JsonToken.STRING?Boolean.valueOf(Boolean.parseBoolean(in.nextString())):Boolean.valueOf(in.nextBoolean()); } } public void write(JsonWriter out, Boolean value) throws IOException { if(value == null) { out.nullValue(); } else { out.value(value.booleanValue()); } } };
2.找到TypeAdapters的所有內(nèi)部類,獲取內(nèi)部類的read()方法的返回值,如果是Number或Boolean類型,添加try-catch代碼塊,并回調(diào)ReaderTools.onJsonTokenParseException()方法。
/** * Created by tangfuling on 2018/10/30. */ public class InjectTypeAdapters { public static void inject(String dirPath) { ClassPool classPool = MyClassPool.getClassPool() File dir = new File(dirPath) if (dir.isDirectory()) { dir.eachFileRecurse { File file -> if (file.name.contains("TypeAdapters$")) { String innerClassName = file.name.substring(13, file.name.length() - 6) CtClass ctClass = classPool.getCtClass("com.google.gson.internal.bind.TypeAdapters$" + innerClassName) //only deal type Boolean and Number CtMethod[] methods = ctClass.declaredMethods boolean isModified = false for (CtMethod ctMethod : methods) { if ("read".equals(ctMethod.name)) { String returnTypeName = ctMethod.getReturnType().name if ("java.lang.Number".equals(returnTypeName) || "java.lang.Boolean".equals(returnTypeName)) { CtClass etype = classPool.get("java.lang.Exception") ctMethod.addCatch("{com.ke.gson.sdk.ReaderTools.onJsonTokenParseException($1, $e); return null;}", etype) isModified = true } } } if (isModified) { ctClass.writeFile(dirPath) println("GsonPlugin: inject TypeAdapters success") } ctClass.detach() } } } } }
3.其中$1表示read()方法的第1個(gè)參數(shù)JsonReader,$e表示捕獲的Exception
八、目錄1.gson-plugin告別Json數(shù)據(jù)類型不一致(一)
2.gson-plugin基礎(chǔ)源碼分析(二)
3.gson-plugin深入源碼分析(三)
4.gson-plugin如何在JitPack發(fā)布(四)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/72237.html
摘要:一項(xiàng)目地址項(xiàng)目地址二與關(guān)系普通的庫(kù)可以通過(guò)源碼的方式直接引入并使用,是一個(gè)插件,無(wú)法通過(guò)源碼的方式使用,只能編譯并發(fā)布以后,才能被正常使用。是一個(gè)代碼倉(cāng)庫(kù),我們可以將源代碼托管在這個(gè)平臺(tái)上。 一、項(xiàng)目地址 項(xiàng)目地址:github-gson-plugin 二、github與JitPack關(guān)系 1.普通的java庫(kù)可以通過(guò)源碼的方式直接引入并使用,gson-plugin是一個(gè)插件,無(wú)法通過(guò)...
摘要:六原理說(shuō)明侵入編譯流程,在編譯過(guò)程中,修改庫(kù)的字節(jié)碼,修改解析相關(guān)的方法,在數(shù)據(jù)類型不一致的時(shí)候,跳過(guò)當(dāng)前字段的解析。 一、目錄 1.gson-plugin告別Json數(shù)據(jù)類型不一致(一)2.gson-plugin基礎(chǔ)源碼分析(二)3.gson-plugin深入源碼分析(三)4.gson-plugin如何在JitPack發(fā)布(四) 看完這4篇文章,對(duì)Gson解析會(huì)有更加深刻的認(rèn)識(shí),對(duì)A...
閱讀 3470·2021-09-22 15:02
閱讀 3507·2021-09-02 15:21
閱讀 2133·2019-08-30 15:55
閱讀 2780·2019-08-30 15:44
閱讀 776·2019-08-29 16:56
閱讀 2414·2019-08-23 18:22
閱讀 3342·2019-08-23 12:20
閱讀 3091·2019-08-23 11:28