Làm thế nào để lưu một trạng thái hoạt động bằng cách sử dụng trạng thái lưu?


2620

Tôi đã làm việc trên nền tảng SDK Android và có một chút không rõ làm thế nào để lưu trạng thái của ứng dụng. Vì vậy, với công cụ tái sử dụng nhỏ này của ví dụ 'Xin chào, Android':

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

Tôi nghĩ rằng nó sẽ đủ cho trường hợp đơn giản nhất, nhưng nó luôn trả lời bằng tin nhắn đầu tiên, bất kể tôi điều hướng khỏi ứng dụng như thế nào.

Tôi chắc chắn rằng giải pháp đơn giản như ghi đè onPausehoặc một cái gì đó tương tự, nhưng tôi đã chọc vào tài liệu trong 30 phút hoặc lâu hơn và không tìm thấy điều gì rõ ràng.


9
Khi nào được lưuInstanceState == null và khi nào nó không null?
Trojan.ZBOT

90
Bạn rõ ràng đang hủy hoại hoạt động của mình bằng cách - như bạn đã nói, điều hướng khỏi nó, chẳng hạn như bằng cách nhấn lại. Trên thực tế, kịch bản sử dụng 'saveInstanceState' này là khi Android phá hủy hoạt động của bạn để giải trí. Đối với mục đích: Nếu bạn thay đổi ngôn ngữ của điện thoại trong khi hoạt động đang chạy (và do đó, các tài nguyên khác nhau từ dự án của bạn cần được tải). Một kịch bản rất phổ biến khác là khi bạn xoay điện thoại sang một bên để hoạt động được tạo lại và hiển thị theo chiều ngang.
dân làng

16
Để nhận thông báo thứ hai, hãy bật "Không giữ hoạt động" trong tùy chọn nhà phát triển. Nhấn nút home và lấy lại từ khoảng cách.
Yaroslav Mytkalyk

5
điều này khá hữu ích cho nhà phát
Syed Raza Mehdi

6
bạn có thể làm điều đó với: onSaveInstanceState (Gói đã lưuInstanceState)
VahidHoseini

Câu trả lời:


2568

Bạn cần ghi đè onSaveInstanceState(Bundle savedInstanceState)và ghi các giá trị trạng thái ứng dụng bạn muốn thay đổi thành Bundletham số như thế này:

@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.
}

Gói về cơ bản là một cách lưu trữ bản đồ NVP ("Cặp giá trị tên") và nó sẽ được chuyển đến onCreate()và cũng là onRestoreInstanceState()nơi bạn sẽ trích xuất các giá trị từ hoạt động như sau:

@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");
}

Hoặc từ một mảnh.

@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");
}

Bạn thường sẽ sử dụng kỹ thuật này để lưu trữ các giá trị cá thể cho ứng dụng của bạn (các lựa chọn, văn bản chưa được lưu, v.v.).


24
Bất kỳ cơ hội này hoạt động trên điện thoại, nhưng không phải trong trình giả lập? Tôi dường như không thể có được một nullInstanceState không null.
Adam Jack

491
CẨN THẬN: bạn cần gọi super.onSaveInstanceState (yetInstanceState) trước khi thêm các giá trị của bạn vào Gói, nếu không chúng sẽ bị xóa trong cuộc gọi đó (Droid X Android 2.2).
jkschneider

121
Cẩn thận: tài liệu chính thức nêu rõ, bạn nên lưu thông tin quan trọng trong Phương thức onPause vì phương thức onsaveinstance không phải là một phần của vòng đời Android. developer.android.com/reference/android/app/Activity.html
schlingel

32
Thực tế đó thực sự làm cho onSaveInstanceStategần như vô dụng trừ trường hợp thay đổi hướng màn hình. Trong hầu hết các trường hợp khác, bạn không bao giờ có thể dựa vào nó và sẽ cần lưu thủ công trạng thái UI của bạn ở một nơi khác. Hoặc ngăn ứng dụng của bạn khỏi bị giết bằng cách ghi đè hành vi nút BACK. Tôi không hiểu tại sao họ thậm chí thực hiện nó như thế này ngay từ đầu. Hoàn toàn không trực quan. Và bạn không thể có Gói đó mà hệ thống cung cấp cho bạn để lưu mọi thứ vào ngoại trừ trong phương pháp rất đặc biệt này.
chakrit

12
Lưu ý rằng việc lưu / khôi phục trạng thái UI đến / từ Gói được tự động chăm sóc cho Viewcác s đã được gán id . Từ các onSaveInstanceStatetài liệu: "Việc triển khai mặc định sẽ xử lý hầu hết trạng thái giao diện người dùng UI cho bạn bằng cách gọi onSaveInstanceState()từng chế độ xem trong cấu trúc phân cấp có id và bằng cách lưu id của chế độ xem hiện đang tập trung (tất cả đều được khôi phục bằng cách triển khai mặc định onRestoreInstanceState(Bundle)) "
Vicky Chijwani

433

Các savedInstanceStatechỉ dành cho tiết kiệm nhà nước liên quan đến một trường hợp hiện tại của một hoạt động, ví dụ như chuyển hướng hiện tại hoặc thông tin lựa chọn, do đó nếu huỷ hoại Android và tái tạo lại một hoạt động, nó có thể trở lại như trước. Xem tài liệu cho onCreateonSaveInstanceState

Đối với trạng thái tồn tại lâu hơn, hãy xem xét sử dụng cơ sở dữ liệu SQLite, tệp hoặc tùy chọn. Xem Tiết kiệm Nhà nước liên tục .


3
Khi nào được lưuInstanceState == null và khi nào nó không null?
Trojan.ZBOT

6
yetInstanceState là null khi hệ thống đang tạo một phiên bản mới của Activity và không null khi nó khôi phục.
Gabriel Câmara

7
... Điều này đặt ra câu hỏi khi nào hệ thống cần tạo một thể hiện mới của Activity. Một số cách thoát khỏi một ứng dụng không tạo ra một gói, do đó, một thể hiện mới phải được tạo. Đây là vấn đề cơ bản; nó có nghĩa là người ta không thể dựa vào sự tồn tại của gói và phải thực hiện một số phương tiện lưu trữ liên tục khác. Lợi ích của onSave / onRestoreInstanceState là đây là một cơ chế mà hệ thống có thể thực hiện đột ngột mà không tiêu tốn nhiều tài nguyên hệ thống. Vì vậy, thật tốt khi hỗ trợ điều đó, cũng như có bộ lưu trữ liên tục để thoát khỏi ứng dụng duyên dáng hơn.
ToolmakerSteve

415

Lưu ý rằng KHÔNG an toàn khi sử dụng onSaveInstanceStateonRestoreInstanceState đối với dữ liệu liên tục , theo tài liệu về các trạng thái Hoạt động trong http://developer.android.com/reference/android/app/Activity.html .

Tài liệu nêu rõ (trong phần 'Vòng đời hoạt động'):

Lưu ý rằng điều quan trọng là phải lưu dữ liệu liên tục onPause()thay onSaveInstanceState(Bundle) vì vì sau này không phải là một phần của các cuộc gọi lại vòng đời, do đó sẽ không được gọi trong mọi tình huống như được mô tả trong tài liệu của nó.

Nói cách khác, đặt mã lưu / khôi phục của bạn cho dữ liệu liên tục onPause()onResume()!

EDIT : Để làm rõ hơn, đây là onSaveInstanceState()tài liệu:

Phương pháp này được gọi trước khi một hoạt động có thể bị giết để khi nó quay lại một thời gian trong tương lai, nó có thể khôi phục trạng thái của nó. Ví dụ: nếu hoạt động B được khởi chạy trước hoạt động A và tại một thời điểm nào đó, hoạt động A bị giết để lấy lại tài nguyên, hoạt động A sẽ có cơ hội lưu trạng thái hiện tại của giao diện người dùng thông qua phương thức này để khi người dùng quay lại đến hoạt động A, trạng thái của giao diện người dùng có thể được khôi phục thông qua onCreate(Bundle)hoặc onRestoreInstanceState(Bundle).


55
Chỉ với nitpick: nó cũng không an toàn. Điều này chỉ phụ thuộc vào những gì bạn muốn bảo tồn và trong bao lâu, điều mà @Bernard không hoàn toàn rõ ràng trong câu hỏi ban đầu của anh ấy. InstanceState hoàn hảo để duy trì trạng thái UI hiện tại (dữ liệu được nhập vào các điều khiển, vị trí hiện tại trong danh sách, v.v.), trong khi Tạm dừng / Tiếp tục là khả năng duy nhất để lưu trữ lâu dài.
Pontus Gagge

30
Điều này nên được hạ cấp. Không an toàn khi sử dụng trên (Lưu | Khôi phục) InstanceState như các phương thức vòng đời (nghĩa là làm bất cứ điều gì khác trong chúng hơn là lưu / khôi phục trạng thái). Chúng hoàn toàn tốt để tiết kiệm / khôi phục trạng thái. Ngoài ra, làm thế nào để bạn muốn lưu / khôi phục trạng thái trong onPause và onResume? Bạn không nhận Gói trong các phương thức mà bạn có thể sử dụng, vì vậy bạn phải sử dụng một số cách tiết kiệm trạng thái khác, trong cơ sở dữ liệu, tệp, v.v.
Felix

141
Chúng ta không nên bỏ phiếu cho người này ít nhất là anh ta đã nỗ lực để xem qua tài liệu và tôi nghĩ rằng mọi người đang ở đây để thực sự xây dựng một cộng đồng hiểu biết và giúp nhau không bỏ phiếu. Vì vậy, 1 phiếu bầu cho nỗ lực và tôi sẽ yêu cầu mọi người không bỏ phiếu thay vì bỏ phiếu hoặc không bỏ phiếu .... người này xóa sự nhầm lẫn mà người ta muốn có khi xem qua tài liệu. 1 phiếu bầu lên :)
AZ_

21
Tôi không nghĩ rằng câu trả lời này xứng đáng với một downvote. Atleast ông đã nỗ lực để trả lời và đã trích dẫn một phần từ doco.
GSree

34
Câu trả lời này là hoàn toàn chính xác và xứng đáng bỏ phiếu UP, không xuống! Hãy để tôi làm rõ sự khác biệt giữa các tiểu bang cho những người không nhìn thấy nó. Trạng thái GUI, như các nút radio được chọn và một số văn bản trong trường đầu vào, ít quan trọng hơn trạng thái dữ liệu, như các bản ghi được thêm vào danh sách được hiển thị trong ListView. Cái sau phải được lưu trữ vào cơ sở dữ liệu trong onPause vì đó là cuộc gọi được bảo đảm duy nhất. Nếu bạn đặt nó vào onSaveInstanceState, bạn có nguy cơ mất dữ liệu nếu điều đó không được gọi. Nhưng nếu lựa chọn nút radio không được lưu vì cùng một lý do - đó không phải là vấn đề lớn.
JBM

206

Đồng nghiệp của tôi đã viết một bài báo giải thích trạng thái ứng dụng trên các thiết bị Android trong đó có giải trình về vòng đời hoạt động và thông tin trạng thái, làm thế nào để lưu trữ thông tin trạng thái, và tiết kiệm cho nhà nước BundleSharedPreferenceshãy nhìn vào đây .

Bài viết bao gồm ba cách tiếp cận:

Lưu trữ dữ liệu điều khiển biến / UI cục bộ cho thời gian tồn tại của ứng dụng (tức là tạm thời) bằng cách sử dụng gói trạng thái cá thể

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

Lưu trữ dữ liệu điều khiển biến / UI cục bộ giữa các phiên bản ứng dụng (tức là vĩnh viễn) bằng cách sử dụng tùy chọn chia sẻ

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

Giữ các thể hiện đối tượng tồn tại trong bộ nhớ giữa các hoạt động trong vòng đời ứng dụng bằng cách sử dụng một thể hiện không cấu hình được giữ lại

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

3
@ MartinBelcher-Eigo Article nói về dữ liệu trong SharedPreferences rằng "Dữ liệu này được ghi vào cơ sở dữ liệu trên thiết bị .." Tôi tin rằng dữ liệu được lưu trữ trong một tệp trong thư mục ứng dụng của hệ thống tệp.
Tom

2
Dữ liệu @Tom SharefPrefs được ghi vào tệp xml. Xml có phải là một loại cơ sở dữ liệu không? Tôi muốn nói là vậy;)
MaciejGórski

148

Đây là một 'gotcha' cổ điển về phát triển Android. Có hai vấn đề ở đây:

  • Có một lỗi Android Framework tinh vi làm phức tạp đáng kể việc quản lý ngăn xếp ứng dụng trong quá trình phát triển, ít nhất là trên các phiên bản cũ (không hoàn toàn chắc chắn nếu / khi / cách nó được sửa). Tôi sẽ thảo luận về lỗi này dưới đây.
  • Cách 'bình thường' hoặc dự định để quản lý vấn đề này, bản thân nó, khá phức tạp với tính hai mặt của onPause / onResume và onSaveInstanceState / onRestoreInstanceState

Duyệt qua tất cả các chủ đề này, tôi nghi ngờ rằng phần lớn các nhà phát triển đang nói về hai vấn đề khác nhau này đồng thời ... do đó tất cả sự nhầm lẫn và báo cáo về "điều này không phù hợp với tôi".

Đầu tiên, để làm rõ hành vi 'dự định': onSaveInstance và onRestoreInstance rất mong manh và chỉ dành cho trạng thái nhất thời. Việc sử dụng dự định (afaict) là để xử lý Hoạt động giải trí khi điện thoại được xoay (thay đổi hướng). Nói cách khác, mục đích sử dụng là khi Hoạt động của bạn vẫn hợp lý 'trên đỉnh', nhưng vẫn phải được hệ thống xác nhận lại. Gói đã lưu không được duy trì bên ngoài tiến trình / bộ nhớ / gc, vì vậy bạn không thể thực sự dựa vào điều này nếu hoạt động của bạn đi vào nền. Có, có lẽ bộ nhớ Hoạt động của bạn sẽ tồn tại trong chuyến đi đến nền và thoát khỏi GC, nhưng điều này không đáng tin cậy (cũng không thể dự đoán được).

Vì vậy, nếu bạn có một kịch bản có ý nghĩa 'tiến trình người dùng' hoặc trạng thái cần được duy trì giữa 'khởi chạy' ứng dụng của bạn, hướng dẫn là sử dụng onPause và onResume. Bạn phải chọn và chuẩn bị một cửa hàng liên tục cho mình.

NHƯNG - có một lỗi rất khó hiểu làm phức tạp tất cả những điều này. Chi tiết tại đây:

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

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

Về cơ bản, nếu ứng dụng của bạn được khởi chạy với cờ SingleTask, và sau đó, bạn khởi chạy nó từ màn hình chính hoặc menu trình khởi chạy, thì lần gọi tiếp theo đó sẽ tạo ra một nhiệm vụ MỚI ... bạn sẽ có hai phiên bản ứng dụng khác nhau cư trú trong cùng một ngăn xếp ... điều này trở nên rất kỳ lạ rất nhanh. Điều này dường như xảy ra khi bạn khởi chạy ứng dụng của mình trong quá trình phát triển (tức là từ Eclipse hoặc Intellij), vì vậy các nhà phát triển gặp phải vấn đề này rất nhiều. Nhưng cũng thông qua một số cơ chế cập nhật cửa hàng ứng dụng (vì vậy nó cũng tác động đến người dùng của bạn).

Tôi đã chiến đấu với các chủ đề này trong nhiều giờ trước khi tôi nhận ra rằng vấn đề chính của tôi là lỗi này, không phải là hành vi khung dự định. Một bài viết tuyệt vời vàcách giải quyết (CẬP NHẬT: xem bên dưới) dường như là từ người dùng @kaciula trong câu trả lời này:

Hành vi bấm phím Home

CẬP NHẬT Tháng 6 năm 2013 : Nhiều tháng sau, cuối cùng tôi cũng đã tìm được giải pháp 'chính xác'. Bạn không cần phải tự mình quản lý bất kỳ cờ startApp nào, bạn có thể phát hiện điều này từ khung và bảo lãnh một cách thích hợp. Tôi sử dụng cái này gần đầu LauncherActivity.onCreate của tôi:

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

87

onSaveInstanceStateđược gọi khi hệ thống cần bộ nhớ và giết chết một ứng dụng. Nó không được gọi khi người dùng chỉ cần đóng ứng dụng. Vì vậy, tôi nghĩ trạng thái ứng dụng cũng nên được lưu trong onPauseNó nên được lưu vào một số lưu trữ liên tục như PreferenceshoặcSqlite


36
Xin lỗi, điều đó không hoàn toàn chính xác. onSaveInstanceState được gọi trước khi hoạt động cần được thực hiện lại. tức là mỗi khi người dùng xoay thiết bị. Nó có nghĩa là để lưu trữ trạng thái xem thoáng qua. Khi android buộc ứng dụng đóng, onSaveInstanceState thực sự KHÔNG được gọi (đó là lý do tại sao nó không an toàn để lưu trữ dữ liệu ứng dụng quan trọng). Tuy nhiên, onPause được đảm bảo được gọi trước khi hoạt động bị hủy, do đó, nó nên được sử dụng để lưu trữ thông tin vĩnh viễn trong tùy chọn hoặc Squlite. Trả lời đúng, sai lý do.
Moveaway00

74

Cả hai phương pháp đều hữu ích và hợp lệ và cả hai đều phù hợp nhất cho các tình huống khác nhau:

  1. Người dùng chấm dứt ứng dụng và mở lại vào một ngày sau đó, nhưng ứng dụng cần tải lại dữ liệu từ phiên trước - điều này đòi hỏi một cách tiếp cận lưu trữ liên tục như sử dụng SQLite.
  2. Người dùng chuyển ứng dụng và sau đó quay lại bản gốc và muốn chọn nơi họ rời đi - lưu và khôi phục dữ liệu gói (như dữ liệu trạng thái ứng dụng) trong onSaveInstanceState()onRestoreInstanceState()thường là đầy đủ.

Nếu bạn lưu dữ liệu trạng thái một cách liên tục, nó có thể được tải lại trong một onResume()hoặc onCreate()(hoặc thực tế trên bất kỳ cuộc gọi vòng đời nào). Điều này có thể hoặc không thể là hành vi mong muốn. Nếu bạn lưu trữ nó trong một gói trong một InstanceState, thì nó là nhất thời và chỉ phù hợp để lưu trữ dữ liệu để sử dụng trong cùng một phiên 'người dùng' (tôi sử dụng thuật ngữ phiên lỏng lẻo) nhưng không phải giữa 'phiên'.

Không phải là một cách tiếp cận tốt hơn cách tiếp cận khác, giống như mọi thứ, điều quan trọng là phải hiểu hành vi bạn yêu cầu và chọn cách tiếp cận phù hợp nhất.


70

Trạng thái tiết kiệm là một loại bùn tốt nhất theo như tôi nghĩ. Nếu bạn cần lưu dữ liệu liên tục, chỉ cần sử dụng cơ sở dữ liệu SQLite . Android làm cho nó SOOO dễ dàng.

Một cái gì đó như thế này:

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) {
        }
    }
}

Một cuộc gọi đơn giản sau đó

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

9
Bởi vì mất quá nhiều thời gian để tải cơ sở dữ liệu SQLite, xem xét rằng đây là trên đường dẫn quan trọng để hiển thị giao diện người dùng của ứng dụng. Tôi chưa thực sự hẹn giờ, vì vậy tôi rất vui khi được sửa, nhưng chắc chắn việc tải và mở tệp cơ sở dữ liệu sẽ không nhanh?
Tom

5
Cảm ơn bạn rất nhiều vì đã cung cấp một giải pháp mà một người mới có thể cắt và dán vào ứng dụng của họ và sử dụng ngay! @Tom Theo tốc độ, phải mất khoảng bảy giây để lưu trữ 1000 cặp, nhưng bạn có thể làm điều đó trong AsyncTask. Tuy nhiên, bạn cần thêm {con trỏ cuối cùng) nếu không nó sẽ bị sập do rò rỉ bộ nhớ trong khi thực hiện việc này.
Noumenon

3
Tôi đã bắt gặp điều này và trong khi nó có vẻ gọn gàng, tôi ngần ngại thử sử dụng nó trên Google Glass, đây là thiết bị tôi đang làm việc / gần đây.
Stephen Tetreault

61

Tôi nghĩ rằng tôi đã tìm thấy câu trả lời. Hãy để tôi nói những gì tôi đã làm bằng những từ đơn giản:

Giả sử tôi có hai hoạt động, hoạt động1 và hoạt động2 và tôi đang điều hướng từ hoạt động 1 đến hoạt động 2 (Tôi đã thực hiện một số công việc trong hoạt động 2) và quay lại hoạt động 1 bằng cách nhấp vào nút trong hoạt động1. Bây giờ ở giai đoạn này tôi muốn quay lại hoạt động2 và tôi muốn thấy hoạt động của mình trong tình trạng tương tự khi tôi rời hoạt động lần cuối2.

Đối với kịch bản trên, điều tôi đã làm là trong bản kê khai tôi đã thực hiện một số thay đổi như thế này:

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

Và trong hoạt động1 trên nút bấm sự kiện tôi đã làm như thế này:

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

Và trong hoạt động 2 trên nút bấm sự kiện tôi đã làm như thế này:

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

Bây giờ điều sẽ xảy ra là bất cứ thay đổi nào chúng ta đã thực hiện trong hoạt động2 sẽ không bị mất và chúng ta có thể xem hoạt động 2 ở cùng trạng thái như chúng ta đã để lại trước đó.

Tôi tin rằng đây là câu trả lời và điều này làm việc tốt cho tôi. Đúng nếu tôi đã sai lầm.


2
@bagusflyer quan tâm để cụ thể hơn ??? Nhận xét của bạn không hữu ích và không ai có thể giúp bạn dựa trên điều đó.
Stephen Tetreault

2
Đây là một câu trả lời cho một tình huống khác nhau: hai hoạt động trong cùng một ứng dụng. OP sắp rời khỏi ứng dụng (ví dụ: nút home hoặc các phương tiện khác để chuyển sang một ứng dụng khác).
ToolmakerSteve

44

onSaveInstanceState()đối với dữ liệu nhất thời (được khôi phục trong onCreate()/ onRestoreInstanceState()), onPause()đối với dữ liệu liên tục (được khôi phục trong onResume()). Từ tài nguyên kỹ thuật Android:

onSaveInstanceState () được gọi bởi Android nếu Hoạt động đang bị dừng và có thể bị hủy trước khi được nối lại! Điều này có nghĩa là nó sẽ lưu trữ bất kỳ trạng thái cần thiết nào để khởi tạo lại cùng điều kiện khi Hoạt động được khởi động lại. Nó là đối tác của phương thức onCreate () và trên thực tế Gói BundInstanceState được truyền vào onCreate () là cùng một Gói mà bạn xây dựng như outState trong phương thức onSaveInstanceState ().

onPause ()onResume () cũng là các phương thức miễn phí. onPause () luôn được gọi khi Activity kết thúc, ngay cả khi chúng tôi kích hoạt lệnh gọi (với kết thúc () chẳng hạn). Chúng tôi sẽ sử dụng điều này để lưu ghi chú hiện tại trở lại cơ sở dữ liệu. Thực hành tốt là phát hành bất kỳ tài nguyên nào có thể được phát hành trong onPause (), để chiếm ít tài nguyên hơn khi ở trạng thái thụ động.


40

Thực sự onSaveInstanceState()được gọi khi các hoạt động đi đến nền.

Trích dẫn từ các tài liệu: "Phương pháp này được gọi trước khi một hoạt động có thể bị giết để khi nó quay trở lại một thời gian trong tương lai, nó có thể khôi phục trạng thái của nó." Nguồn


37

Để giúp giảm nồi hơi, tôi sử dụng cách sau interfaceclassđọc / ghi vào Bundletrạng thái lưu.


Đầu tiên, tạo một giao diện sẽ được sử dụng để chú thích các biến thể hiện của bạn:

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 {

}

Sau đó, tạo một lớp trong đó sự phản chiếu sẽ được sử dụng để lưu các giá trị vào gói:

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

}

Ví dụ sử dụng:

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

}

Lưu ý: Mã này được điều chỉnh từ dự án thư viện có tên AndroidAutowire , được cấp phép theo giấy phép MIT .


34

Trong khi đó tôi nói chung không sử dụng nữa

Bundle savedInstanceState & Co

Vòng đời dành cho hầu hết các hoạt động quá phức tạp và không cần thiết.

Và Google tuyên bố, nó thậm chí KHÔNG đáng tin cậy.

Cách của tôi là lưu mọi thay đổi ngay lập tức trong các tùy chọn:

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

Theo một cách nào đó, SharedPreferences hoạt động tương tự như Gói. Và tự nhiên và đầu tiên các giá trị như vậy phải được đọc từ sở thích.

Trong trường hợp dữ liệu phức tạp, bạn có thể sử dụng SQLite thay vì sử dụng tùy chọn.

Khi áp dụng khái niệm này, hoạt động chỉ tiếp tục sử dụng trạng thái được lưu cuối cùng, bất kể đó là lần mở đầu tiên với khởi động lại ở giữa hoặc mở lại do ngăn xếp phía sau.


31

Để trả lời câu hỏi ban đầu trực tiếp. saveInstancestate là null vì Activity của bạn không bao giờ được tạo lại.

Hoạt động của bạn sẽ chỉ được tạo lại với gói trạng thái khi:

  • Thay đổi cấu hình như thay đổi hướng hoặc ngôn ngữ điện thoại có thể yêu cầu tạo phiên bản hoạt động mới.
  • Bạn quay lại ứng dụng từ nền sau khi HĐH đã hủy hoạt động.

Android sẽ hủy các hoạt động nền khi chịu áp lực bộ nhớ hoặc sau khi chúng ở chế độ nền trong một khoảng thời gian dài.

Khi kiểm tra ví dụ về thế giới xin chào của bạn, có một số cách để rời khỏi và quay lại Hoạt động.

  • Khi bạn nhấn nút quay lại, Hoạt động kết thúc. Khởi chạy lại ứng dụng là một ví dụ hoàn toàn mới. Bạn không tiếp tục từ nền tảng.
  • Khi bạn nhấn nút home hoặc sử dụng trình chuyển đổi tác vụ, Activity sẽ đi vào nền. Khi điều hướng trở lại ứng dụng, onCreate sẽ chỉ được gọi nếu Activity phải bị hủy.

Trong hầu hết các trường hợp, nếu bạn chỉ nhấn vào nhà và sau đó khởi chạy lại ứng dụng, hoạt động sẽ không cần phải được tạo lại. Nó đã tồn tại trong bộ nhớ nên onCreate () sẽ không được gọi.

Có một tùy chọn trong Cài đặt -> Tùy chọn nhà phát triển được gọi là "Không giữ hoạt động". Khi được bật, Android sẽ luôn hủy các hoạt động và tạo lại chúng khi chúng được chạy nền. Đây là một tùy chọn tuyệt vời để bật kích hoạt khi phát triển vì nó mô phỏng trường hợp xấu nhất. (Một thiết bị bộ nhớ thấp tái chế các hoạt động của bạn mọi lúc).

Các câu trả lời khác có giá trị ở chỗ chúng dạy cho bạn các cách chính xác để lưu trữ trạng thái nhưng tôi không cảm thấy chúng thực sự trả lời TẠI SAO mã của bạn không hoạt động theo cách bạn mong đợi.


28

Các phương thức onSaveInstanceState(bundle)onRestoreInstanceState(bundle)hữu ích cho việc duy trì dữ liệu chỉ trong khi xoay màn hình (thay đổi hướng).
Họ thậm chí không tốt trong khi chuyển đổi giữa các ứng dụng (kể từ khi onSaveInstanceState()phương pháp được gọi nhưng onCreate(bundle)onRestoreInstanceState(bundle)không được gọi trở lại.
Để biết thêm chi sử dụng kiên trì sở thích chia sẻ. Đọc bài viết này


2
Trong trường hợp của bạn onCreateonRestoreInstanceStatekhông được gọi vì hoàn toàn Activitykhông bị phá hủy khi bạn chuyển đổi ứng dụng, do đó không cần phải khôi phục bất cứ điều gì. Android onSaveInstanceStatechỉ gọi trong trường hợp Activity bị hủy sau đó (điều này xảy ra với độ chắc chắn 100% khi xoay màn hình vì toàn bộ cấu hình thiết bị đã thay đổi và Activity phải được tạo lại từ đầu).
Vicky Chijwani

20

Vấn đề của tôi là tôi chỉ cần kiên trì trong suốt thời gian sử dụng ứng dụng (tức là thực hiện một lần duy nhất bao gồm bắt đầu các hoạt động phụ khác trong cùng một ứng dụng và xoay thiết bị, v.v.). Tôi đã thử kết hợp nhiều câu trả lời ở trên nhưng không đạt được điều mình muốn trong mọi tình huống. Cuối cùng, điều làm việc với tôi là để có được một tham chiếu đến saveInstanceState trong khi onCreate:

mySavedInstanceState=savedInstanceState;

và sử dụng nó để có được nội dung của biến khi tôi cần, dọc theo dòng:

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

Tôi sử dụng onSaveInstanceStateonRestoreInstanceStatenhư được đề xuất ở trên nhưng tôi đoán tôi cũng có thể hoặc sử dụng phương pháp của mình để lưu biến khi nó thay đổi (ví dụ: sử dụng putBoolean)


19

Mặc dù câu trả lời được chấp nhận là chính xác, có một phương pháp nhanh hơn và dễ dàng hơn để lưu trạng thái Hoạt động trên Android bằng thư viện có tên Icepick . Icepick là một bộ xử lý chú thích, đảm nhiệm tất cả các mã soạn sẵn được sử dụng để lưu và khôi phục trạng thái cho bạn.

Làm một cái gì đó như thế này với 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);
  }
}

Cũng giống như làm điều này:

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 sẽ làm việc với bất kỳ đối tượng nào lưu trạng thái của nó bằng a Bundle.


16

Khi một hoạt động được tạo, phương thức onCreate () được gọi.

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

SavingInstanceState là một đối tượng của lớp Bundle lần đầu tiên là null, nhưng nó chứa các giá trị khi nó được tạo lại. Để lưu trạng thái của Activity, bạn phải ghi đè lênSaveInstanceState ().

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

đặt các giá trị của bạn trong đối tượng Gói "outState" như outState.putString ("key", "Welcome Back") và lưu bằng cách gọi super. Khi hoạt động sẽ bị hủy, trạng thái của nó sẽ được lưu trong đối tượng Bundle và có thể được khôi phục sau khi giải trí trong onCreate () hoặc onRestoreInstanceState (). Gói nhận được trong onCreate () và onRestoreInstanceState () là như nhau.

   @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");
            }
    }

hoặc là

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

15

Về cơ bản có hai cách để thực hiện thay đổi này.

  1. sử dụng onSaveInstanceState()onRestoreInstanceState().
  2. Trong bảng kê khai android:configChanges="orientation|screenSize".

Tôi thực sự không khuyên bạn nên sử dụng phương pháp thứ hai. Vì theo một trong những trải nghiệm của tôi, nó đã khiến một nửa màn hình thiết bị bị đen khi xoay từ dọc sang ngang và ngược lại.

Sử dụng phương pháp đầu tiên được đề cập ở trên, chúng tôi có thể duy trì dữ liệu khi thay đổi hướng hoặc bất kỳ thay đổi cấu hình nào xảy ra. Tôi biết một cách mà bạn có thể lưu trữ bất kỳ loại dữ liệu nào bên trong đối tượng trạng thái đã lưu.

Ví dụ: Xem xét một trường hợp nếu bạn muốn duy trì đối tượng Json. tạo một lớp mô hình với getters và setters.

class MyModel extends Serializable{
JSONObject obj;

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

JSONObject getJsonObject()
return this.obj;
} 
}

Bây giờ trong hoạt động của bạn trong phương thức onCreate và onSaveInstanceState, hãy làm như sau. Nó sẽ trông giống như thế này:

@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); 

}

11

Dưới đây là một nhận xét từ câu trả lời của Steve Moseley (bởi ToolmakerSteve ) đưa mọi thứ vào viễn cảnh (trong toàn bộ onSaveInstanceState vs onPause, chi phí phía đông so với chi phí phía tây)

@VVK - Tôi không đồng ý một phần. Một số cách thoát ứng dụng không kích hoạt onSaveInstanceState (oSIS). Điều này giới hạn tính hữu dụng của oSIS. Nó đáng để hỗ trợ, cho các tài nguyên hệ điều hành tối thiểu, nhưng nếu một ứng dụng muốn đưa người dùng trở về trạng thái mà họ đã sử dụng, bất kể ứng dụng đã thoát như thế nào, thay vào đó, cần phải sử dụng phương pháp lưu trữ liên tục. Tôi sử dụng onCreate để kiểm tra gói và nếu nó bị thiếu, thì hãy kiểm tra bộ nhớ liên tục. Điều này tập trung vào việc ra quyết định. Tôi có thể khôi phục sau sự cố, hoặc thoát nút quay lại hoặc mục menu tùy chỉnh Thoát hoặc quay lại màn hình người dùng trong nhiều ngày sau đó. - ToolmakerSteve 19 tháng 9, 15 lúc 10:38


10

Mã Kotlin:

tiết kiệm:

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

và sau đó trong onCreate()hoặconRestoreInstanceState()

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

Thêm giá trị mặc định nếu bạn không muốn có Tùy chọn


9

Để có được dữ liệu trạng thái hoạt động được lưu trữ onCreate(), trước tiên, bạn phải lưu dữ liệu trong saveInstanceState bằng SaveInstanceState(Bundle savedInstanceState)phương thức ghi đè .

Khi SaveInstanceState(Bundle savedInstanceState)phương thức hủy hoạt động được gọi và ở đó bạn lưu dữ liệu bạn muốn lưu. Và bạn sẽ nhận được tương tự onCreate()khi hoạt động khởi động lại. (SaveInstanceState sẽ không có giá trị vì bạn đã lưu một số dữ liệu trong đó trước khi hoạt động bị hủy)


6

Đơn giản nhanh chóng để giải quyết vấn đề này đang sử dụng IcePick

Đầu tiên, thiết lập thư viện trong app/build.gradle

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

Bây giờ, hãy kiểm tra ví dụ này bên dưới cách lưu trạng thái trong Hoạt động

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

Nó hoạt động cho các Hoạt động, Mảnh vỡ hoặc bất kỳ đối tượng nào cần tuần tự hóa trạng thái của nó trên Gói (ví dụ: ViewPftimeers của vữa)

Icepick cũng có thể tạo mã trạng thái cá thể cho Chế độ xem tùy chỉnh:

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
}

1
@ralphspoon vâng, nó hoạt động cho Fragment và Custom View. Vui lòng kiểm tra mã ví dụ. Tôi chỉnh sửa câu trả lời của tôi. Tôi khuyên bạn nên truy cập tài liệu chính thức tại đây github.com/frankiesardo/icepick để tìm thêm mẫu mã.
THANN Phearum

@ChetanMehra bạn có nghĩa là lớp xem tùy chỉnh, phải không? Nếu đó là chế độ xem tùy chỉnh, chúng ta có thể ghi đè onSaveInstanceState và onRestoreInstanceState như ví dụ trên của CustomView.
THANN Phearum

Ý tôi là đối tượng lớp bên trong lớp view chẳng hạn: class CustomView mở rộng View {@State ClassA a;} hoặc class CustomView mở rộng View {@ State Inside class {}}
Chetan Mehra

@THANNPhearum Tôi có nên hỏi nó như một câu hỏi khác không?
Chetan Mehra

Tôi hiểu rồi. Nếu vậy, ClassA của bạn sẽ là Parcelable. Như đã đề cập rằng Nó hoạt động cho các Hoạt động, Mảnh vỡ hoặc bất kỳ đối tượng nào cần nối tiếp trạng thái của nó trên Gói
THANN Phearum

6

Không chắc giải pháp của tôi có được tán thành hay không, nhưng tôi sử dụng dịch vụ ràng buộc để duy trì trạng thái ViewModel. Việc bạn lưu trữ nó trong bộ nhớ trong dịch vụ hay tiếp tục và truy xuất nó từ cơ sở dữ liệu SQLite tùy thuộc vào yêu cầu của bạn. Đây là những gì dịch vụ của bất kỳ hương vị nào làm, họ cung cấp các dịch vụ như duy trì trạng thái ứng dụng và logic kinh doanh chung trừu tượng.

Do các hạn chế về bộ nhớ và xử lý vốn có trên thiết bị di động, tôi đối xử với các chế độ xem Android theo cách tương tự như một trang web. Trang không duy trì trạng thái, nó hoàn toàn là một thành phần lớp trình bày với mục đích duy nhất là trình bày trạng thái ứng dụng và chấp nhận đầu vào của người dùng. Các xu hướng gần đây trong kiến ​​trúc ứng dụng web sử dụng mẫu Model, View, Controller (MVC) lâu đời, trong đó trang là View, dữ liệu miền là mô hình và bộ điều khiển nằm sau một dịch vụ web. Mẫu tương tự có thể được sử dụng trong Android với Chế độ xem, ... Chế độ xem, mô hình là dữ liệu miền của bạn và Bộ điều khiển được triển khai như một dịch vụ ràng buộc của Android. Bất cứ khi nào bạn muốn một khung nhìn tương tác với bộ điều khiển, hãy liên kết với nó khi bắt đầu / tiếp tục và hủy liên kết khi dừng / tạm dừng.

Cách tiếp cận này mang lại cho bạn phần thưởng bổ sung khi thực thi Nguyên tắc thiết kế tách biệt trong đó tất cả logic kinh doanh ứng dụng của bạn có thể được chuyển vào dịch vụ của bạn nhằm giảm logic trùng lặp trên nhiều chế độ xem và cho phép khung nhìn thực thi một nguyên tắc thiết kế quan trọng khác, Trách nhiệm đơn lẻ.


5

Kotlin

Bạn phải ghi đè onSaveInstanceStateonRestoreInstanceState lưu trữ và truy xuất các biến bạn muốn liên tục

Biểu đồ vòng đời

Lưu trữ các biến

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")
}

Lấy các biến

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
}

2

Bây giờ Android cung cấp ViewModels để lưu trạng thái, bạn nên thử sử dụng trạng thái đó thay vì saveInstanceState.


3
Đây không phải là sự thật. Từ tài liệu: "Không giống như trạng thái cá thể đã lưu, ViewModels bị hủy trong quá trình chết do hệ thống khởi tạo. Đây là lý do tại sao bạn nên sử dụng các đối tượng ViewModel kết hợp với onSaveInstanceState () (hoặc một số lưu trữ đĩa khác) các mô hình tải lại dữ liệu sau khi hệ thống chết. "
Vyacheslav Martynenko

Chỉ cần chạy vào đây với quyền thay đổi trong nền.
Brill Pappin

Tôi đồng ý, từ tài liệu "nếu bạn cần xử lý cái chết của quá trình do hệ thống khởi tạo, bạn có thể muốn sử dụng onSaveInstanceState () làm bản sao lưu."
Zhar

2

Có một cách để làm cho Android lưu các trạng thái mà không cần thực hiện bất kỳ phương pháp nào. Chỉ cần thêm dòng này vào Bản kê khai hoạt động của bạn:

android:configChanges="orientation|screenSize"

Nó sẽ giống như thế này:

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

Ở đây bạn có thể tìm thêm thông tin về khách sạn này.

Bạn nên để Android xử lý việc này cho bạn hơn là xử lý thủ công.


2
Điều này không liên quan gì đến việc lưu trạng thái, bạn chỉ cần từ bỏ thay đổi định hướng, hãy nhớ rằng ứng dụng của bạn có thể được khởi động lại và tạm dừng và tiếp tục lại bất cứ lúc nào cho các sự kiện khác nhau
lord-ralf-adolf

1
Câu trả lời này dành cho những người muốn lưu trạng thái khi định hướng thay đổi và muốn tránh hiểu và thực hiện một cách phức tạp
IgniteCoders

đủ công bằng Tôi thấy quan điểm của bạn, tôi nghĩ rằng hầu hết những người đấu tranh để cứu nhà nước đều sử dụng các đoạn vì các hoạt động thực sự tiết kiệm được các thành phần UI miễn là họ có ID, nhưng các đoạn đặc biệt hơn, tôi đã sử dụng các đoạn một lần nhưng tôi sẽ không bao giờ sử dụng một lần nữa, chỉ số lưu trữ là một nỗi đau để đối phó
chúa tể-ralf-adolf

nó hoạt động ... cảm ơn
Fanadez

1

Những gì để tiết kiệm và những gì không?

Bạn đã bao giờ tự hỏi tại sao văn bản trong EditTextđược lưu tự động trong khi thay đổi hướng? Vâng, câu trả lời này là dành cho bạn.

Khi một phiên bản của Hoạt động bị hủy và Hệ thống sẽ tạo lại một phiên bản mới (ví dụ: thay đổi cấu hình). Nó cố gắng tạo lại nó bằng cách sử dụng một tập hợp dữ liệu đã lưu của Trạng thái hoạt động cũ ( trạng thái thể hiện ).

Trạng thái sơ thẩm là một tập hợp các cặp khóa-giá trị được lưu trữ trong một Bundleđối tượng.

Theo mặc định, Hệ thống lưu các đối tượng Xem trong Gói chẳng hạn.

  • Văn bản trong EditText
  • Vị trí cuộn trong a ListView, v.v.

Nếu bạn cần một biến khác được lưu dưới dạng một phần của trạng thái thể hiện, bạn nên phương thức QUÁ HẠN onSavedInstanceState(Bundle savedinstaneState) .

Ví dụ: int currentScoretrong GameActivity

Chi tiết hơn về onSattedInstanceState (Gói đã lưu trong tài khoản) trong khi lưu dữ liệu

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

Vì vậy, do nhầm lẫn nếu bạn quên gọi super.onSaveInstanceState(savedInstanceState);hành vi mặc định sẽ không hoạt động tức là Văn bản trong EditText sẽ không lưu.

Lựa chọn nào để khôi phục trạng thái Hoạt động?

 onCreate(Bundle savedInstanceState)

HOẶC LÀ

onRestoreInstanceState(Bundle savedInstanceState)

Cả hai phương thức đều có cùng một đối tượng Bundle, vì vậy việc bạn viết logic khôi phục của bạn không thực sự quan trọng. Sự khác biệt duy nhất là trong onCreate(Bundle savedInstanceState)phương thức bạn sẽ phải kiểm tra null trong khi không cần thiết trong trường hợp sau. Các câu trả lời khác đã có đoạn mã. Bạn có thể giới thiệu họ.

Chi tiết hơn về onRestoreInstanceState (Gói đã lưu trong tài khoản)

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

Luôn gọi super.onRestoreInstanceState(savedInstanceState);để Hệ thống khôi phục cấu trúc xem theo mặc định

Tặng kem

Các onSaveInstanceState(Bundle savedInstanceState)được gọi bởi hệ thống chỉ khi người dùng có ý định quay trở lại hoạt động. Ví dụ: bạn đang sử dụng App X và đột nhiên bạn nhận được một cuộc gọi. Bạn di chuyển đến ứng dụng người gọi và quay lại ứng dụng X. Trong trường hợp này,onSaveInstanceState(Bundle savedInstanceState) phương thức sẽ được gọi.

Nhưng hãy xem xét điều này nếu người dùng nhấn nút quay lại. Giả định rằng người dùng không có ý định quay lại Hoạt động, do đó trong trường hợp onSaveInstanceState(Bundle savedInstanceState)này sẽ không được hệ thống gọi ra. Điểm là bạn nên xem xét tất cả các kịch bản trong khi lưu dữ liệu.

Các liên kết có liên quan:

Bản demo về hành vi mặc định
Tài liệu chính thức của Android .


1

Bây giờ nó có ý nghĩa để làm 2 cách trong mô hình xem. nếu bạn muốn lưu lần đầu tiên dưới dạng phiên bản đã lưu: Bạn có thể thêm tham số trạng thái trong mô hình xem như thế này https://developer.android.com/topic/lologists/arch architecture / viewmodel-sattedstate # java

hoặc bạn có thể lưu các biến hoặc đối tượng trong mô hình khung nhìn, trong trường hợp này mô hình khung nhìn sẽ giữ vòng đời cho đến khi hoạt động bị hủy.

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

bạn nói đúng, nhưng thư viện này vẫn đang được phát hành nên tôi nghĩ chúng ta nên đợi ...
Zhar

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.