Android 小知识点笔记
获取 view 的位置
View.getLocationInWindow(int[] location)
一个控件在其父窗口中的坐标位置
View.getLocationOnScreen(int[] location)
一个控件在其整个屏幕上的坐标位置
getLocationInWindow是以B为原点的C的坐标
getLocationOnScreen以A为原点。返回的都是左上角的位置。
Activity 类的 super.overridenMethod() 调用顺序
知道 Java 语言规范没有指定调用 super.overridenMethod() 必须放置的顺序(或者如果必须放置调用)。
在 Activity 类的情况下,super.overridenMethod()调用是必需的并强制:
-
对于创建半周期 onCreate,onStart 和 onResume,建议先调用 super.overridenMethod(),这样可以确保你在操作自己的逻辑时候,activity 已经准备好了,不会出现异常情况;
-
对于销毁半周期:onPause,onStop,onDestroy,建议先做自己的逻辑,最后再调用 super.overridenMethod(),这样可以确保调用你得方法时候,activity 没有销毁你可能需要用到的东西,避免出现错误。
包含内部类的.java文件编译后生成几个.class文件
如果一个类有内部类,编译将生成几个字节码文件,规则是怎样呢?
写在前,自己动手丰衣足食,结论只有个人实验支持,没有官方数据支持,欢迎自行查阅文档然后来指正。
普通类包含内部类的样例
public class Test319 { private static class StaticInner{ }//静态内部类 private class Inner{}//成员内部类 public void outerFunction1(){ class PartInner3{}//局部内部类3 } public void outerFunction2(){ class PartInner1{}//局部内部类1 class PartInner2{}//局部内部类2 } public Thread thread1 = new Thread(new Runnable() {//匿名内部类1 @Override public void run() { } }, "thread1"); public Thread thread2 = new Thread(new Runnable() {//匿名内部类2 @Override public void run() { } }, "thread2"); public Thread thread3 = new Thread(()->{//匿名内部类(使用lambda表达式) },"thread3"); }
2.编译后生成的文件目录
总结
首先,包括外部类在内一共有 9 个类,而目录中只有 8 个 class 文件,可以看出一套规律:全部内部类编译后都会生成字节码文件,但是匿名内部类有个特点
-
成员内部类:外部类名后加一个 dollar 接内部类名
-
静态内部类:和成员内部类一样(毕竟都是外部类的成员,静态非静态而已)
-
局部内部类:在 dollar 后比成员内部类多了个数字
-
匿名内部类:dollar 后只有一个数字,如果使用 lambda 表达式创建匿名内部类将不生成 class 文件,否则会生成。
其次,局部内部类和不使用 lambda 表达式创建的匿名内部类,他们的 class 文件名都包含数字,数字究竟是什么含义?
详情点击 参考文章
WindowManagerGlobal
一个进程只有一个 WindowManagerGlobal 实例,维护当前进程中已经添加到系统中的窗口信息;会判断当前view 是否已经添加过,一个 view 只能被添加一次;
android:fitsSystemWindows属性
fitsSystemWindows
用于设置view在当前窗口的表现。一个app页面,包含:状态栏、标题栏、内容区、底部导航栏(比如有些手机的虚拟导航按键),如图:
整个界面可以看做是一个window,fitsSystemWindows 生效的前提是状态栏(StatusBar)或导航栏(NavigationBar)透明并且不能有标题栏,默认fitsSystemWindows = true,表示页面布局(内容区)不会扩展到状态栏,会针对透明的状态栏会自动添加一个值等于状态栏高度的paddingTop;针对透明的系统导航栏会自动添加一个值等于导航栏高度的paddingBottom,当fitsSystemWindows = false时,表示页面布局(内容区)扩展到状态栏,设置代码如下:
<style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <!--透明状态栏--> <item name="android:windowTranslucentStatus">true</item> </style>
为布局view设置:
android:fitsSystemWindows="false"
效果如下:
总结:
-
fitsSystemWindows 生效前提:当前页面没有标题栏,并且状态栏或者底部导航栏透明
-
fitsSystemWindows = true,表示内容区不延伸到状态栏或底部导航栏
-
fitsSystemWindows = false,表示内容区延伸到状态栏或底部导航栏
Android Intent.FLAG_ACTIVITY_NEW_TASK
熟悉Android委托机制的应该知道,这个mBase实际指向的是ContextImpl这个类,我们再来看这个类中的startActivity方法
@Override public void startActivity(Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options); }
注意看上面的这段代码:if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0)
如果intent中没有添加FLAG_ACTIVITY_NEW_TASK这个属性,那么就会报错。
这也是为什么在Service中调用startActivity和在BroadcastReceiver(静态注册)中通过onReceive传递过来的context.startActivity时(该context类型为ReceiverRestrictedContext,和Service一样,都没有重写startActivity),如果不加FLAG_ACTIVITY_NEW_TASK的话会报如下错误的原因.
那到这里可能就有疑问了,为什么在Activity中不加FLAG_ACTIVITY_NEW_TASK调用startActivity时不会报错呢。原因是因为Activity重写了ContextWrapper中的startActivity方法
@Override public void startActivity(Intent intent) { this.startActivity(intent, null); } @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); }
可以发现,里面是没有加FLAG_ACTIVITY_NEW_TASK判断的。
现在在回到开头,FLAG_ACTIVITY_NEW_TASK从字面上来理解,意思是:把将要启动的Activity放在一个新栈中,既然是新栈,那它的taskId和启动它的Activity所在的taskId肯定是不一样的。
它们的TaskId竟然是一样的,在同一个栈中,说好的new_task呢?
原来这个这个属性,还有不少隐藏的信息,它的原则是:设置此状态,首先会查找是否存在和被启动的Activity具有相同的亲和性的任务栈(即taskAffinity,注意同一个应用程序中的activity的亲和性一样),如果有,则直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的activity顺序不变,如果没有,则新建一个栈来存放被启动的activity。
原来如此,因为我们是在同一个应用中进行Activity跳转的,所以它不会创建新的Task。那现在试一下在不同的应用中跳转看看,先创建两个module
FragmentStatePagerAdapter与FragmentPagerAdapter的区别:
俩种方法基本一致,唯一的区别在于:卸载不需要的fragment时,各自采用的方法不同。FragmentStatePagerAdapter会销毁不需要的Fragment,FragmentManager中的Fragment会被彻底移除掉,而FragmentPagerAdapter不会销毁实例,实例保存在FragmentManager中,它只是把视图销毁掉。
FragmentStatePagerAdapter的Fragment管理如下图:
图.FragmentStatePagerAdapter的Fragment管理
当滑动屏幕时(从Item1滑动到Item2),Item0的视图以及实例都将被销毁,同时ViewPager回去缓存Item3的Fragment(Item1的已经缓存过故不重复缓存)
FragmentPagerAdapter的Fragment管理如下图:
同样,当滑动时FragmentPagerAdapter只是销毁了Item0的View视图,实例依然保存在FragmentManager中。
另外,FragmentStatePagerAdapter可在onSaveInstanceState(Bundle)中保存fragment的Bundle信息。显然FragmentPagerAdapter更占用内存。
adb 打开存储权限