Ý nghĩa của các tệp tin sau quá trình Proguard

Khi proguard xử lý một ứng dụng android, nó tạo ra một vài files ghi lại những già đã xảy ra trong từng quá trình. Những files đó thực sự hữu ích nếu bạn muốn hình dung ra proguard đã thay đổi những gì hoặc tại sao source code của bạn lại trở thành như cách mà nó đã làm. Chỉ có điều những files đó không có tài liệu nào mô tả về nó, vì vậy tôi viết bài này giải thích tại sao những files đó được tạo ra và nó cho bạn thấy những gì.

Những file trên nằm trong thư mục build

app/build/outputs/mapping/{buildType}/

Dưới đây là mô hình tổng quan về các bước Proguard xử lý khi nó phân tích ứng dụng của bạn.

seeds.txt

Việc đầu tiên mà Proguard xử lý đó là đọc tất cả các files cấu hình và sau đó đọc tất cả các files Java bytecode (.class files) để tạo ra một thứ gọi là class pool. Sau đó Proguard duyệt qua class pool và in ra file seeds.txt một danh sách mọi class và member mà nó phù hợp với bất kỳ keep rules của bạn. File này sẽ hữu ích cho bạn debug quá trình bạn viết keep rules, bạn sẽ biết được keep rule mà bạn viết có thực sự khớp với các classes mà bạn đang cố gắng giữ lại hay không?

Nếu một class khớp với keep rules, sẽ có một dòng với tên đầy đủ của class in ra trong seeds.txt. Nếu một member khớp với keep rules, dòng đó sẽ là tên đầy đủ của class và theo sau là dấu 2 chấm ":" và theo sau là member's signature

usage.txt

Sau khi biết được nó sẽ phải giữ lại những gì, Proguard sẽ duyệt qua class pool và tìm những thứ mà nó không cần keep. Đó là quá trình shrinking, nơi mà Proguard trải ra những code không sử dụng đến trong ứng dụng. Nó sẽ in ra những code không sử dụng vào file usage.txt và những code đó sẽ bị loại bỏ. Và cái tên file nghe có vẻ ngược ngược, tôi nghĩ file này nên đặt tên là unused.txt hoặc shrinkage.txt =))

File này sẽ hữu ích cho bạn khi bạn muốn hình dung ra tại sao một class của bạn lại không có trong quá trình runtime. Bạn có thể kiểm tra liệu nó có bị removed đi tại đây.

Nếu toàn bộ class bị removed, nó sẽ in 1 dòng với tên đầy đủ của class. Nếu chỉ có 1 phần trong số các member bị removed, nó sẽ in 1 dòng với tên đầy đủ của class theo sau là dấu 2 chấm ":" và theo sau các dòng được thụt lề 4 dấu cách (space) cho mỗi member bị removed. Như hình trên

mapping.txt

Điều tiếp theo Proguard cần làm đó là obfuscate càng nhiều code càng tốt. Quá trình này nó sẽ đổi tên cách classes và members thành những cái tên vô nghĩa như "a", "b", vân vân. Proguard sẽ in tên cũ và tên mới cho mọi class và member mà nó đổi tên vào file mapping.txt. Không phải tất cả code đều bị đổi tên mà chỉ có những code được liệt kê trong file mapping.txt

mapping.txt giúp bạn de-obfuscate một stacktrace. Nó giúp bạn có thể biết được tên gốc của code đã bị đổi tên.

Mỗi dòng có dạng "{tên cũ} -> {tên mới}". Nó sẽ in một dòng cho class name và mỗi dòng tiếp theo cho các member của class đó. Chú ý rằng constructors chính là "<init>()"

dump.txt

Sau khi Proguard hoàn thành tất cả các phép màu (shrinking and obfuscating), nó in ra một file cuối cùng về bản chất là tất cả các code sau khi xử lý. Đó là mọi thứ còn lại trong các class nhưng trong định dạng chưa tối ưu, vì vậy nó là một file rất lớn. Tôi có một file demo sử dụng cho testing Proguard, cuối cùng khi mà ứng dụng chỉ có 1MB nhưng dump.txt có kích thước gần 18MB. Nó thật khổng lồ. Dưới đây là output cho một class nhỏ.

File này thực sự hữu ích nếu bạn muốn thấy được trong file class của bạn có những gì nhưng không muốn decompile file .class hoặc file .dex

Kết luận

Bạn đã hiểu được ý nghĩa của các file đầu ra sau quá trình Proguard và quá trình viết proguard config phần nào dễ dàng hơn. Ngoài ra một chú ý quan trọng đó là khi bạn build ứng dụng với proguard bạn cần lưu trữ lại file mapping.txt của bản build đó nếu bạn phân phối ứng dụng qua Play Store hoặc bất cứ đâu bạn có thể dễ dàng de-obfuscate stacktraces.

Bonus

Dưới đây là đoạn code gradle mình dùng để khi build apk release nó sẽ tự động đổi tên file apk theo tên thư mục của project, gắn thêm version name hiện tại và copy file mapping đến cùng thư mục chứa file apk, đổi tên giống với file apk của bạn. Như vậy file apk sẽ đi cùng file mapping mà không sợ bị nhầm lẫn hoặc quên copy mà build đè thì đến lúc có crash sẽ khó để đọc lỗi.

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            outputFileName = "${parent.name}-${variant.name}-${variant.versionName}.apk"
            def mappingName = "${variant.name}-${variant.versionName}.txt"
            if(variant.getBuildType().isMinifyEnabled()){
                variant.assemble.doLast {
                    copy{
                        from variant.mappingFile
                        into output.outputFile.parent
                        rename{String fileName -> mappingName}
                    }
                }
            }
        }
    }

HẾT Nguồn @jebstuart


All Rights Reserved