Android 9 Pie და Android 10 ავრცელებენ გაფრთხილებებს ან პირდაპირ ბლოკავს წვდომას ფარულ API-ებზე. აი, როგორ შეუძლიათ დეველოპერებს გადალახონ ფარული API შეზღუდვები.
Flashback ერთი წლის წინანდელი და ჩვენ ყველანი აღფრთოვანებულნი ვართ იმის ხილვით, თუ რა გველოდება Android P-ის ბეტა ვერსიაში. მომხმარებლები მოუთმენლად ელიან ახალ ფუნქციებს, ხოლო დეველოპერები მოუთმენლად ელიან ახალ ინსტრუმენტებს მათი აპლიკაციების გასაუმჯობესებლად. სამწუხაროდ ზოგიერთი ამ დეველოპერისთვის, პირველი Android P ბეტა მოვიდა ცოტა საზიზღარი სიურპრიზით: ფარული API შეზღუდვები. სანამ ჩავუღრმავდები კონკრეტულად რას ნიშნავს ეს, ნება მომეცით აგიხსნათ მისი კონტექსტის შესახებ.
რა არის ეს ყველაფერი?
Android აპლიკაციის შემქმნელებს არ უწევთ ნულიდან დაწყება აპის შექმნისას. Google გთავაზობთ ინსტრუმენტებს აპების შემუშავების გასაადვილებლად და ნაკლებად განმეორებადი. ერთ-ერთი ასეთი ინსტრუმენტი არის Android SDK. SDK არსებითად არის მინიშნება ყველა იმ ფუნქციის შესახებ, რომელსაც დეველოპერებს შეუძლიათ უსაფრთხოდ გამოიყენონ თავიანთ აპებში. ეს ფუნქციები სტანდარტულია Android-ის ყველა ვარიანტზე, რომელსაც Google ამტკიცებს. თუმცა, SDK არ არის ამომწურავი. Android-ის ჩარჩოს საკმაოდ "ფარული" ნაწილია, რომლებიც არ არის SDK-ის ნაწილი.
ეს "დამალული" ნაწილები შეიძლება წარმოუდგენლად სასარგებლო იყოს უფრო ჰაკიური ან დაბალი დონის ნივთებისთვის. მაგალითად, ჩემი ვიჯეტის უჯრის აპლიკაცია იყენებს არა SDK ფუნქციას, რათა აღმოაჩინოს, როდის გაუშვებს მომხმარებელი აპს ვიჯეტიდან, რათა უჯრა ავტომატურად დაიხუროს. შეიძლება ფიქრობთ: "რატომ არ გახადოთ ეს არა-SDK ფუნქციები SDK-ის ნაწილად?" ისე, პრობლემა ის არის, რომ მათი ოპერაცია არ არის სრულად პროგნოზირებადი. Google ვერ დარწმუნდება, რომ ჩარჩოს თითოეული ნაწილი მუშაობს თითოეულ მოწყობილობაზე, რომელიც მას ამტკიცებს, ამიტომ უფრო მნიშვნელოვანი მეთოდებია არჩეული დასადასტურებლად. Google არ იძლევა გარანტიას, რომ დანარჩენი ჩარჩო დარჩება თანმიმდევრული. მწარმოებლებს შეუძლიათ შეცვალონ ან მთლიანად წაშალონ ეს ფარული ფუნქციები. AOSP-ის სხვადასხვა ვერსიებშიც კი, თქვენ არასოდეს იცით, ფარული ფუნქცია მაინც იარსებებს ან იმუშავებს ისე, როგორც ადრე.
თუ გაინტერესებთ, რატომ ვიყენებ სიტყვას "დამალული", ეს იმიტომ ხდება, რომ ეს ფუნქციები SDK-ის ნაწილიც კი არ არის. ჩვეულებრივ, თუ თქვენ ცდილობთ გამოიყენოთ ფარული მეთოდი ან კლასი აპში, ის ვერ მოხერხდება. მათი გამოყენება მოითხოვს ანარეკლი ან შეცვლილი SDK.
Android P-ით Google-მა გადაწყვიტა, რომ მათი დამალვა საკმარისი არ იყო. როდესაც პირველი ბეტა გამოვიდა, გამოცხადდა, რომ ფარული ფუნქციების უმეტესობა (არა ყველა) აღარ იყო ხელმისაწვდომი აპის დეველოპერებისთვის გამოსაყენებლად. პირველი ბეტა გაფრთხილებთ, როდესაც თქვენი აპლიკაცია იყენებდა შავ სიაში არსებულ ფუნქციას. შემდეგმა ბეტამა უბრალოდ გააფუჭა თქვენი აპლიკაცია. ყოველ შემთხვევაში ჩემთვის, ეს შავი სია საკმაოდ შემაშფოთებელი იყო. არა მხოლოდ საკმაოდ დაარღვია ნავიგაციის ჟესტები, მაგრამ იმის გამო, რომ ფარული ფუნქციები ნაგულისხმევად არის შავ სიაში (გუგლს ხელით უწევს თეთრ სიაში რამდენიმე გამოშვების სიაში), ვიჯეტის უჯრის ამუშავება ძალიან გამიჭირდა.
ახლა არსებობდა რამდენიმე გზა შავი სიის გარშემო მუშაობისთვის. პირველი იყო თქვენი აპლიკაციის უბრალოდ API 27-ის (Android 8.1) მიზნობრივი შენარჩუნება, რადგან შავი სია მხოლოდ უახლეს API-ზე გათვლილ აპებზე ვრცელდება. სამწუხაროდ, Google-თან ერთად მინიმალური API მოთხოვნები Play Store-ზე გამოსაქვეყნებლად, ამდენი ხნის განმავლობაში მხოლოდ API 27-ის დამიზნება იქნება შესაძლებელი. 2019 წლის 1 ნოემბრის მდგომარეობით, Play Store-ის აპების ყველა განახლება უნდა იყოს მიმართული API 28 ან უფრო ახალი.
მეორე გამოსავალი არის ის, რაც Google-მა ჩაუშვა Android-ში. შესაძლებელია ADB ბრძანების გაშვება შავი სიის მთლიანად გამორთვისთვის. ეს შესანიშნავია პირადი გამოყენებისა და ტესტირებისთვის, მაგრამ მე შემიძლია პირდაპირ გითხრათ, რომ საბოლოო მომხმარებლების მიერ ADB-ის დაყენებისა და გაშვების მცდელობა კოშმარია.
მაშ სად გვტოვებს ეს? ჩვენ აღარ შეგვიძლია მივმართოთ API 27-ს, თუ გვსურს ატვირთვა Play Store-ში და ADB მეთოდი უბრალოდ არ არის სიცოცხლისუნარიანი. თუმცა, ეს არ ნიშნავს, რომ არჩევანის გარეშე ვართ.
დამალული API შავი სია ვრცელდება მხოლოდ თეთრ სიაში მოხვედრილ მომხმარებლის აპლიკაციებზე. სისტემური აპლიკაციები, პლატფორმის ხელმოწერით ხელმოწერილი აპლიკაციები და კონფიგურაციის ფაილში მითითებული აპლიკაციები გათავისუფლებულია შავი სიიდან. სასაცილოა, Google Play სერვისების კომპლექტი ყველა მითითებულია ამ კონფიგურაციის ფაილში. ვფიქრობ, გუგლი ჩვენზე უკეთესია.
ყოველ შემთხვევაში, გავაგრძელოთ საუბარი შავ სიაზე. ნაწილი, რომელიც ჩვენ დღეს გვაინტერესებს არის ის, რომ სისტემური აპლიკაციები გათავისუფლებულია. ეს ნიშნავს, რომ, მაგალითად, სისტემის ინტერფეისის აპს შეუძლია გამოიყენოს ყველა ფარული ფუნქცია, რომელიც მას სურს, რადგან ის სისტემის დანაყოფზეა. ცხადია, ჩვენ არ შეგვიძლია უბრალოდ გადავიტანოთ აპლიკაცია სისტემის დანაყოფზე. ამას სჭირდება root, კარგი ფაილის მენეჯერი, ნებართვების ცოდნა... უფრო ადვილი იქნება ADB-ის გამოყენება. ეს არ არის ერთადერთი გზა, რომ ჩვენ შეგვიძლია ვიყოთ სისტემური აპლიკაცია, თუმცა, ყოველ შემთხვევაში, ფარული API შავ სიაში.
გადაიტანეთ თქვენი გონება შვიდი აბზაცის წინ, როდესაც მე ვახსენე ასახვა. თუ არ იცით რა არის ასახვა, გირჩევთ წაიკითხოთ ეს გვერდი, მაგრამ აქ არის სწრაფი შეჯამება. Java-ში ასახვა არის ნორმალურად მიუწვდომელ კლასებზე, მეთოდებსა და ველებზე წვდომის საშუალება. ეს წარმოუდგენლად ძლიერი ინსტრუმენტია. როგორც ამ აბზაცში ვთქვი, ასახვა იყო არა SDK ფუნქციებზე წვდომის საშუალება. API შავი სია ბლოკავს ასახვის გამოყენებას, მაგრამ არ ბლოკავს ორმაგი- ანარეკლი.
ახლა, აი, სად ხდება ცოტა უცნაური. ჩვეულებრივ, თუ გსურთ ფარული მეთოდის გამოძახება, გააკეთებდით ასეთ რამეს (ეს არის კოტლინში, მაგრამ Java მსგავსია):
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 შავი სიის ახლა. ეს ცოტა უხერხულია, მაგრამ ჩვენ შეგვიძლია დავწეროთ wrapper ფუნქცია, რათა არ მოგვიწიოს ყოველ ჯერზე ორმაგი ასახვა. ეს არ არის კარგი, მაგრამ ეს უკეთესია, ვიდრე არაფერი. მაგრამ რეალურად, ჩვენ არ დასრულებულა. არსებობს ამის უკეთესი გზა, რომელიც მოგვცემს საშუალებას გამოვიყენოთ ნორმალური ასახვა ან შეცვლილი 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;
.
ახლა ჩვენ რეალურად დავასრულეთ. შექმენით პერსონალური აპლიკაციის კლასი, ჩადეთ ეს კოდი მასში onCreate()
მეთოდი და ბამ, აღარ არის შეზღუდვები.
მადლობა XDA Member weishu-ს, VirtualXposed-ისა და Taichi-ის შემქმნელს, ამ მეთოდის თავდაპირველად აღმოჩენისთვის. ჩვენ ასევე გვინდა მადლობა გადავუხადოთ XDA Recognized Developer topjohnwu-ს ამ გამოსავლის შესახებ მითითებისთვის. აქ არის ცოტა მეტი იმის შესახებ, თუ როგორ მუშაობს, თუმცა ჩინურ ენაზეა. მე ასევე ამის შესახებ Stack Overflow-ზე დაწერა, მაგალითად JNI-შიც.