10
InternalAccessor: Membaca Resource com.android.internal
InternalAccessor adalah kelas utilitas di modul resources-helper yang membaca resource milik com.android.internal.R tanpa memerlukan Context aplikasi. Javadoc kelasnya menyatakan kelas ini tidak lagi memakai Java reflection untuk mengambil resource, sehingga aman dipakai (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:14-211). Kelasnya final dan konstruktornya private, jadi seluruh akses berjalan lewat metode static (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:92-932). Nilai yang dibaca selalu berasal dari Resources.getSystem(), yaitu tabel resource milik framework, bukan tabel resource aplikasi pemanggil.
Satu pintu lookup id, banyak getter
Setiap getter resource memakai pola yang sama: cari id lewat getResourceId, lalu serahkan id itu ke Resources.getSystem(). Builder menambah satu langkah di belakangnya, yaitu menyalin hasil ke ResourcesHolder.
graph TD
caller["Pemanggil"] --> getString["getString(resName)"]
caller --> builderGet["Builder.getString(resName)"]
builderGet --> getString
getString --> getResourceId["getResourceId(STRING, resName)"]
getResourceId --> getIdentifier["Resources.getSystem().getIdentifier(resName, clas, 'android')"]
getIdentifier -->|"id == 0"| notFound["Resources.NotFoundException"]
getIdentifier -->|"id != 0"| resourceCall["Resources.getSystem().getString(id)"]
resourceCall --> value["nilai String"]
builderGet --> put["holder.put(resName, nilai)"]
put --> holder["ResourcesHolder"]
style getResourceId fill:#f59e0b,color:#000
style getIdentifier fill:#f59e0b,color:#000
style notFound fill:#dc2626,color:#fff
style resourceCall fill:#2d7d46,color:#fff
style value fill:#2d7d46,color:#fff
style builderGet fill:#8b5cf6,color:#fff
style put fill:#8b5cf6,color:#fff
style holder fill:#336791,color:#fff
%% fig: Alur pembacaan resource com.android.internal
%% desc: Getter statis mencari id lewat getResourceId lalu membaca nilai dari Resources.getSystem(); Builder menyalin nilai itu ke ResourcesHolder.Konstanta tipe resource
Argumen clas diisi nama tipe resource sebagai String. Ada 21 konstanta String publik yang dideklarasikan berurutan dari ANIM sampai TRANSITION (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:50-903).
| Konstanta | Nilai |
|---|---|
ANIM | "anim" |
ARRAY | "array" |
ATTR | "attr" |
BOOL | "bool" |
COLOR | "color" |
DIMEN | "dimen" |
DRAWABLE | "drawable" |
ID | "id" |
INTEGER | "integer" |
LAYOUT | "layout" |
PLURALS | "plurals" |
RAW | "raw" |
STRING | "string" |
STYLE | "style" |
STYLEABLE | "styleable" |
XML | "xml" |
FRACTION | "fraction" |
INTERPOLATOR | "interpolator" |
MENU | "menu" |
MIPMAP | "mipmap" |
TRANSITION | "transition" |
Konstanta ini hanya nama tipe; tidak ada objek Class yang dipetakan. Pendekatan lama yang memakai field Class<?> per tipe masih tersimpan sebagai blok komentar di kepala berkas, lengkap dengan catatan bahwa sebagian kelas hanya tersedia sejak API 7 dan sebagian lain belum tentu ada di API 10 (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:23-484).
getResourceId sebagai inti
getResourceId(String clas, String resName) adalah satu-satunya tempat lookup id dilakukan (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:95-1115). Ia memanggil Resources.getSystem().getIdentifier(resName, clas, "android"), sehingga namespace selalu "android" dan pemanggil tidak bisa memilih paket lain. Javadoc-nya memberi contoh com.android.internal.R.xml.audio_assets yang kembali sebagai integer 17891329.
Ketika id bernilai 0, metode ini tidak mengembalikan 0. Ia melempar Resources.NotFoundException dengan pesan yang dirakit dari argumen:
Cannot find '<clas>' for '<resName>', or this resource currently is not available for API <Build.VERSION.SDK_INT>
Pesan itu menyebut Build.VERSION.SDK_INT, jadi kegagalan lookup selalu bisa dibaca sebagai petunjuk bahwa resource belum ada di level API perangkat yang sedang dipakai.
Getter per tipe resource
Di atas getResourceId berdiri 17 getter yang masing-masing menutup satu tipe resource: getter menyisipkan konstanta tipe yang tepat, memanggil satu metode Resources.getSystem(), lalu mengembalikan nilai bertipe Java (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:113-2716).
| Getter | Konstanta | Pemanggilan Resources.getSystem() | Hasil |
|---|---|---|---|
getAnimation(resName) | ANIM | getAnimation(id) | XmlResourceParser |
getBoolean(resName) | BOOL | getBoolean(id) | boolean |
getColor(resName) | COLOR | getColor(id) | int |
getDimension(resName) | DIMEN | getDimension(id) | float |
getDrawable(resName) | DRAWABLE | getDrawable(id) | Drawable |
getDrawable(resName, theme) | DRAWABLE | getDrawable(id, theme) | Drawable |
getFraction(resName, base, pbase) | FRACTION | getFraction(id, base, pbase) | float |
getInteger(resName) | INTEGER | getInteger(id) | int |
getLayout(resName) | LAYOUT | getLayout(id) | XmlResourceParser |
getMenu(resName) | MENU | getLayout(id) | XmlResourceParser |
getMipmap(resName) | MIPMAP | getDrawable(id) | Drawable |
getMipmap(resName, theme) | MIPMAP | getDrawable(id, theme) | Drawable |
getRaw(resName) | RAW | openRawResource(id) | InputStream |
getIntArray(resName) | ARRAY | getIntArray(id) | int[] |
getStringArray(resName) | ARRAY | getStringArray(id) | String[] |
getString(resName) | STRING | getString(id) | String |
getXml(resName) | XML | getXml(id) | XmlResourceParser |
Dua baris terakhir pada tabel itu layak diperhatikan karena nama metode tidak selalu sama dengan tipe resource yang dibaca.
⚠️
getMenutidak memanggilgetXmlmaupun metode khusus menu. Ia memakaiResources.getSystem().getLayout(...)terhadap id dari konstantaMENU(resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:202-2047).getMipmapjuga membaca lewatgetDrawable, bukan metode khusus mipmap (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:211-2148). Jangan mengandalkan nama metode untuk menebak jalur pembacaan; periksa tabel di atas.
Builder dan pengisian ResourcesHolder
Builder adalah pembungkus yang sekarang ditandai @deprecated. Javadoc-nya menjelaskan alasannya: karena resource internal tidak lagi diambil lewat Context dan Java reflection, menyimpan hasil ke ResourcesHolder menjadi redundan dan getter statis bisa dipanggil langsung (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:341-3679).
Bentuknya sederhana. Konstruktor Builder(boolean saveToResourcesHolder) menyimpan satu flag, dan field holder sudah diinisialisasi sebagai new ResourcesHolder(). Setiap metode Builder mendelegasikan ke getter statis, lalu menulis ke holder hanya jika flag bernilai true (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:499-50710). Kunci yang dipakai saat menyimpan selalu argumen resName, sesuai javadoc konstruktornya.
Karena delegasinya mengikuti getter satu per satu, Builder tidak menyediakan padanan untuk overload bertema (getDrawable(resName, theme), getMipmap(resName, theme)) maupun untuk getResourceId (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:341-51811). Isi dan pengiriman holder dibahas di ResourcesHolder dan Pengiriman via Broadcast.
Pemakaian nyata
Aplikasi sample memanggil getter langsung dengan nama resource internal: getString("accept"), getDimension("status_bar_height"), dan getColor("config_defaultNotificationColor") (sample/src/main/java/com/anggrayudi/hiddenapi/sample/MainActivity.kt:73-9312). getResourceId juga dipakai langsung saat tipe resource ditentukan di luar daftar getter, misalnya InternalAccessor.getResourceId(InternalAccessor.INTERPOLATOR, "accelerate_cubic") dan getResourceId(InternalAccessor.TRANSITION, "no_transition") (sample/src/main/java/com/anggrayudi/hiddenapi/sample/MainActivity.kt:135-14913). Pemakaian Builder masih tersimpan sebagai contoh yang dikomentari pada berkas yang sama (sample/src/main/java/com/anggrayudi/hiddenapi/sample/MainActivity.kt:151-16414).
Pemeriksaan kelas dan method
Dua metode terakhir tidak membaca resource, tetapi sering dipakai bersama getter untuk menjaga kode tetap jalan lintas API. isClassExists(String className) mengembalikan true bila Class.forName berhasil (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:279-28615). isMethodExists punya dua bentuk: satu memeriksa nama method beserta tipe parameternya, satu lagi hanya mencocokkan nama method pada getDeclaredMethods (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:310-33916). Keduanya mengembalikan false saat kelas tidak ditemukan, bukan melempar exception.