Android 小知识点(一)

  1. OOM 可以被try catch 么?
    OOM 属于Error 发生在try catch 可以被catch ,但不推荐这么做
    Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正。一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。 常见 OutofMemoryError StackOverflowError

Exception(异常)表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。常见 NullPointerException IllegalArgumentException
1.android:clipChildren =false 的父布局里面放一个webview 有bug

  1. Serializable与Parcable的区别。 在什么情况下需要用到序列化和反序列化,Serializable中为什么要设置UID,设置UID与不设置UID值的区别和影响?
    Serializable 默认会为你生成一个Uid 当类结构发生变化的时候Uid 会发生变化, 如果序列化到一个文件之后,修改了类结构,反序列化的时候,会认为不是同一个类 java.io.InvalidClassException FC , 如果自己制定Uid 不会发生变化,可以避免fc ,但是只能恢复之前结构里有的数据

  2. 为什么要使用动态代理 需求模型:在一个类的所有方法执行前后打印log ,静态代理的做法,需要根据被代理的类的方法创建n个方法,但是动态代理,通过反射实现,不需要关心,被代理类 的方法有多少什么样子,统统走到handler 的invoke 方法中 ,能够做统一的处理,还有一些比如说类似于jar 包中的类,我们不知道具体实现的方法是哪些,可以通过动态代理处理

/**
 * 本来用于验证动态代理的使用场景,动态代理比静态代理优势在哪里
 * 需求场景 : 在某个方法执行前后打印log
 *
 */
public class InvokeProxy {
    private ICalculator target;
    public static String TAG="fs InvokeProxy TAG";

    public InvokeProxy(ICalculator target) {
        this.target = target;
    }

    public ICalculator getIcalculatorProxy() {
        ClassLoader classLoader = target.getClass().getClassLoader();
        Object proxyInstance = Proxy.newProxyInstance(classLoader, new Class[]{ICalculator.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.d(TAG, "invoke: " + Arrays.asList(args));
                Object invoke = method.invoke(target, args);
                Log.d(TAG, "invoke: " + invoke);
                return invoke;
            }
        });
        return (ICalculator) proxyInstance;
    }
}
  1. 关于intentFlag FLAG_ACTIVITY_NEW_TASK
    如果在不指定栈(比如从manifest中) 是不会独立创建一个栈的,可能是安卓避免用户过多的创建栈,只有在制定栈的时候才生效,否则相当于没有加这个flag,详情查看谷歌文档
    [https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack?hl=zh_cn#Affinities]

  2. adb的常用命令
    https://juejin.im/entry/57c00fe4c4c971006179838a#%E8%B0%83%E8%B5%B7-activity

1)启动一个activity
adb shell am start -n com.android.email/com.kingsoft.email2.ui.MailActivityEmail
2)复制文件

adb pull /sdcard/01.png C:\Users\fushuang\Desktop\test.png
3)导入文件
adb push C:\Users\fushuang\Desktop\数仓1期埋点20190719.xlsx /sdcard/test11.xlsx
4)输入文本
adb shell input text "aaaaa"
5)打印activity 栈信息
adb shell dumpsys activity | findstr Run
6)adb 截屏
adb shell screencap -p /sdcard/01.png
7)发送广播
adb shell am broadcast -a com.kingsoft.email.action.battery_setting_changed -p com.android.email

  1. 关于Intent.FLAG_ACTIVITY_SINGLE_TOP
    Intent 的Flag 只适用于ams 将activity 完全初始化完成之后生效,一下情况是无法生效的,仍然会启动三个同样的界面在栈顶,同样 设置android:launchMode="singleTop"也不会生效。
    如果需要产生严格的SingleTop的现象,需要Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP
        final Intent intent = new Intent(this, Empty2Activity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivity(intent);
        startActivity(intent);
        startActivity(intent);
  1. Intent.FLAG_ACTIVITY_CLEAR_TOP 并不等同于 android:launchMode="singleTask"
    栈内A,B,C,D 启动 B ,对于C,D是没有区别的,但是Intent.FLAG_ACTIVITY_CLEAR_TOP 会导致B Destory 然后onCreate 但是 android:launchMode="singleTask" 不会重新创建,只会onNewIntent 。要想通过设置Flag 实现一样的效果,需要 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 这样才能防止重新创建

  2. Intent 在传递枚举(enum) 的时候通过Serialiazeable传递, 但是反序列化之后并没有创建新的对象,对象地址不变,但是在传递Class 的时候,发序列化地址会变,只是内容被clone 了. 所以比较枚举反序列化前后的值可以用==判断,返回是true ,但是class == 判断前后的值,返回为false

  1. 一个线程崩溃会影响到其他线程么?
    经过试验发现其实是不会的,之所以安卓fc 会导致整个应用结束是因为Thread 的DefaultUncaughtExceptionHandler 中应该是作了终结进程的操作
android.os.Process.killProcess(android.os.Process.myPid())

当我们自己设置主线程DefaultUncaughtExceptionHandler 之后,主线程发生崩溃,子线程可以继续工作,不受影响,但是主线程在接收事件之后会发生anr, 考虑可能是主线程事件响应机制中做了某些判断.可能有主线程是否fc的标记位

  1. LinkCanary
    linkcanary 中依赖的aar 文件,在AS 中可能不会看到清单文件,因为as 在展示有些包结构的时候展示的并不完整,在文件中查看会看到aar 中存在清单文件,并且在其中声明了所需的Service 和Activity , 在编译的时候会merge 到最终的Manifast 里.
    另外LinkCanary 中使用弱引用机制检测内存泄漏,将Activity 通过弱引用持有.弱引用有一个特点,如果被弱引用指向的对象,有一个强引用持有的话,这种情况gc是不会回收弱引用的,所以如果发生泄漏,弱引用get() 方法是一直可以获取到activity或者fragment 的,反之,如果不存在泄漏,那么弱引用作为唯一的引用,是一定会被gc发现回收的.
    在发生泄漏之后会调用系统VM 方法生成hprof 文件,然后读取通过算法分析,算出泄漏对象到达gc root 的最短路径,,关于计算算法可能才是linkcanary 核心的东西, 然后交由展示页面展示

如果这个 activity 可以被回收,那么弱引用就会被添加到 ReferenceQueue 中。
等待主线程进入 idle后,通过一次遍历,在 ReferenceQueue 中的弱引用所对应的 key 将从 retainedKeys 中移除,说明其没有内存泄漏。
如果 activity 没有被回收,先强制进行一次 gc,再来检查,如果 key 还存在 retainedKeys 中,说明 activity 不可回收,同时也说明了出现了内存泄漏。

  1. 关于启动模式:
    使用Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_ACTIVITY_SINGLE_TOP时调用了onNewIntent方法,复用了MainActivity,说明他们可以实现和singleTask一样的效果
    Intent.FLAG_ACTIVITY_CLEAR_TOP 不等同于singleTask 启动模式,因为不会调用onNewIntent

  2. 关于Android 文件路径获取

  • 文件包内路径(随app 删除而删除)
Context.getCacheDir():               /data/data/com.learn.test/cache
Context.getFilesDir():               /data/data/com.learn.test/files
Context.getFileStreamPath(""):       /data/data/com.learn.test/files
Context.getFileStreamPath("test"):   /data/data/com.learn.test/files/test
  • SDcard 外部存储(也可以是内置Sdcard)
//外部存储应有私有
Context.getExternalCacheDir():                           /storage/emulated/0/Android/data/com.learn.test/cache
Context.getExternalFilesDir(""):                         /storage/emulated/0/Android/data/com.learn.test/files
Context.getExternalFilesDir("test"):                     /storage/emulated/0/Android/data/com.learn.test/files/test
Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES):    /storage/emulated/0/Android/data/com.learn.test/files/Pictures

###以上在包名内部的均不需要权限申请
//外部存储public share 目录
//高版本需要动态申请权限
Environment.getExternalStorageDirectory():                     /storage/emulated/0
Environment.getExternalStoragePublicDirectory(""):             /storage/emulated/0
Environment.getExternalStoragePublicDirectory("test"):         /storage/emulated/0/test
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES):  /storage/emulated/0/Pictures
  1. 子线程是否能更新UI
    在ViewRootImpl 中 记录初始化线程mTread
  void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

checkTread会在onLayout 等方法中调用,如果settext 不引起laytou 变化,可以设置,不会fc

Android 8.0 适配
1.通知需要有Channel 很多本来对Notification 的设置,都转换到了对Channel 设置,8.0之后对通知有所简化,副文本标题取消显示,显示通知时间需要单独设置 否则默认不显示时间
2.对于电量消耗的优化,安卓采用对后台Service进行限制,进入后台60s 之后,service 会调用自身onDestory ,调用之后,内部没有完成的线程还会继续执行(可能产生内存泄漏,尽量在onDestory中stopSelf) 但是不能再在Service中startService ,防止服务互相唤醒或者启动更多的服务消耗电量.
对于之前Service方案,可采用一下两种替代方案
(1) 采用 startForegroundService(new Intent(getBaseContext(),ServiceTest.class)); 方法,开启一个前台Service, 在开启后的5s后,必须调用 startForeground(1, notification); 但目前8.0版本 没有设置channel 的通知不会被显示,所以只是对于系统来说,属于前台Service,对于用户来说,不会有其他的表现,估计以后版本,会修复吧.尽量还是按照会在界面展示考虑
(2)采用JobService 代替Service ,JobService Add in Api 21. 用于在一些特定时刻触发操作,类似于定时任务.内部封装了方便的监听网络变化,开机 (之前用广播监听在高版本也会失效,也需要用JobService 替代) . 通过setOverrideDeadline 或者setMinimumLatency 设置一个很短的时间,来让他立即执行,可以起到同普通Service 一样的效果.
(3) 对于广播: 隐式广播(通过Action 启动的) ,在未指定包名的情况下.无法全局广播.

  1. 关于postInvalidate() requestLayout()
    requestLayout() 只是通知父容器位置变化,重新进行measure 和layout 不会重新绘制ondraw (但实际上经过测试发现,第一次是会调用一次ondraw 的,但是后续就不会调用了,只会递归的向上调用,比如Viewgroup 调用 requestLayout 只会引起他的parent measure 和layout ,子view 并不会重新测量)
    postInvalidate() 不关心位置变化,不走measure 和layout 只走ondraw,并且不会引起递归调用,调用哪个控件的ondraw 就只是ondraw 他本身

  2. SharePreferance 是否是线程安全的?
    SharedPreferences是线程安全的,它的内部实现使用了大量synchronized关键字SharedPreferences不是进程安全的第一次调用getSharedPreferences会加载磁盘 xml 文件(这个加载过程是异步的,通过new Thread来执行,所以并不会在构造SharedPreferences的时候阻塞线程,但是会阻塞getXxx/putXxx/remove/clear等调用),但后续调用getSharedPreferences会从内存缓存中获取。 如果第一次调用getSharedPreferences时还没从磁盘加载完毕就马上调用 getXxx/putXxx, 那么getXxx/putXxx操作会阻塞,直到从磁盘加载数据完成后才返回所有的getXxx都是从内存中取的数据,数据来源于SharedPreferences.mMapapply同步回写(commitToMemory())内存SharedPreferences.mMap,然后把异步回写磁盘的任务放到一个单线程的线程池队列中等待调度。apply不需要等待写入磁盘完成,而是马上返回commit同步回写(commitToMemory())内存SharedPreferences.mMap,然后如果mDiskWritesInFlight(此时需要将数据写入磁盘,但还未处理或未处理完成的次数)的值等于1,那么直接在调用commit的线程执行回写磁盘的操作,否则把异步回写磁盘的任务放到一个单线程的线程池队列中等待调度。commit会阻塞调用线程,知道写入磁盘完成才返回MODE_MULTI_PROCESS是在每次getSharedPreferences时检查磁盘上配置文件上次修改时间和文件大小,一旦所有修改则会重新从磁盘加载文件,所以并不能保证多进程数据的实时同步从 Android N 开始,,不支持MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE。一旦指定, 直接抛异常

  3. 方法时间性能优化
    android.os.Debug.startMethodTracing(String traceName);
    android.os.Debug.stopMethodTracing();


    微信截图_20190123201312.png
  4. 关于切换语言 导致栈内activity 重新创建的问题
    如果栈里有两个activity 如果切换语言导致界面重新创建 , 则两个activity 创建时间是不同的,系统只会在activity 变为可见的时候重新创建,如果在A1 oncreate 中启动了A2 A1 onActivityResult 中finish() A1
    那么 栈内A1 A2 的情况下,切换语言,a2 finish 生命周期如下
    A1 onDestroy A1onCreate (先重新创建)
    A1onActivityResult (中执行finish)
    A2 onCreate (由此可见,是A2 先创建,为了resume 做好准备之后A1才销毁的)
    A1 stop destory
    A2 resume()

    微信截图_20190130171528.png

19.关于.9 png问题
.9 png 的内容区域(右边黑线)如果不是全部图片高度,比如上面少个100 px,那么把这个设置给一个LinearLayout 这个drawable 。会导致内容变高,比原来的高100个像素,这在LinearLayout 的measure 和layout 方法里应该有体现,没仔细在源码里找,有时间看看

20.Android onTouchEvent event.getY() 是相对于view的getTop的位置么?
不是的,不是的,getTop并不会计算到translation 的值。是相对于View.getY的偏移量

 public float getY() {
        return mTop + getTranslationY();
    }

原因如此,mTop是View 的Translation之前的位置,如果在onTouch中给View 向下Translation,那么Translation结束之后,MotionEvent中的getY 会返回新的根据getY 计算的值,也就是说会发生突变

在java中一个线程的崩溃是不会导致进程崩溃的 setCrashDefaultHandler只能在抛出异常的时候进行最后善尾处理,并不能防止线程终止.安卓子线程或者主线程崩溃之后之所会终止 是因为activity thread初始化的时候设置了CrashDefaultHandler 在崩溃的时候kill了进程

.addToBackStack("Mystack")
Fragment 添加返回栈之后会对生命周期产生影响

不加返回栈 FragmentTransaction replace


image.png

加返回栈 FragmentTransaction replace


image.png

加了返回栈会导致前一个被Replace 的 Fragment不走 onDestroy onDetach

同步屏障
MessageQueue#postSyncBarrier()
同步屏障会阻塞同步Message 消息 的执行,但不会阻止异步消息Message#setAsynchronous(true)执行。在同步屏障移除之后,同步消息继续中执行(如果时间到了的话)

你可能感兴趣的:(Android 小知识点(一))