Android 支持屏幕旋转的方式

目录

[TOC]

###0. Preface 在Android中activity默认是支持屏幕旋转操作的。在屏幕旋转过程中所在的Activity会重建。

所谓的重建大致是这样一个流程:

  1. onSaceInstanceState
  2. Destroy current activity
  3. reCreate current activity

因此Activity的状态都是保存在“InstanceState”中的,于是我们需要在重建Activity的时候恢复其状态。

###1. 关于旋转

####1.1 配置/禁用旋转 屏幕旋转是在Manifest可以配置的,对应的attribute是android:screenOrientation,常用的配置有portraitlandscape,分别对应 “竖屏”和“横屏”。可以配置支持多种方向,中间用管道符号|隔开,像酱紫portrait|landscap.

如果你只配置一个方向,就相当于禁用了屏幕旋转了。

这个属性的取值还是相当丰富的,详细内容请参阅官方文档

当然还可以在代码中设置屏幕方向和获取当前方向。

设置

1
2
3
4
5
6
7
8
9
10
//set orentation in java code programatically

//For Landscape mode
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

//For Portrait Mode
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

//For ANYTHING, please RTFM
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_<ANYTHING>);

获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//get current screen orientation
public String getOrientation() {
    Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
    int orientation = display.getRotation();
    switch (orientation) {
        case Surface.ROTATION_0:
            return "portrait";
        case Surface.ROTATION_90:
            return "land";
        case Surface.ROTATION_180:
            return "portrait reverse";
        case Surface.ROTATION_270:
            return "land reverse";
        default:
            throw new AssertionError("not gonna happen");
    }
}

####1.2 资源 我们知道Android的Resources是配置了一个android.content.res.Configuration的,这个配置内容很丰富,其中就包括了屏幕方向。Resources会根据配置去取得相应的资源,比如根据语言去哪字符资源。 在Resources的强力支持下,你可以给不同的屏幕方向相应的资源文件,默认都是竖屏的,把横屏的放在 xxx-land,如layout-land,你可以引用同一个资源id,在Resources加载资源时会根据Configuration自动选择。

###2. Configuration 根据Android的android.content.res.Configuration文档说明,这些“配置”是能够影响Resources怎么拿到资源的所有信息的集合。举个例子,系统语言设置会影响Resources拿到字符串资源,中文还是英文?详细信息可以参看官方文档. 因此当Configuration变化的时候,Activity能拿到的resources也会发生变化。于是Android系统会重建该Activity,来更新界面。其中屏幕方向也是Configuration中的一个维度。因此屏幕旋转时也会导致Activity重建。

但是有时候Activity希望自行处理相应的Configuration变化事件,这时候系统给我们提供了这样的机会。

在Manifest的Activity标签中有这样一个attribute android:configChanges,在这里声明希望自行处理的事件,多个事件之间用管道符号|来分隔,举个栗子

1
2
3
4
5
6
<activity
    android:name=".mediacodec.MediaCodecActivity"
    android:theme="@style/AppTheme"
    android:label="MediaCodec"
    android:configChanges="orientation|screenSize|locale"
    >

需要注意一点:在Android3.2 (API 13)之后,屏幕旋转会同时触发orientationscreenSize两个变化。

然后在Activity::onConfigurationChanged(Configuration)中处理相应的变化即可,这时候不会重新创建Activity。

notice: >即使不重建Activity,屏幕旋转的时候,界面也会跟着转过来,只是布局还是之前那个布局。

###3. 状态保存

我们知道Activity重建时ActivityThread直接通过反射创建了一个新的Activity实例,之前的所有实例相关的内容都会丢失。 那么,界面重建时,==状态怎么保存==。

前面我们说到了onSaveInstanceState,但是这个方法有一个超级,及其,特别严重的问题:他只能存可以被序列化的值类型。想象这样的场景,我正在下载文件,于是我有一个tcp的socket,我当然希望界面重建不会打断下载过程。我可以序列化下载进度,但是要让我序列化socket!!!请恕臣妾做不到啊!!!

然后这时候脑海里开始蹦出来黑魔法:static变量!存在Application里!使用单例!

嗯~淡定,淡定。系统已经考虑到这种情况了。并且专门提供了相应的方法给我们用。

####3.1 Activity保存状态的方法

1
2
3
4
5
//1
Object Activity::onRetainNonConfigurationInstance();

//2
Object Activity::getLastNonConfigurationInstance();

Activity可以覆盖1,然后return需要保存的实例(比如Socket,数据库Cursor)。然后在onCreate中通过方法2拿到上面返回的内容。

所以大致是这样一个使用方式:

1
2
3
4
5
6
7
8
9
10
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mDownloadTcpSocket = (Socket) getLastNonConfigurationInstance();
}

@Override
public Object onRetainNonConfigurationInstance() {
    return mDownloadTcpSocket;
}

关于onRetainNonConfigurationInstance需要注意的是: 1. 文档中说你可以在中返回任何实例,甚至是Activity自身。但是这个只是理论上,因为这里返回的实例会被新创建的Activity引用住,如果真的返回了Activity自身,估计屏幕旋转几次就会OOM了。

  1. 尽量不要在这个方法中保存Resources相应的东西,甚至是View。因为Configuration变换本身会导致Resource(可能)拿到不同的资源。(取决于res的内容)。如果一定要保存,请三思而行,并留下充足的文档说明。

  2. 避免Activity自身实现一些回调的interface。原因你懂得。因为当前的实例会销毁,新创建的实例和前一个引用是不一样的。所以在Activity销毁之前发出的请求,永远不会回调给新创建的Activity。另外如果这个回调是被强引用的就更危险了,当你旋转几次屏幕时,系统就会给你发来贺电“恭喜你OOM了”。

  3. 文档中还说,这个方法再onStoponDestroy之间调用。并且在onDestroy调用之后,会立即创建一个新的Activity,在这段时间里会阻止主线程的消息分发。

  4. 然后官方文档有一句是这样的话,让我很困惑 > This function is called purely as an optimization, and you must not rely on it being called. When it is called, a number of guarantees will be made to help optimize configuration switching…

    这让我很害怕,如果这个方法不一定会调用那不是很糟!!于是我被迫看了看系统源代码,在ActivityClientRecord ActivityThread::performDestroyActivity(...)中找到了蛛丝马迹。只要Configuration change,onRetainNonConfigurationInstance一定会调用。API文档的意思估计是说非Configuration change的时候不调用吧__(:3」∠)_

  5. 这个方法被废弃了。。。。

####3.2 Fragment保存状态的方法

蛤?刚才说那个方法被废除了。嗯,是的,看到文档有这一句我也是这个心情。原话这么说的

@deprecated Use the new {@link Fragment} API {@link Fragment#setRetainInstance(boolean)} instead; this is also available on older platforms through the Android compatibility package.

既然如此去了解一下Fragment的这方面内容吧。

Fragment::setRetainInstance的文档是这么说的.

Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change).

原来这个方法也是做状态保存的,通过设置这个flag为true你可以做到: 1. 保证Fragment在Activity重建的过程中不被销毁 2. 保存Fragment的实例,并在新的Activity重建的时候自动恢复所有Fragment的状态(包括BackStack)

啊哈!新API果然牛叉!这次连Fragment自身都能保留了。

もう何も怖くない(已经没什么好害怕的了) —— ともえ まみ

于是新的Fragment就可以在旋转屏幕的时候无缝切换了。简直爽呀!!!

关于setRetainInstance需要注意的地方: 1. 该方法的文档中说到 > This can only be used with fragments not in the back stack.

然而经过亲身实践和查阅源代码我发现这里和BackStack并没有半毛钱的关系\_\_(:3」∠)\_,不信找代码看看。
```java
public void Fragment::setRetainInstance(boolean retain) {
    if (retain && mParentFragment != null) {
        throw new IllegalStateException(
                "Can't retain fragements that are nested in other fragments");
    }
	//直接set不判断是够在BackStack
    mRetainInstance = retain;
}

ArrayList<Fragment> FragmentManager.FragmenagerImpl::retainNonConfig() {
    ArrayList<Fragment> fragments = null;
    if (mActive != null) {
        for (int i=0; i<mActive.size(); i++) {
            Fragment f = mActive.get(i);
			//这里只判断是否retainInstance,并没有判断BackStack之类的。
            if (f != null && f.mRetainInstance) {
                if (fragments == null) {
                    fragments = new ArrayList<Fragment>();
                }
                fragments.add(f);
                f.mRetaining = true;
                f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
                if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
            }
        }
    }
    return fragments;
}
```
所以不知这句doc意义何在。StackOverFlow有人说如果BackStack中有Fragment会retain有的不会retain,这样会导致问题,但是我觉得理由有点牵强,不retain的也会重新创建然后恢复状态呀。

###4. 生命周期

####4.1 Activity 在发生ConfigurationChange时,Activity会经历先销毁再重新创建的过程。该过程的生命周期和一般情况无疑。 在从onPause开始的销毁过程中可以通过Activity::isChangingConfigurations()方法判断是因为ConfigurationChange导致销毁。

####4.2 Fragment 如果是一般的Fragment,和Activity一样会经历 销毁->FragmentManager恢复Fragment->重建新Fragment 的过程。 如果调用了上面说到的Fragment::setRetainInstance(true),则

Fragment的声明周期会发生细微差别。总体的销毁创建的生命周期方法都会调用,除了下面的这一对

  • onDestroy
  • onCreate

因为Fragment并没有真正的销毁,在Activiyt重建之后使用的Fragment引用还是重建之前的那一个。我认为这样处理也是很合理的。 如果需要在onPause和onStop中判断是否彻底释放相应资源可以通过getActivity().isConfigurationChanges()来判断。

整个生命周期变成这样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
graph TD;

	subgraph destroy
	A(Configuration change) -- destroy --> B[onPause]
	B --> C[onStop]
	C --> D[onDestroyView]
	D --> E[onDetach]
	end

	subgraph restart
	AA(Configuration change) -- restart --> F[onAttach]
	F --> G[onCreateView]
	G --> H[onActivityCreated]
	H --> I[onStart]
	I --> J[onResume]
	end

	D -.not called.-> onDestroy[onDestroy]
	onDestroy -.-> E;
	F -.not called.-> onCreate[onCreate]
	onCreate -.-> G;

#retrolambda测试报告

先说结论

  1. 堆栈行号完全匹配!没有任何问题,包括最新的android build tools自身支持的switch string,MultiCatch,和retrolambda的所有特性都没问题。
  2. lambda是通过生成class来实现的,而且是生成static class而不是anonumous class,因此没有this$0引用。且支持method reference (推荐阅读)
  3. 但是lambda会生成冗余方法,每个Lambda类4个方法(下面有代码),其中一个是原接口的方法;增加3个,一个是构造方法,两个是静态工厂方法。
  4. 支持MultiCatch。
  5. 支持interface加default方法,亲测可行,但是使用该特性必须禁用增量编译。
  6. 支持interface加static方法,亲测不可行(可能版本bug),同上需要禁用增量编译。
  7. 支持tryWithResource但是估计这个用的不多,问题在于默认会吞掉Exception。同时Android Studio会报错说API19才能用,但是可以编译。
  8. 不支持Java8的新增API,包括Lambda相关的function包,和其他新包比如stream api。
Read on →

#IjkMediaPlayer 使用手册

taylorcyang@tencent.com

  1. 首先编译环境是OS X或者linux或者其他*nix系统(Windows暂不支持,因为ijkplayer中使用了软链接o(╯□╰)o)。
  2. 其次系统中需要安装以下工具:
    1. git
    2. Android ndk 并配置环境变量 ANDROID_NDK
    3. Android sdk 并配置环境变量 ANDROID_SDK

项目地址:https://github.com/Bilibili/ijkplayer

note:

如果你觉得TL;DR。不想知道ijkmediaplayer是怎么编译的那就执行./init.sh脚本。让他来帮你做初始化(就是下面的1-3步),然后使用./make.sh做编译甚至是编译之后so的拷贝工作,下文就不用看了。

三个需要的文件分别是:

  1. init.sh
  2. make.sh
  3. module.sh
Read on →

java中实例的序列化是指将一个实例转成 二进制流以用于网络传输或者固化存储之用。事实上,简单点说就是把一个类实例的成员变量存储下来,然而这个过程根据成员变量的类型的不同,可能会很简单,也可能会很复杂。序列化的用处还是很大的,比如跨进程通信(IPC,安卓中的IPC用到了序列化,只是安卓自身实现了一个比java更轻量级、更简单的序列化方式。但是原理大同小异),远程方法调用(RMI,事实上这个和安卓中的Binder通讯很类似);此外还常见的是把实例序列化到数据库中以blob的形式存储。并且因为java语言本身就是跨平台设计的,序列化之后的数据也是平台无关的,因此你无需关心大小端之类的问题(以及类似于C语言的内存对齐问题)!既然序列化这么有用还是有必要学习一下的。(BTW,java EE中提供了更加严格的固化方案Java Date Object,或着也可以考虑使用Hibernate框架。)

参考的资料是《Thinking in java》,和IBM的一篇博文。下面来做几个实验,一边学习一边实践一下。

Read on →

##Android 中service的使用 笔记

###Service是何物
service是安卓四大组件(Activity、BroadcastReceiver、ContentProvider、Service)之一。Service用于执行耗时比较长的操作,在后台运行,没有界面显示。

Service与进程
Service可以在应用的主进程中运行,也可以在单独的进程中运行。只需要在Service的[Manifest声明][manifest]中指明进程名即可。同一个应用中所有具有相同进程名的组件(四大组件,包括Activity)均可以运行在同一个进程中。

Service的寿命
这里说的寿命只是service什么时候运行什么时候终止而已并非生命周期的概念。service的启动方式有两种:
1. 通过Context::strtService来启动一个Service,这个Service会一直在后台运行,即使启动该Service的组件已经结束。它只能自己结束自己,通过Service::stopSelf()或者是被其他组件显示的结束,通过Context::StopService。或者系统可用内存不够,android系统会选择性的kill掉一些service,在需要的时候再重新启动他们。当然如果进程自己crash也是会结束的(╯‵□′)╯︵┻━┻(这不是废话)。
2. 通过Context::bindService启动Service。一个绑定的Service一般是 C-S(客户端-服务器)类型的,通常会和其他组件进行通信,包括:发送请求,收取返回结果,甚至是进程间通信——IPC。一个绑定的Service的寿命和绑定他的组件相关,当所有绑定到他上面的组件都结束(或者取消绑定——通过Context::unbindService方法)的时候,这个service就自然结束。

Read on →

android系统在手机屏幕锁定之后一般会让手机休眠,以提高电池的使用时间。但是休眠意味着CPU频率降低,有时候可能需要做一些需要大量运算的任务,所以需要唤醒CPU。WakeLock可以做到这一点。

Read on →

JNI全称是Java Native Interface是在JAVA和Native层(包括但不限于C/C++)相互调用的接口规范。

JNI在JAVA1.1中正式推出,在JAVA1.2版本中加入了JNI_OnLoadJNI_OnUnload方法,这两个方法还是很有用的,后面再说。

Read on →

安卓收到短信的事件是由系统发一个有序广播的,所以这里需要一个BroadcastReceiver。receiver收到的Intent里面并不是直接存储的短信内容,而是短信的原始数据。所以我们需要自己解码。

获取短信的原始数据: 原始数据被叫做PDU,一个PDU就是一个数据段,如果短信比较长的话可能是由几个PDU组成的。

1
2
3
4
5
6
7
8
9
10
@Override
public void onReceive(Context context, Intent intent) {
    //监听到验证码短信后自动填写验证码
    Log.i(TAG, "SMSBroadcastReceiver SMS_RECEIVED");
    Bundle smsBundle = intent.getExtras();

    if (smsBundle != null) {
        Object[] pdus = (Object[]) smsBundle.get("pdus");
    }
}
Read on →

java SE5提供了java.util.concurrent.Executors类来实现线程池的功能。 Thinking in Java 中这么解释Executors:

Executors允许你执行异步的任务(task)而不用显式的去管理线程的生命周期。

可以说Executors是线程们的管理者,让线程们的生存方式从放养变成了圈养。Executors来处理一次能同时运行多少个线程,哪个线程在哪个线程的后面执行。总的来说Executors之于线程就像操作系统止于进程一样——管理者与被管理者的关系。

Read on →

Copyright © 2017 - LanderlYoung - Powered by Octopress

Recent Posts

Categories

Tags

>