这是一个非常难题:
我打开我的应用。它启动一个活动,作为一个启动画面(ASplashscreen
),在其中我从本地存储(JSON
文件夹)加载一些raw
数据并将其存储在singleton object
的内存中(静态的)。完成此过程后,它会自动移动到主要活动(AMain
)
我按home button
退出应用程序并运行其他应用程序,游戏等。当我重新打开我的应用程序时,应用程序在onCreate
的{{1}}方法内崩溃,因为它尝试使用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>
如果你们真的想要它,这里是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'
}
}
}
}
答案 0 :(得分:7)
使用单身时,应该只使用getInstane
方法return instance
,因此您可以将支票放入其中,如下所示:
public static SingletonClass getInstance() {
if(instance == null) {
instance = StaticMethodToLoadInstance();
}
return instance;
}
我猜您可以将所有数据加载代码放在静态StaticMethodToLoadInstance()
内。
<强>已更新强>
是的,加载数据可能会花费很多时间,因此可以使用其他方法。首先,创建自己的界面:
public static interface OnInstanceLoadedListener {
public void onIntsanceLoaded(SingletonClass instance);
}
然后通过以下方式更改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();
}
您的getInstance
用法从
SingletonClass object = SingletonClass.getInstance();
String data = object.getData();
到
getInstance(new OnInstanceLoadedListener() {
@Override
public void onIntsanceLoaded(SingletonClass instance) {
String data = instance.getData();
}
}, YourActivityClass.this);
通过这种方式,您的数据将异步加载。是的,它看起来更难,但它可以显示进度对话框 - 用户可以看到应用程序仍在工作。
答案 1 :(得分:7)
这是非常标准的Android行为。当您的应用程序在后台时,无论出于何种原因,它都可以随时被杀死。 Android只会杀死托管您应用的操作系统进程。
当用户返回您的应用程序(或重新启动您的应用程序)时,Android意识到它之前已经杀死了您的应用程序,因此它创建了一个新的操作系统进程来托管您的应用程序,然后它实例化Application
实例你的应用程序,然后它实例化任务堆栈中最顶层的Activity
(即:应用程序进入后台时屏幕上显示的Activity
),然后调用onCreate()
那个Activity
使Activity
可以自我恢复。 Android将最新保存的Activity
实例状态作为Bundle
参数传递给onCreate()
。通过这种方式,Activity
有机会恢复自己。
您的Activity
崩溃了,因为它依赖于之前应该设置的数据。在Android杀死然后重新创建应用程序的操作系统进程的情况下,这些数据就消失了。
有多种方法可以解决这个问题,其中一种方法已经使用过:
在所有活动的onCreate()
中,检查&#34; app初始化&#34;已使用public static
变量或单例执行。如果尚未完成初始化,您知道应用程序的进程已被终止并重新创建,您需要将用户重定向到根Activity
(即:重新启动应用程序)或执行此操作立即在onCreate()
。{/ p>
Activity
中进行初始化
在onSaveInstanceState()
中保存所需的数据,并将其还原到onCreate()
和/或onRestoreInstanceState()
或两者中。
不要将这些数据保存在内存中。将其保存在数据库或其他基于非内存的持久性结构中。
注意:一般情况下,您不应使用launchMode="singleTask"
。在大多数情况下,这是不必要的,并且通常会导致比解决的问题更多的问题。这与您遇到的进程终止/重新创建问题无关,但您仍应避免使用特殊启动模式singleTask
和singleInstance
。只有在创建HOME屏幕替换时才需要这些。
答案 2 :(得分:3)
解决方案:
您不应将启动活动设置为singleTask
,这意味着活动堆栈的根,而您的MainActivity应将singleTask
设置为root。
当你的应用程序回到前台时,在onCreate(...)
中,你应该在使用它们之前检查单例类的静态引用(如果不是null),如果为null则跳回到你的启动活动(意味着重新加载和恢复)数据到静态引用中)。换句话说,如果系统回收静态引用,则只需重新启动应用程序。
希望这有帮助!
答案 3 :(得分:2)
嗯,在我看来,有两种方法可以改善你当前的重启式应用程序方法(你的方法还可以,但有点脏):
1)摆脱ASplashscreen
,将加载逻辑移至某个帮助程序类,并安排从MainActivity
加载数据,同时在Activity
布局上显示您的启动。如果您对RelativeLayout
使用MainActivity
,则可以通过在视图层次结构的底部添加"match_parent"
参数的不可见视图(以重叠其他视图)并在必要时使其可见来轻松实现此目的。与两个Activities
和app重新启动相比,这更干净。
2)制作单身Parcelable
并将其存储在onSaveInstanceState()
MainActivity
中。 (当然,你的单身人士凭借这种方法不再是单身人士)。在这种情况下,Android会将您的数据与MainActivity
一起保存,并在OnCreate()
中恢复后,所有内容都将到位。这比将其保存到SharedPreferences
更简洁。
答案 4 :(得分:2)
我猜您的JSON数据采用以下格式。
{
a : "A",
b : "B",
c : "C"
}
现在您可以拥有一个名为JsonData
的类,其结构如下,
public class JsonData {
public String a;
public String b;
public String c;
}
现在,您可以使用gson库将您的json数据转换为Java对象。
现在创建一个类似ObjectHolder
的类,其结构如下。
public class ObjectHolder {
public static JsonData jsonData;
}
将转换后的对象存储在ObjectHolder.jsonData
中。现在,您可以随时在整个项目中访问此对象。
注意:当您从null
清除应用时,此对象将变为resent apps list
。
这种方法对我有用,所以我希望这对你也有帮助。