Android使用APT在編譯時期修改類程式碼
在做Android專案時候需要將專案中類中的一些敏感常量進行保護,尤其是專案中的URL地址,所以想到的一個策略就是在編譯時將該類中的URL進行加密然後生成對應java檔案,然後在apk編譯時期將原來的class檔案刪除,在Android Studio 編譯apk將class編譯成dex檔案之前將原來常量的URL對應類的class檔案刪除。
這裡主要是利用apt生成類,然後寫一個簡單的gradle外掛將class檔案刪除。在專案中新建一個模組該模組中主要就是包含一個註解,該註解就是定義在需要加密的URL的Field上:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ConstantEncript { String value(); }
在需要處理的變數上使用:
@ConstantEncript("http://www.*****.com") private String URL;在AS中配置apt,在新建模組的gradle中新增
apply plugin: 'java' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.auto.service:auto-service:1.0-rc2'} sourceCompatibility = "1.7" targetCompatibility = "1.7"
在專案下的gradle中dependencies中新增
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
在app下的gradle中新增apply plugin: 'com.neenbedankt.android-apt'
並在dependencies中新增
apt project(":新建module名稱")
compile project(':新建module名稱')
這樣AS中apt就配置好了,剩下的就是寫程式碼了,在新的module中新建一個類CustomeProcessor 讓這個類整合AbstractProcessor
@AutoService(Processor.class) @SupportedAnnotationTypes("catkin.com.annation.ConstantEncript") public class CustomeProcessor extends AbstractProcessor { private Filer filer; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); filer = processingEnv.getFiler(); }
AutoService這個註解是google提供的就是在module中的gradle中新增那個compile。然後要重寫AbstractProcessor中的幾個方法getSupportedSourceVersion()獲取支援的版本這裡寫SourceVersion.LatestSupported()就行,還有一個就是 getSupportedAnnotationTypes()這個就是獲取支援的註解型別,也可以寫到類的上面用@SupportedAnnationTypes裡面就是註解的路徑,多個註解可以用逗號隔開就是{"","".....}。重寫init方法,並獲取Filer物件用於生成類檔案,方法中的processingEnv是父類中的成員變數。重寫process方法,這個方法中主要就是做生成類的操作。
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
String package_name = "";String class_name = ""; for (Element ele : roundEnvironment.getElementsAnnotatedWith(ConstantEncript.class)) {
//獲取類的包名,要在該類的同級包下面新新增一個類 package_name = processingEnv.getElementUtils().getPackageOf(ele).getQualifiedName().toString()+"."; Element typeElement = ele.getEnclosingElement();
//獲取被ConstantEncript註解 ConstantEncript annation = ele.getAnnotation(ConstantEncript.class);
//獲取類名 class_name = "Complier_$" + typeElement.getSimpleName().toString();} try { String finalName = package_name + class_name;
//在呼叫該方法前最好要把需要生成的類的全路徑拼好,最好不要手動在反方中新增
// JavaFileObject source = filer.createSourceFile("abc.bcd.a"+"b")這樣會有莫名其妙的問題 JavaFileObject source = filer.createSourceFile(finalName); Writer writer = source.openWriter(); StringBuilder builder = new StringBuilder() .append("package"+package_name+";\n\n") .append("import java.io.IOException;\n\n") .append("import catkin.com.Connect;\n\n") .append("public class ") .append(class_name + " implements Connect") .append(" {\n\n") // open class .append("\tprivate static final String URL = \""加密處理的URl"\";\n") .append("\t@Override\n") .append("\tpublic String connect(){\n") .append("\t\t return URL;\n") .append("}\n") .append("}\n"); writer.write(builder.toString()); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } return true; }
這樣就實現了生成加密處理的URl的類,還有一個就是在gradle編譯時刪除掉原來的url的類,以後再寫,
還有一個就是在生成程式碼時候繼承了一個類,這個就是為了在獲取物件,然後呼叫物件的方法,因為這個類是編譯生成的,在程式碼中new不出來,所以寫了一個介面,用反射將父類的引用指向子類,呼叫子類的方法,這樣就不會報錯了。
public interface Connect { String connect(); }
Connect connect = (Connect) Class.forName(Complier_$類的名字.class.getName()).newInstance();
String result = connect.connect();
由於專案中設計到的隱私這裡只能簡單的寫一下邏輯,方便以後查閱。