Lompat ke konten
dokumentasi.id
Commit 98ceeb4
v2
Star3,6 rb

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-21). Kelasnya final dan konstruktornya private, jadi seluruh akses berjalan lewat metode static (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:92-93). 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.
Fig. 1: Alur pembacaan resource com.android.internalGetter 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-90).

KonstantaNilai
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-48).

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-111). 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-271).

GetterKonstantaPemanggilan Resources.getSystem()Hasil
getAnimation(resName)ANIMgetAnimation(id)XmlResourceParser
getBoolean(resName)BOOLgetBoolean(id)boolean
getColor(resName)COLORgetColor(id)int
getDimension(resName)DIMENgetDimension(id)float
getDrawable(resName)DRAWABLEgetDrawable(id)Drawable
getDrawable(resName, theme)DRAWABLEgetDrawable(id, theme)Drawable
getFraction(resName, base, pbase)FRACTIONgetFraction(id, base, pbase)float
getInteger(resName)INTEGERgetInteger(id)int
getLayout(resName)LAYOUTgetLayout(id)XmlResourceParser
getMenu(resName)MENUgetLayout(id)XmlResourceParser
getMipmap(resName)MIPMAPgetDrawable(id)Drawable
getMipmap(resName, theme)MIPMAPgetDrawable(id, theme)Drawable
getRaw(resName)RAWopenRawResource(id)InputStream
getIntArray(resName)ARRAYgetIntArray(id)int[]
getStringArray(resName)ARRAYgetStringArray(id)String[]
getString(resName)STRINGgetString(id)String
getXml(resName)XMLgetXml(id)XmlResourceParser

Dua baris terakhir pada tabel itu layak diperhatikan karena nama metode tidak selalu sama dengan tipe resource yang dibaca.

⚠️ getMenu tidak memanggil getXml maupun metode khusus menu. Ia memakai Resources.getSystem().getLayout(...) terhadap id dari konstanta MENU (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:202-204). getMipmap juga membaca lewat getDrawable, bukan metode khusus mipmap (resources-helper/src/main/java/com/anggrayudi/hiddenapi/InternalAccessor.java:211-214). 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-367).

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-507). 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-518). 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-93). 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-149). Pemakaian Builder masih tersimpan sebagai contoh yang dikomentari pada berkas yang sama (sample/src/main/java/com/anggrayudi/hiddenapi/sample/MainActivity.kt:151-164).

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-286). 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-339). Keduanya mengembalikan false saat kelas tidak ditemukan, bukan melempar exception.