DeadObjectException только на Android 10
Я разрабатываю приложение для смартфона/планшета для версии Android с 9 по 12 (на данный момент). Для версии 9, 11 и 12 все нормально, особых проблем нет вообще. Я попробовал свое приложение на Android 10, и когда оно переключается с экрана-заставки на действие входа в систему, оно вылетает с этим сообщением:
android.os.DeadObjectException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:575)
at android.view.IWindow$Stub$Proxy.windowFocusChanged(IWindow.java:829)
at com.android.server.wm.WindowState.reportFocusChangedSerialized(WindowState.java:3691)
at com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:5254)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:237)
at android.os.HandlerThread.run(HandlerThread.java:67)
at com.android.server.ServiceThread.run(ServiceThread.java:44)
ничего не указав о некоторых моих файлах или части кода.
Любые идеи о том, что это за ошибка?
Это мой код SplashScreenActivity:
package it.company.product.activities.initialization;
import butterknife.ButterKnife;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import it.company.product.BuildConfig;
import it.company.product.R;
import it.company.product.activities.dashboard.PatientDashboardActivity;
import it.company.product.activities.login.LoginActivity;
import it.company.product.application.App;
import it.company.product.databinding.ActivitySplashScreenBinding;
import it.company.product.di.InternalDatabaseInstance;
import it.company.product.di.NetworkInstance;
import it.company.product.license.LicenseManager;
import it.company.product.license.OnRegistrationListener;
import it.company.etmlib.datacommunication.update.OnUpdateDownloadListener;
import it.company.etmlib.datacommunication.update.SimpleEventBus;
import it.company.product.update.UpdateManager;
import it.company.etmlib.datacommunication.license.protocol.VerifyLicenseResponse;
import it.company.etmlib.datahandling.globalconfiguration.GlobalConfiguration;
import it.company.etmlib.datahandling.globalconfiguration.GlobalConfiguration.InternalCode;
import it.company.etmlib.utilities.Utils;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import static it.company.etmlib.utilities.Utils.sleep;
@SuppressLint("CustomSplashScreen")
public class SplashScreenActivity extends Activity {
private final Logger easyLog = LoggerFactory.getLogger(SplashScreenActivity.class.getSimpleName());
private ActivitySplashScreenBinding viewBinding;
private UpdateManager mUpdateManager;
private LicenseManager mLicenseManager;
private CompositeDisposable mCompositeDisposable;
private static final String[] ANDROID_9_10_BLE_PERMISSIONS = new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private static final String[] ANDROID_11_BLE_PERMISSIONS = new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
};
@RequiresApi(api = Build.VERSION_CODES.S)
private static final String[] ANDROID_12_BLE_PERMISSIONS = new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
};
@Inject
InternalDatabaseInstance mInternalDatabaseInstance;
@Inject
NetworkInstance mNetworkInstance;
@Inject
@Named("Custom")
SharedPreferences mCustomSharedPreferences;
@Inject
@Named("Default")
SharedPreferences mDefaultSharedPreferences;
@Inject
SimpleEventBus mSimpleEventBus;
private int mProgress = 20;
private static final int STEP_PROGRESS = 20;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding = ActivitySplashScreenBinding.inflate(getLayoutInflater());
View view = viewBinding.getRoot();
setContentView(view);
configureLogBack();
ButterKnife.bind(this);
App application = (App)getApplication();
application.getAppComponent().inject(this);
mCompositeDisposable = new CompositeDisposable();
mLicenseManager = new LicenseManager();
mLicenseManager.setOnRegistrationListener(new OnRegistrationListener() {
@Override
public void onRegistrationSuccess() {
mCustomSharedPreferences.edit().putBoolean(App.KEY_FIRST_TIME, false).apply();
verifyLicense();
}
@Override
public void onFinishActivity() {
finish();
}
@Override
public void onRegistrationExpired() {
}
});
mUpdateManager = new UpdateManager(this, mNetworkInstance, mSimpleEventBus);
mUpdateManager.setOnUpdateDownloadListener(new OnUpdateDownloadListener() {
@Override
public void onDownloadSuccess(File file) {
// launchUpdateInstallation(file);
}
@Override
public void onDownloadError() {
}
@Override
public void onDownloadDelayed() {
}
});
initializeProgress();
View decorView = getWindow().getDecorView();
int uiOptions = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE);
decorView.setSystemUiVisibility(uiOptions);
evaluateAppPermissions();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1234 && resultCode == Activity.RESULT_OK) {
if (getPackageManager().canRequestPackageInstalls()) {
easyLog.info("[onActivityResult] Evaluating permissions...");
evaluateAppPermissions();
}
}
else {
easyLog.error("[onActivityResult] Error on requestCode or result for canRequestPackageInstalls(). Closing app...");
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_permission_error_title)
.setMessage(R.string.dialog_permission_error_message)
.setPositiveButton(R.string.button_ok, (dialog, which) -> finishAndRemoveTask())
.show();
}
}
private void evaluateAppPermissions() {
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.P || Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
ActivityCompat.requestPermissions(this, ANDROID_9_10_BLE_PERMISSIONS, GlobalConfiguration.PERMISSIONS_REQUEST_CODE);
else if(Build.VERSION.SDK_INT == Build.VERSION_CODES.R)
ActivityCompat.requestPermissions(this, ANDROID_11_BLE_PERMISSIONS, GlobalConfiguration.PERMISSIONS_REQUEST_CODE);
else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
ActivityCompat.requestPermissions(this, ANDROID_12_BLE_PERMISSIONS, GlobalConfiguration.PERMISSIONS_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
int error = 0;
for(int g=0; g<grantResults.length; g++) {
easyLog.warn("[onRequestPermissionsResult] permission --> " + permissions[g] + " | granted --> " + grantResults[g]);
if (requestCode != GlobalConfiguration.PERMISSIONS_REQUEST_CODE || grantResults[g] == PackageManager.PERMISSION_DENIED)
error++;
}
if(error == 0) {
// All missing permissions have been granted
easyLog.info("[onRequestPermissionsResult] All missing permissions have been granted, proceeding...");
verifyLicense();
}
else {
// If after requesting the missing permissions some have not yet been granted, I notify the user and close the app
easyLog.info("[onRequestPermissionsResult] Some permits have not been granted. Closing the app...");
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_permission_error_title)
.setMessage(R.string.dialog_permission_error_message)
.setPositiveButton(R.string.button_ok, (dialog, which) -> finishAndRemoveTask())
.show();
}
}
private void initializeProgress() {
runOnUiThread(() -> viewBinding.splashProgressBar.setProgress(mProgress));
}
void doProgress() {
mProgress += STEP_PROGRESS;
runOnUiThread(() -> viewBinding.splashProgressBar.setProgress(mProgress));
}
void setText(String text) {
runOnUiThread(() -> viewBinding.splashProgressText.setText(text));
}
private void verifyLicense() {
doProgress();
setText(getString(R.string.progress_verifing_license));
sleep(1000);
mCompositeDisposable.add(
mNetworkInstance.verifyLicense(this)
.subscribeOn(Schedulers.io())
.delay(1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe( this::manageLicenseStatus,
throwable -> {
if(throwable instanceof HttpException) {
easyLog.error("[VerifyLicense] Http status -> "+ ((HttpException) throwable).code() + "\n" + Utils.stacktraceToString(throwable));
}
else {
easyLog.warn("[VerifyLicense] Connessione internet assente, verifico la licenza in un secondo momento");
}
}
)
);
}
private void manageLicenseStatus(VerifyLicenseResponse response) {
switch(response.getResponse().getType()) {
case ok:
if(response.getVersionAvailable() != null) {
mUpdateManager.showAvailableVersionDialog(mCompositeDisposable, response.getVersionAvailable());
}
else {
startConnection();
}
break;
case not_found:
startProductRegistrationDialog();
break;
case expired:
mLicenseManager.showExpiredContractDialog(this);
break;
case blocked:
case contract_blocked:
mLicenseManager.showBlockedDeviceDialog(this);
break;
}
}
private void startProductRegistrationDialog() {
mLicenseManager.showProductActivationDialog(this, mNetworkInstance, mCompositeDisposable);
}
void startConnection() {
setText(getString(R.string.progress_gateway_initialization));
sleep(1000);
doProgress();
setText(getString(R.string.progress_configuration));
sleep(2000);
searchActivePatient();
}
void searchActivePatient() {
String patientCode = mCustomSharedPreferences.getString(App.KEY_PERMANENT_ASSIGNMENT_CODE, null);
String patientCodeType = mCustomSharedPreferences.getString(App.KEY_PERMANENT_ASSIGNMENT_CODE_TYPE, null);
if(patientCode != null && patientCodeType != null) {
mCompositeDisposable.add(
mInternalDatabaseInstance.getInternalDatabase().patientDao().getByCodeRx(patientCode, InternalCode.valueOf(patientCodeType))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe( patient -> {
Intent intent;
// request PIN if feature is set
if(mDefaultSharedPreferences.getBoolean(getString(R.string.key_feature_request_pin), false)){
intent = new Intent(this, RequestPinActivity.class);
}
else {
intent = new Intent(this, PatientDashboardActivity.class);
}
startActivity(intent);
finish();
},
throwable -> {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
}
)
);
}
else {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
}
}
static final String LOGBACK_XML_DEBUG =
"<configuration debug='true'>" +
" <property name='LOG_DIR' value='/storage/emulated/0/Android/data/it.company.product.debug/files' />" +
" <appender name='LogCat' class='ch.qos.logback.classic.android.LogcatAppender'>" +
" <encoder>" +
" <pattern>%msg%n</pattern>" +
" </encoder>" +
" </appender>" +
" <appender name='LogFile' class='ch.qos.logback.core.rolling.RollingFileAppender'>" +
" <file>${LOG_DIR}/log.txt</file>" +
" <encoder>" +
" <pattern>[%d{dd-MM-yyyy HH:mm:ss.SSS}] %.-1level/[%logger{0}] %msg%n</pattern>" +
" </encoder>" +
" <filter class='ch.qos.logback.classic.filter.LevelFilter'>" +
" <level>DEBUG</level>" +
" <onMatch>DENY</onMatch>" +
" </filter>" +
" <rollingPolicy class='ch.qos.logback.core.rolling.TimeBasedRollingPolicy'>" +
" <fileNamePattern>${LOG_DIR}/log.%d{yyyy-MM}.txt</fileNamePattern>" +
" <maxHistory>6</maxHistory>" +
" </rollingPolicy>" +
" </appender>" +
" <root level='DEBUG'>" +
" <appender-ref ref='LogCat' />" +
" <appender-ref ref='LogFile' />" +
" </root>" +
"</configuration>";
static final String LOGBACK_XML_RELEASE =
"<configuration debug='true'>" +
" <property name='LOG_DIR' value='/storage/emulated/0/Android/data/it.company.product/files' />" +
" <appender name='LogCat' class='ch.qos.logback.classic.android.LogcatAppender'>" +
" <encoder>" +
" <pattern>%msg%n</pattern>" +
" </encoder>" +
" </appender>" +
" <appender name='LogFile' class='ch.qos.logback.core.rolling.RollingFileAppender'>" +
" <file>${LOG_DIR}/log.txt</file>" +
" <encoder>" +
" <pattern>[%d{dd-MM-yyyy HH:mm:ss.SSS}] %.-1level/[%logger{0}] %msg%n</pattern>" +
" </encoder>" +
" <filter class='ch.qos.logback.classic.filter.LevelFilter'>" +
" <level>DEBUG</level>" +
" <onMatch>DENY</onMatch>" +
" </filter>" +
" <rollingPolicy class='ch.qos.logback.core.rolling.TimeBasedRollingPolicy'>" +
" <fileNamePattern>${LOG_DIR}/log.%d{yyyy-MM}.txt</fileNamePattern>" +
" <maxHistory>6</maxHistory>" +
" </rollingPolicy>" +
" </appender>" +
" <root level='DEBUG'>" +
" <appender-ref ref='LogCat' />" +
" <appender-ref ref='LogFile' />" +
" </root>" +
"</configuration>";
private void configureLogBack() {
// Usually, LogBack framework loads the logback.xml file from the asset folder
// From Android 10 (API 29) and above, to improve privacy, direct access to shared/external storage device is deprecated (EXT_DIR variable)
// Also access areas that are not owned by the app is denied
// A workaround consist in postponing the loading of the LogBack configuration after creating a log folder within the app and load the configuration from a String
// that mirrors the XML file content
File mediaStorageDir = getApplicationContext().getExternalFilesDir(null);
// Create the storage directory if it does not exist
if (mediaStorageDir != null && !mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
return;
}
}
// Reset the default context (which may already have been initialized) since we want to reconfigure it
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
lc.stop();
JoranConfigurator config = new JoranConfigurator();
config.setContext(lc);
// Load the configuration from the String representing the XML logback configuration file
InputStream stream;
if(BuildConfig.DEBUG) {
stream = new ByteArrayInputStream(LOGBACK_XML_DEBUG.getBytes());
}
else {
stream = new ByteArrayInputStream(LOGBACK_XML_RELEASE.getBytes());
}
try {
config.doConfigure(stream);
}
catch (JoranException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
mCompositeDisposable.dispose();
}
}
заранее спасибо