Pertanyaan Menyimpan status Aktivitas Android menggunakan Save Instance State


Saya telah bekerja pada platform Android SDK, dan itu sedikit tidak jelas bagaimana cara menyimpan negara aplikasi. Jadi, mengingat ini kecil perkakas dari 'Halo, Android' contoh:

package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

Saya pikir itu akan cukup untuk kasus yang paling sederhana, tetapi selalu merespon dengan pesan pertama, tidak peduli bagaimana saya menavigasi jauh dari aplikasi.

Saya yakin solusinya sesederhana menimpa onPause atau sesuatu seperti itu, tetapi saya telah mengaduk-aduk dokumentasi selama 30 menit atau lebih dan belum menemukan sesuatu yang jelas.


2234
2017-09-30 04:41


asal


Jawaban:


Anda perlu mengesampingkan onSaveInstanceState(Bundle savedInstanceState) dan tulis nilai status aplikasi yang ingin Anda ubah ke Bundle parameter seperti ini:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  // Save UI state changes to the savedInstanceState.
  // This bundle will be passed to onCreate if the process is
  // killed and restarted.
  savedInstanceState.putBoolean("MyBoolean", true);
  savedInstanceState.putDouble("myDouble", 1.9);
  savedInstanceState.putInt("MyInt", 1);
  savedInstanceState.putString("MyString", "Welcome back to Android");
  // etc.
}

Bundle pada dasarnya adalah cara menyimpan peta NVP ("Nama-Nilai Pasangan"), dan itu akan diteruskan ke onCreate() dan juga onRestoreInstanceState() di mana Anda akan mengekstrak nilai-nilai seperti ini:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  // Restore UI state from the savedInstanceState.
  // This bundle has also been passed to onCreate.
  boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
  double myDouble = savedInstanceState.getDouble("myDouble");
  int myInt = savedInstanceState.getInt("MyInt");
  String myString = savedInstanceState.getString("MyString");
}

Anda biasanya akan menggunakan teknik ini untuk menyimpan nilai instan untuk aplikasi Anda (pilihan, teks yang tidak disimpan, dll.).


2271
2017-09-30 06:12



Itu savedInstanceState hanya untuk menyimpan status yang terkait dengan kejadian saat ini dari suatu Kegiatan, misalnya info navigasi atau pemilihan saat ini, sehingga jika Android menghancurkan dan membuat ulang suatu Kegiatan, itu dapat kembali seperti sebelumnya. Lihat dokumentasi untuk onCreate dan onSaveInstanceState

Untuk keadaan hidup yang lebih lama, pertimbangkan untuk menggunakan database SQLite, file, atau preferensi. Lihat Menyimpan Negara Persisten.


375
2017-09-30 05:03



Perhatikan itu TIDAK aman digunakan onSaveInstanceState dan onRestoreInstanceState  untuk data persisten, menurut dokumentasi di Activity menyatakan dalam http://developer.android.com/reference/android/app/Activity.html.

Dokumen menyatakan (di bagian 'Siklus Hidup Aktivitas'):

Perhatikan bahwa penting untuk disimpan   data persisten dalam onPause() sebagai gantinya   dari onSaveInstanceState(Bundle)   karena nanti bukan bagian dari   callback siklus hidup, jadi tidak akan   dipanggil dalam setiap situasi seperti yang dijelaskan   dalam dokumentasinya.

Dengan kata lain, simpan kode simpan / kembalikan untuk data persisten onPause() dan onResume()!

EDIT: Untuk klarifikasi lebih lanjut, inilah yang onSaveInstanceState() dokumentasi:

Metode ini disebut sebelum suatu kegiatan dapat dibunuh sehingga ketika itu   kembali beberapa waktu ke depan, ia dapat memulihkan kondisinya. Untuk   Misalnya, jika aktivitas B diluncurkan di depan aktivitas A, dan pada beberapa   Aktivitas titik A dibunuh untuk merebut kembali sumber daya, kegiatan A akan memiliki   kesempatan untuk menyimpan status antarmuka penggunanya saat ini melalui ini   metode sehingga ketika pengguna kembali ke aktivitas A, status   antarmuka pengguna dapat dipulihkan melalui onCreate(Bundle) atau    onRestoreInstanceState(Bundle).


365
2018-05-25 23:22



Rekan saya menulis artikel yang menjelaskan Status Aplikasi di perangkat Android termasuk penjelasan tentang Siklus Hidup Aktivitas dan Informasi Negara, Cara Menyimpan Informasi Negara, dan menyimpan ke Negara Bundle dan SharedPreferences dan lihat di sini.

Artikel ini mencakup tiga pendekatan:

Simpan data kontrol varible / UI lokal untuk seumur hidup aplikasi (mis. Sementara) menggunakan Instance State Bundle

[Code sample – Store State in State Bundle]
@Override
public void onSaveInstanceState(Bundle savedInstanceState) 
{
  // Store UI state to the savedInstanceState.
  // This bundle will be passed to onCreate on next call.  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  savedInstanceState.putString(“Name”, strName);
  savedInstanceState.putString(“Email”, strEmail);
  savedInstanceState.putBoolean(“TandC”, blnTandC);

  super.onSaveInstanceState(savedInstanceState);
}

Simpan data kontrol varible / UI lokal di antara instance aplikasi (yaitu secara permanen) menggunakan Preferensi Bersama

[Code sample – Store State in SharedPreferences]
@Override
protected void onPause() 
{
  super.onPause();

  // Store values between instances here
  SharedPreferences preferences = getPreferences(MODE_PRIVATE);
  SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UI
  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  editor.putString(“Name”, strName); // value to store
  editor.putString(“Email”, strEmail); // value to store
  editor.putBoolean(“TandC”, blnTandC); // value to store    
  // Commit to storage
  editor.commit();
}

Menjaga objek contoh hidup dalam memori antara kegiatan dalam seumur hidup aplikasi menggunakan Retained Non-Configuration Instance

[Code sample – store object instance]
private cMyClassType moInstanceOfAClass;// Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance() 
{
  if (moInstanceOfAClass != null) // Check that the object exists
      return(moInstanceOfAClass);
  return super.onRetainNonConfigurationInstance();
}

171
2017-08-27 13:54



Ini adalah 'gotcha' klasik pengembangan Android. Ada dua masalah di sini:

  • Ada bug Kerangka Android halus yang sangat mempersulit aplikasi manajemen tumpukan selama pengembangan, setidaknya pada versi warisan (tidak sepenuhnya yakin jika / kapan / bagaimana itu diperbaiki). Saya akan membahas bug ini di bawah ini.
  • Cara 'normal' atau yang dimaksudkan untuk mengelola masalah ini, itu sendiri, agak rumit dengan dualitas onPause / onResume dan onSaveInstanceState / onRestoreInstanceState

Browsing di semua thread ini, saya menduga bahwa banyak pengembang waktu berbicara tentang dua masalah yang berbeda secara bersamaan ... maka semua kebingungan dan laporan tentang "ini tidak bekerja untuk saya".

Pertama, untuk memperjelas perilaku 'yang dimaksudkan': onSaveInstance dan onRestoreInstance adalah rapuh dan hanya untuk keadaan sementara. Penggunaan yang dimaksudkan (afaict) adalah untuk menangani kegiatan rekreasi ketika telepon diputar (perubahan orientasi). Dengan kata lain, penggunaan yang dimaksudkan adalah ketika Aktivitas Anda masih secara logis 'di atas', tetapi masih harus dipulihkan kembali oleh sistem. Bundle yang disimpan tidak bertahan di luar proses / memori / gc, jadi Anda tidak dapat benar-benar bergantung pada ini jika aktivitas Anda berjalan ke latar belakang. Ya, mungkin memori Kegiatan Anda akan bertahan dari perjalanan ke latar belakang dan melarikan diri dari GC, tetapi ini tidak dapat diandalkan (juga tidak dapat diprediksi).

Jadi jika Anda memiliki skenario di mana ada 'kemajuan pengguna' yang berarti atau status yang harus dipertahankan antara 'peluncuran' aplikasi Anda, panduannya adalah menggunakan onPause dan onResume. Anda harus memilih dan menyiapkan toko persisten sendiri.

TAPI - ada bug yang sangat membingungkan yang mempersulit semua ini. Detail ada di sini:

http://code.google.com/p/android/issues/detail?id=2373

http://code.google.com/p/android/issues/detail?id=5277

Pada dasarnya, jika aplikasi Anda diluncurkan dengan bendera SingleTask, dan kemudian Anda meluncurkannya dari layar awal atau menu peluncur, maka permintaan berikutnya akan membuat tugas BARU ... Anda akan memiliki dua contoh aplikasi yang berbeda secara efektif menghuni tumpukan yang sama ... yang menjadi sangat aneh dengan sangat cepat. Hal ini tampaknya terjadi ketika Anda meluncurkan aplikasi Anda selama pengembangan (yaitu dari Eclipse atau Intellij), sehingga pengembang mengalami hal ini. Tetapi juga melalui beberapa mekanisme pembaruan app store (sehingga berdampak juga bagi pengguna Anda).

Saya berjuang melalui benang ini selama berjam-jam sebelum saya menyadari bahwa masalah utama saya adalah bug ini, bukan perilaku kerangka yang dimaksudkan. Langgan yang bagus dan solusi (PEMBARUAN: lihat di bawah) tampaknya dari pengguna @kaciula dalam jawaban ini:

Perilaku menekan tombol home

PERBARUI Juni 2013: Beberapa bulan kemudian, saya akhirnya menemukan solusi yang 'benar'. Anda tidak perlu mengelola flags startApp stateful sendiri, Anda dapat mendeteksi ini dari framework dan jaminan dengan tepat. Saya menggunakan ini dekat awal LauncherActivity.onCreate saya:

if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}

126
2017-10-19 23:47



onSaveInstanceStatedipanggil ketika sistem membutuhkan memori dan membunuh aplikasi. Itu tidak disebut ketika pengguna hanya menutup aplikasi. Jadi saya pikir negara aplikasi juga harus disimpan onPause Ini harus disimpan ke beberapa penyimpanan persisten seperti Preferences atau Sqlite


70
2018-05-07 00:21



Kedua metode tersebut berguna dan valid dan keduanya paling cocok untuk skenario yang berbeda:

  1. Pengguna mengakhiri aplikasi dan membukanya kembali di kemudian hari, tetapi aplikasi perlu memuat ulang data dari sesi terakhir - ini memerlukan pendekatan penyimpanan persisten seperti menggunakan SQLite.
  2. Pengguna beralih aplikasi dan kemudian kembali ke aslinya dan ingin mengambil di mana mereka tinggalkan - menyimpan dan mengembalikan data bundel (seperti data negara aplikasi) di onSaveInstanceState() dan onRestoreInstanceState() biasanya cukup.

Jika Anda menyimpan data negara secara gigih, itu bisa dimuat ulang dalam onResume() atau onCreate() (atau sebenarnya pada panggilan siklus hidup apa pun). Ini mungkin atau mungkin bukan perilaku yang diinginkan. Jika Anda menyimpannya dalam satu bundel ke dalam InstanceState, maka itu sementara dan hanya cocok untuk menyimpan data untuk digunakan dalam 'sesi' pengguna yang sama (saya menggunakan sesi istilah longgar) tetapi tidak di antara 'sesi'.

Bukan pendekatan yang lebih baik dari yang lain, seperti semuanya, itu hanya penting untuk memahami perilaku apa yang Anda butuhkan dan untuk memilih pendekatan yang paling tepat.


59
2018-06-27 16:17



Menyimpan negara adalah kludge yang terbaik sejauh yang saya ketahui. Jika Anda perlu menyimpan data persisten, gunakan saja SQLite database. Android membuatnya SOOO mudah.

Sesuatu seperti ini:

import java.util.Date;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class dataHelper {

    private static final String DATABASE_NAME = "autoMate.db";
    private static final int DATABASE_VERSION = 1;

    private Context context;
    private SQLiteDatabase db;
    private OpenHelper oh ;

    public dataHelper(Context context) {
        this.context = context;
        this.oh = new OpenHelper(this.context);
        this.db = oh.getWritableDatabase();
    }

    public void close()
    {
        db.close();
        oh.close();
        db = null;
        oh = null;
        SQLiteDatabase.releaseMemory();
    }


    public void setCode(String codeName, Object codeValue, String codeDataType)
    {
        Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        String cv = "" ;

        if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            cv = String.valueOf(((Date)codeValue).getTime());
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            String.valueOf(codeValue);
        }
        else
        {
            cv = String.valueOf(codeValue);
        }

        if(codeRow.getCount() > 0) //exists-- update
        {
            db.execSQL("update code set codeValue = '" + cv +
                "' where codeName = '" + codeName + "'");
        }
        else // does not exist, insert
        {
            db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
                    "'" + codeName + "'," +
                    "'" + cv + "'," +
                    "'" + codeDataType + "')" );
        }
    }

    public Object getCode(String codeName, Object defaultValue)
    {
        //Check to see if it already exists
        String codeValue = "";
        String codeDataType = "";
        boolean found = false;
        Cursor codeRow  = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        if (codeRow.moveToFirst())
        {
            codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
            codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
            found = true;
        }

        if (found == false)
        {
            return defaultValue;
        }
        else if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (long)0;
            }
            return Long.parseLong(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (int)0;
            }
            return Integer.parseInt(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            if (codeValue.equals("") == true)
            {
                return null;
            }
            return new Date(Long.parseLong(codeValue));
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            if (codeValue.equals("") == true)
            {
                return false;
            }
            return Boolean.parseBoolean(codeValue);
        }
        else
        {
            return (String)codeValue;
        }
    }


    private static class OpenHelper extends SQLiteOpenHelper {

        OpenHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE IF  NOT EXISTS code" +
            "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
}

Panggilan sederhana setelah itu

dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;

50
2018-06-23 17:07



Saya pikir saya menemukan jawabannya. Biarkan saya menceritakan apa yang telah saya lakukan dengan kata-kata sederhana:

Misalkan saya memiliki dua aktivitas, activity1 dan activity2 dan saya menavigasi dari activity1 ke activity2 (saya telah melakukan beberapa pekerjaan dalam aktivitas2) dan kembali lagi ke aktivitas 1 dengan mengklik tombol dalam activity1. Sekarang pada tahap ini saya ingin kembali ke activity2 dan saya ingin melihat activity2 saya dalam kondisi yang sama ketika saya terakhir meninggalkan activity2.

Untuk skenario di atas yang telah saya lakukan adalah bahwa dalam manifes saya membuat beberapa perubahan seperti ini:

<activity android:name=".activity2"
          android:alwaysRetainTaskState="true"      
          android:launchMode="singleInstance">
</activity>

Dan di activity1 pada event klik tombol yang telah saya lakukan seperti ini:

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);

Dan di activity2 pada event klik tombol yang telah saya lakukan seperti ini:

Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);

Sekarang apa yang akan terjadi adalah apa pun perubahan yang kita buat dalam aktivitas2 tidak akan hilang, dan kita dapat melihat aktivitas2 dalam keadaan yang sama seperti yang kita tinggalkan sebelumnya.

Saya percaya ini adalah jawabannya dan ini berfungsi dengan baik untuk saya. Koreksi saya jika saya salah.


50
2018-02-05 11:35