Bagaimana cara menyimpan status aktivitas menggunakan save instance state?

2620

Saya telah bekerja pada platform Android SDK, dan sedikit tidak jelas bagaimana cara menyimpan status aplikasi. Jadi, mengingat tooling kecil contoh 'Hello, Android' ini:

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 keluar dari aplikasi.

Saya yakin solusinya sesederhana mengganti onPauseatau semacamnya, tapi saya sudah mencari-cari di dokumentasi selama 30 menit atau lebih dan belum menemukan sesuatu yang jelas.

Bernard
sumber
9
Kapan disimpanInstanceState == nol dan kapan bukan nol?
Trojan.ZBOT
90
Anda secara eksplisit menghancurkan aktivitas Anda dengan - seperti yang Anda katakan, menavigasi menjauh darinya, seperti dengan menekan kembali. Sebenarnya, skenario di mana 'saveInstanceState' ini digunakan, adalah ketika Android menghancurkan aktivitas Anda untuk rekreasi. Misalnya: Jika Anda mengubah bahasa ponsel Anda saat aktivitas sedang berjalan (maka sumber daya yang berbeda dari proyek Anda perlu dimuat). Skenario lain yang sangat umum adalah ketika Anda memutar telepon ke samping sehingga aktivitas dibuat kembali dan ditampilkan dalam lanskap.
villoren
16
Untuk mendapatkan pesan kedua, aktifkan "Jangan simpan aktivitas" di opsi dev. Tekan tombol beranda dan kembali dari baru.
Yaroslav Mytkalyk
6
Anda dapat melakukannya dengan: onSaveInstanceState (Bundle SavedInstanceState)
VahidHoseini

Jawaban:

2568

Anda perlu mengganti onSaveInstanceState(Bundle savedInstanceState)dan menulis nilai status aplikasi yang ingin Anda ubah ke Bundleparameter 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 ("Pasangan Nama-Nilai"), dan itu akan diteruskan ke onCreate()dan juga di onRestoreInstanceState()mana Anda kemudian akan mengekstrak nilai dari aktivitas 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");
}

Atau dari sebuah fragmen.

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(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 instance untuk aplikasi Anda (pilihan, teks yang belum disimpan, dll.).

Reto Meier
sumber
24
Adakah kemungkinan ini berfungsi di ponsel, tetapi tidak di emulator? Sepertinya saya tidak bisa mendapatkan saveInstanceState yang bukan nol.
Adam Jack
491
HATI-HATI: Anda perlu menelepon super.onSaveInstanceState (disimpanInstanceState) sebelum menambahkan nilai Anda ke Bundle, atau mereka akan terhapus pada panggilan itu (Droid X Android 2.2).
jkschneider
121
Hati-hati: dokumentasi resmi menyatakan, bahwa Anda harus menyimpan informasi penting dalam onPause-Method karena onsaveinstance-method bukan bagian dari siklus hidup android. developer.android.com/reference/android/app/Activity.html
schlingel
32
Fakta itu secara efektif membuat onSaveInstanceStatehampir tidak berguna kecuali hanya untuk perubahan orientasi layar. Di hampir semua kasus lain, Anda tidak akan pernah bisa mengandalkannya dan perlu menyimpan keadaan UI secara manual di tempat lain. Atau mencegah aplikasi Anda terbunuh dengan mengabaikan perilaku tombol KEMBALI. Saya tidak mengerti mengapa mereka bahkan menerapkannya seperti ini sejak awal. Benar-benar tidak intuitif. Dan Anda tidak dapat memiliki Bundle yang diberikan sistem untuk menyelamatkan sesuatu kecuali dalam metode yang sangat khusus ini.
chakrit
12
Perhatikan bahwa menyimpan / memulihkan keadaan UI ke / dari Bundle secara otomatis diurus untuk Viewid yang telah ditetapkan id . Dari onSaveInstanceStatedokumen: "Implementasi default menangani sebagian besar keadaan UI per-instance untuk Anda dengan memanggil onSaveInstanceState()setiap tampilan dalam hierarki yang memiliki id, dan dengan menyimpan id dari tampilan yang saat ini fokus (semuanya dipulihkan oleh implementasi default dari onRestoreInstanceState(Bundle)) "
Vicky Chijwani
433

The savedInstanceStatehanya untuk menyimpan negara terkait dengan contoh saat suatu kegiatan, misalnya navigasi saat ini atau info seleksi, sehingga jika Menghancurkan Android dan recreates suatu kegiatan, itu bisa kembali seperti sebelum. Lihat dokumentasi untuk onCreatedanonSaveInstanceState

Untuk keadaan yang lebih lama, pertimbangkan untuk menggunakan database SQLite, file, atau preferensi. Lihat Menyimpan Status Tetap .

Dave L.
sumber
3
Kapan disimpanInstanceState == nol dan kapan bukan nol?
Trojan.ZBOT
6
saveInstanceState adalah nol ketika sistem membuat instance baru dari Aktivitas Anda dan bukan nol ketika itu memulihkan.
Gabriel Câmara
7
... yang menimbulkan pertanyaan kapan sistem perlu membuat instance baru Activity. Beberapa cara untuk keluar dari aplikasi tidak membuat bundel, jadi instance baru harus dibuat. Ini adalah masalah mendasar; itu berarti seseorang tidak dapat mengandalkan keberadaan bundel, dan harus melakukan beberapa cara alternatif penyimpanan persisten. Manfaat onSave / onRestoreInstanceState adalah bahwa ini adalah mekanisme yang dapat dilakukan sistem secara tiba - tiba , tanpa menghabiskan banyak sumber daya sistem. Jadi, sangat baik untuk mendukung itu, serta memiliki penyimpanan yang persisten untuk keluar dengan lebih anggun dari aplikasi.
ToolmakerSteve
415

Perhatikan bahwa ini TIDAK aman untuk digunakan onSaveInstanceStatedan onRestoreInstanceState untuk data yang persisten , menurut dokumentasi pada status Aktivitas di http://developer.android.com/reference/android/app/Activity.html .

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

Perhatikan bahwa penting untuk menyimpan data persisten di onPause() alih-alih onSaveInstanceState(Bundle) karena nanti bukan bagian dari panggilan balik siklus hidup, jadi tidak akan dipanggil dalam setiap situasi seperti yang dijelaskan dalam dokumentasinya.

Dengan kata lain, masukkan kode save / restore Anda untuk data yang persisten onPause() dan onResume()!

EDIT : Untuk klarifikasi lebih lanjut, inilah onSaveInstanceState()dokumentasinya:

Metode ini dipanggil sebelum suatu aktivitas dapat dibunuh sehingga ketika kembali beberapa waktu di masa depan ia dapat memulihkan kondisinya. Misalnya, jika aktivitas B diluncurkan di depan aktivitas A, dan di beberapa titik aktivitas A dibunuh untuk merebut kembali sumber daya, aktivitas A akan memiliki kesempatan untuk menyimpan keadaan saat ini dari antarmuka penggunanya melalui metode ini sehingga ketika pengguna kembali untuk aktivitas A, keadaan antarmuka pengguna dapat dipulihkan melalui onCreate(Bundle)atau onRestoreInstanceState(Bundle).

Steve Moseley
sumber
55
Hanya untuk nitpick: itu juga tidak aman. Ini hanya tergantung pada apa yang ingin Anda pertahankan dan untuk berapa lama, yang @Bernard tidak sepenuhnya jelas dalam pertanyaan aslinya. InstanceState sangat cocok untuk menjaga keadaan UI saat ini (data dimasukkan ke dalam kontrol, posisi saat ini dalam daftar dan sebagainya), sedangkan Jeda / Lanjutkan adalah satu-satunya kemungkinan penyimpanan persisten jangka panjang.
Pontus Gagge
30
Ini harus diturunkan. Ini tidak aman untuk digunakan di (Simpan | Kembalikan) InstanceState seperti metode siklus hidup (yaitu melakukan hal lain di dalamnya selain menyimpan / mengembalikan keadaan). Mereka sangat baik untuk menyimpan / memulihkan keadaan. Juga, bagaimana Anda ingin menyimpan / mengembalikan keadaan di onPause dan onResume? Anda tidak mendapatkan bundel dalam metode-metode yang dapat Anda gunakan, jadi Anda harus menggunakan beberapa tabungan negara lain, dalam database, file, dll. Yang bodoh.
Felix
141
Kita tidak boleh memilih orang ini setidaknya dia melakukan upaya untuk memeriksa dokumentasi dan saya pikir kita orang di sini untuk benar-benar membangun komunitas berpengetahuan dan saling membantu untuk tidak BAWAH VOTE. jadi 1 suara untuk upaya dan saya akan meminta Anda orang untuk tidak memilih suara memilih atau tidak memilih .... orang ini menghapus kebingungan bahwa seseorang ingin memiliki ketika melalui dokumentasi. 1 suara :)
AZ_
21
Saya kira jawaban ini tidak layak untuk downvote. Setidaknya ia berusaha menjawab dan mengutip satu bagian dari dokumen.
GSree
34
Jawaban ini benar-benar benar dan pantas untuk suara UP, tidak turun! Biarkan saya mengklarifikasi perbedaan antara negara untuk orang-orang yang tidak melihatnya. Status GUI, seperti tombol radio yang dipilih dan beberapa teks di bidang input, jauh lebih penting daripada status data, seperti catatan yang ditambahkan ke daftar yang ditampilkan dalam ListView. Yang terakhir harus disimpan ke database di onPause karena itu satu-satunya panggilan yang dijamin. Jika Anda meletakkannya di onSaveInstanceState, Anda berisiko kehilangan data jika itu tidak dipanggil. Tetapi jika pemilihan tombol radio tidak disimpan untuk alasan yang sama - itu bukan masalah besar.
JBM
206

Rekan saya menulis sebuah artikel yang menjelaskan kondisi aplikasi pada perangkat Android termasuk penjelasan tentang aktivitas siklus hidup dan informasi negara, bagaimana untuk menyimpan informasi negara, dan menabung untuk negara Bundledan SharedPreferencesdan lihatlah disini .

Artikel ini mencakup tiga pendekatan:

Menyimpan data kontrol variabel / UI lokal untuk masa aplikasi (yaitu sementara) menggunakan bundel keadaan instance

[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);
}

Menyimpan data kontrol variabel / UI lokal 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();
}

Membuat instance objek tetap hidup di memori antara aktivitas dalam masa aplikasi menggunakan contoh non-konfigurasi yang dipertahankan

[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();
}
Martin Belcher - AtWrk
sumber
3
@ MartinBelcher-Eigo Article mengatakan tentang data dalam SharedPreferences bahwa "Data ini ditulis ke database pada perangkat .." Saya percaya bahwa data tersebut disimpan dalam file di direktori aplikasi sistem file.
Tom
2
@Tom data SharefPrefs ditulis ke file xml. Apakah xml semacam basis data? Saya akan mengatakan;)
MaciejGórski
148

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

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

Menjelajahi semua utas ini, saya curiga bahwa sebagian besar pengembang waktu membicarakan dua masalah berbeda ini secara bersamaan ... maka semua kebingungan dan laporan "ini tidak berhasil untuk saya".

Pertama, untuk mengklarifikasi perilaku 'yang dimaksudkan': onSaveInstance dan onRestoreInstance rapuh dan hanya untuk keadaan sementara. Penggunaan yang dimaksud (afaict) adalah untuk menangani rekreasi Aktivitas ketika ponsel diputar (perubahan orientasi). Dengan kata lain, penggunaan yang dimaksud adalah ketika Aktivitas Anda secara logis masih 'di atas', tetapi masih harus diperkuat kembali oleh sistem. Bundel yang disimpan tidak bertahan di luar proses / memori / gc, jadi Anda tidak dapat benar-benar mengandalkan ini jika aktivitas Anda mengarah ke latar belakang. Ya, mungkin memori Aktivitas Anda akan bertahan dalam perjalanannya ke latar belakang dan lolos dari GC, tetapi ini tidak dapat diandalkan (juga tidak dapat diprediksi).

Jadi, jika Anda memiliki skenario di mana ada 'kemajuan pengguna' yang berarti atau kondisi 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 menyulitkan 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 pemanggilan berikutnya akan membuat tugas BARU ... Anda secara efektif akan memiliki dua contoh berbeda dari aplikasi Anda menghuni tumpukan yang sama ... yang menjadi sangat aneh sangat cepat. Ini tampaknya terjadi ketika Anda meluncurkan aplikasi selama pengembangan (yaitu dari Eclipse atau Intellij), sehingga pengembang sering mengalami hal ini. Tetapi juga melalui beberapa mekanisme pembaruan toko aplikasi (sehingga berdampak pada pengguna Anda juga).

Saya berjuang melalui utas ini selama berjam-jam sebelum saya menyadari bahwa masalah utama saya adalah bug ini, bukan perilaku kerangka yang dimaksud. Langgan yang bagus dansolusi (PEMBARUAN: lihat di bawah) tampaknya berasal dari pengguna @kulaula dalam jawaban ini:

Perilaku tombol tekan rumah

PEMBARUAN Juni 2013 : Beberapa bulan kemudian, saya akhirnya menemukan solusi 'benar'. Anda tidak perlu mengelola sendiri flag yang dimulaiApp stateful, Anda dapat mendeteksi ini dari kerangka kerja dan jaminan dengan tepat. Saya menggunakan ini di 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;
    }
}
Mike Repass
sumber
87

onSaveInstanceStatedipanggil ketika sistem membutuhkan memori dan membunuh aplikasi. Itu tidak dipanggil ketika pengguna hanya menutup aplikasi. Jadi saya pikir negara aplikasi juga harus disimpan. onPauseIni harus disimpan ke beberapa penyimpanan persisten seperti PreferencesatauSqlite

Fedor
sumber
36
Maaf, itu tidak sepenuhnya benar. onSaveInstanceState dipanggil sebelum aktivitas perlu dibuat kembali. yaitu setiap kali pengguna memutar perangkat. Ini dimaksudkan untuk menyimpan status tampilan sementara. Ketika android memaksa aplikasi untuk menutup, onSaveInstanceState sebenarnya TIDAK disebut (itulah sebabnya tidak aman untuk menyimpan data aplikasi penting). onPause, namun dijamin akan dipanggil sebelum aktivitas terbunuh, sehingga harus digunakan untuk menyimpan info permanen di preferensi atau Squlite. Jawaban yang benar, alasan yang salah.
moveaway00
74

Kedua metode ini bermanfaat 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 membutuhkan pendekatan penyimpanan yang 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 status aplikasi) onSaveInstanceState()dan onRestoreInstanceState()biasanya memadai.

Jika Anda menyimpan data keadaan dengan cara yang terus-menerus, itu dapat dimuat ulang dalam onResume()atau onCreate()(atau sebenarnya pada panggilan siklus hidup). Ini mungkin atau mungkin bukan perilaku yang diinginkan. Jika Anda menyimpannya dalam bundel dalam InstanceState, maka itu bersifat sementara dan hanya cocok untuk menyimpan data untuk digunakan dalam 'sesi' pengguna yang sama (saya menggunakan istilah sesi secara longgar) tetapi tidak di antara 'sesi'.

Bukannya satu pendekatan lebih baik dari yang lain, seperti segalanya, hanya penting untuk memahami perilaku apa yang Anda butuhkan dan memilih pendekatan yang paling tepat.

David
sumber
70

Menyimpan keadaan adalah kludge yang terbaik sejauh yang saya ketahui. Jika Anda perlu menyimpan data persisten, cukup gunakan database SQLite . 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;
Mike A.
sumber
9
Karena butuh terlalu lama untuk memuat database SQLite, mengingat ini berada di jalur kritis untuk menunjukkan kepada pengguna aplikasi UI. Saya belum menghitung waktunya, jadi saya senang bisa dikoreksi, tetapi tentu saja memuat dan membuka file database tidak akan cepat?
Tom
5
Terima kasih banyak telah memberikan solusi yang dapat dipotong dan ditempelkan oleh pemula ke dalam aplikasi mereka dan langsung digunakan! @ Tom Sejauh kecepatannya, dibutuhkan sekitar tujuh detik untuk menyimpan 1000 pasang, tetapi Anda bisa melakukannya dalam AsyncTask. Namun, Anda perlu menambahkan akhirnya {cursor.close ()} atau itu akan macet dari kehabisan memori saat melakukan ini.
Noumenon
3
Saya menemukan ini dan sementara tampaknya rapi saya ragu untuk mencoba menggunakan ini di Google Glass, yang merupakan perangkat yang saya kerjakan / dengan akhir-akhir ini.
Stephen Tetreault
61

Saya pikir saya menemukan jawabannya. Biarkan saya memberi tahu 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 di activity2) dan kembali lagi ke aktivitas 1 dengan mengklik tombol di activity1. Sekarang pada tahap ini saya ingin kembali ke aktivitas2 dan saya ingin melihat aktivitas2 saya dalam kondisi yang sama ketika saya terakhir meninggalkan aktivitas2.

Untuk skenario di atas apa 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 acara 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 pada 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 bahwa 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.

roy mathew
sumber
2
@bagusflyer peduli untuk lebih spesifik ??? Komentar Anda tidak membantu dan tidak ada yang bisa membantu Anda berdasarkan itu.
Stephen Tetreault
2
Ini adalah jawaban untuk situasi yang berbeda: dua aktivitas dalam aplikasi yang sama. OP adalah tentang meninggalkan aplikasi (mis. Tombol beranda, atau cara lain untuk beralih ke aplikasi lain).
ToolmakerSteve
44

onSaveInstanceState()untuk data sementara (dikembalikan ke onCreate()/ onRestoreInstanceState()), onPause()untuk data persisten (dikembalikan ke onResume()). Dari sumber daya teknis Android:

onSaveInstanceState () dipanggil oleh Android jika Aktivitas dihentikan dan dapat dibunuh sebelum dilanjutkan! Ini berarti ia harus menyimpan kondisi apa pun yang diperlukan untuk menginisialisasi ulang ke kondisi yang sama ketika Kegiatan dimulai kembali. Ini adalah lawan dari metode onCreate (), dan pada kenyataannya bundel SimpananInstanceState yang diteruskan ke onCreate () adalah Bundel yang sama yang Anda bangun sebagai outState dalam metode onSaveInstanceState ().

onPause () dan onResume () juga merupakan metode gratis. onPause () selalu dipanggil saat Activity berakhir, bahkan jika kita menghasutnya (dengan panggilan finish () misalnya). Kami akan menggunakan ini untuk menyimpan catatan saat ini kembali ke database. Praktik yang baik adalah melepaskan sumber daya apa pun yang dapat dirilis selama onPause () juga, untuk mengambil lebih sedikit sumber daya saat dalam keadaan pasif.

Ixx
sumber
40

Betulkah onSaveInstanceState() - dipanggil saat Kegiatan beralih ke latar belakang.

Kutipan dari dokumen: "Metode ini dipanggil sebelum suatu aktivitas dapat dimatikan sehingga ketika kembali suatu saat nanti ia dapat memulihkan kondisinya." Sumber

kamu-foka
sumber
37

Untuk membantu mengurangi boilerplate, saya menggunakan yang berikut ini interfacedan classuntuk membaca / menulis ke Bundlekeadaan instance yang hemat.


Pertama, buat antarmuka yang akan digunakan untuk membubuhi keterangan variabel instan Anda:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.FIELD
})
public @interface SaveInstance {

}

Lalu, buat kelas tempat refleksi akan digunakan untuk menyimpan nilai ke bundel:

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;

import java.io.Serializable;
import java.lang.reflect.Field;

/**
 * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
 * SaveInstance}.</p>
 */
public class Icicle {

    private static final String TAG = "Icicle";

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #load(Bundle, Object)
     */
    public static void save(Bundle outState, Object classInstance) {
        save(outState, classInstance, classInstance.getClass());
    }

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #load(Bundle, Object, Class)
     */
    public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
        if (outState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    field.setAccessible(true);
                    String key = className + "#" + field.getName();
                    try {
                        Object value = field.get(classInstance);
                        if (value instanceof Parcelable) {
                            outState.putParcelable(key, (Parcelable) value);
                        } else if (value instanceof Serializable) {
                            outState.putSerializable(key, (Serializable) value);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not added to the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #save(Bundle, Object)
     */
    public static void load(Bundle savedInstanceState, Object classInstance) {
        load(savedInstanceState, classInstance, classInstance.getClass());
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #save(Bundle, Object, Class)
     */
    public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
        if (savedInstanceState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    String key = className + "#" + field.getName();
                    field.setAccessible(true);
                    try {
                        Object fieldVal = savedInstanceState.get(key);
                        if (fieldVal != null) {
                            field.set(classInstance, fieldVal);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

}

Contoh penggunaan:

public class MainActivity extends Activity {

    @SaveInstance
    private String foo;

    @SaveInstance
    private int bar;

    @SaveInstance
    private Intent baz;

    @SaveInstance
    private boolean qux;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icicle.load(savedInstanceState, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icicle.save(outState, this);
    }

}

Catatan: Kode ini diadaptasi dari proyek perpustakaan bernama AndroidAutowire yang dilisensikan di bawah lisensi MIT .

Jared Rummler
sumber
34

Sementara itu saya secara umum tidak menggunakan lagi

Bundle savedInstanceState & Co

Siklus hidup bagi kebanyakan kegiatan terlalu rumit dan tidak perlu.

Dan Google menyatakan sendiri, bahkan TIDAK dapat diandalkan.

Cara saya adalah segera menyimpan perubahan apa pun dalam preferensi:

 SharedPreferences p;
 p.edit().put(..).commit()

Dalam beberapa hal, SharedPreferences berfungsi seperti halnya Bundel. Dan secara alami dan pada awalnya nilai-nilai tersebut harus dibaca dari preferensi.

Dalam hal data yang kompleks, Anda dapat menggunakan SQLite daripada menggunakan preferensi.

Saat menerapkan konsep ini, aktivitas hanya terus menggunakan status yang terakhir disimpan, terlepas dari apakah itu merupakan pembukaan awal dengan reboot di antara atau membuka kembali karena tumpukan belakang.

Stefan bachert
sumber
31

Untuk menjawab pertanyaan asli secara langsung. saveInstancestate adalah nol karena Aktivitas Anda tidak pernah dibuat ulang.

Aktivitas Anda hanya akan dibuat ulang dengan bundel status bila:

  • Perubahan konfigurasi seperti mengubah orientasi atau bahasa telepon yang mungkin memerlukan instance aktivitas baru untuk dibuat.
  • Anda kembali ke aplikasi dari latar belakang setelah OS menghancurkan aktivitas.

Android akan menghancurkan aktivitas latar belakang saat berada di bawah tekanan memori atau setelah mereka berada di latar untuk jangka waktu yang lama.

Ketika menguji halo dunia Anda, ada beberapa cara untuk pergi dan kembali ke Aktivitas.

  • Ketika Anda menekan tombol kembali Kegiatan selesai. Meluncurkan ulang aplikasi adalah contoh baru. Anda tidak melanjutkan dari latar belakang sama sekali.
  • Saat Anda menekan tombol beranda atau menggunakan pengalih tugas, Aktivitas akan masuk ke latar belakang. Saat menavigasi kembali ke aplikasi onCreate hanya akan dipanggil jika Aktivitas harus dihancurkan.

Dalam kebanyakan kasus, jika Anda hanya menekan di rumah dan kemudian meluncurkan aplikasi lagi aktivitas tidak perlu dibuat kembali. Sudah ada di memori sehingga onCreate () tidak akan dipanggil.

Ada opsi di bawah Pengaturan -> Opsi Pengembang yang disebut "Jangan simpan aktivitas". Saat diaktifkan, Android akan selalu menghancurkan aktivitas dan membuatnya kembali ketika mereka dilatar belakangi. Ini adalah opsi yang bagus untuk dibiarkan aktif ketika berkembang karena ini mensimulasikan skenario terburuk. (Perangkat memori rendah mendaur ulang aktivitas Anda sepanjang waktu).

Jawaban lain sangat berharga karena mereka mengajarkan Anda cara yang benar untuk menyimpan keadaan tetapi saya tidak merasa mereka benar-benar menjawab MENGAPA kode Anda tidak berfungsi seperti yang Anda harapkan.

Jared Kells
sumber
28

Metode onSaveInstanceState(bundle)dan onRestoreInstanceState(bundle)berguna untuk persistensi data hanya sambil memutar layar (perubahan orientasi).
Mereka bahkan tidak baik saat beralih di antara aplikasi (karena onSaveInstanceState()metode ini dipanggil tetapi onCreate(bundle)dan onRestoreInstanceState(bundle)tidak dipanggil lagi.
Untuk lebih gigih menggunakan preferensi bersama. Baca artikel ini

Mahorad
sumber
2
Dalam kasus Anda onCreatedan onRestoreInstanceStatetidak dipanggil karena Activitytidak dihancurkan sama sekali ketika Anda beralih aplikasi, jadi tidak perlu mengembalikan apa pun. Panggilan Android onSaveInstanceStateuntuk berjaga-jaga jika Aktivitas dihancurkan kemudian (yang terjadi dengan kepastian 100% ketika memutar layar karena seluruh konfigurasi perangkat telah berubah dan Aktivitas harus dibuat ulang dari awal).
Vicky Chijwani
20

Masalah saya adalah bahwa saya membutuhkan kegigihan hanya selama masa aplikasi (yaitu satu eksekusi termasuk memulai sub-kegiatan lainnya dalam aplikasi yang sama dan memutar perangkat, dll). Saya mencoba berbagai kombinasi jawaban di atas tetapi tidak mendapatkan apa yang saya inginkan dalam semua situasi. Pada akhirnya yang berhasil bagi saya adalah mendapatkan referensi ke saveInstanceState selama onCreate:

mySavedInstanceState=savedInstanceState;

dan menggunakannya untuk mendapatkan isi dari variabel saya ketika saya membutuhkannya, di sepanjang baris:

if (mySavedInstanceState !=null) {
   boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}

Saya menggunakan onSaveInstanceStatedan onRestoreInstanceStateseperti yang disarankan di atas tetapi saya kira saya juga bisa atau sebagai alternatif menggunakan metode saya untuk menyimpan variabel ketika itu berubah (misalnya menggunakan putBoolean)

penjelajah
sumber
19

Meskipun jawaban yang diterima benar, ada metode yang lebih cepat dan lebih mudah untuk menyimpan status Aktivitas di Android menggunakan perpustakaan yang disebut Icepick . Icepick adalah prosesor anotasi yang menangani semua kode boilerplate yang digunakan untuk menyimpan dan memulihkan kondisi untuk Anda.

Melakukan sesuatu seperti ini dengan Icepick:

class MainActivity extends Activity {
  @State String username; // These will be automatically saved and restored
  @State String password;
  @State int age;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}

Sama dengan melakukan ini:

class MainActivity extends Activity {
  String username;
  String password;
  int age;

  @Override
  public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString("MyString", username);
    savedInstanceState.putString("MyPassword", password);
    savedInstanceState.putInt("MyAge", age); 
    /* remember you would need to actually initialize these variables before putting it in the
    Bundle */
  }

  @Override
  public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    username = savedInstanceState.getString("MyString");
    password = savedInstanceState.getString("MyPassword");
    age = savedInstanceState.getInt("MyAge");
  }
}

Icepick akan bekerja dengan objek apa pun yang menyimpan statusnya dengan a Bundle.

kevinc
sumber
16

Ketika suatu kegiatan dibuat itu metode onCreate () dipanggil.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

SavedInstanceState adalah objek dari kelas Bundle yang null untuk pertama kalinya, tetapi berisi nilai saat dibuat ulang. Untuk menyimpan status Aktivitas Anda harus mengganti onSaveInstanceState ().

   @Override
    protected void onSaveInstanceState(Bundle outState) {
      outState.putString("key","Welcome Back")
        super.onSaveInstanceState(outState);       //save state
    }

letakkan nilai Anda di objek Bundel "outState" seperti outState.putString ("kunci", "Selamat Datang Kembali") dan simpan dengan memanggil super. Ketika aktivitas akan dihancurkan, statusnya akan disimpan dalam objek Bundle dan dapat dikembalikan setelah rekreasi di onCreate () atau onRestoreInstanceState (). Bundel yang diterima di onCreate () dan onRestoreInstanceState () sama.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }

atau

  //restores activity's saved state
 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
      String restoredMessage=savedInstanceState.getString("key");
    }
Mansuu ....
sumber
15

Pada dasarnya ada dua cara untuk mengimplementasikan perubahan ini.

  1. menggunakan onSaveInstanceState()dan onRestoreInstanceState().
  2. Dalam manifes android:configChanges="orientation|screenSize".

Saya benar-benar tidak merekomendasikan untuk menggunakan metode kedua. Karena dalam salah satu pengalaman saya itu menyebabkan setengah dari layar perangkat hitam saat berputar dari potret ke lanskap dan sebaliknya.

Menggunakan metode pertama yang disebutkan di atas, kita dapat bertahan data ketika orientasi diubah atau perubahan konfigurasi terjadi. Saya tahu cara di mana Anda dapat menyimpan semua jenis data di dalam objek state saveInstance.

Contoh: Pertimbangkan kasus jika Anda ingin tetap objek Json. buat kelas model dengan getter dan setter.

class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}

Sekarang dalam aktivitas Anda di metode onCreate dan onSaveInstanceState lakukan hal berikut. Akan terlihat seperti ini:

@override
onCreate(Bundle savedInstaceState){
MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey")
JSONObject obj=data.getJsonObject();
//Here you have retained JSONObject and can use.
}


@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Obj is some json object 
MyModel dataToSave= new MyModel();
dataToSave.setJsonObject(obj);
oustate.putSerializable("yourkey",dataToSave); 

}
Krishna
sumber
11

Berikut adalah komentar dari jawaban Steve Moseley (oleh ToolmakerSteve ) yang menempatkan segala sesuatu ke dalam perspektif (secara keseluruhan onSaveInstanceState vs onPause, biaya timur vs saga biaya barat)

@VVK - Saya sebagian tidak setuju. Beberapa cara untuk keluar dari aplikasi tidak memicu onSaveInstanceState (oSIS). Ini membatasi kegunaan oSIS. Nilainya mendukung, untuk sumber daya OS minimal, tetapi jika sebuah aplikasi ingin mengembalikan pengguna ke keadaan di mana mereka berada, tidak peduli bagaimana aplikasi itu keluar, perlu menggunakan pendekatan penyimpanan persisten sebagai gantinya. Saya menggunakan onCreate untuk memeriksa bundel, dan jika ada yang hilang, maka periksa penyimpanan persisten. Ini memusatkan pengambilan keputusan. Saya dapat pulih dari kerusakan, atau keluar dari tombol kembali atau item menu kustom Keluar, atau kembali ke layar pengguna pada beberapa hari kemudian. - ToolmakerSteve 19 Sep 15 'jam 10:38

Sam adalah
sumber
10

Kode Kotlin:

menyimpan:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState.apply {
        putInt("intKey", 1)
        putString("stringKey", "String Value")
        putParcelable("parcelableKey", parcelableObject)
    })
}

dan kemudian di onCreate()atauonRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

Tambahkan nilai default jika Anda tidak ingin memiliki Opsional

Rafol
sumber
9

Untuk mendapatkan data status aktivitas yang disimpan onCreate(), pertama-tama Anda harus menyimpan data di disimpanInstanceState dengan SaveInstanceState(Bundle savedInstanceState)metode utama .

Ketika SaveInstanceState(Bundle savedInstanceState)metode aktivitas penghancuran dipanggil dan di sana Anda menyimpan data yang ingin Anda simpan. Dan Anda mendapatkan hal yang sama onCreate()ketika aktivitas dimulai kembali. (SavedInstanceState tidak akan menjadi nol karena Anda telah menyimpan beberapa data di dalamnya sebelum aktivitas hancur)

ascii_walker
sumber
6

Sederhana cepat untuk menyelesaikan masalah ini adalah menggunakan IcePick

Pertama, atur perpustakaan di app/build.gradle

repositories {
  maven {url "https://clojars.org/repo/"}
}
dependencies {
  compile 'frankiesardo:icepick:3.2.0'
  provided 'frankiesardo:icepick-processor:3.2.0'
}

Sekarang, mari kita periksa contoh di bawah ini bagaimana cara menyimpan status di Aktivitas

public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}

Ini berfungsi untuk Aktivitas, Fragmen atau objek apa pun yang perlu membuat serial keadaannya pada suatu Bundel (mis. ViewPresenters mortar)

Icepick juga dapat menghasilkan kode status instance untuk Tampilan kustom:

class CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
  }

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}
THANN Phearum
sumber
1
@ralphspoon ya, ini berfungsi untuk Fragmen dan Tampilan Kustom. Silakan periksa kode contoh. Saya mengedit jawaban saya. Saya sarankan Anda pergi ke dokumen resmi di sini github.com/frankiesardo/icepick untuk menemukan lebih banyak contoh kode.
THANN Phearum
@ ChetanMehra maksudmu kelas tampilan khusus, kan? Jika ini tampilan khusus, kita dapat mengganti onSaveInstanceState dan onRestoreInstanceState seperti contoh CustomView di atas.
THANN Phearum
Maksud saya objek kelas di dalam tampilan kelas misalnya: kelas CustomView meluas View {@State ClassA a;} atau kelas CustomView meluas View {@ State Inner class {}}
Chetan Mehra
@ THANNPhearum Haruskah saya menanyakannya sebagai pertanyaan lain?
Chetan Mehra
Saya melihat. Jika demikian, ClassA Anda harus Parcelable. Seperti disebutkan bahwa itu berfungsi untuk Aktivitas, Fragmen atau objek apa pun yang perlu membuat serialisasi keadaannya pada sebuah Bundel
THANN Phearum
6

Tidak yakin apakah solusi saya tidak disukai atau tidak, tapi saya menggunakan layanan terikat untuk mempertahankan status ViewModel. Apakah Anda menyimpannya dalam memori dalam layanan atau bertahan dan mengambilnya dari database SQLite tergantung pada kebutuhan Anda. Ini adalah apa layanan rasa apa pun, mereka menyediakan layanan seperti mempertahankan status aplikasi dan logika bisnis umum abstrak.

Karena keterbatasan memori dan pemrosesan yang melekat pada perangkat seluler, saya memperlakukan tampilan Android dengan cara yang mirip dengan laman web. Halaman tidak mempertahankan keadaan, itu murni komponen lapisan presentasi yang hanya bertujuan untuk menyajikan keadaan aplikasi dan menerima input pengguna. Tren terbaru dalam arsitektur aplikasi web menggunakan pola Model, View, Controller (MVC) kuno, di mana halaman adalah View, data domain adalah model, dan pengontrol duduk di belakang layanan web. Pola yang sama dapat digunakan di Android dengan View sedang, well ... View, model adalah data domain Anda, dan Controller diimplementasikan sebagai layanan terikat Android. Kapan pun Anda ingin tampilan berinteraksi dengan controller, ikat padanya saat start / resume dan lepaskan saat stop / pause.

Pendekatan ini memberi Anda bonus tambahan untuk menegakkan prinsip desain Pemisahan Kepedulian karena Anda semua logika bisnis aplikasi dapat dipindahkan ke layanan Anda yang mengurangi duplikat logika di beberapa tampilan dan memungkinkan pandangan untuk menegakkan prinsip desain penting lainnya, Tanggung Jawab Tunggal.

Masuk
sumber
5

Kotlin

Anda harus mengganti onSaveInstanceStatedan onRestoreInstanceStatemenyimpan serta mengambil variabel yang ingin Anda persisten

Grafik siklus hidup

Simpan variabel

public override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)

    // prepare variables here
    savedInstanceState.putInt("kInt", 10)
    savedInstanceState.putBoolean("kBool", true)
    savedInstanceState.putDouble("kDouble", 4.5)
    savedInstanceState.putString("kString", "Hello Kotlin")
}

Ambil variabel

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    val myInt = savedInstanceState.getInt("kInt")
    val myBoolean = savedInstanceState.getBoolean("kBool")
    val myDouble = savedInstanceState.getDouble("kDouble")
    val myString = savedInstanceState.getString("kString")
    // use variables here
}
Sazzad Hissain Khan
sumber
2

Sekarang Android menyediakan ViewModels untuk menyimpan keadaan, Anda harus mencoba menggunakannya bukan saveInstanceState.

M Abdul Sami
sumber
3
Ini tidak benar. Dari dokumentasi: "Tidak seperti keadaan instance yang disimpan, ViewModels dihancurkan selama proses kematian yang diprakarsai oleh sistem. Inilah mengapa Anda harus menggunakan objek ViewModel dalam kombinasi dengan onSaveInstanceState () (atau persistensi disk lain), menyembunyikan pengidentifikasi di saveInstanceState untuk membantu melihat model memuat ulang data setelah kematian sistem. "
Vyacheslav Martynenko
Hanya mengalami ini dengan izin berubah di latar belakang.
Brill Pappin
Saya setuju, dari doc "jika Anda perlu menangani proses kematian yang diprakarsai sistem, Anda mungkin ingin menggunakan onSaveInstanceState () sebagai cadangan."
Zhar
2

Ada cara untuk membuat Android menyelamatkan negara tanpa menerapkan metode apa pun. Cukup tambahkan baris ini ke pernyataan Manifest in Activity Anda:

android:configChanges="orientation|screenSize"

Seharusnya terlihat seperti ini:

<activity
    android:name=".activities.MyActivity"
    android:configChanges="orientation|screenSize">
</activity>

Di sini Anda dapat menemukan informasi lebih lanjut tentang properti ini.

Dianjurkan untuk membiarkan Android menangani ini untuk Anda daripada penanganan secara manual.

IgniteCoders
sumber
2
Ini tidak ada hubungannya dengan menyelamatkan negara, Anda hanya menyerah perubahan orientasi, perlu diingat aplikasi Anda dapat dimulai kembali dan dijeda dan dilanjutkan kapan saja untuk acara yang berbeda
lord-ralf-adolf
1
Jawaban ini adalah untuk mereka yang ingin menyelamatkan negara ketika orientasi berubah dan ingin menghindari pemahaman dan implementasi cara yang kompleks
IgniteCoders
Cukup adil saya mengerti maksud Anda, saya pikir sebagian besar orang yang berjuang untuk menyelamatkan negara menggunakan fragmen karena aktivitas sebenarnya menyimpan stat komponen UI selama mereka memiliki ID, tetapi fragmen lebih istimewa, saya menggunakan fragmen sekali tetapi saya tidak akan pernah menggunakan lagi mereka stat save save as the deal untuk berurusan dengan
lord-ralf-adolf
itu bekerja ... terima kasih
Fanadez
1

Apa yang harus disimpan dan yang tidak?

Pernah bertanya-tanya mengapa teks di dalam EditTextdisimpan secara otomatis saat orientasi berubah? Nah, jawaban ini untuk Anda.

Ketika sebuah instance dari suatu Aktivitas dihancurkan dan Sistem menciptakan sebuah instance baru (misalnya, perubahan konfigurasi). Itu mencoba untuk membuatnya kembali menggunakan set data yang disimpan dari Aktivitas Aktivitas lama ( misalnya keadaan ).

Keadaan instance adalah kumpulan pasangan nilai kunci yang disimpan dalam Bundleobjek.

Secara default, Sistem menyimpan objek Lihat di Bundel misalnya.

  • Teks masuk EditText
  • Gulir posisi dalam ListView, dll.

Jika Anda memerlukan variabel lain untuk disimpan sebagai bagian dari keadaan instance Anda harus OVERRIDE onSavedInstanceState(Bundle savedinstaneState) metode.

Misalnya, int currentScoredalam GameActivity

Lebih detail tentang onSavedInstanceState (Bundle SavedinstaneState) saat menyimpan data

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

Jadi secara tidak sengaja jika Anda lupa untuk memanggil super.onSaveInstanceState(savedInstanceState);perilaku default tidak akan berfungsi yaitu Teks di EditText tidak akan menyimpan.

Yang mana yang harus dipilih untuk memulihkan status Aktivitas?

 onCreate(Bundle savedInstanceState)

ATAU

onRestoreInstanceState(Bundle savedInstanceState)

Kedua metode mendapatkan objek Bundle yang sama, sehingga tidak masalah di mana Anda menulis logika pemulihan Anda. Satu-satunya perbedaan adalah bahwa dalam onCreate(Bundle savedInstanceState)metode Anda harus memberikan cek nol sementara itu tidak diperlukan dalam kasus terakhir. Jawaban lain sudah memiliki cuplikan kode. Anda bisa merujuk mereka.

Lebih detail tentang onRestoreInstanceState (Bundle SavedinstaneState)

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from the saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}

Selalu panggil super.onRestoreInstanceState(savedInstanceState);agar Sistem mengembalikan hierarki Tampilan secara default

Bonus

Itu onSaveInstanceState(Bundle savedInstanceState)dipanggil oleh sistem hanya ketika pengguna berniat untuk kembali ke Aktivitas. Misalnya, Anda menggunakan App X dan tiba-tiba Anda mendapat panggilan. Anda pindah ke aplikasi pemanggil dan kembali ke aplikasi X. Dalam hal inionSaveInstanceState(Bundle savedInstanceState) metode akan dipanggil.

Tetapi pertimbangkan ini jika pengguna menekan tombol kembali. Diasumsikan bahwa pengguna tidak berniat untuk kembali ke Aktivitas, maka dalam hal ini onSaveInstanceState(Bundle savedInstanceState)tidak akan diminta oleh sistem. Poinnya adalah Anda harus mempertimbangkan semua skenario saat menyimpan data.

Tautan yang relevan:

Demo tentang perilaku default
Dokumentasi Resmi Android .

Rohit Singh
sumber
1

Sekarang masuk akal untuk melakukan 2 cara dalam model tampilan. jika Anda ingin menyimpan yang pertama sebagai instance yang disimpan: Anda dapat menambahkan parameter status dalam model tampilan seperti ini https://developer.android.com/topic/libraries/architecture/viewmodel-savedstate#java

atau Anda dapat menyimpan variabel atau objek dalam model tampilan, dalam hal ini model tampilan akan menahan siklus hidup hingga aktivitas dihancurkan.

public class HelloAndroidViewModel extends ViewModel {
   public Booelan firstInit = false;

    public HelloAndroidViewModel() {
        firstInit = false;
    }
    ...
}

public class HelloAndroid extends Activity {

  private TextView mTextView = null;
  HelloAndroidViewModel viewModel = ViewModelProviders.of(this).get(HelloAndroidViewModel.class);
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    //Because even if the state is deleted, the data in the viewmodel will be kept because the activity does not destroy
    if(!viewModel.firstInit){
        viewModel.firstInit = true
        mTextView.setText("Welcome to HelloAndroid!");
    }else{
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}
Umut ADALI
sumber
Anda benar, tetapi perpustakaan ini masih dirilis, jadi saya pikir kita harus menunggu ...
Zhar