teratail header banner
teratail header banner
質問するログイン新規登録

質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.30%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

1回答

1105閲覧

Androidアプリ Activityの破棄について

nana7038

総合スコア5

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2023/07/18 08:15

0

0

Androidアプリにおいて、アプリがバックグラウンドへ移動した際、Activityが自動で破棄される場合があるかと思います。
この場合、アプリのエントリーポイントへ戻るアプリが多くあったのですが、
これはそのように実装しているのでしょうか?
(開発者オプションの「アクティビティを保持しない」をオンに、バックグラウンド→フォアグラウンドにし確認しています。)

現在FragmentのViewの状態保持部分で行き詰まっています。。。
以下の部分を考慮していたのですが、Activityの破棄時がうまくいきません。

・画面回転時
・Activityの破棄時(開発者オプションの「アクティビティを保持しない」)
・FragmentからFragmentへの遷移時

何かアドバイスを頂けますと嬉しいです。。
よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jimbe

2023/07/18 09:41

アクティビティが無くなっているのはつまりアプリが終わっているということで、その状態からアプリを実行すればエントリポイント(?)から始まるのでは。 それとフラグメントの状態保存とどう関係するのでしょうか。
nana7038

2023/07/18 10:29

コメントくださり、ありがとうございます。 開発者オプションの「アクティビティを保持しない」の設定をオンにし、アプリをバックグラウンド→フォアグラウンドへ戻ると、一番最初のアクティビティ(MainActivity)ではなく、最後に表示していたアクティビティが表示されます。。 他の有名なアプリではMainActivityに戻るアプリが多かったです。。
jimbe

2023/07/18 13:12 編集

こちらには問題のアプリが複数アクティビティであることさえ分からないのですが・・・マルチアクティビティならそのオプションは使用しないほうが良いでしょう。 [開発者オプションの「アクティビティを保持しない」について] http://zze128.blog9.fc2.com/blog-entry-504.html というか開発者オプションを想定してまでコードを作り込もうとするのはやり過ぎにも思います。 フラグメントを使用しているならわざわざマルチアクティビティにする必要は無いように思いますが。(アクティビティが1つしか無ければ、それが表示されますよね?)
nana7038

2023/07/18 16:10

開発者オプションまで想定する必要はないのですね。 Androidアプリを一から作るのは初めてでして、分からないことばかりでした。。 すみません。。 Androidアプリはメモリが枯渇してくると、バックグラウンドにあるアプリは勝手にアクティビティが破棄されるということがあるという情報を見ました。 その際の挙動の確認をするために、その開発者オプションをオンにして試していたのですが、この辺りの認識自体が間違っているのでしょうか? メモリの枯渇でアクティビティが破棄されることは想定する必要はないのでしょうか? またはメモリの枯渇でアクティビティが破棄される時の挙動と、開発者オプションの「アクティビティを保持しない」という設定時の挙動は違うのでしょうか? ちなみに複数のアクティビティ+複数のフラグメントで作成しております。
jimbe

2023/07/18 17:21

アクティビティがシステムの判断にによって破棄されることがあるのはその通りです。 そして、破棄されたアクティビティが必要になったらこれもシステムによって(再)生成されます。 これら一連の動作は開発者オプションがどうであっても Android OS では当然考えておかなければならないことですが、開発者オプションで『保持しない』とした場合は必ず破棄されることになるのに対して、しなかった場合は「破棄されないことがある」という点で動作が違います。 破棄されなければ再生成も無いですから、画面遷移を再生成ありきで考えてしまうと、それはそれで問題があるコードになるかもしれません。 まぁここ迄全て想定でしかありません。 他のアプリがどうやっているかよりも、お作りになっているアプリが今どのようなコードでどのような動きをしており、でも本当はどのような動きをしてほしいのかを具体的なコード・画面(スクリーンショット)等で提示して頂いたほうが説明も具体的に出来ます。
jimbe

2023/07/18 17:33

繰り返しになりますが、 >現在FragmentのViewの状態保持部分で行き詰まっています。。。 >以下の部分を考慮していたのですが、Activityの破棄時がうまくいきません。 というのはアクティビティが複数あるから発生する問題であって、アクティビティがアプリに1つしか無ければ問題自体が無くなるのではないでしょうか。
guest

回答1

0

ベストアンサー

簡単なサンプルで実験してみます。二つのアクティビティ(MainActivity/SubActivity)それぞれにフラグメント(MainFragment/SubFramgnet)を載せ、ボタンでアクティビティを行き来します。

イメージ画像

MainActivity.kt

kotlin

1import android.content.Intent 2import android.os.Bundle 3import android.util.Log 4import androidx.appcompat.app.AppCompatActivity 5 6class MainActivity : AppCompatActivity() { 7 override fun onCreate(savedInstanceState: Bundle?) { 8 super.onCreate(savedInstanceState) 9 setContentView(R.layout.activity_main) 10 11 Log.d("MainActivity", "onCreate()") 12 13 val fm = supportFragmentManager 14 15 fm.setFragmentResultListener("toSub", this) { requestKey, result -> 16 startActivity(Intent(this, SubActivity::class.java)) 17 } 18 19 fm.beginTransaction().replace(R.id.fragment_container_view, MainFragment()).commit(); 20 } 21}

res/layout/activity_main.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:id="@+id/fragment_container_view" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".MainActivity" />

MainFragment.kt

kotlin

1import android.os.Bundle 2import android.util.Log 3import android.view.View 4import android.widget.Button 5import androidx.fragment.app.Fragment 6 7class MainFragment : Fragment(R.layout.fragment_main) { 8 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 9 super.onViewCreated(view, savedInstanceState) 10 11 Log.d("MainFragment", "onViewCreated()") 12 13 view.findViewById<Button>(R.id.toSubButton).setOnClickListener { v: View? -> 14 parentFragmentManager.setFragmentResult("toSub", Bundle()); 15 } 16 } 17}

res/layout/fragment_main.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:background="#00ffff" 8 tools:context=".MainFragment"> 9 10 <TextView 11 android:id="@+id/textView" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="MAIN" 15 android:textSize="40dp" 16 app:layout_constraintBottom_toTopOf="@id/toSubButton" 17 app:layout_constraintEnd_toEndOf="parent" 18 app:layout_constraintStart_toStartOf="parent" 19 app:layout_constraintTop_toTopOf="parent" /> 20 <Button 21 android:id="@+id/toSubButton" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:text="to SUB" 25 app:layout_constraintBottom_toBottomOf="parent" 26 app:layout_constraintEnd_toEndOf="parent" 27 app:layout_constraintStart_toStartOf="parent" 28 app:layout_constraintTop_toBottomOf="@id/textView" /> 29 30</androidx.constraintlayout.widget.ConstraintLayout>

※ 以下のサブ関係はメインの "sub" と "main" を入れ替えた感じです。

SubActivity.kt

kotlin

1import android.content.Intent 2import android.os.Bundle 3import android.util.Log 4import androidx.appcompat.app.AppCompatActivity 5 6class SubActivity : AppCompatActivity() { 7 override fun onCreate(savedInstanceState: Bundle?) { 8 super.onCreate(savedInstanceState) 9 setContentView(R.layout.activity_sub) 10 11 Log.d("SubActivity", "onCreate()") 12 13 val fm = supportFragmentManager 14 15 fm.setFragmentResultListener("toMain", this) { requestKey, result -> 16 startActivity(Intent(this, MainActivity::class.java)) 17 } 18 19 fm.beginTransaction().replace(R.id.fragment_container_view, SubFragment()).commit(); 20 } 21}

res/layout/activity_sub.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:id="@+id/fragment_container_view" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 tools:context=".SubActivity" />

SubFragment.kt

kotlin

1import android.os.Bundle 2import android.util.Log 3import android.view.View 4import android.widget.Button 5import androidx.fragment.app.Fragment 6 7class SubFragment : Fragment(R.layout.fragment_sub) { 8 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 9 super.onViewCreated(view, savedInstanceState) 10 11 Log.d("SubFragment", "onViewCreated()") 12 13 view.findViewById<Button>(R.id.toMainButton).setOnClickListener { v: View? -> 14 parentFragmentManager.setFragmentResult("toMain", Bundle()); 15 } 16 } 17}

res/layout/fragment_sub.xml

xml

1<?xml version="1.0" encoding="utf-8"?> 2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:background="#ffff00" 8 tools:context=".SubFragment"> 9 10 <TextView 11 android:id="@+id/textView" 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="SUB" 15 android:textSize="40dp" 16 app:layout_constraintBottom_toTopOf="@id/toMainButton" 17 app:layout_constraintEnd_toEndOf="parent" 18 app:layout_constraintStart_toStartOf="parent" 19 app:layout_constraintTop_toTopOf="parent" /> 20 <Button 21 android:id="@+id/toMainButton" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:text="to MAIN" 25 app:layout_constraintBottom_toBottomOf="parent" 26 app:layout_constraintEnd_toEndOf="parent" 27 app:layout_constraintStart_toStartOf="parent" 28 app:layout_constraintTop_toBottomOf="@id/textView" /> 29 30</androidx.constraintlayout.widget.ConstraintLayout>

これを開発者オプション『アクティビティを保持しない』を ON にして実行し、サブアクティビティに移動した上で別のアプリを起動する等して裏に回してから表に出すと、ログには onCreate や onViewCreated が出てサブアクティビティが再生成・表示されます。
これをメインが表示されるようにするというのが目標の一つのようですので、アクティビティをメインだけにし画面遷移はフラグメントの切り替えだけにします。
修正は MainActivity だけです。(だけで済むように作ったんですけど^^; )

MainActivity.kt

kotlin

1//import android.content.Intent 2import android.os.Bundle 3import android.util.Log 4import androidx.appcompat.app.AppCompatActivity 5 6class MainActivity : AppCompatActivity() { 7 override fun onCreate(savedInstanceState: Bundle?) { 8 super.onCreate(savedInstanceState) 9 setContentView(R.layout.activity_main) 10 11 Log.d("MainActivity", "onCreate()") 12 13 val fm = supportFragmentManager 14 15 fm.setFragmentResultListener("toSub", this) { requestKey, result -> 16 //startActivity(Intent(this, SubActivity::class.java)) 17 fm.beginTransaction().replace(R.id.fragment_container_view, SubFragment()).commit(); 18 } 19 fm.setFragmentResultListener("toMain", this) { requestKey, result -> //SubActivity から持ってきた 20 //startActivity(Intent(this, MainActivity::class.java)) 21 fm.beginTransaction().replace(R.id.fragment_container_view, MainFragment()).commit(); 22 } 23 24 fm.beginTransaction().replace(R.id.fragment_container_view, MainFragment()).commit(); 25 } 26}

もし「やっぱり終わった時の状態で再開させたい」となりましたら、 24 行目を savedInstanceState == null の時だけ実行するようにすれば、マルチアクティビティに戻す必要はありません。

kotlin

1 if(savedInstanceState == null) { 2 fm.beginTransaction().replace(R.id.fragment_container_view, MainFragment()).commit(); 3 }

投稿2023/07/19 17:58

編集2023/07/20 06:57
jimbe

総合スコア13352

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nana7038

2023/07/24 02:47

とてもご丁寧にありがとうございます。 実際に検証コードまで載せてくださり、とても勉強になります。 アクティビティが破棄された際にMainActivityに戻るようにするには、やはりシングルアクティビティで作成し、画面遷移はすべてフラグメントで行う必要があるのですね。 また↓のようなやり方があることを初めて知りました。 確かにアクティビティが再生成された場合、「savedInstanceState == null」でチェックしないとフラグメントも初期化され、状態を保持できないですね。。。 if(savedInstanceState == null) { fm.beginTransaction().replace(R.id.fragment_container_view, MainFragment()).commit(); } まだまだ分からないことがたくさんあるので、またアドバイスをいただけますと嬉しいです。 ありがとうございました!
jimbe

2023/07/24 03:28

アクティビティは、それぞれがアプリ一つ分の動作に必要なバックデータを持ちます。アクティビティで画面を作るほど、複数のアプリを動かしているのと同じ程にメモリを食うということです。そしてアクティビティ間はアプリ間と同じくメモリ空間を共有しませんし何時壊されるか分かりませんし…と扱いが面倒です。 これらを解決する為にメモリをアクティビティ程食わずデータ共有が安易になるようにフラグメントが作られたのですから、いまさらマルチアクティビティで苦労する必要は無いのです。 savedInstanceState == null による生成の選択は、データの保持というよりは「既に同じフラグメントがシステムによって再生成済みならわざわざコードで再生成しない」という程度です。 (恐らくは本題であろう)フラグメント内のデータを保存し再生成時に状況を再現するためには、この判断や処理だけでは不十分です。 基本的にはフラグメントは ViewModel と対で使用し、フラグメント内のデータは更新時に ViewModel に保存・フラグメントの生成時に ViewModel からデータを取得という方法によって、例えコードで再生成しても(アプリタスクが継続して動作していれば)フラグメントの内容は再現出来ます。 さらに ViewModel の内容を随時永続化しておくことによって、「アクティビティを保持しない」でアプリがタスクごと消されても、再実行時にフラグメントの内容を再現出来ます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問