PMS 初始化时抛出 installd 空指针问题分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PMS 初始化时抛出 installd 空指针问题分析相关的知识,希望对你有一定的参考价值。

参考技术A https://xianzhu21.space/developer/installd_null_in_pms_constructor/

0. 错误 log

1. SystemServer 连接 installd

2. 初始化 PackageManagerService

3. Boot complete

4. 分析问题原因

5. 总结

0. 错误 log

android 8.1 系统。

1. E PackageManager: Failed to create app datafor com.xxxx.iot.service, but trying to recover: com.android.server.pm.Installer$InstallerException: java.lang.NullPointerException: Attempt to invoke interface method'long android.os.IInstalld.createAppData(java.lang.String, java.lang.String, int, int, int, java.lang.String, int)'on a null object reference2. W PackageManager: com.android.server.pm.Installer$InstallerException: java.lang.NullPointerException: Attempt to invoke interface method'void android.os.IInstalld.destroyAppData(java.lang.String, java.lang.String, int, int, long)'on a null object reference3. D PackageManager: Recovery failed!4. W PackageManager: Failed to migrate com.xxxx.iot.service: java.lang.NullPointerException: Attempt to invoke interface method'void android.os.IInstalld.migrateAppData(java.lang.String, java.lang.String, int, int)'on a null object reference

1. SystemServer 连接 installd

从 log 中看出异常原因是调用 IInstalld 方法的时候抛出了空指针异常,IInstalld 其实就是 installd 在 SystemServer 中的代理,Java 层对应的 Service 是 Installer,在 systemServer 的 startBootstrapServices() 方法中连接 installd。

// frameworks/base/services/java/com/android/server/SystemServer.java/**

* Starts the small tangle of critical services that are needed to get

* the system off the ground.  These services have complex mutual dependencies

* which is why we initialize them all in one place here.  Unless your service

* is also entwined in these dependencies, it should be initialized in one of

* the other functions.

*/privatevoidstartBootstrapServices()...Installerinstaller=mSystemServiceManager.startService(Installer.class);mActivityManagerService=mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class).getService();mActivityManagerService.setSystemServiceManager(mSystemServiceManager);mActivityManagerService.setInstaller(installer);...

Android 8.0 之前 SystemServer 与 installd 是通过 socket 连接的,在连接的时候会阻塞等待 installd 连接成功。

// frameworks/base/core/java/com/android/internal/os/InstallerConnection.javapublicvoidwaitForConnection()for(;;)tryexecute("ping");return;catch(InstallerExceptionignored)Slog.w(TAG,"installd not ready");SystemClock.sleep(1000);

但是在 Android 8.0 把 installd 的连接方式改成了 binder,且不会阻塞等待 installd 连接成功,而是每隔一秒重试,直到连接成功。

// frameworks/base/services/core/java/com/android/server/pm/Installer.javaprivatevoidconnect()IBinderbinder=ServiceManager.getService("installd");if(binder!=null)trybinder.linkToDeath(newDeathRecipient()@OverridepublicvoidbinderDied()Slog.w(TAG,"installd died; reconnecting");connect();,0);catch(RemoteExceptione)binder=null;if(binder!=null)mInstalld=IInstalld.Stub.asInterface(binder);elseSlog.w(TAG,"installd not found; trying again");// 一秒后重试BackgroundThread.getHandler().postDelayed(()->connect();,DateUtils.SECOND_IN_MILLIS);

2. 初始化 PackageManagerService

PackageManagerService(下面称为 PMS)的初始化也是在 startBootstrapServices() 方法中,且是在创建 Installer 对象之后。调用 PMS 的静态方法 main() 进行初始化,参数传入了 Installer 对象。

// frameworks/base/services/java/com/android/server/SystemServer.javamPackageManagerService=PackageManagerService.main(mSystemContext,installer,mFactoryTestMode!=FactoryTest.FACTORY_TEST_OFF,mOnlyCore);

PMS 的初始化中会扫描 APK,并为应用创建私有目录。扫描 APK 时如果没有需要清除的 APK 则不会调用 installd,所以我们关注给应用创建私有目录的过程,它是调用 reconcileAppsDataLI() 方法。

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java// Prepare storage for system user really early during boot,// since core system apps like SettingsProvider and SystemUI// can't wait for user to startfinalintstorageFlags;if(StorageManager.isFileEncryptedNativeOrEmulated())storageFlags=StorageManager.FLAG_STORAGE_DE;elsestorageFlags=StorageManager.FLAG_STORAGE_DE|StorageManager.FLAG_STORAGE_CE;List<String>deferPackages=reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,UserHandle.USER_SYSTEM,storageFlags,true/* migrateAppData */,true/* onlyCoreApps */);

FLAG_STORAGE_CE 指的是 /data/user/0/,即 /data/data,该目录只能在解锁屏幕后才能访问。而 FLAG_STORAGE_DE 指的是 /data/user_de/0,该目录在有屏幕锁的时候也能访问,参考 Android 官网关于  DirectBoot  的介绍。

reconcileAppsDataLI() 方法中判断 ceDir 和 deDir 中的应用是否是已知的(参考 系统启动时 PMS 的扫描应用分析 )且已安装的,如果不是的话调用 installd 的 destroyAppData() 函数删除对应私有目录。

之后对于已安装的应用调用 prepareAppDataAndMigrateLIF() 方法。

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.javaif(ps.getInstalled(userId))prepareAppDataAndMigrateLIF(ps.pkg,userId,flags,migrateAppData);preparedCount++;

prepareAppDataAndMigrateLIF() 最终会调用 Installd 的 createAppData() 函数创建应用的私有目录。第一行 log 就是调用 createAppData() 时抛出的异常。就是说当调用 createAppData() 时 installd 还没连接成功,所以抛出了空指针异常。这样就不能创建私有目录(包括 CE 和 DE)。这不就是启动失败吗?没有创建应用私有目录,如果应用需要访问就会抛出 IO 异常。但是在两种情况下是没有问题的:一种是系统不是第一次启动,之前启动的时候已经创建好了私有目录;另一种是应用在 boot complete 之后才访问 CE 目录,且永不访问 DE 目录。

3. Boot complete

第一种情况比较好理解,因为之前已经创建好了已经安装的私有目录,所以没有什么问题。第二种情况换句话说在 boot complete 的时候会创建 CE 私有目录。

在 ActivityManagerService 的 finishBooting() 方法会调用 UserController 的 sendBootCompletedLocked() 方法。它会调用 finishUserBoot() 方法改变 UserState 的状态。

// frameworks/base/services/core/java/com/android/server/am/UserController.javaprivatevoidfinishUserBoot(UserStateuss,IIIntentReceiverresultTo)if(uss.setState(STATE_BOOTING,STATE_RUNNING_LOCKED))mInjector.getUserManagerInternal().setUserState(userid,uss.state);maybeUnlockUser(userId);

把状态改为 RUNNING_LOCKED 后尝试解锁用户,当设备没有 credential-encrypted storage 时会解锁成功。

/**

    * Attempt to unlock user without a credential token. This typically

    * succeeds when the device doesn't have credential-encrypted storage, or

    * when the the credential-encrypted storage isn't tied to a user-provided

    * PIN or pattern.

    */booleanmaybeUnlockUser(finalintuserId)// Try unlocking storage using empty tokenreturnunlockUserCleared(userId,null,null,null);

如果解锁成功则调用 finishUserUnlocking() 方法。它会把状态改为 RUNNING_UNLOCKING,并调用 UserManagerService 的 onBeforeUnlockUser() 方法。

// frameworks/base/services/core/java/com/android/server/am/UserController.javaprivatevoidfinishUserUnlocking(finalUserStateuss)if(uss.setState(STATE_RUNNING_LOCKED,STATE_RUNNING_UNLOCKED))mInjector.getUserManagerInternal().setUserState(userid,uss.state);proceedWithUnlock=true;if(proceedWithUnlock)mInjector.getUserManager().onBeforeUnlockUser(userId);

在 onBeforeUnlockUser() 方法中会调用 PMS 的 reconcileAppsData() 方法,传入参数是 FLAG_STORAGE_CE。这时会创建应用的 CE 私有目录。所以说,如果系统应用(三方应用不会在 boot complete 之前启动)在 boot complete 之前不访问 CE 目录,且永不访问 DE 目录则不会有问题。

其实 boot complete 之后 DE 目录也有创建的情况,那就是当切换到非 USER_SYSTEM 的时候。这时会调用 startUser() 方法,它会调用 UserManagerService 的 onBeforeStartUser() 方法。在 onBeforeStartUser() 中会调用 reconcileAppsData() 方法(FLAG_STORAGE_DE)。

4. 分析问题原因

那为什么 installd 会空?从 log 分析是因为 SystemServer 启动的比较快,即 PMS 的初始化太快了。为什么不是 installd 启动太慢而是 SystemServer 启动太快?

我计算了 installd 启动时间,从 vold 启动开始计时(Vold 3.0 (the awakening) firing up),到 installd 启动(installd firing up),花了 5.379 秒。我又测试了正常情况下的 installd 启动时间,均在 5.4 秒左右,所以可以判断 installd 启动正常。

但是 vold 启动到 SystemServer StartInstallers 花了 4.250 秒,而正常启动 3 次的平均花费是 6.157 秒。

在第一次连接 installd 失败一秒后尝试时 installd 还没启动,所以又要等一秒,而这时 PMS 已经开始初始化了,所以当 PMS 调用 installd 的函数的时候抛出了空指针异常。

那为什么 SystemServer 启动这么快,分析了 Zygote 和 SystemServer 相关 log 后无法找出原因,因为与正常启动的 log 相同。

那 Android 8.0 之前是等待 installd 连接,为什么 Android 8.0 之后开始异步连接了呢?我猜测是为了加快开机启动速度,而出现 SystemServer 需要使用 installd 的时候 installd 还未连接的状况,我觉得应该当成启动不正常来对待。

5. 总结

抛出空指针异常的直接原因是 installd 还未连接,未连接的原因是 SystemServer 连接 installd 的时候还未启动。为什么还未启动,原因是 SystemServer 启动快,而不是 installd 启动慢。

导致的结果分两种情况,第一次启动和其他:第一次启动时应用的私有目录还未创建,installd 未连接导致私有目录无法创建,但是在 boot complete 的时候(假设这时 installd 连接成功)会再次创建 CE 的私有目录,但 DE 私有目录一直不会创建;但如果 installd 长时间未连接会出现更严重的问题,因为 PMS 的安装卸载等等操作实际是 installd 来实现的。

AudioUnitInitialize 在初始化 AudioComponentInstance 时抛出错误

【中文标题】AudioUnitInitialize 在初始化 AudioComponentInstance 时抛出错误【英文标题】:AudioUnitInitialize throws error while initializing AudioComponentInstance 【发布时间】:2012-05-15 11:24:02 【问题描述】:

我正在使用下面的代码来初始化我的音频组件。

-(void) startListeningWithCoreAudio

     NSError *error = nil;

     [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayAndRecord error:&error];
     if (error) 
          NSLog(@"error setting up audio session: %@", [error localizedDescription]);

     [[AVAudioSession sharedInstance] setDelegate:self];

     OSStatus status = AudioSessionSetActive(YES);
     checkStatus(status);

          // Find the apple mic
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;

    AudioComponent inputComponent = AudioComponentFindNext( NULL, &desc );
    status = AudioComponentInstanceNew( inputComponent, &kAudioUnit );
    checkStatus( status );

          // enable mic output as our input
    UInt32 flag = 1;
    status = AudioUnitSetProperty( kAudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kInputBus, &flag, sizeof(flag) );
    checkStatus(status);


          // Define mic output audio format
    AudioStreamBasicDescription audioFormat;
    audioFormat.mSampleRate         = 16000.0;
    audioFormat.mFormatID           = kAudioFormatLinearPCM;
    audioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
    audioFormat.mFramesPerPacket    = 1;
    audioFormat.mChannelsPerFrame   = 1;
    audioFormat.mBitsPerChannel     = 16;
    audioFormat.mBytesPerPacket     = 2;
    audioFormat.mBytesPerFrame      = 2;

    status = AudioUnitSetProperty( kAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &audioFormat, sizeof(audioFormat) );
    checkStatus(status);

          // Define our callback methods
    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = recordingCallback;
    callbackStruct.inputProcRefCon = self;
    status = AudioUnitSetProperty( kAudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, kInputBus, &callbackStruct, sizeof(callbackStruct) );
    checkStatus(status);

          // By pass voice processing
     UInt32 audiobypassProcessing = [[NSUserDefaults standardUserDefaults] boolForKey:VOICE_BY_PASS_PROCESSING];
     status = AudioUnitSetProperty(kAudioUnit, kAUVoiceIOProperty_BypassVoiceProcessing, 
                                   kAudioUnitScope_Global, kInputBus, &audiobypassProcessing, sizeof(audiobypassProcessing));
     checkStatus(status);

          // Automatic Gain Control
     UInt32 audioAGC = [[NSUserDefaults standardUserDefaults]boolForKey:VOICE_AGC];
     status = AudioUnitSetProperty(kAudioUnit, kAUVoiceIOProperty_VoiceProcessingEnableAGC, 
                                   kAudioUnitScope_Global, kInputBus, &audioAGC, sizeof(audioAGC));
     checkStatus(status);

          //Non Audio Voice Ducking
     UInt32 audioDucking = [[NSUserDefaults standardUserDefaults]boolForKey:VOICE_DUCKING];
     status = AudioUnitSetProperty(kAudioUnit, kAUVoiceIOProperty_DuckNonVoiceAudio, 
                                   kAudioUnitScope_Global, kInputBus, &audioDucking, sizeof(audioDucking));
     checkStatus(status);

          //Audio Quality
     UInt32 quality = [[NSUserDefaults standardUserDefaults]integerForKey:VOICE_QUALITY];
     status = AudioUnitSetProperty(kAudioUnit, kAUVoiceIOProperty_VoiceProcessingQuality, 
                                   kAudioUnitScope_Global, kInputBus, &quality, sizeof(quality));
     checkStatus(status);

     status = AudioUnitInitialize(kAudioUnit);
     checkStatus(status);

    status = AudioOutputUnitStart( kAudioUnit );
    checkStatus(status);

    UInt32 audioRoute = (UInt32)kAudioSessionOverrideAudioRoute_Speaker;
    status = AudioSessionSetProperty(kAudioSessionProperty_OverrideAudioRoute, sizeof (audioRoute), &audioRoute);
    checkStatus(status);     



-(void) stopListeningWithCoreAudio
 
    OSStatus     status = AudioUnitUninitialize( kAudioUnit );
     checkStatus(status);

     status = AudioOutputUnitStop( kAudioUnit );
    checkStatus( status );

//     if(kAudioUnit)
//     
//          status = AudioComponentInstanceDispose(kAudioUnit);
//          checkStatus(status);
//          kAudioUnit = nil;
//     

    status = AudioSessionSetActive(NO);
     checkStatus(status);

     NSError *error = nil;
    [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategorySoloAmbient error:&error];
    if (error)
          NSLog(@"error setting up audio session: %@", [error localizedDescription]);

它第一次运行良好。我的意思是startListeningWithCoreAudio 被按钮按下事件调用。它可以很好地录制/处理音频。在其他事件中,我打电话给stopListeningWithCoreAudio 来停止录制/处理音频。

当我再次尝试调用函数startListeningWithCoreAudio 时,问题就来了。它为两个函数抛出错误。 AudioUnitInitializeAudioOutputUnitStartstartListeningWithCoreAudio 调用。

谁能帮我看看有什么问题?

【问题讨论】:

【参考方案1】:

我找到了解决方案。 如果我们背靠背调用下面的函数,就会产生问题。

extern OSStatus AudioUnitUninitialize(AudioUnit inUnit)                     
extern OSStatus AudioComponentInstanceDispose(AudioComponentInstance inInstance)

所以,我通过以下方式在主线程上调用了 dispose 方法。

[self performSelectorOnMainThread:@selector(disposeCoreAudio) withObject:nil waitUntilDone:NO];

-(void) disposeCoreAudio

     OSStatus status = AudioComponentInstanceDispose(kAudioUnit);
     kAudioUnit = nil;

它解决了这个问题。因此,正确的顺序是停止录制,取消初始化录制器,然后在主线程上释放录制器。

【讨论】:

【参考方案2】:

一个可能的问题是您的代码试图在停止之前取消初始化正在运行的音频单元。

【讨论】:

感谢您的回答。为此+1。实际原因是音频会话。我在应用程序处于活动状态时设置会话,并在应用程序进入后台时删除会话。之后没有音频单元的初始化。

以上是关于PMS 初始化时抛出 installd 空指针问题分析的主要内容,如果未能解决你的问题,请参考以下文章

在使用 java 运行 Hadoop map reduce 作业时抛出空指针异常

Spring Boot 1.5.9和嵌入式jetty服务器在运行时抛出空指针异常?

h2数据库to_char别名抛出空指针

在继承中重写方法时抛出异常的问题

队列为空时抛出测试异常[重复]

为啥我的数组列表即使在初始化后也会抛出空指针异常?