1. 程式人生 > >Android使用APT在編譯時期修改類程式碼

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();

由於專案中設計到的隱私這裡只能簡單的寫一下邏輯,方便以後查閱。