这是一个相当难的问题:
我打开我的应用程序.它启动一个活动,它充当splashscreen(ASplashscreen),在其中我JSON从本地存储(raw文件夹)加载一些数据并将其存储在singleton object(静态)内存中.完成此过程后,它会自动移动到主要活动(AMain)
我通过按下home button并运行其他应用程序,游戏等退出应用程序.当我重新打开我的应用程序时,应用程序在onCreate方法内崩溃,AMain因为它试图使用内部的一些数据,singleton object但数据是null.所以NullPointerException当它这样做时它会抛出一个.看来,它重新启动AMain,而不是ASplashscreen让singleton没有机会重新初始化.
这种情况在多次尝试中随机发生......
我有两个假设......
我的第一个推测,也就是我对Android操作系统的了解,当我运行其他应用程序(尤其是游戏)时,其中一个需要大量内存,所以操作系统从内存中释放我的应用程序以腾出空间,所以singleton data是的garbage collected.
我还假设当gc从内存中删除我的单例时,操作系统仍保留一些与当前运行活动的"状态"相关的数据,因此它至少知道在AMain关闭应用程序之前它已经打开了活动.这可以解释为什么它重新开放AMain活动而不是ASplashscreen.
我对吗?或者是否有另一种解释为什么我得到这个例外?欢迎任何建议/澄清.
另外,处理这个问题的最佳方法是什么?我的方法是每当我尝试使用它时检查单例数据的存在,如果它是null,那么基本上只是重新启动应用程序.这使得它经历了ASplashscreen所以JSON初始化,一切都很好.
编辑根据要求,这是我的AndroidManifest
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:name=".global.App"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:theme="@style/AppTheme">
<!--SPLASH SCREEN-->
<activity
android:name=".activities.ASplashscreen"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!--MAIN-->
<activity
android:name=".activities.AMain"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"/>
<!--MENU-->
<activity
android:name=".activities.AMenu"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"/>
<!--HELP-->
<activity
android:name=".activities.AHelp"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"/>
<!--ADMOB-->
<activity
android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Translucent"/>
<!--FACEBOOK LOGIN ACTIVITY (SDK)-->
<activity
android:name="com.facebook.LoginActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/AppTheme"/>
<!--This meta-data tag is required to use Google Play Services.-->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<!--FACEBOOK STUFF-->
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id"/>
<!--GOOGLE PLUS-->
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<!--CRASHLYTICS-->
<meta-data
android:name="com.crashlytics.ApiKey"
android:value="9249....."/>
</application>
Run Code Online (Sandbox Code Playgroud)
如果你们真的想要它,这里的内容就是 ASplashscreen
/**
* @author MAB
*/
public class ASplashscreen extends ABase implements IIosLikeDialogListener {
private final float SHEEP_WIDTH_FRAC = 0.8f;
private final int SPLASHSCREEN_DELAY_MS = 500;
//View references
private View sheep_image;
/** The timestamp recorded when this screen came into view. We'll used this to determine how much we'll need to keep the splash screen awake */
private long mStartTimestamp;
private IosLikeDialog mDialog;
private IabHelper mIabHelper;
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
// Have we been disposed of in the meantime? If so, quit.
if (mIabHelper == null) {
System.out.println("=== IAB INVENTORY PROBLEM :: WE'VE BEEN DISPOSED");
displayAppStoreUnavailableDialog();
return;
}
// Is it a failure?
if (result.isFailure()) {
displayAppStoreUnavailableDialog();
System.out.println("=== IAB INVENTORY PROBLEM :: FAILED TO QUERY INVENTORY :: " + result);
return;
}
//Sync our static stuff with the app store
HSounds.instance().populate(ASplashscreen.this, inventory);
HLights.instance().populate(ASplashscreen.this, inventory);
//Store the stuff locally just to be sure
HStorage.persistObjectToFile(ASplashscreen.this, HVersions.SOUNDS);
HStorage.persistObjectToFile(ASplashscreen.this, HVersions.LIGHTS);
System.out.println("=== SUCCESSFULLY SYNCED WITH STORE !");
jumpToMainActivity();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.a_splashscreen);
init();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mIabHelper != null) {
mIabHelper.dispose();
}
mIabHelper = null;
}
@Override
public void onIosLikeDialogBtnsClick(int btnStringResID) {
if (btnStringResID == IosLikeDialog.BTN_OK) {
jumpToMainActivity();
}
}
private void init() {
//Get view references
sheep_image = findViewById(R.id.splashscreen_sheep);
mStartTimestamp = System.currentTimeMillis();
VersionTracking.setVersions(this);
//Set the width of the sheep
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) sheep_image.getLayoutParams();
params.width = (int) ((float) UScreen.getScreenWidthInPortrait(this) * SHEEP_WIDTH_FRAC);
sheep_image.setLayoutParams(params);
mDialog = new IosLikeDialog()
.with(findViewById(R.id.ios_like_dialog_main_container))
.listen(this);
new Thread(new Runnable() {
@Override
public void run() {
parseJsons();
//Get the filler bar values from shared prefs
HBrightness.instance().retrieveFromPersist(ASplashscreen.this);
HSensorAndTimer.instance().retrieveFromPersist(ASplashscreen.this);
WsBuilder.build(ASplashscreen.this).getGift(new ResponseListener<EGift>() {
@Override
public void onSuccess(EGift gifts) {
long now = System.currentTimeMillis();
SimpleDateFormat fmt = new SimpleDateFormat(HJsonDataBase.GIFT_DATE_FORMAT);
Date start;
Date end;
//Handle the gifts
if (gifts != null && gifts.data != null && gifts.responseOK()) {
//Go through the SOUNDS and check if we need to set them as gifts, if not reset them
for (ESound sound : HSounds.instance().getValues().getSounds()) {
String sku = sound.getSku(ASplashscreen.this);
sound.giftStart = null;
sound.giftEnd = null;
for (String giftSku : gifts.data.inapps) {
if (giftSku.equals(sku)) {
sound.giftStart = gifts.data.start_date;
sound.giftEnd = gifts.data.end_date;
break;
}
}
//Check if redeemed gift expired and if so, reset the dates
checkSoundGiftExpired(sound, fmt, now);
}
//Go through the LIGHTS and check if we need to set them as gifts, if not reset them
for (ELight light : HLights.instance().getValues().getLights()) {
String sku = light.getSku(ASplashscreen.this);
light.giftStart = null;
light.giftEnd = null;
for (String giftSku : gifts.data.inapps) {
if (giftSku.equals(sku)) {
light.giftStart = gifts.data.start_date;
light.giftEnd = gifts.data.end_date;
break;
}
}
//Check if redeemed gift expired and if so, reset the dates
checkLightGiftExpired(light, fmt, now);
}
//Persist the data in the local storage
HStorage.persistObjectToFile(ASplashscreen.this, HVersions.SOUNDS);
HStorage.persistObjectToFile(ASplashscreen.this, HVersions.LIGHTS);
}
//Run the IAB helper now
runIabHelper();
}
@Override
public void onErrorResponse(VolleyError error) {
//This might mean we're in offline mode, so check if the gifts expired
checkAllLightsGiftExpired();
checkAllSoundsGiftExpired();
//Run the IAB helper now
runIabHelper();
}
}, getPackageName());
}
});
}
/**
* This is run on a non-UI thread !!
*/
private void parseJsons() {
/**
* Versions
*/
parseVersions();
/**
* BACKGROUND
*/
parseBackgrounds();
try {
validateBackgrounds();
} catch (NullPointerException e) {
removeBackgroundsFile();
parseBackgrounds();
}
/**
* LIGHTS
*/
parseLights();
try {
validateLights();
} catch (NullPointerException e) {
removeLightsFile();
parseLights();
}
/**
* SOUNDS
*/
parseSounds();
try {
validateSounds();
} catch (NullPointerException e) {
removeSoundsFile();
parseSounds();
}
}
private void parseVersions() {
InputStream in = getResources().openRawResource(R.raw.versions);
EVersions versions = null;
try {
versions = UGson.jsonToObject(in, EVersions.class);
} catch (Exception e) {
System.out.println("==== PARSE ERROR :: VERSIONS :: " + e.getMessage());
e.printStackTrace();
return;
}
HVersions.instance().setValues(this, versions);
}
private void parseBackgrounds() {
//Get the version of he JSONS at which we've last updated them from the "raw" folder
int lastVersionBckgnds = UPersistent.getInt(ASplashscreen.this, HVersions.SHARED_PREF_LAST_JSONS_VERSION_BCKGNDS, 0);
InputStream in;
//If there are no files in local storage OR there's a new version of the JSON files that we need to retrieve
if (!HStorage.fileExists(ASplashscreen.this, HStorage.FILE_JSON_BACKGROUNDS) ||
HVersions.instance().shouldUpdateFromResources(HVersions.BACKGROUNDS, lastVersionBckgnds)) { //Update from raw folder
in = getResources().openRawResource(R.raw.backgrounds);
} else { //Update from local storage
in = HStorage.getInputStreamForFile(ASplashscreen.this, HStorage.FILE_JSON_BACKGROUNDS);
}
EBackgrounds bckgnds = null;
try {
bckgnds = UGson.jsonToObject(in, EBackgrounds.class);
} catch (Exception e) {
System.out.println("==== PARSE ERROR :: BACKGROUNDS :: " + e.getMessage());
e.printStackTrace();
}
HBackgrounds.instance().setValues(this, bckgnds);
}
private void parseLights() {
//Get the version of he JSONS at which we've last updated them from the "raw" folder
int lastVersionLights = UPersistent.getInt(ASplashscreen.this, HVersions.SHARED_PREF_LAST_JSONS_VERSION_LIGHTS, 0);
InputStream in;
//If there are no files in local storage OR there's a new version of the JSON files that we need to retrieve
if (!HStorage.fileExists(ASplashscreen.this, HStorage.FILE_JSON_LIGHTS) ||
HVersions.instance().shouldUpdateFromResources(HVersions.LIGHTS, lastVersionLights)) { //Update from raw folder
in = getResources().openRawResource(R.raw.lights);
} else { //Update from local storage
in = HStorage.getInputStreamForFile(ASplashscreen.this, HStorage.FILE_JSON_LIGHTS);
}
ELights lights = null;
try {
lights = UGson.jsonToObject(in, ELights.class);
} catch (Exception e) {
System.out.println("==== PARSE ERROR :: LIGHTS :: " + e.getMessage());
e.printStackTrace();
}
if (lights != null) {
HLights.instance().setValues(this, lights);
}
}
private void parseSounds() {
int lastVersionSounds = UPersistent.getInt(ASplashscreen.this, HVersions.SHARED_PREF_LAST_JSONS_VERSION_SOUNDS, 0);
InputStream in;
//If there are no files in local storage OR there's a new version of the JSON files that we need to retrieve
if (!HStorage.fileExists(ASplashscreen.this, HStorage.FILE_JSON_SOUNDS) ||
HVersions.instance().shouldUpdateFromResources(HVersions.SOUNDS, lastVersionSounds)) { //Update from raw folder
in = getResources().openRawResource(R.raw.sounds);
} else { //Update from local storage
in = HStorage.getInputStreamForFile(ASplashscreen.this, HStorage.FILE_JSON_SOUNDS);
}
ESounds sounds = null;
try {
sounds = UGson.jsonToObject(in, ESounds.class);
} catch (Exception e) {
System.out.println("==== PARSE ERROR :: SOUNDS" + e.getMessage());
}
if (sounds != null) {
HSounds.instance().setValues(this, sounds);
}
}
private void validateBackgrounds() throws NullPointerException {
if (HBackgrounds.instance().getValues() == null) {
throw new NullPointerException();
}
if (HBackgrounds.instance().getValues().getBackgrounds() == null) {
throw new NullPointerException();
}
}
private void validateLights() throws NullPointerException {
if (HLights.instance().getValues() == null) {
throw new NullPointerException();
}
if (HLights.instance().getValues().getLights() == null) {
throw new NullPointerException();
}
}
private void validateSounds() throws NullPointerException {
if (HSounds.instance().getValues() == null) {
throw new NullPointerException();
}
if (HSounds.instance().getValues().getSounds() == null) {
throw new NullPointerException();
}
}
private void removeBackgroundsFile() {
HStorage.deleteFile(this, HStorage.FILE_JSON_BACKGROUNDS);
}
private void removeLightsFile() {
HStorage.deleteFile(this, HStorage.FILE_JSON_LIGHTS);
}
private void removeSoundsFile() {
HStorage.deleteFile(this, HStorage.FILE_JSON_SOUNDS);
}
private void runIabHelper() {
//If there's no network connection, then ... sorry
if (!UNetwork.isNetworkAvailable(this)) {
displayAppStoreUnavailableDialog();
System.out.println("=== IAB ERROR :: NO NETWORK");
return;
}
try {
mIabHelper = new IabHelper(ASplashscreen.this, CIab.IAB_PUBLIC_KEY);
mIabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
@Override
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh noes, there was a problem.
System.out.println("=== IAB ERROR :: CONNECTION :: " + result);
displayAppStoreUnavailableDialog();
return;
}
//Obtain and create the list of skus from both the LIGHTS and the SOUNDS handlers
List<String> skus = new ArrayList<String>();
skus.addAll(HSounds.instance().createSkuList(ASplashscreen.this, true));
skus.addAll(HLights.instance().createSkuList(ASplashscreen.this, true));
//Get the inventory
try {
mIabHelper.queryInventoryAsync(true, skus, mGotInventoryListener, new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// Crashlytics.logException(ex);
System.out.println("=== IAB ERROR :: query inventory crashed :: " + ex.getMessage());
displayAppStoreUnavailableDialog();
}
});
} catch (IllegalStateException e) {
displayAppStoreUnavailableDialog();
}
}
});
} catch (NullPointerException e1) {
// Crashlytics.logException(e1);
System.out.println("=== IAB ERROR :: query inventory crashed :: " + e1.getMessage());
displayAppStoreUnavailableDialog();
} catch (IllegalArgumentException e2) {
// Crashlytics.logException(e2);
System.out.println("=== IAB ERROR :: query inventory crashed :: " + e2.getMessage());
displayAppStoreUnavailableDialog();
}
}
private void displayAppStoreUnavailableDialog() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mDialog == null) {
return;
}
mDialog.reset()
.header(R.string.inapp_store_unavailable_header)
.subheader(R.string.inapp_store_unavailable_subheader)
.btnOK()
.show();
}
});
}
private void jumpToMainActivity() {
int timePassed = (int) (System.currentTimeMillis() - mStartTimestamp);
int delay = (timePassed > SPLASHSCREEN_DELAY_MS) ? 0 : (SPLASHSCREEN_DELAY_MS - timePassed);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//In case we need to display the tutorial, then do so
if (AHelp.shouldDisplayTutorial(ASplashscreen.this)) {
CrashReport.log("ASplashscreen -> AHelp");
Intent i = new Intent(ASplashscreen.this, AHelp.class);
i.putExtra(AHelp.BUNDLE_SHOW_TUTORIAL, true);
startActivity(i);
finish();
overridePendingTransition(R.anim.anim_slide_in_from_bottom, R.anim.anim_stay_put);
return;
} else { //Otherwise continue with normal flow
CrashReport.log("ASplashscreen -> AMain");
Intent i = new Intent(ASplashscreen.this, AMain.class);
i.putExtra(AMain.BUNDLE_DEBUGGING_CAME_FROM_SPLASHSCREEN, true);
startActivity(i);
finish();
}
}
}, delay);
}
private void checkAllSoundsGiftExpired() {
SimpleDateFormat fmt = new SimpleDateFormat(HJsonDataBase.GIFT_DATE_FORMAT);
long now = System.currentTimeMillis();
for (ESound sound : HSounds.instance().getValues().getSounds()) {
if (sound != null) {
checkSoundGiftExpired(sound, fmt, now);
}
}
}
private void checkAllLightsGiftExpired() {
SimpleDateFormat fmt = new SimpleDateFormat(HJsonDataBase.GIFT_DATE_FORMAT);
long now = System.currentTimeMillis();
for (ELight light : HLights.instance().getValues().getLights()) {
if (light != null) {
checkLightGiftExpired(light, fmt, now);
}
}
}
private void checkSoundGiftExpired(ESound sound, SimpleDateFormat fmt, long now) {
if (UString.stringsExist(sound.giftExpireStart, sound.giftExpireEnd)) {
try {
Date start = fmt.parse(sound.giftExpireStart);
Date end = fmt.parse(sound.giftExpireEnd);
if (now < start.getTime() || end.getTime() < now) {
sound.giftExpireStart = null;
sound.giftExpireEnd = null;
}
} catch (ParseException e) {
//Do nothin'
}
}
}
private void checkLightGiftExpired
(ELight light, SimpleDateFormat fmt, long now) {
if (UString.stringsExist(light.giftExpireStart, light.giftExpireEnd)) {
try {
Date start = fmt.parse(light.giftExpireStart);
Date end = fmt.parse(light.giftExpireEnd);
if (now < start.getTime() || end.getTime() < now) {
light.giftExpireStart = null;
light.giftExpireEnd = null;
}
} catch (ParseException e) {
//Do nothin'
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用单例时,应该有一些getInstane方法return instance,所以你可以把你的支票放在里面,如下所示:
public static SingletonClass getInstance() {
if(instance == null) {
instance = StaticMethodToLoadInstance();
}
return instance;
}
Run Code Online (Sandbox Code Playgroud)
我想你可以将所有数据加载代码放在静态内StaticMethodToLoadInstance().
更新
是的,加载数据可能会花费很多时间,因此可以使用其他方法.首先,创建自己的界面:
public static interface OnInstanceLoadedListener {
public void onIntsanceLoaded(SingletonClass instance);
}
Run Code Online (Sandbox Code Playgroud)
然后getInstance按以下方式更改:
public static void getInstance(final OnInstanceLoadedListener listener, Activity context) {
final ProgressDialog dialog = null;
if(instance == null) {//if there should be loading
dialog = StaticMethodToCreateProgressDialog(context);
dialog.show();
}
new Thread(new Runnable() {
@Override
public void run() {
if(instance == null) {
instance = StaticMethodToLoadInstance();
}
context.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onIntsanceLoaded(instance);
if(dialog != null && dialog.isShowing())
dialog.dismiss();
}
});
}
}).start();
}
Run Code Online (Sandbox Code Playgroud)
而且你的getInstance用法会改变
SingletonClass object = SingletonClass.getInstance();
String data = object.getData();
Run Code Online (Sandbox Code Playgroud)
至
getInstance(new OnInstanceLoadedListener() {
@Override
public void onIntsanceLoaded(SingletonClass instance) {
String data = instance.getData();
}
}, YourActivityClass.this);
Run Code Online (Sandbox Code Playgroud)
这样,您的数据将异步加载.是的,它看起来更难,但它可以显示进度对话框 - 用户可以看到应用程序仍然有效.
这是非常标准的Android行为.当您的应用程序在后台时,无论出于何种原因,它都可以随时被杀死.Android只会杀死托管您应用的操作系统进程.
当用户返回您的应用程序(或重新启动您的应用程序)时,Android意识到它之前已经杀死了您的应用程序,因此它创建了一个新的操作系统进程来托管您的应用程序,然后它Application为您的应用程序实例化实例,然后它实例化最顶层Activity在任务堆栈中(即:Activity当你的应用程序进入后台时屏幕上的那个),然后它调用onCreate()它Activity以便Activity可以自我恢复.Android将最近保存的instaces状态Activity作为Bundle参数传递给onCreate().这样,Activity就有机会恢复自己.
您Activity正在崩溃,因为它依赖于之前应该设置的数据.在Android杀死并重新创建应用程序的操作系统进程的情况下,此数据消失了.
有多种方法可以解决这个问题,其中一种方法已经使用过:
在onCreate()所有活动中,检查是否已使用public static变量或单例执行"应用程序初始化" .如果初始化尚未完成,你知道你的应用程序的过程中被打死,重建,你需要或者将用户重定向到你的根Activity(即:从头再来的应用程序)或立即做初始化中onCreate()的Activity.
保存所需的数据onSaveInstanceState()并将其恢复到onCreate()和/ onRestoreInstanceState()或两者中.
不要将此数据保留在内存中.将其保存在数据库或其他基于非内存的持久性结构中.
注意:一般情况下,您不应该使用launchMode="singleTask".在大多数情况下,这是不必要的,并且通常会导致比解决的问题更多的问题.这与您遇到的进程终止/重新创建问题无关,但您仍应避免使用特殊的启动模式singleTask和singleInstance.只有在创建HOME屏幕替换时才需要这些.
| 归档时间: |
|
| 查看次数: |
1918 次 |
| 最近记录: |