安静的存在角落,从不打扰,从不离去。时刻准备着在你需要的时候。
至:activity中一直存在却被我忽略的方法。
没有存在的必要早就被干掉了,如丛林法则,如职场法则。
被忽略的方法
- onSaveInstanceState()在进行横竖屏幕切换的时候需要进行使用
- Intent的隐式传递
- Intent传递对象,Serializable Parcelable
- 管理自己的栈
- 活动的四种启动模式
activity横竖屏幕
android开发对于Activity的优化一直在改进,extends ActionBarActivity
目前是extends AppCompatActivity
,android开发团队一直在进行优化,对于开发者的影响不是很大,使用的方法一如从前。有兴趣的朋友可以baidu这些底层的内容。
好了,生命周期的模式如此,接下去就是自己的实践了,对于这写方法在继承的Activity中都会有用到,自己可以试试。
2.关于手机的重力感应,横(land)竖(port)屏的切换
方法1:AndroidManifest.xml中设置activity中通过android:screenOrientation属性值来实现。
android:screenOrientation=”portrait” 竖屏
android:screenOrientation=”landscape”,横屏
方法2:Java代码中通过类似如下代码来设置
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
解释:若你的程序是固定的需求,一定要为横屏或者竖屏时进行这样的设置,比如游戏的横屏。一般情况下,你开发出来的程序应该支持重力感应由用户来进行横竖屏幕的切换。
但是,说说容易,做起来难,所以微信
的开发者真是令人敬佩,已经是APP霸主依然考虑尽善尽美,永远值得学习。
1.实现这样的功能以及适配,是需要你考虑到很多的事情,有些时候对于一些图片需要UI的配合,给予你横屏和竖屏两种图,还要考虑对不同手机分辨率的问题。
2.另外界面在竖屏的时候所有界面正常就可以显示,但是一转横屏就完了,所以你就需要进行使用ScrollView或者NestedScrollView布局来进行所有的数据和界面可以滑动显示。
3.在界面旋转的时候,手机会对该界面进行destory()之后再执行oncreate(),如下Activity每次横竖屏切换都会重新调用
onPause-> onStop-> onDestory-> onCreate->onStart->onResume,
这里就需要一些数据的存储,为了不让程序Crash
上面3点是我们开发的时候必须考虑到的,也许老板根本不在乎你处理过程有多么复杂或者说你的困难有多少,需求就是这样,这也是开发者本身的工作不是吗?难也要做。
1、2点比较好解决,你就麻烦一下UI,顺便自己在建立不同的布局或者进行代码中的判断
1)在res目录下建立layout-land
和layout-port
目录,相应的layout文件名不变,比如main.xml。layout-land是横屏的layout,layout-port是竖屏的layout,其他的不用管,横竖屏切换时程序为调用Activity的onCreate方法,从而加载相应的布局。
2)通过java代码来判断当前是横屏还是竖屏然后来加载相应的xml布局文件。因为当屏幕变为横屏的时候,系统会重新呼叫当前Activity的onCreate方法,你可以把以下方法放在你的onCreate中来检查当前的方向,然后可以让你的setContentView来载入不同的layout xml。
if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
{
Log.i("info", "landscape"); // 横屏
} else if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
{
Log.i("info", "portrait"); // 竖屏
}
对于第3点:
方法1:需要用到
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
方法进行一些数据的存储(该方法只在你切换横竖屏幕的时候会进行调用,所以存储必要的数据,防止Crash,然后在onCreate()方法中应用)
方法2:
关于android横竖屏幕切换的问题(这种方法亲自测试无用,如果有人实践了请留言告诉我,感激不尽)
我实践的方法是按照上述的步骤,但是在 onConfigurationChanged()方法中根本没有进行执行,所以还是使用之前的 onSaveInstanceState()方法
Android提供了在manifest中设置android:configChanges属性,从而让Activity不延续重建流程。在Android工程的Mainfest.xml中配置对应Activity:android:configChanges=”keyboardHidden|orientation”,横竖屏切换之后就不会去执行OnCreat函数了,而是会去调用onConfigurationChanged()这样就能控制横竖屏的切换了。
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
switch (newConfig.orientation){
case Configuration.ORIENTATION_LANDSCAPE:
//执行横屏的操作
break;
case Configuration.ORIENTATION_PORTRAIT:
//执行竖屏的操作
break;
}
}
关于Intent的隐式调用
在android中的隐式调用相信开发的工作人员应该都用过,如果你说没有啊,我从来没有用到过。看看下面的例子:
1.在自己的程序中调用 手机的拨打电话功能
Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:18030043775"));
startActivity(intent);
还有使用本地的浏览器功能
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com/"));
startActivity(intent);
对于上述的两种方法不陌生吧,这些都属于隐式调用,只是ACTION/CALL等是android系统设置的动作。
我们自己也可以进行隐式的调用,接着聊:
显示的调用不多说了:直接上代码
Intent intent = new Intent(MainActivity.this,AboutIntentActivity.class);
//putExtra()用来传递数据
intent.putExtra("message","进行横竖屏幕的切换演示");
startActivity(intent);
那我们就以上述的显示调用改变成隐式调用吧!
现在我呢需要执行一个操作,就是点击按钮,在当前的MainActivity界面跳转至AboutIntentActivity界面。
step1:在manifest.xml文件下面添加如下的代码,如图:
解释说明一下:
<!-- action : 活动的响应 -->
<!-- category : 一些附加信息,明确的指明能够响应的Intent中还有可能 带有的category-->
<!-- 重点声明:只有<action>和<category>同时匹配才能被响应 -->
<!-- 也可以自己定义响应的category-->
<!-- data : 用于更精确的指定当前活动能够响应什么类型的数据 -->
<!-- data标签中可以进行配置以下的内容 -->
<!-- android:scheme 指定数据的协议部分,如 https -->
<!-- android:host 指定数据的主机名部分,如 www.baidu.com -->
<!-- android:port 指定数据的端口部分,一般紧随在主机名之后 -->
<!-- android:path 指定主机名和端口之后的部分 如一段网址中跟在域名之后的部分 -->
<!-- android:mimeType 用于指定可以处理的数据类型,允许使用通配符 -->
<activity android:name=".aboutintent.AboutIntentActivity">
<intent-filter>
<action android:name="" />
<category android:name="" />
<data android:scheme=""
android:host=""
android:path=""
android:port=""
android:mimeType=""/>
</intent-filter>
</activity>
step2: 进行隐式的调用,如图
你看,上面我们不是说要 action,和category 同时匹配才可以调用成功吗?但是这样子的调用也不会出错,为什么?
android.intent.category.DEFAULT //是一种默认的category,在调用startActivity()时会自动将这个属性添加到intent中。
对应的manifest.xml中的配置:
比如我们想要添加属于自己的category的时候,需要这样调用
相应manifest的配置
最后data,关于data运用的并不多,甚至在使用Intent的时候使用隐式的也不多,这里直接使用一个可以打开浏览器的data配置
我们创建一个类AboutAndroidIntent,并进行配置如下,对
<data android:scheme = "https"/>
进行设置,标明这个是可以打开一个具备https协议的,和浏览器的功能一样。
进行调用
当你点击按钮之后,系统会显示一个列表,来表示能够响应这个Intent的所有 程序,之后你进行选择即可
说说Intent的传值
1.Intent的传值方式:比较简单的值,使用
intent.putExtra("name","wewdfrend")
//里面的类型根据需要,比如传递字符串
Stirng name = getIntent().getStringExtra("name");
//在另外的界面进行使用的时候
2.传递对象
对于传递对象写在Activity的篇幅中似乎不太合理,但是Intent可以说生来就是为Activity提供服务的,先看看Intent到底可以传递什么样的对象:
除了可以传递简单的数据类型,还有可以传递对象。
那么实现方式有两种Serializable
和Parcelable
Serializable
Serializable是序列化的意思,表示将一个对象转换成可存储或者可传输的状态。
序列化后的对象可以再网络上进行传输,也可以存储到本地。
实现方法很简单,只需要你的类 实现 Serializable接口就可以了。
//调用方法和使用简单的传值一样,只不过 personInfo是一个实现Serializable的借口类
intent.putExtra("Serializable",personInfo);
//在接收Activity中调用传进来的内容,需要调用 getSerializableExtra()方法
PersonInfo personInfo = ((PersonInfo) getIntent().getSerializableExtra("Serializable"));
tv.setText(personInfo.getName()+"--"+personInfo.getAge()+"--"+personInfo.getAddress());
Parcelable
Parcelable也可以实现相同的效果,不过Parcelable不是进行序列化而是将一个完整的对象进行分解,而分解后的每一步都是Intent的简单类型,比起Serializable相对复杂。
详情请看:
需求:现在我需要将 一个人的 信息 :name,age,address以对象的形式进行传递,并且使用 Parcelable
1.新建类 PersonMessage,并声明对象
//如果是 private 的变量,需要提供 get 和 set 方法
//如果是 public 的变量,则正常使用就好
2.令 PersonMessage 类 implement Parcelable 接口
2.实现该接口 需要重写两 方法
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//将需要的只进行写出,调用 Parcel的writeXxx()方法
//需要注意数据类型要一至
dest.writeString(name);
dest.writeInt(age);
dest.writeString(address);
}
3.除此之外,还需要你提供一个名为CREATOR的常量,并重写两方法
public static final Parcelable.Creator<PersonMessage> CREATOR = new Parcelable.Creator<PersonMessage>(){
@Override
public PersonMessage createFromParcel(Parcel source) {
//将需要的值读出来
//需要注意的是读取的时候相应的顺序需要和写出的顺序一致
PersonMessage personMessage = new PersonMessage();
personMessage.name = source.readString();
personMessage.age = source.readInt();
personMessage.address = source.readString();
return personMessage;
}
//返回的数量直接传递size
@Override
public PersonMessage[] newArray(int size) {
return new PersonMessage[size];
}
};
至此Parcelable就算完成了
比较来看:实现 Serializable接口比Parcelable接口方便而且省事的多,但是从性能上分析来看,Serializable是将整个对象进行序列化,因此效率上是比Parcelable低了一些,在项目的长期开发中,建议还是使用Parcelable
调用方法:
btn_sendValueParcelable.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PersonMessage personMessage = new PersonMessage();
personMessage.setName("wedfrend");
personMessage.setAge(24);
personMessage.setAddress("XM china");
//传值方法
Intent intent = new Intent(MainActivity.this,ParcelableActivity.class);
intent.putExtra("parcelable",personMessage);
startActivity(intent);
}
});
/**
* 获取数据
*/
PersonMessage personMessage = getIntent().getParcelableExtra("parcelable");
mEmailView.setText(personMessage.getName()+"--"+personMessage.getAge()+"--"+personMessage.getAddress());
返回数据给上一个活动
这个相对来讲也是不常用的一个方法,甚至可以说我们用的不多,市场上多数的APP都是一路向前的,基本上没有太多的APP是需要将数据返回到上面一个界面,即使有某些界面,使用的并不多,但是我们还是要掌握这种方法。当然你自己也可以写一些静态的调用,接口的使用等等的方法来实现相同的效果,但是使用android原生的方法并不是说我们无能,不懂得创新等等,而是android团队在整个架构中做优化之后推出的。换句话说:你认为你的技术比android研发团队还牛B吗???
将数据返回到上一个界面使用的方法是startActivityForResult()
在跳转目标类中 调用 serResult()
来进行数据的返回
之后需要在跳转的类中重写 onActivityResult()
方法
/**
* @params intent
* @params requestCode:请求的判断值
*/
startActivityForResult(Intent intent, int requestCode)
/**
* @resultCode 返回的结果判断值
* @data 返回的附带参数值
*/
setResult(int resultCode, Intent data)
/**
* @params requestCode
* @params resultCode
* @params data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
实践一下:
我们就使用 Parcelable 中的示例进行延伸的展示
step1:将startActivity(intent)
修改为startActivityForResult(intent,1);
step2:在ParcelableActivity中添加一个Button,并添加点击事件的方法,调用serResult()方法并销毁ParcelableActivity界面
Button btn_back = (Button) findViewById(R.id.btn_back);
btn_back.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("value","我是返回值");
setResult(1,intent);
finish();
}
});
step3:在MainActivity中重写onActivityResult()方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case 0:
if(resultCode == 1){
Toast.makeText(this,data.getStringExtra("value"),Toast.LENGTH_SHORT).show();
}
break;
}
}
至此,已经完成将值返回至上一个界面的效果
但是,android手机有个特例,那就是back按钮,很奇怪,同样是返回上一个界面,点击button按钮和点击手机本身的back按钮是两种不同的结果,怎么办,这需要我们重写一个方法
@Override
public void onBackPressed() {
super.onBackPressed();
//书写方法,和button的点击方法一致就好了
}
关于栈的管理
android是使用任务(Task)来管理Activity的,栈是一种先进后出的数据结构(FILO),android系统总是显示处于栈顶的活动给用户。(貌似所有的系统都是 这样的吧)
那先在说说需求:栈按照 FILO的数据结构并没有问题,android系统默认也是这样子去管理Application的,实际情况呢?在我们开发的过程中,有事后需要在一定条件触发后销毁掉前面一个Activity,你怎么办?写静态方法也能实现,多了呢?你对手机的性能也提出挑战,这样并不是合格的开发者,那么我么就只能自建管理栈来管理我们的Activity做到可控的状态。
public class ActivityCollector {
private static final String TAG = "ActivityCollector";
private ActivityCollector() {
}
private static ActivityCollector activityCollector = new ActivityCollector();
public static ActivityCollector newInstance() {
return activityCollector;
}
private Stack<Activity> stackActivity;
/**
* 将活动添加入栈
*
* @param activity
*/
public void addActvity(Activity activity) {
if (stackActivity == null || stackActivity.isEmpty()) {
stackActivity = new Stack<Activity>();
}
Log.i(TAG, "addActivity: " + activity.getClass());
stackActivity.add(activity);
}
/**
* 将活动移除出栈
*
* @param activity
*/
public void removeActivity(Activity activity) {
if (!stackActivity.isEmpty()) {
stackActivity.remove(activity);
}
}
/**
* 将制定的Activity移除出栈
*
* @param cls
*/
public String removeAssignActivity(Class<?> cls) {
if (!stackActivity.isEmpty()) {
Iterator<Activity> iterator = stackActivity.iterator();
while (iterator.hasNext()) {//执行遍历循环
Activity activity = iterator.next();
if (activity.getClass().equals(cls)) {//查看是否一致
//一致便将该activity移除
iterator.remove();
activity.finish();
}
}
}
return checkActivity();
}
/**
* 查询当前栈中的Activity
*/
public String checkActivity() {
StringBuffer stringBuffer = new StringBuffer();
if (!stackActivity.isEmpty()) {
Iterator<Activity> iterator = stackActivity.iterator();
while (iterator.hasNext()) {
Activity activity = iterator.next();
Log.d(TAG, "checkActivity: " + activity.getClass());
stringBuffer.append(activity.getClass()+"---");
}
}
return stringBuffer.toString();
}
public void finishApplication(){
if (!stackActivity.isEmpty()) {
Iterator<Activity> iterator = stackActivity.iterator();
while (iterator.hasNext()) {//执行遍历循环
Activity activity = iterator.next();
iterator.remove();
activity.finish();
}
//杀掉进程
android.os.Process.killProcess(android.os.Process.myPid());
}
}
}
因为使用栈来进行管理,那么我们每一个Activity都是需要进行执行添加操作,自然也有移除,但是在每一个Activity中进行是不是太过于麻烦,所以在这里创建一个基类,BaseAppCompatActivity ,那么所有的活动类均继承基类,就像这样的
XxxxActivity extends BaseAppCompatActivity
而
BaseAppCompatActivity extends AppCompatActivity
BaseAppcompatActivity的相应配置可以如下:
public class BaseAppCompatActivity extends AppCompatActivity {
private static final String TAG = "BaseAppCompatActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate: "+getClass().getName());
Log.i(TAG, "onCreate: "+getClass().getSimpleName());
ActivityCollector.newInstance().addActvity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.newInstance().removeActivity(this);
}
}
实践时间:
创建3个Activity
跳转方式
MainActivity–>SecondActivity–>ThridActivity–>fourActivity
具体界面的点击跳转方法就不累述类,使用Intent跳转就好了。
如图,点击 查看栈中的所有活动,显示如下
点击移除SecondActivity可以看到之后剩下的Activity
activity的启动模式
在实际的项目中我们应该根据特定的需求为每一个活动指定恰当的启动模式。activity的启动模式共有四种,如图
standard:android中的默认启动模式,也就是完全符合栈的数据结构,无论你的栈中是否存在相对应的Activity,只要属于新建的类,就会进行创建。
singleTop: 在进行跳转之前,会进行检查目标Activity是否存在栈顶,若存在,则进行加载而不会重新创建。该模式也只是检查栈顶而已
singleTask:在进行跳转之前,会查询整个栈中的Activity是否已经创建过目标Activity,如果存在会将该Activity拿出来放在栈顶重新加载,而不会再一次的创建
singleInstance:不同于上述三种模式,这种模式会重新创建一个栈来进行管理Activity。
这种模式的意义解释:想象以下情景,假设程序A中有一个活动是允许其他程序进行调用的,但是每一个程序都有自己的管理栈,所以你就需要 使用这种模式将你的活动进行分开管理了。这种模式下就会有一个单独的返回栈来管理这个活动了,不管是哪一个应用程序来进行访问这个活动,都共用一个返回栈。
协作开发的一些经验
在实际情况下,大型的项目不可能是你一个人完成的,需要和同事们协同合作,那么在Activity的跳转的时候是需要传递一些值,如果是你自己一个人写完整个APP,那我下面的建议纯属多余。
比如 你在开发一个界面 XxxxActivity,而这个界面是需要另外一个开发者在完成某一功能之后在调用,而XxxxActivity需要一些参数值的传递,你的界面才能正常的执行,协同开发过程就是大家一起向目标出发,而你需要做的就是在调用你的界面是给予对方足够的方便。
办法:在你自己的 类中写静态的调用方法
public static void startXxxxActivity(Context context,String xxx,String xxxx ){
Intent intent = new Intent(context,XxxxActivity.class);
intent.putExtra("params1",xxx);
intent.putExtra("params2",xxxx);
startActivity(intent);
}
这样对于传递值的命名你自己决定,之后调用也不会存在键值不同的问题,因为都是你自己写下的。而你的同事调用的时候只需要传递相应的参数就好了。
XxxxActivity.startXxxxActivity(this,"","");
是否很方便呢?看个人吧!
关于 Activity中的一些强大而不经常使用的方法总结完了,有些时候,自己心里明白但就是用语言解释不出来,还是需要提高境界啊
对于这一部分的示例,请在个人的github中下载
https://github.com/wedfrendwang/PrivateProject.git
说明一下:后面的相应总结实践也会在这个项目中,所以关于Activity的实例我会在上传之后开辟 名为 activityUnit
的分支,这样如果被需要的朋友下载只需要切换分支就可以了。
看了电影《摆渡人》,每个人都有故事,每个人都有自己的一段艰难时光,失意岁月吧。如同歌词说的:时间向前走一定只有路口没有尽头。愿所有人都有摆渡人。