Dezynsekcja w praktyce
Od dobrych paru tygodni mam jakiś wstręt do androidowych wnętrzności. Po części to efekt presji rzeczywistości, po części – jakaś głupia blokada. Waham się czy przejść już na kolejną wersję Androida (Gingerbread czyli 2.3) czy pozostać z FroYo. Jednak odłóżmy rozterki 'młodego’ wyrobnika na bok i zajmijmy się tępieniem robali czyli dezynsekcją właśnie.
Chyba przedostatnia wersja Androidowego ROMu Laszlo opublikowana na forum XDA miała dość ciekawy błąd mianowicie nie widziała aplikacji zainstalowanych na partycji Ext.
Dorabiając z Fireratem natywną obsługę SdExt prawie w ogóle nie zwracaliśmy uwagi na to czy stosowna partycja karty w ogóle istnieje (o podmontowaniu jej we właściwym miejscu nawet nie wspomnę). Ubocznym efektem (i, do pewnego stopnia, powodem moich programistycznych wypocin) był fakt, że telefon potrafił wpadać w bootloop (czyli w kółko się restartował).
Pierwszym krokiem było zmodyfikowanie Volda (VOLume Daemon – usługa systemowa zarządzająca 'dyskami’), tak by montował odpowiednią partycję pod /sd-ext i sprzedał informację o sukcesie (lub porażce) reszcie systemu. Jak już umiał przekazać informację to trzeba było sprawić by framework wiedział co z tą informacją zrobić i tak powstała funkcja o nazwie getSdExtState(). Tak prosta, że nie sposób popełnić w niej błędu. Od razu też wykorzystałem ją do sprawdzania dostępności /sd-ext przed próbą skanowania zainstalowanych tam aplikacji przy starcie systemu. Kawałek kodu tak trywialny, że nawet nie przyszło mi do głowy jakieś większe testowanie.
if (Environment.getSdExtState().equals(Environment.MEDIA_MOUNTED)) { scanDirLI(mSdExtInstallDir, PackageParser.PARSE_ON_SDEXT, scanMode); }
Jak podmontowane to skanuj, jak nie – to nie. Prawda, że proste? W samym frameworku jest parę newralgicznych miejsc (związanych z instalacją aplikacji) w których użyłem identycznego testu. Świat stał się lepszym miejscem :-) a ja mogłem się zająć dorabianiem informacji o SdExt w Ustawieniach czy czymś takim. Mniej więcej w tym momencie Laszlo zapewne zsynchronizował swoje źródła z moimi i wypuścił na świat kolejną wersję swojego ROMu. Chyba w sobotę Firerat dał mi znać na Twitterze, że ludzie mają kłopoty z Apps2SdExt na najnowszym FbL. Zerknąłem na forum i, na podstawie zamieszczonych tam postów, doszedłem do wniosku, że dochodzi do tzw race condition. Vold działa asynchronicznie względem reszty systemu i w czasie gdy jeszcze zajmuje się sprawdzaniem systemów plikowych uruchamiane są pozostałe komponenty systemu takie jak usługa PackageManagera. To ona właśnie przeprowadza początkowy skan katalogów z aplikacjami. Skoro nie widać aplikacji zainstalowanych na SdExt to (patrząc na powyższy kawałek kodu) getSdExtState zwraca coś innego niż MEDIA_MOUNTED i system nawet nie próbuje zajrzeć do /sd-ext/app. Wniosek z tego jest taki, że Vold jeszcze nie zdążył pozałatwiać swoich spraw z /sd-ext. Problem znany zatem teraz wystarczy znaleźć rozwiązanie. Na szybko Firerat przekompilował framework.jar z wywalonym sprawdzaniem getSdExtState(), wrzucił na XDA i 'rozwiązał’ problem (to był akurat weekend i, zdaje się, byłem po jakimś większym maratonie Singstara w towarzystwie sąsiadów i paru butelek Dark Whisky :-)).
To, co zrobił Firerat było jednak tylko obejściem problemu a nie jego rozwiązaniem. Poczytałem jeszcze raz posty na forum, popsioczyłem, że nikt z pomstujących nie zdobył się na złapanie logów ze startu systemu i doszedłem do identycznego wniosku jak wcześniej: Race condition. Na swoim telefonie nie miałem problemu z brakiem aplikacji z SdExt ale też miałem wtedy zainstalowaną wersję bardzo rozwojową (czyt: powiązaną sznureczkiem i podpartą patyczkiem z każdej strony). Złapałem logi z restartu swojego telefonu i patrzyłem w nie jak sroka w gnat. No nijak nie było szans na race condition. Zgodnie z tym co widziałem w logach /sd-ext było już dawno podmontowane. Przecież nie da się zrobić błędu w 6 linijkach kodu z jakich składa się getSdExt():
/** * Gets the current state of SD-Ext * Note: this call should be deprecated as it doesn't support * multiple volumes. */ public static String getSdExtState() { try { if (mMntSvc == null) { mMntSvc = IMountService.Stub.asInterface(ServiceManager.getService("mount")); } return mMntSvc.getVolumeState(getSdExtDirectory().toString()); } catch (Exception rex) { return Environment.MEDIA_REMOVED; } }
Poza tym w innych miejscach kodu funkcja ta działa perfekcyjnie. Tak się zafiksowałem na tym race condition, że dobre dwa dni nad tym spędziłem. Albo raczej zmarnowałem. Zmarnowałem bo z całego logu (około 150+ KB tekstu) skoncentrowałem się wyłącznie na części związanej ze PackageManagerem i skanowaniem /sd-ext/app. W końcu mój umysł zaczął rejestrować inne linijki logu i wyłapał coś takiego:
I/sysproc ( 147): System server: starting Android runtime. I/sysproc ( 147): System server: starting Android services. I/SystemServer( 147): Entered the Android system server! I/sysproc ( 147): System server: entering thread pool. I/SystemServer( 147): Entropy Service I/SystemServer( 147): Power Manager I/SystemServer( 147): Activity Manager I/ActivityManager( 147): Memory class: 16 D/dalvikvm( 147): GC_FOR_MALLOC freed 641 objects / 80848 bytes in 138ms D/dalvikvm( 147): GC_FOR_MALLOC freed 2131 objects / 317920 bytes in 159ms I/SystemServer( 147): Telephony Registry I/SystemServer( 147): Package Manager D/dalvikvm( 147): GC_FOR_MALLOC freed 5971 objects / 222640 bytes in 181ms I/dalvikvm( 147): DexOpt: source file mod time mismatch (3e4c9ef4 vs 3e618cf4) D/installd( 101): DexInv: --- BEGIN '/system/framework/android.test.runner.jar' --- D/dalvikvm( 163): DexOpt: load 111ms, verify 722ms, opt 13ms
Kilkanaście linijek niżej pojawia się:
I/PackageManager( 147): Pruning dalvik file: sd-ext@[email protected]@classes.dex I/PackageManager( 147): Pruning dalvik file: sd-ext@[email protected]@classes.dex D/PackageManager( 147): Scanning app dir /system/framework D/PackageManager( 147): Scanning app dir /system/app D/dalvikvm( 147): GC_FOR_MALLOC freed 4987 objects / 280408 bytes in 193ms W/PackageParser( 147): No actions in intent filter at /system/app/Bluetooth.apk Binary XML file line #124 I/PackageManager( 147): /system/app/CMParts.apk changed; collecting certs
I jeszcze kilkadziesiąt linijek niżej:
D/dalvikvm( 147): GC_EXPLICIT freed 7597 objects / 563168 bytes in 221ms I/SystemServer( 147): Account Manager I/SystemServer( 147): Content Manager I/SystemServer( 147): System Content Providers I/SystemServer( 147): Battery Service I/SystemServer( 147): Lights Service I/SystemServer( 147): Vibrator Service I/SystemServer( 147): Alarm Manager D/AlarmManagerService( 147): Kernel timezone updated to -60 minutes west of GMT I/SystemServer( 147): Init Watchdog I/SystemServer( 147): Sensor Service I/SystemServer( 147): Window Manager I/SystemServer( 147): Bluetooth Service I/SystemServer( 147): Device Policy I/bluedroid( 147): Starting hciattach daemon I/SystemServer( 147): Status Bar I/SystemServer( 147): Clipboard Service I/SystemServer( 147): Input Method Service I/SystemServer( 147): NetStat Service I/SystemServer( 147): NetworkManagement Service I/SystemServer( 147): Connectivity Service V/ConnectivityService( 147): ConnectivityService starting up D/ConnectivityService( 147): getMobileDataEnabled returning true V/ConnectivityService( 147): Starting Wifi Service. I/WifiService( 147): WifiService starting up with Wi-Fi disabled D/Tethering( 147): Tethering starting D/NetworkManagmentService( 147): Registering observer I/bluedroid( 147): Starting bluetoothd deamon I/SystemServer( 147): Throttle Service I/SystemServer( 147): Accessibility Manager I/SystemServer( 147): Mount Service I/SystemServer( 147): Notification Manager
Widać?
Nie widać?? Hmmm… A teraz?
I/SystemServer( 147): Entropy Service I/SystemServer( 147): Power Manager I/SystemServer( 147): Activity Manager I/SystemServer( 147): Telephony Registry I/SystemServer( 147): Package Manager I/SystemServer( 147): Account Manager I/SystemServer( 147): Content Manager I/SystemServer( 147): System Content Providers I/SystemServer( 147): Battery Service I/SystemServer( 147): Lights Service I/SystemServer( 147): Vibrator Service I/SystemServer( 147): Alarm Manager I/SystemServer( 147): Init Watchdog I/SystemServer( 147): Sensor Service I/SystemServer( 147): Window Manager I/SystemServer( 147): Bluetooth Service I/SystemServer( 147): Device Policy I/SystemServer( 147): Status Bar I/SystemServer( 147): Clipboard Service I/SystemServer( 147): Input Method Service I/SystemServer( 147): NetStat Service I/SystemServer( 147): NetworkManagement Service I/SystemServer( 147): Connectivity Service I/SystemServer( 147): Throttle Service I/SystemServer( 147): Accessibility Manager I/SystemServer( 147): Mount Service
Jeszcze nie widać? No, dobra. Widzisz kiedy jest startowany Mount Service? A teraz zobacz gdzie jest Package Manager. Między jednym a drugim wpisem mija dobrych kilkanaście – kilkadziesiąt sekund. W tym czasie skanowane są aplikacje systemowe (/system/app) i użytkownika (/data/app i ewentualnie /sd-ext/app). Poprawność działania getSdExtState() jest uzależniona od działania Mount Service a w chwili próby skanowania /sd-ext/app ta usługa jeszcze nie działa. I to właśnie było źródło problemu :-)
W systemie Android część usług została zaliczona do usług krytycznych (w powyższym wyciągu z logów od Entropy Service do Bluetooth Service). Na moje nieszczęście Mount Service nie trafił do tej kategorii. Z punktu widzenia twórców Androida nie było takiej potrzeby. Usługa ta jest potrzebna tylko do dostępu do kart pamięci. Zmiana kategorii tej usługi nie jest za bardzo możliwa bez potężnej ingerencji w nią samą i szereg innych komponentów a to oznacza więcej potencjalnych bugów. Najprostszym rozwiązaniem było porozmawianie (prawie) bezpośrednio z Voldem. Ubocznym skutkiem jest potencjalne wydłużenie startu systemu o maksymalnie 20 sekund. W skrajnej sytuacji może się okazać, że i tak aplikacje zainstalowane na SdExt się nie pokażą (w przypadku kłopotów z filesystemem). Wygląda to mniej więcej tak. Zdaję sobie sprawę z braków tego rozwiązania. Właściwie jest to tylko nieco hakerskie obejście. Prawidłowym rozwiązaniem jest skanowanie aplikacji z SdExt już po starcie systemu, tak jak to jest robione z aplikacjami zainstalowanymi na partycji FAT karty. Mam już nawet mgliste pojęcie jak to zrobić :-) Jako bonus dostaniecie wtedy możliwość odmontowania i wyjęcia karty pamięci bez wyłączania telefonu :-) Cieszycie się?
Mam nadzieję że wszystko się powiedzie. Nie zapominajcie o G1 chłopaki ;]
Powodzenia i fajnie się czytało :-). W moim telefonie i tak aby wyjąć kartę, trzeba wyciągnąć baterię… więc nic nie pomoże :-))). No i na razie zostaję przy standardowych romach, ale to dla mnie „normalne”, mój typ tak ma że jak już coś skonfiguruję to lubię żeby sobie działało samo z siebie bez dłubania. Żeby się pobawić w eksperymenty musiałbym mieć 2 telefony :), tak aby przynajmniej jeden działał w każdym momencie :-). Ale chylę głowę przed twoimi eksperymentami, dobra robota – może kiedyś będę miał 2 telefony :-)
Ja tam mogę się ustosunkować tylko do pierwszej części :) . Gdy pierwszy raz wgrałem nowy system po rocie był już 2.2 froyo, i tak jakoś z nim żyłem, gdy zrobiło się nudno zainteresowałem się chińskimi romami, tam odkryłem 2.1 ;). Ale znając sytuacje z 1.6 ( gdzie połowa aplikacji już nie chce działać, to 2.1 i 2.2 też tak skończą – chyba). Wersje systemu 2.3 na G1działają coraz lepiej, więc trzymam kciuki za to że się za nie weźmiesz. Jeżeli tak to poproszę równolegle pokrojoną wersje, bez usług google:). Gdy kupie tablecik. pozwoli mi to z G1 zrobić zwykły telefon ( no z dodatkami ) Mam nadzieje Że dorówna Sony E. k800i :)