这是我的第一篇文章,请耐心等待。 我是一名高中开发人员,最近在Play商店发布了一款Android应用程序。我正在使用Crashlytics捕获异常,由于这个原因,它会抛出此错误。
Fatal Exception: android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@1989547c is not valid; is your activity running?
有关LG D855,Nexus 5以及版本5.0,5.0.2,5.1.1和6.0.1的华为PLK AL10的具体报道。我已经在线查看,并且发现当活动不存在时会发生这种情况。在应用程序的初始启动时会发生此错误。
以下是我用于警报对话框的代码,它只是询问用户是否想要查看教程(是/否)
public void showTutorialDialog() {
AlertDialog tutorialDialog = new AlertDialog.Builder(this)
.setTitle(R.string.tutorial_question_title)
.setCancelable(false)
.setMessage(R.string.tutorial_question)
.setPositiveButton(getResources().getString(R.string.tutorial_question_pos), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Take to tutorial
// Assume isn't backer for now..
finish();
Intent i = new Intent(StartupActivity.this, TutorialActivity.class);
i.putExtra("from", "StartupActivity");
startActivity(i);
}
})
.setNegativeButton(getResources().getString(R.string.tutorial_question_neg), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// No tutorial, ask if they are a backer
showBackerDialog();
}
}).show();
首次启动应用程序时,我会在单独的类中使用IabHelper加载用户购买详细信息。此类称为PurchaseRetriever,它以异步方式检索用户购买的内容,并将其存储在ArrayList中。这就是我的代码的工作方式。
if (mManager.isUserFirstTime()) {
// Initialize purchase retriever.
// The rest will be done when the observer reports that purchase data has been retrieved.
mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
mPurchases.addObserver(new FirstStartupObserver(this));
StartupManager.FIRST = true;
loadImageContent();
它使用Observer模式运行,因此在查询购买详细信息时,它会调用FirstStartupObserver中的update()方法,然后通过对StartupActivity的引用调用发生错误的startupActivity.showTutorialDialog();
。
我已经在我和我的朋友亲自拥有的多个设备(Nexus 6,Nexus 5,Nexus 7平板电脑,三星Galaxy Tab,三星远程实验室的各种设备)上进行了测试,但它在我的最终工作正常... 感谢任何建议,谢谢。
编辑:这是StartupActivity。
/**
* Main startup activity. Determines which activity to launch.
* Puts the user in one place or another depending on if they are a backer.
*/
public class StartupActivity extends AppCompatActivity {
private StartupManager mManager;
private ProgressBar bar;
// --- Used if first time app loading to query purchase info
private PurchaseRetriever mPurchases;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Used in either cases
// If first time, displayed, if not, hidden//
//hideNavBar();
User.init(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_startup);
bar = (ProgressBar)this.findViewById(R.id.progressBar);
mManager = new StartupManager(this);
// Returns true if data was corrupt before
if (mManager.isDataCorrupt()) {
bar.setVisibility(View.VISIBLE);
loadImageContent();
// Reset watch to default black
// Internally starts NewMainActivity
ErrorManager.fixCorruptData(bar, this);
} else {
// Stays true until user selects watch
if (mManager.isUserFirstTime()) {
// Initialize purchase retriever.
// The rest will be done when the observer reports that purchase data has been retrieved.
mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
mPurchases.addObserver(new FirstStartupObserver(this));
StartupManager.FIRST = true;
loadImageContent();
} else {
// NOT first time starting app.
mPurchases = PurchaseRetriever.getInstance(StartupActivity.this);
mPurchases.addObserver(new AfterFirstStartupObserver(this));
loadImageContent();
}
}
}
// Two main dialogs used
public void showTutorialDialog() {
AlertDialog tutorialDialog = new AlertDialog.Builder(this)
.setTitle(R.string.tutorial_question_title)
.setCancelable(false)
.setMessage(R.string.tutorial_question)
.setPositiveButton(getResources().getString(R.string.tutorial_question_pos), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Take to tutorial
// Assume isn't backer for now..
finish();
Intent i = new Intent(StartupActivity.this, TutorialActivity.class);
i.putExtra("from", "StartupActivity");
startActivity(i);
}
})
.setNegativeButton(getResources().getString(R.string.tutorial_question_neg), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// No tutorial, ask if they are a backer
showBackerDialog();
}
}).show();
tutorialDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
tutorialDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
}
private void showBackerDialog() {
// Show AlertDialog ask if they are kickstarter backer
AlertDialog askDialog = new AlertDialog.Builder(this)
.setTitle(getResources().getString(R.string.startup_dialog_title))
.setCancelable(false)
.setMessage(getResources().getString(R.string.startup_dialog_message))
.setPositiveButton(getResources().getString(R.string.startup_dialog_pos), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// User is a backer, take to watch chooser screen, then it takes to login screen
// Also look at Timer with TimerTask
new Thread(new Runnable() {
@Override
public void run() {
try {
Intent i = new Intent(StartupActivity.this, WatchChooserActivity.class);
i.putExtra("from", "StartupActivityBacker");
startActivity(i);
} finally {
finish();
}
}
}).start();
}
})
.setNegativeButton(getResources().getString(R.string.startup_dialog_neg), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// User is not a backer, take to MainActivity
new Thread(new Runnable() {
@Override
public void run() {
try {
Intent i = new Intent(StartupActivity.this, WatchChooserActivity.class);
i.putExtra("from", "StartupActivityNonBacker");
startActivity(i);
} finally {
finish();
}
}
}).start();
}
}).show();
askDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(Color.RED);
askDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.RED);
}
以下是FirstStartupObserver的代码。'
public class FirstStartupObserver implements Observer {
private StartupActivity startupActivity;
public FirstStartupObserver(StartupActivity startupActivity) {
this.startupActivity = startupActivity;
}
// Called when the observable is done loading purchase detail
// (Only called when user runs app first time)
@Override
public void update(Observable observable, Object data) {
// Set default first-time watch
// Query product data (the Watchfaces purchased in the form of a WatchFace object)
PurchaseRetriever mPurchases = PurchaseRetriever.getInstance(startupActivity);
if (mPurchases.hasSuccess()) {
ArrayList<DynamicLoader.WatchFace> facesOwned = mPurchases.getPurchasedFaces();
for (DynamicLoader.WatchFace f : facesOwned) {
f.setPurchased(true);
}
// Check if coming from v1.4
if (UpgradeManager.isUpgrading(startupActivity)) {
// Then it calls the code below, but after the async task.
String accessCode = UpgradeManager.getOldAccessCode(startupActivity);
String accessToken = UpgradeManager.getOldAccessToken(startupActivity);
UpgradeManager.migrateBacker(startupActivity, accessCode, accessToken);
} else {
// Ask if they want to see tutorial.
// This is when the exception occurs!!!
startupActivity.showTutorialDialog();
}
return;
} else {
Log.d("TAG", "Showing fail dialog");
DialogUtils.showIabFailDialog(startupActivity, this);
}
}
}
答案 0 :(得分:0)
令牌android.os.BinderProxy@1989547c无效;是你的活动 运行
当活动不存在时,您试图过早加载AlertDialog
!在我的应用程序中,我在活动生命周期完成时加载了一些教程:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_screen);
...
...
...
showTutorialDialog();
}
答案 1 :(得分:0)
令牌android.os.BinderProxy@1989547c无效;是你的活动 运行
这意味着您正在尝试在活动被销毁或销毁之后显示弹出窗口。
您可以检查您的活动isDestroyed
是否如下所示:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && !isDestroyed()) {
showTutorialDialog();
}
如果您支持以下api 17设备,则可以尝试在其他情况下使用isFinishing
。我没有测试它是否按预期工作。 (如果我错了请纠正我。)
else {
if (!isFinishing()) {
showTutorialDialog();
}
}
或者,您可以使用try catch
答案 2 :(得分:0)
这通常是由于在AsyncTask
或其他后台任务中执行某些操作而导致对Activity
的引用,并尝试在完成工作时显示对话框。在这种情况下,听起来您的FirstStartupObserver
持有对活动的引用并尝试显示对话框,但活动可能在PurchaseRetriever
完成其工作时被销毁。
不要尝试测试活动状态,也不要捕获BadTokenException
。这只是掩盖了问题。最简单的解决方案是在活动暂停时取消PurchaseRetriever
。如果您希望后台工作能够在轮换等配置更改后继续工作,但仍限于用户感知活动的生命周期,请在保留的片段中进行工作。最后,如果当用户在活动之间导航或将应用程序放在后台时后台工作应该继续,请在Service
中进行工作并将结果保存在活动可以检索它的位置。