Android 9 Pie ו-Android 10 זורקים אזהרות או חוסמים באופן מוחלט גישה לממשקי API נסתרים. הנה איך מפתחים יכולים לעקוף את מגבלות ה-API הנסתרות.
פלאשבק מלפני יותר משנה, וכולנו נרגשים לראות מה צפוי בגרסת הבטא של Android P. משתמשים מצפים לתכונות חדשות, ומפתחים מצפים לכמה כלים חדשים כדי לשפר את האפליקציות שלהם. לרוע המזל עבור חלק מאותם מפתחים, גרסת הבטא הראשונה של אנדרואיד P הגיעה עם קצת הפתעה לא נעימה: הגבלות API נסתרות. לפני שאני צולל למה בדיוק זה אומר, הרשו לי להסביר קצת על ההקשר שלו.
על מה כל זה?
מפתחי אפליקציות אנדרואיד לא צריכים להתחיל מאפס כשהם יוצרים אפליקציה. גוגל מספקת כלים כדי להפוך את פיתוח האפליקציות לקל יותר ופחות חוזר על עצמו. אחד מהכלים הללו הוא ה-SDK של אנדרואיד. ה-SDK הוא בעצם התייחסות לכל הפונקציות שמפתחים יכולים להשתמש בבטחה באפליקציות שלהם. פונקציות אלו מגיעות כסטנדרט בכל גרסאות האנדרואיד ש-Google מאשרת. עם זאת, ה-SDK אינו ממצה. ישנם לא מעט חלקים "חבויים" של המסגרת של אנדרואיד שאינם חלק מה-SDK.
החלקים ה"מוסתרים" האלה יכולים להיות שימושיים להפליא עבור דברים פריקים יותר או ברמה נמוכה יותר. למשל, שלי
אפליקציית מגירת יישומונים עושה שימוש בפונקציה שאינה SDK כדי לזהות מתי משתמש מפעיל אפליקציה מווידג'ט כך שהמגירה יכולה להיסגר אוטומטית. אולי אתה חושב: "למה לא פשוט להפוך את הפונקציות האלה שאינן SDK לחלק מה-SDK?" ובכן, הבעיה היא שהפעולה שלהם לא ניתנת לחיזוי לחלוטין. גוגל לא יכולה לוודא שכל חלק של המסגרת עובד על כל מכשיר שהיא מאשרת, ולכן נבחרות שיטות חשובות יותר לאימות. Google לא מבטיחה ששאר המסגרת תישאר עקבית. יצרנים יכולים לשנות או להסיר לחלוטין את הפונקציות הנסתרות הללו. אפילו בגרסאות שונות של AOSP, אתה אף פעם לא יודע אם פונקציה נסתרת עדיין קיימת או תעבוד כמו פעם.אם אתה תוהה מדוע השתמשתי במילה "מוסתר", זה בגלל שהפונקציות האלה אפילו לא חלק מה-SDK. בדרך כלל, אם תנסה להשתמש בשיטה נסתרת או במחלקה באפליקציה, היא לא תצליח להדר. השימוש בהם דורש הִשׁתַקְפוּת או SDK שונה.
עם אנדרואיד P, גוגל החליטה שעצם הסתרתם לא מספיקה. כשהגרסת הבטא הראשונה שוחררה, זה הוכרז רוב (לא כל) הפונקציות הנסתרות כבר לא היו זמינות לשימוש למפתחי אפליקציות. הבטא הראשונה תזהיר אותך כאשר האפליקציה שלך השתמשה בפונקציה ברשימה השחורה. הבטא הבאות פשוט הרס את האפליקציה שלך. לפחות עבורי, הרשימה השחורה הזו הייתה די מעצבנת. לא רק שזה נשבר לא מעט מחוות ניווט, אבל מכיוון שפונקציות נסתרות נמצאות ברשימה השחורה כברירת מחדל (גוגל צריכה לרשום ידנית חלק מההפצה לכל מהדורה), התקשיתי מאוד לגרום למגירת ה-Widget Drawer לעבוד.
כעת, היו כמה דרכים לעקוף את הרשימה השחורה. הראשון היה פשוט לשמור על API 27 למיקוד האפליקציה שלך (אנדרואיד 8.1), מכיוון שהרשימה השחורה חלה רק על אפליקציות המתמקדות ב-API העדכני ביותר. למרבה הצער, עם זה של גוגל דרישות API מינימליות לפרסום בחנות Play, ניתן יהיה למקד רק ל-API 27 למשך זמן כה רב. החל מה-1 בנובמבר 2019, כל עדכוני האפליקציה לחנות Play חייבים למקד ל-API 28 ואילך.
הפתרון השני הוא למעשה משהו שגוגל בנו באנדרואיד. אפשר להפעיל פקודת ADB כדי להשבית את הרשימה השחורה לחלוטין. זה נהדר לשימוש אישי ולבדיקות, אבל אני יכול להגיד לך ממקור ראשון שהניסיון לגרום למשתמשי קצה להגדיר ולהפעיל את ADB הוא סיוט.
אז איפה זה משאיר אותנו? אנחנו לא יכולים למקד יותר ל-API 27 אם אנחנו רוצים להעלות לחנות Play, ושיטת ADB פשוט לא קיימת. זה לא אומר שנגמרו לנו האפשרויות.
הרשימה השחורה הנסתרת של ממשק API חלה רק על יישומי משתמשים שאינם ברשימה הלבנה. יישומי מערכת, יישומים חתומים בחתימת הפלטפורמה ויישומים המצוינים בקובץ תצורה כולם פטורים מהרשימה השחורה. באופן מצחיק למדי, חבילת שירותי Google Play מצוינת כולן בקובץ התצורה הזה. אני מניח שגוגל טובה יותר מכולנו.
בכל מקרה, בואו נמשיך לדבר על הרשימה השחורה. החלק שאנו מעוניינים בו היום הוא שיישומי מערכת פטורים. זה אומר, למשל, שאפליקציית ממשק המשתמש של המערכת יכולה להשתמש בכל הפונקציות הנסתרות שהיא רוצה מכיוון שהיא נמצאת במחיצת המערכת. ברור, אנחנו לא יכולים פשוט לדחוף אפליקציה למחיצת המערכת. זה צריך שורש, מנהל קבצים טוב, ידע בהרשאות... יהיה קל יותר להשתמש ב-ADB. עם זאת, זו לא הדרך היחידה שבה אנחנו יכולים להיות אפליקציית מערכת, לפחות בכל הנוגע לרשימה השחורה של ה-API הנסתרת.
החזר את דעתך לשבע פסקאות, כאשר הזכרתי את השתקפות. אם אתה לא יודע מה זה השתקפות, אני ממליץ לקרוא הדף הזה, אבל הנה סיכום קצר. ב-Java, השתקפות היא דרך לגשת למחלקות, שיטות ושדות שאינם נגישים בדרך כלל. זה כלי חזק להפליא. כמו שאמרתי בפסקה ההיא, השתקפות הייתה פעם דרך לגשת לפונקציות שאינן SDK. הרשימה השחורה של ה-API חוסמת את השימוש בהשתקפות, אבל היא לא חוסמת את השימוש ב לְהַכפִּיל-הִשׁתַקְפוּת.
עכשיו, כאן זה נהיה קצת מוזר. בדרך כלל, אם תרצה לקרוא לשיטה נסתרת, היית עושה משהו כזה (זה בקוטלין, אבל ג'אווה דומה):
val someHiddenClass = Class.forName("android.some.hidden.Class")
val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java)
someHiddenMethod.invoke(null, "some important string")
עם זאת, הודות לרשימה השחורה של API, תקבל רק ClassNotFoundException. עם זאת, אם תחשוב פעמיים, זה עובד מצוין:
val forName = Class::class.java.getMethod("forName", String:: class.java)
val getMethod = Class::class.java.getMethod("getMethod", String:: class.java, arrayOf>()::class.java)
val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") asClass
val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java)
someHiddenMethod.invoke(null, "some important string")
מוזר נכון? ובכן, כן, אבל גם לא. הרשימה השחורה של API עוקבת אחר מי שקורא לפונקציה. אם המקור אינו פטור, הוא קורס. בדוגמה הראשונה, המקור הוא האפליקציה. עם זאת, בדוגמה השנייה, המקור הוא המערכת עצמה. במקום להשתמש בהשתקפות כדי להשיג את מה שאנחנו רוצים ישירות, אנחנו משתמשים בה כדי לומר למערכת להשיג את מה שאנחנו רוצים. מכיוון שמקור הקריאה לפונקציה הנסתרת הוא המערכת, הרשימה השחורה לא משפיעה עלינו יותר.
אז סיימנו. יש לנו דרך לעקוף את הרשימה השחורה של API עכשיו. זה קצת מגושם, אבל נוכל לכתוב פונקציית עטיפה כדי שלא נצטרך להרהר כפול בכל פעם. זה לא נהדר, אבל זה עדיף מכלום. אבל למעשה, לא סיימנו. יש דרך טובה יותר לעשות זאת שתאפשר לנו להשתמש בהשתקפות רגילה או ב-SDK שונה, כמו בימים הטובים.
מכיוון שהאכיפה של הרשימה השחורה מוערכת בכל תהליך (שזה זהה לכל אפליקציה ברוב המקרים), ייתכן שיש דרך כלשהי עבור המערכת לתעד אם האפליקציה המדוברת פטורה או לא. למזלנו, יש, והוא נגיש לנו. באמצעות הפריצה החדשה של השתקפות כפולה, יש לנו בלוק קוד חד פעמי:
val forName = Class::class.java.getDeclaredMethod("forName", String:: class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String:: class.java, arrayOf>()::class.java) val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") asClass
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) asMethodval vmRuntime = getRuntime.invoke(null)
setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))
אוקיי, אז מבחינה טכנית, זה לא אומר למערכת שהאפליקציה שלנו פטורה מהרשימה השחורה של API. למעשה יש עוד פקודת ADB שאתה יכול להפעיל כדי לציין פונקציות שאסור להכניס לרשימה השחורה. זה מה שאנחנו מנצלים למעלה. הקוד בעצם עוקף את כל מה שהמערכת חושבת שהוא פטור עבור האפליקציה שלנו. מעבר "L" בסוף אומר שכל השיטות פטורות. אם אתה רוצה לפטור שיטות ספציפיות, השתמש בתחביר Smali: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;
.
עכשיו בעצם סיימנו. צור מחלקה מותאמת אישית של Application, שים את הקוד הזה ב- onCreate()
שיטה, ובאם, אין יותר הגבלות.
תודה לחבר XDA weishu, המפתח של VirtualXposed ו-Taichi, על שגילה במקור את השיטה הזו. כמו כן, ברצוננו להודות ל-XDA Recognized Developer topjohnwu על שהצביעה בפניי על פתרון זה. הנה קצת יותר על איך זה עובד, למרות שזה בסינית. אני גם כתב על כך ב-Stack Overflow, עם דוגמה גם ב-JNI.