Activity系列之---<Activity中的两大守护>

本文探讨了Android中Activity的onSaveInstanceState和onRestoreInstanceState方法的作用及使用场景,通过实例展示了这两种方法如何帮助保存和恢复瞬时数据。

大家好,又到了今天探讨Activity的时间啦。那么今天要研究些什么内容呢?还记得上一篇博文中小菜提到的一种情形吗?从一个Activity启动了新的Activity,旧的Activity就会转入到后台。这时候如果系统需要释放内存,很有可能把旧的Activity给销毁掉。当时小菜建议大家在onPause这个方法中将持久数据保存起来。那么除了咱们手动去保存一些数据,难道就没有其他办法了吗?系统忽然就把Activity销毁了,难道就一点不为用户考虑?

   其实android系统的开发者早就为我们考虑到这种情形了,并且提供了相应的方法应对这种突如其来的状况。onSaveInstanceState方法就可以在Activity变得“脆弱”时被调用在此期间可以将一些瞬时数据保存起来。而onRestoreInstanceState方法和onCreate方法又可以将这些瞬时数据恢复。所以小菜就称onSaveInstanceStateonRestoreInstanceState 为两大守护。

  肯定有童鞋要问啦:“小菜,你说的是啥啊?啥叫变得脆弱?你就不能说些正常点的词语吗?”其实脆弱一词我也是根据英文直译过来的。那什么时候Activity将变得“脆弱”呢?

比如从一个Activity中启动了一个新的Acitivty,而旧的Activity不确定什么时候会再被运行,它就有可能被系统销毁,这时候它就会变得脆弱。大家还记得我上一篇文章中提到的2种不同的退出方式吗?其中一种是按Home键退出的,这时候的Activity实际上是没有被销毁掉的,这时候的Activity也是有可能被系统销毁掉的,所以它也是“脆弱”的。变得“脆弱”的例子有很多,但是我们只要抓住一点,就基本上能判别系统是否会调用onSaveInstanceState方法,那这一点是什么呢?

  这一点就是,如果系统有可能在“未经你许可”的情况下销毁Activity,都会调用此方法。因为系统对不起你,所以它会给你一个机会补偿你。如果是你自愿要销毁Activity的话,是不会调用onSaveInstanceState方法的,比如你按back键退出,系统就不会去调用这个方法,因为是你明确的要退出。但是如果调用了这个方法它调用的时机有可能是在onStop方法之前或者onPause方法之前。

  “呀,小菜,听你这么一说,我感觉还挺危险的。因为我以前写过的Activity重来都没有实现过这个方法。”

   别着急,androdi系统的几乎所有view都已经默认的实现了onSaveInstanceState方法,你只需要为这些View提供一个ID,剩余的事系统会自动完成。如果没有提供这个ID就不会进行自动的保存和恢复操作了。

  “那你说个P啊,害我瞎着急,别急别急,当你需要保存额外的数据时,你就应该覆写该方法了。但是记住,该方法只应该存放瞬时数据,而不应该存放持久性数据,因为它不是每次都会被调用。

   那么onRestoreInstanceState方法呢它是什么时候被调用呢?

   onRestoreInstanceState方法被调用的前提一定是Activity的确被销毁了。如果只是有可能被销毁是不会调用这个方法的。例如,当用户按下Home键时,由于此刻的Activity将变得“脆弱”,所以系统会调用onSaveInstanceState方法,但是用户紧接着又回到了Activity中,那么这时候就不会调用onRestoreInstanceState方法。从这我们也可以看出,这两个方面并不一定是成对出现的。

  接下来我们就动手验证一下,这两大守护的启动时机以及如何应用。

1.     创建一个android 项目,取名叫Activity_03,Activity名称为ActivityGuard。

2.     修改布局文件,在布局文件中添加一个可编辑框和一个按钮。

3.     修改ActivityGuard文件。实现咱们Activity的7个回调方法,当然还有我们的onSaveInstanceState方法,onRestoreInstanceState方法。在这些方法中打印一句话。并在onCreate方法中通过findViewById(),去找到我们的可编辑框和按钮。

package edu.activity.viking.test;

 

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

 

public class ActivityGuard extends Activity

{

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState)

    {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        System.out.println("run onCreate");

        //找到可编辑框

        EditText editText=(EditText)findViewById(R.id.edit);

        //找到按钮

        Button jumpButton=(Button)findViewById(R.id.button);

        //为按钮添加监听器

        jumpButton.setOnClickListener(new View.OnClickListener()

        {

 

           public void onClick(View v)

           {

              Intent intent = new Intent();

       intent.setClass(ActivityGuard.this, OtherActivity.class);

              ActivityGuard.this.startActivity(intent);

           }

       

        });

    }

   

    @Override

    protected void onStart()

    {

    System.out.println("run onStart");

    super.onStart();

    }

   

    @Override

    protected void onResume()

    {

    System.out.println("run onResume");

    super.onResume();

    }

   

    @Override

    protected void onRestart()

    {

    System.out.println("run onRestart");

    super.onRestart();

    }

   

    @Override

    protected void onSaveInstanceState(Bundle outState)

    {

    System.out.println("run onSaveInstanceState");

    super.onSaveInstanceState(outState);

    }

   

    @Override

    protected void onRestoreInstanceState(Bundle savedInstanceState)

    {

    System.out.println("run onRestoreInstanceState");

    super.onRestoreInstanceState(savedInstanceState);

    }

   

    @Override

    protected void onPause()

    {

    System.out.println("run onPause");

    super.onPause();

    }

   

    @Override

    protected void onStop()

    {

    System.out.println("run onStop");

    super.onStop();

    }

   

    @Override

    protected void onDestroy()

    {

    System.out.println("run onDestroy");

    super.onDestroy();

    }

}


 

4.    创建一个新的Activity,取名为OtherActivity.在这个新的Activity中我们只使用TextView控件显示一句话。因为只测试旧的Activity所以,新的Activity我只实现它的onCreate方法。

 

好啦,我们现在可以开始验证我们的想法了。

首先我们的看看onSaveInstanceState方法的调用时机。是否在Activity变得脆弱时,系统会自动调用它。

测试步骤:

1.     跳转到新的Activity

2.     按Home键退出

我们可以看到点击跳转按钮以后,旧的Activity运行了,onSaveInstanceState方法。

这是我们按back键后的图,从图中我们可以看到,虽然旧的Activity一度成为脆弱的状态,但是它并没有被销毁,所以当返回的时候也并没有调用onRestoreInstanceState方法。

  我们继续测试,按Home键的情况。

我们可以看到按home键后一样调用了onSaveInstanceState方法.

当又回到Activity时也同样没有调用onRestoreInstanceState方法。

那么什么时候会用到onRestoreInstanceState方法呢?让我们进入下一个测试。

  我们知道手机有横竖屏之分,当它们之间进行切换的时候,系统就会把Activity销毁,这种销毁是未经我们允许的。我们来试试,这个时候会不会调用onSaveInstanceStateonRestoreInstanceState方法。

  为了实现屏幕转换,我们在onResume方法中加入一些代码。

OK,这时候我们再次启动程序。

我们可以看到,执行到onResume方法的时候,屏幕进行了切换,接着就调用了onSaveInstanceState方法,由于屏幕的切换Activity被停止销毁了,切换好以后重新运行了onCreate方法,并且运行了onRestoreInstanceState方法.

  现在我们找到了2个方法同时运行的情况了,前文我们说到,基本上所有的View都默认实现了onSaveInstanceState方法,如果我在编辑框输入了数据,当屏幕切换后,数据是否还存在呢?

  为了进行测试,我们新增加一个按钮,当用户点击这个按钮时,屏幕会进行切换。

这个是布局文件中新添加的按钮。

然后我们将onResume里面的修改屏幕方向的代码移到按钮事件中去。

这时我们来看看,在编辑框中输入了数据,当点击旋转按钮时数据是否仍然存在,如果存在则说明编辑框自己已经默认实现了onSaveInstanceState方法和onRestoreInstanceState方法。

从图中可以看到,编辑框中的数据还是存在的,说明编辑框自己也实现了这2个方法。

  那么接下来,我们完成我们最后一个测试,那就是尝试重写onSaveInstanceState方法和onRestoreInstanceState方法,保存我们的变量。

  为了让大家更好的体会到这2个方法的用途,咱们先把onSaveInstanceState方法中的super.onSaveInstanceState(outState); 这段代码注释起来。

我们在看看编辑框是否还会自动帮我们保存数据。

变换前。

变换后,我们可以看到,就算系统调用了onSaveInstanceState方法也没有把数据保存起来。

  所以,接下来就看我们的啦,自己动手丰衣足食。怎么做呢?咱们在先得到编辑框的内容,然后保存起来,在变换后,在赋值到编辑框就行了,就是这个思路,咱们试试。

先修改onSaveInstanceState方法。

代码很简单,只不过做了一下保存的操作。

咱们再修改onRestoreInstanceState方法。

也就是把刚刚保存的数据提取出来,显示在编辑框。

我们这时再来运行一下程序。

变化前。

变化后。

我们可以看到,数据的确被保存了。当然onRestoreInstanceState方法中的代码放到onCreate方法中也一样可以的噢。我这里就不做演示了,童鞋们可以自己试试。

  那么今天的博文就到这了,因为博文写的时间跨度有点长,所以中间思路可能断了,如果童鞋们觉得我语无伦次的话,那也纯属正常。OK,下一篇博文我们继续深入Activity.我是你们的小菜。88

代码下载地址:http://pan.baidu.com/share/link?shareid=3765&uk=2852626994

 

 

flowchart TD A[用户在Activity点击按钮] --> B[主线程启动子线程(避免UI阻塞,防止ANR)] B --> C[主线程显示ProgressDialog:“软件正在生成中 请稍等...”] C --> D[子线程调用FileUtils.copyAssetsToFile: - 输入:当前Activity、Assets内模板文件“dc.zip” - 输出:复制到ROOT_DIR(外部存储根目录)/dc.zip - 校验:判断目标文件是否已存在,存在则覆盖] D -->|抛出IOException(如Assets文件不存在/存储权限被拒)| Z[子线程捕获异常→主线程显示错误对话框:“生成失败:{IO异常信息}”] D --> E[子线程显示ProgressDialog:“验证传入图片合法性...”] E --> F[子线程初始化支付码检测: 1. 前置校验:BaseLockActivity已通过CheckPayQrCode.init(Context)初始化上下文 2. 实例化CheckPayQrCode:传入背景图片路径(bjlj.getText().toString()) 3. 调用checkPay2code()开始检测] F --> F1[checkPay2code()内部流程1:删除历史debug目录 - 调用deleteDebugDir()→获取sAppContext的ExternalFilesDir/debug - 目录存在则递归删除(deleteDirRecursively()),不存在则打印日志] F1 --> F2[checkPay2code()内部流程2:按顺序检测支付码(任一命中即抛异常) - ① 检测微信支付码:调用hasWeChatPay()→WeChatPay.containsWeChatPay() - 读取图片→缩放至1024px→ZXing解析→判断是否含“wechatpay/wxp:///weixin://” - 检测耗时记录+日志打印,命中则抛UnsupportedPayQrCodeException(WECHAT_PAY类型) - ② 未命中则检测支付宝码:调用hasAliPay()→AlipayCode.containsAlipay() - 同样ZXing解析,命中则抛异常(Alipay类型) - ③ 未命中则检测QQ钱包码:调用hasQQWallet()→QQWalletCode.containsQQWallet() - 命中则抛异常(QQ_Wallet类型) - ④ 未命中则检测微信小程序/赞赏码:调用hasWeChatMiniAppCode() - 启动子线程(CountDownLatch同步)→MiniAppQrcode.processImage() - 先ZXing识别,失败则检测定位点(需≥3个)+核心区/头像/Logo,命中则抛异常(WECHAT_MINI_APP类型)] F2 -->|检测到任一支付码(抛UnsupportedPayQrCodeException)| Z F2 -->|未检测到支付码,打印总耗时日志| G[子线程显示ProgressDialog:“复制资源文件成功 正在解密资源包...”] G --> H[子线程调用FileUtils.decryptAndUnpack: - 输入:assets/dc.zip、解密密钥、目标目录WORK_DIR(ROOT_DIR/闪电博士生成器) - 操作:1. 对称解密dc.zip文件 2. 解压解密后的文件到WORK_DIR - 校验:解压后检查WORK_DIR是否存在res、resources.arsc等核心目录/文件] H -->|解密失败(密钥错误)/解压失败(压缩包损坏)| Z H --> I[子线程显示ProgressDialog:“解密资源包成功 替换软件图标及背景并删除原签名...”] I --> J[子线程调用FileUtils.copyImageFiles替换软件图标: - 输入:用户选择的图标路径(tblj.getText().toString())、目标路径WORK_DIR/res/drawable/ic_launcher.png - 校验:1. 源文件是否为图片格式(png/jpg) 2. 目标目录是否存在,不存在则创建 3. 读写权限判断] J -->|图片格式错误/路径不存在| Z J --> K[子线程调用FileUtils.copyImageFiles替换软件背景: - 输入:用户选择的背景路径(bjlj.getText().toString())、目标路径WORK_DIR/res/drawable/sd.png - 校验:同图标替换逻辑] K -->|图片格式错误/路径不存在| Z K --> L[子线程显示ProgressDialog:“写入密码及内容...”] L --> M[子线程调用writeEncryptedPasswords()写入加密数据到WORK_DIR/res/raw: 1. 处理密码(passwordEdits[0]:用户输入的密码框): - 调用sf.computeHash()计算密码哈希→转16进制字符串→写入mm.txt 2. 处理内容(passwordEdits[1]:用户输入的内容框): - 调用TextUtil.encode(),以“闪”为密钥加密内容字节→写入nr.txt 3. 处理追溯数据(pz文件): - 拼接内容:“内容:{nr}+密码:{mm}+设备ID:{Settings.Secure.ANDROID_ID}” - 以空字符为密钥加密→写入pz文件 - 校验:确保res/raw目录存在,文件写入成功后刷新目录] M -->|文件写入失败(如目录不存在)| Z M --> N[子线程判断音频编辑框(audioEdit)是否非空?] N -->|是| O[子线程调用FileUtils.copyImageFiles处理音频: - 输入:用户选择的音频路径(audioEdit.getText().toString())、目标路径WORK_DIR/res/raw/a.mp3 - 校验:1. 源文件是否为音频格式(mp3等) 2. 文件小是否超限(避免打包失败) 3. 目标目录存在性] O -->|音频格式错误/路径不存在| Z O --> P[跳过音频处理] N -->|否| P P --> Q[子线程判断应用名编辑框(rjm)是否非空?] Q -->|是| R[子线程显示ProgressDialog:“正在更改应用名称...”] R --> S[子线程调用ArscFile.ArscAppName()修改应用名: - 输入:WORK_DIR/resources.arsc(资源表文件)、用户输入的应用名(rjm.getText().toString()) - 内部流程:1. 读取arsc文件→解析ResTable_header/ResStringPool 2. 定位“app_name”资源条目 3. 替换资源值→生成新的字符串池→保存修改后的arsc文件] S -->|arsc文件损坏/未找到app_name资源| Z S --> T[跳过应用名修改] Q -->|否| T T --> U[子线程删除原APK签名目录: - 调用FileUtils.deleteDirectory()→删除WORK_DIR/META-INF - 校验:目录存在则递归删除所有文件/子目录,不存在则打印“无需删除”日志] U -->|删除失败(如文件被占用)| Z U --> V[子线程显示ProgressDialog:“删除签名成功 正在打包...”] V --> W[子线程初始化ApkZipModeBuilder并调用build()打包: - 输入:源目录WORK_DIR、输出APK路径ROOT_DIR/闪电博士.apk - 打包逻辑: 1. 初始化ZipFile(读写+创建+截断模式) 2. 遍历res目录:按资源类型选择压缩模式(raw/dex/jar不压缩,drawable/layout/xml等Deflate压缩) 3. 处理根目录文件:AndroidManifest.xml压缩,resources.arsc/dex/so不压缩 4. 生成LocalFileHeader+CentralDirEntry,写入PK头(0x04034b50) 5. 刷新中央目录,校验EOCD签名(0x06054b50)] W -->|打包失败(如文件权限不足)| Z W --> X[子线程显示ProgressDialog:“签名中...”] X --> Y[子线程调用apksigner.Main.sign()进行APK签名: - 输入:当前Activity、待签名APK(闪电博士.apk)、输出APK(OUTPUT_APK=ROOT_DIR/锁机生成(闪电博士生成器).apk) - 签名配置:使用内置签名证书 - 校验:签名后检查输出APK是否存在,文件小是否正常] Y -->|签名失败(如证书无效)| Z Y --> AA[子线程显示ProgressDialog:“删除残留...”] AA --> AB[子线程调用FileUtils.deleteDirectory()删除WORK_DIR: - 递归删除WORK_DIR下所有文件/子目录(包括res、resources.arsc等) - 校验:确保目录删除干净,避免残留占用存储] AB -->|删除残留失败| AC[打印“残留删除警告”日志,不中断流程] AB --> AC AC --> AD[子线程显示ProgressDialog:“成功生成...”] AD --> AE[子线程通知主线程显示成功对话框: - 标题:“成功” - 内容:“路径: {OUTPUT_APK}” - 按钮:“确定”→点击后关闭对话框] AE --> AF[主线程隐藏ProgressDialog] Z --> AF[主线程隐藏ProgressDialog→流程结束] AF --> AG[生成流程结束] 生成思维导图
最新发布
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值