onSaveInstanceState(转)

本文探讨了Android应用中活动被系统回收的情况及解决方案。通过onSaveInstanceState()方法保存临时数据,并在onCreate()方法中恢复数据,确保应用在内存不足导致活动重启时能保持良好的用户体验。

当一个活动进入到了停止状态,是有可能被系统回收的。那么想象以下场景,应用中有一个活动A,用户在活动A的基础上启动了活动B,活动A就进入了停止状态,这个时候由于系统内存不足,将活动A回收掉了,然后用户按下Back键返回活动A,会出现什么情况呢?其实还是会正常显示活动A的,只不过这时并不会执行onRestart()方法,而是会执行活动A的onCreate()方法,因为活动A在这种情况下会被重新创建一次。 
这样看上去好像一切正常,可是别忽略了一个重要问题,活动A中是可能存在临时数据和状态的。打个比方,MainActivity中有一个文本输入框,现在你输入了一段文字,然后启动NormalActivity,这时MainActivity由于系统内存不足被回收掉,过了一会你又点击了Back键回到MainActivity,你会发现刚刚输入的文字全部都没了,因为MainActivity被重新创建了。 
如果我们的应用出现了这种情况,是会严重影响用户体验的,所以必须要想想办法解决这个问题。查阅文档可以看出,Activity中还提供了一个onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。 
onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。 
在MainActivity中添加如下代码就可以将临时数据进行保存:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onSaveInstanceState</span>(Bundle outState) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onSaveInstanceState(outState);
        String tempData = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Something you just typed"</span>;
        outState.putString(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"data_key"</span>, tempData);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

数据是已经保存下来了,那么我们应该在哪里进行恢复呢?细心的你也许早就发现,我们一直使用的onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null,但是当活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。 
修改MainActivity的onCreate()方法,如下所示:

<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>(Bundle savedInstanceState) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate(savedInstanceState);
        Log.d(TAG, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"onCreate"</span>);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (savedInstanceState != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
            String tempData = savedInstanceState.getString(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"data_key"</span>);
            Log.d(TAG, tempData);
        }
        ……
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul><div class="save_code tracking-ad" data-mod="popu_249" style="box-sizing: border-box; position: absolute; height: 60px; right: 30px; top: 5px; color: rgb(255, 255, 255); cursor: pointer; z-index: 2;"><a target=_blank target="_blank" style="color: rgb(202, 0, 0); box-sizing: border-box;"><img src="http://static.blog.youkuaiyun.com/images/save_snippets.png" style="border: none; box-sizing: border-box; max-width: 100%;" alt="" /></a></div><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul>

取出值之后再做相应的恢复操作就可以了,比如说将文本内容重新赋值到文本输入框上,这里我们只是简单地打印一下。

<think>我们需要在Activity的onSaveInstanceState保存SurfaceView或TextureView的状态。但是SurfaceView本身不能直接保存,因为它的Surface是异步创建和销毁的。通常我们需要保存视频播放的位置等信息,并在恢复时重新设置。 注意:SurfaceView的Surface状态不能直接保存,因为它是与窗口绑定的。当Activity被销毁重建时,Surface也会被销毁和重新创建。因此,我们需要保存的是与Surface关联的数据状态(如视频URI、播放位置等),然后在Surface重新创建时恢复播放。 以下是保存和恢复Surface关联状态的步骤: 1. 在onSaveInstanceState保存必要的播放状态(如视频URI、播放位置、是否正在播放等)。 2. 在onRestoreInstanceState或onCreate中恢复这些状态。 3. 当Surface创建时(如SurfaceHolder.Callback.surfaceCreated或TextureView.SurfaceTextureListener.onSurfaceTextureAvailable),根据保存的状态重新设置播放器。 但是,由于视频播放器(MediaPlayer)本身不能跨Activity重建保存,所以我们需要在重建时重新初始化MediaPlayer。 具体实现: 假设我们使用TextureView(推荐,因为它在Activity暂停时不会立即销毁Surface,更适合保存状态),但即使如此,在Activity重建时TextureView也会重建,所以我们需要保存状态并在重建后恢复。 代码示例: 步骤: - 在onSaveInstanceState保存当前播放的视频URI和播放位置。 - 在onCreate或onRestoreInstanceState中读取保存的状态。 - 当TextureView的Surface可用时,重新设置MediaPlayer并跳保存的位置。 注意:如果使用SurfaceView,原理类似,但SurfaceView在Activity进入后台时Surface会被销毁,回到前台时重新创建,所以也需要在surfaceCreated中恢复。 以下是一个完整的示例代码(使用TextureView): </think>### 保存和恢复 Surface 状态解决方案 #### 核心问题分析 在 Android 中,`SurfaceView` 的状态不能直接保存到 `onSaveInstanceState()` 中,因为: 1. **Surface 是系统托管资源**:由 WindowManager 管理,不属于 Activity 视图层级 2. **生命周期不同步**:Surface 的创建/销毁与 Activity 生命周期不同步 3. **异步特性**:Surface 在专用线程渲染,状态恢复需要特殊处理 --- ### 完整实现代码 ```java public class VideoPlayerActivity extends AppCompatActivity implements SurfaceHolder.Callback { private static final String KEY_VIDEO_URI = "video_uri"; private static final String KEY_PLAYBACK_POSITION = "playback_position"; private static final String KEY_IS_PLAYING = "is_playing"; private SurfaceView surfaceView; private SurfaceHolder surfaceHolder; private MediaPlayer mediaPlayer; private Uri videoUri; private int playbackPosition; private boolean isPlaying; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_player); surfaceView = findViewById(R.id.surface_view); surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(this); // 恢复保存的状态 if (savedInstanceState != null) { videoUri = Uri.parse(savedInstanceState.getString(KEY_VIDEO_URI)); playbackPosition = savedInstanceState.getInt(KEY_PLAYBACK_POSITION); isPlaying = savedInstanceState.getBoolean(KEY_IS_PLAYING); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 保存关键播放状态 if (mediaPlayer != null) { outState.putString(KEY_VIDEO_URI, videoUri.toString()); outState.putInt(KEY_PLAYBACK_POSITION, mediaPlayer.getCurrentPosition()); outState.putBoolean(KEY_IS_PLAYING, mediaPlayer.isPlaying()); } } // ========== SurfaceHolder.Callback 实现 ========== @Override public void surfaceCreated(SurfaceHolder holder) { Log.d("Surface", "Surface created"); if (videoUri != null) { initializeMediaPlayer(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.d("Surface", "Surface changed: " + width + "x" + height); // 调整视频尺寸 if (mediaPlayer != null) { mediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d("Surface", "Surface destroyed"); releaseMediaPlayer(); } // ========== 媒体播放器管理 ========== private void initializeMediaPlayer() { try { // 释放之前的播放器(如果有) releaseMediaPlayer(); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(this, videoUri); mediaPlayer.setDisplay(surfaceHolder); // 设置准备完成监听器 mediaPlayer.setOnPreparedListener(mp -> { // 恢复播放位置 if (playbackPosition > 0) { mediaPlayer.seekTo(playbackPosition); } // 恢复播放状态 if (isPlaying) { mediaPlayer.start(); } // 设置视频比例 adjustVideoAspectRatio(); }); // 设置错误监听器 mediaPlayer.setOnErrorListener((mp, what, extra) -> { Log.e("MediaPlayer", "Error occurred: what=" + what + ", extra=" + extra); releaseMediaPlayer(); return true; }); // 异步准备播放器 mediaPlayer.prepareAsync(); } catch (IOException e) { Log.e("MediaPlayer", "Error initializing media player", e); } } private void adjustVideoAspectRatio() { if (mediaPlayer == null || surfaceView.getWidth() == 0) return; // 获取视频尺寸 int videoWidth = mediaPlayer.getVideoWidth(); int videoHeight = mediaPlayer.getVideoHeight(); // 计算正确比例 if (videoWidth > 0 && videoHeight > 0) { float videoRatio = (float) videoWidth / videoHeight; float screenRatio = (float) surfaceView.getWidth() / surfaceView.getHeight(); // 调整 SurfaceView 布局参数 ViewGroup.LayoutParams lp = surfaceView.getLayoutParams(); if (videoRatio > screenRatio) { // 视频比屏幕宽 lp.height = (int) (surfaceView.getWidth() / videoRatio); } else { // 视频比屏幕高 lp.width = (int) (surfaceView.getHeight() * videoRatio); } surfaceView.setLayoutParams(lp); } } private void releaseMediaPlayer() { if (mediaPlayer != null) { // 保存当前播放位置 if (mediaPlayer.isPlaying()) { playbackPosition = mediaPlayer.getCurrentPosition(); } mediaPlayer.release(); mediaPlayer = null; } } // ========== Activity 生命周期管理 ========== @Override protected void onPause() { super.onPause(); if (mediaPlayer != null && mediaPlayer.isPlaying()) { // 保存播放位置但不释放资源 playbackPosition = mediaPlayer.getCurrentPosition(); isPlaying = true; mediaPlayer.pause(); } } @Override protected void onResume() { super.onResume(); if (mediaPlayer != null && isPlaying) { mediaPlayer.start(); } } @Override protected void onDestroy() { super.onDestroy(); releaseMediaPlayer(); } // ========== 视频加载方法 ========== public void loadVideo(Uri uri) { this.videoUri = uri; this.playbackPosition = 0; this.isPlaying = false; if (surfaceHolder.getSurface().isValid()) { initializeMediaPlayer(); } } } ``` ### 关键解决方案解析 #### 1. 状态保存策略 ```java @Override protected void onSaveInstanceState(Bundle outState) { if (mediaPlayer != null) { outState.putString(KEY_VIDEO_URI, videoUri.toString()); outState.putInt(KEY_PLAYBACK_POSITION, mediaPlayer.getCurrentPosition()); outState.putBoolean(KEY_IS_PLAYING, mediaPlayer.isPlaying()); } } ``` - **保存内容**:视频URI、播放位置、播放状态 - **不保存内容**:Surface 本身(系统会自动重建) - **最佳实践**:只保存可序列化的轻量级数据 #### 2. Surface 生命周期处理 ```java @Override public void surfaceCreated(SurfaceHolder holder) { if (videoUri != null) { initializeMediaPlayer(); // 重建播放器 } } @Override public void surfaceDestroyed(SurfaceHolder holder) { releaseMediaPlayer(); // 必须释放资源 } ``` - **重建规则**:Surface 销毁时释放 MediaPlayer - **恢复时机**:Surface 重建时重新初始化播放器 #### 3. 播放位置恢复 ```java mediaPlayer.setOnPreparedListener(mp -> { if (playbackPosition > 0) { mediaPlayer.seekTo(playbackPosition); } if (isPlaying) { mediaPlayer.start(); } }); ``` - **异步恢复**:在 `OnPreparedListener` 中恢复位置 - **关键点**:不能在 `prepareAsync()` 后立即 `seekTo()` #### 4. 视频比例自适应 ```java private void adjustVideoAspectRatio() { // 获取视频宽高比 float videoRatio = (float) videoWidth / videoHeight; // 调整 SurfaceView 布局参数... } ``` - **必要性**:防止屏幕方向改变时视频变形 - **实现方式**:动态计算并设置 SurfaceView 尺寸 --- ### 高级场景:使用 TextureView 的改进方案 #### TextureView 优势 1. **更好的状态保存**:集成在 View 层级中 2. **支持变换操作**:旋、缩放等效果 3. **无缝恢复**:Activity 重建时自动保留最后一帧 ```java public class TextureVideoActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener { private TextureView textureView; private MediaPlayer mediaPlayer; private Uri videoUri; private int savedPosition; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_texture_video); textureView = findViewById(R.id.texture_view); textureView.setSurfaceTextureListener(this); // 恢复保存的状态 if (savedInstanceState != null) { videoUri = Uri.parse(savedInstanceState.getString("video_uri")); savedPosition = savedInstanceState.getInt("position"); } } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { if (videoUri != null) { setupMediaPlayer(new Surface(surface)); } } // 其他 SurfaceTextureListener 方法省略... @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mediaPlayer != null) { outState.putString("video_uri", videoUri.toString()); outState.putInt("position", mediaPlayer.getCurrentPosition()); } } private void setupMediaPlayer(Surface surface) { try { mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(this, videoUri); mediaPlayer.setSurface(surface); mediaPlayer.setOnPreparedListener(mp -> { mp.seekTo(savedPosition); mp.start(); }); mediaPlayer.prepareAsync(); } catch (IOException e) { Log.e("TextureVideo", "Error setting up media player", e); } } } ``` ### 状态保存最佳实践 | **场景** | **保存内容** | **不保存内容** | **恢复时机** | |---------|-------------|---------------|------------| | 屏幕旋 | 播放位置、URI | MediaPlayer 实例 | surfaceCreated | | 应用切换到后台 | 播放状态 | Surface 引用 | onResume | | Activity 重建 | 所有关键状态 | GL 上下文 | onCreate/surfaceCreated | | 部分可见 (画中画) | 播放位置 | 不保存 | 自动处理 | --- ### 常见问题解决方案 #### 问题1:黑屏或绿屏 **原因**:Surface 未准备好时操作 MediaPlayer **解决**:在 `surfaceCreated()` 回调中初始化播放器 #### 问题2:播放位置丢失 **原因**:`seekTo()` 调用过早 **解决**:在 `OnPreparedListener` 中调用 `seekTo()` #### 问题3:内存泄漏 **原因**:未正确释放 MediaPlayer **解决**: ```java @Override protected void onDestroy() { releaseMediaPlayer(); super.onDestroy(); } private void releaseMediaPlayer() { if (mediaPlayer != null) { mediaPlayer.release(); mediaPlayer = null; } } ``` #### 问题4:方向改变导致崩溃 **原因**:Surface 重建时访问无效引用 **解决**:使用 `surfaceDestroyed()` 清理资源 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值