前语

面试专题前面的百度篇,腾讯篇,阿里篇,京东篇,bilibili篇,网易篇,字节篇,小红书,小米,携程十大板块现已更新完了,还剩下最终个专题~继续更新中。

1.12W字;2022最新Android11位大厂面试专题(一)百度篇

2.12W字;2022最新Android11位大厂面试专题(二)阿里篇

3.12W字;2022最新Android11位大厂面试专题(三)腾讯篇

4.面霸养成记;50万字Android面试文档(四五)字节,京东篇

5.面霸养成记;50万字Android面试文档(六七)网易,Bilibili篇

6.面霸养成记;50万字Android面试文档(八九)小红书,小米篇

7.含泪刷128道面试题,50万字2022最新Android11位大厂面试专题(七)

一共50W字的文档,面试专题12W字仅仅一小部分,字数束缚,分几篇更。

重视大众号:初一十五a

提前解锁 《整套50W字Android体系PDF》,让学习更靠近未来实战。

一共包含

1.腾讯Android开发笔记(33W字)

2.2022最新Android十一位大厂面试专题(12W字)

3.音视频经典面试题(6W字)

4.Jetpack全家桶

5.Android 功用监控结构Matrix

6.JVM

7.车载运用开发

共十一模块,今日来更新第11专题爱奇艺篇,面试专题完毕啦

十一丶爱奇艺

1.Android布局层级过深为什么会对功用有影响?为什么Compose没有布局嵌套问题?

做过布局功用优化的同学都知道,为了优化界面加载速度,要尽或许的削减布局的层级。这首要是由于布局层级的增加,或许会导致丈量时刻呈指数级增加。

而Compose却没有这个问题,它从根本上处理了布局层级对布局功用的影响: Compose界面只允许一次丈量。这意味着随着布局层级的加深,丈量时刻也仅仅线性增加的.

下面咱们就一同来看看Compose到底是怎样只丈量一次就把活给干了的,本文首要包含以下内容:

  • 布局层级过深为什么影响功用?
  • Compose为什么没有布局嵌套问题?

①布局层级过深为什么影响功用?

咱们总说布局层级过深会影响功用,那么到底是怎样影响的呢?首要是由于在某些状况下ViewGroup会对子View进行屡次丈量

举个比方

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <View
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_red_dark" />
    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@android:color/black" />
</LinearLayout>
  • LinearLayout宽度为wrap_content,因此它将挑选子View的最大宽度为其最终的宽度
  • 可是有个子View的宽度为match_parent,意思它将以LinearLayout的宽度为宽度,这就陷入死循环了
  • 因此这时分, LinearLayout 就会先以0为强制宽度丈量一下子View,并正常地丈量剩下的其他子View,然后再用其他子View里最宽的那个的宽度,二次丈量这个match_parent的子 View,终究得出它的尺度,并把这个宽度作为自己终究的宽度。
  • 这是对单个子View的二次丈量,假如有多个子View写了match_parent ,那就需求对它们每一个都进行二次丈量。
  • 除此之外,假如在LinearLayout中运用了weight会导致丈量3次乃至更多,重复丈量在Android中是很常见的

上面介绍了为什么会呈现重复丈量,那么会有什么影响呢?不过是多丈量了几回,会对功用有什么大的影响吗?

之所以需求防止布局层级过深是由于它对功用的影响是指数级的

  • 假如咱们的布局有两层,其间父View会对每个子View做二次丈量,那它的每个子View一共需求被丈量 2 次
  • 假如增加到三层,而且每个父View仍然都做二次丈量,这时分最下面的子View被丈量的次数就直接翻倍了,变成 4 次
  • 同理,增加到 4 层的话会再次翻倍,子 View 需求被丈量 8 次

撒花,2022Android最新面试专题:完结篇

也便是说,关于会做二次丈量的体系,层级加深对丈量时刻的影响是指数级的,这便是Android官方文档建议咱们削减布局层级的原因

②Compose为什么没有布局嵌套问题?

咱们知道,Compose只允许丈量一次,不允许重复丈量。

假如每个父组件对每个子组件只丈量一次,那就直接意味着界面中的每个组件只会被丈量一次

撒花,2022Android最新面试专题:完结篇

这样即便布局层级加深,丈量时刻却没有增加,把组件加载的时刻复杂度从O(2ⁿ) 降到了 O(n)。

那么问题就来了,上面咱们现已知道,屡次丈量有时是必要的,可是为什么Compose不需求呢?

Compose中引入了固有特性丈量(Intrinsic Measurement)

固有特性丈量即Compose允许父组件在对子组件进行丈量之前,先丈量一下子组件的「固有尺度」

咱们上面说的,ViewGroup的二次丈量,也是先进行这种「粗略丈量」再进行终究的「正式丈量」,运用固有特性丈量能够发生同样的作用

而运用固有特性丈量之所以有功用优势,首要是由于其不会随着层级的加深而加倍,固有特性丈量也只进行一次

Compose会先对整个组件树进行一次Intrinsic丈量,然后再对全体进行正式的丈量。这样开辟两个平行的丈量进程,就能够防止由于层级增加而对同一个子组件反复丈量所导致的丈量时刻的不断加倍了。

撒花,2022Android最新面试专题:完结篇
总结成一句话便是,在Compose里张狂嵌套地写界面,和把所有组件全都写进同一层里边,功用是相同的!所以Compose没有布局嵌套问题

2.kotlin协程

kotlin(完整版)→33W字开发笔记中有详细版

3.HashMap原理(第三章第10题)

4.算法:手写快排

快速排序算法思路

从小到大排序时

快速排序算法原理: 快速排序利用了分治的思想,选用递归来完结 假如要排序一个数组,先取一个参照数(这儿取数组最终一个数作为参照)把数组从分成以参数为”中心“结点的前后2部分,数组中参照数之前的都小于参照数,参照数之后的都大于参照数, 然后,对参照数的前后两部分别离排序 这样整个数组就有序了。

递推公式:

  • quickSort(p,r) = partition(p,r)+quickSortCore(p,q-1)+quickSortCore(q+1,r);
  • q = partition(p,r);
  • 中止条件:p>=r 不用继续分化
  • p: 每段的开端下标
  • r: 每段的结尾下标
  • q: 每段的”中心”下标

最好状况下 :

撒花,2022Android最新面试专题:完结篇

最坏状况下

撒花,2022Android最新面试专题:完结篇

时刻复杂度:O(N*logN) ~ O(n 2 n^2n 2 )

1.快速排序算法的耗时是在拆分这儿(partition),经过上图能够看出是个彻底二叉树,当n规模足够大时就能够近似看成是满二叉树,由partition函数来看兼并的时刻复杂度是O(n)

  • 最好状况下 O(nlogn):即拆分时左右2部分均分,时刻复杂度由上图可知每层处理的数据规模都是n 假定每层耗时常量t 树高为h,那么总耗时=nth, T(n) = ntlog ⁡ 2 n \log_2 nlog 2 n (参考:彻底二叉树的树高H和结点数N的联系)

  • 最坏状况下 O(n 2):即当数组数据现已有序时例如{1,2,3,4,5},此刻无法均分,取5为参照数,每次就只会履行quickSortCore(p,q-1)办法,partition处理的数据规模便是每次:T(n) = (n+n-1+ …1)t = tn*(n-1)/2 (t指单个数据处理耗时,是个常量值,n指数据规模)

    空间复杂度:O(logn)~O(n nn),快速排序尽管没有申请额外的空间,可是有递归函数调用栈的内存消耗。

  • 最好状况下 O(logn):即拆分时左右2部分均分时树深度最深也便是树的高度可表明log ⁡ 2 n \log_2 nlog 2 n。

  • 最坏状况下 O(n nn):即当数组数据现已有序时例如{1,2,3,4,5},此刻无法均分,取5为参照数,每次就只会履行quickSortCore(p,q-1)办法一向往下递归

    快速排序是不稳定的排序算法。 例如{6,8,7,6,3,5,9,4},取结尾4为参照数,拆分时,当下标j=4指向数3时 此刻下标i=-1,r=7 ,arr[j] < arr[r] 进入交流导致第一个6在第二个6的后边次序发生改动。

总结:快速排序算法时刻复杂度:O(N*logN) ~ O(n 2 n^2n 2), 空间复杂度:O(logn) ~ O(n nn),该算法能够进行进一步优化:出发点便是尽量使其每次左右均分数据规模

①中心代码

private static int[] quickSort(int[] arr) {
        if (null != arr && arr.length > 0) {
            quickSortCore(arr, 0, arr.length - 1);
        }
        return arr;
    }
    private static void quickSortCore(int[] arr, int p, int r) {
        if (p >= r) {
            return;
        }
        int q = partition(arr, p, r);
        quickSortCore(arr, p, q - 1);
        quickSortCore(arr, q + 1, r);
    }
    /**
     * [p,i] 小于 arr[r]的区域
     * [i+1,j-1] 大于 arr[r]的区域
     * [j,r-1]  未处理的区域
     * r 下标默许是分区点参照的元素
     * p,j,i,r 均指下标,arr[r]指参照数
     *
     * @param arr
     * @param p
     * @param r
     * @return
     */
    private static int partition(int[] arr, int p, int r) {
        // 初始时设置i= -1,表明小于参照数的区域是空,i标识小于参照数的区域结尾方位
        int i = p - 1;
        // 扫描未处理区域和挨个和参照数进行比较
        for (int j = p; j < r; j++) {
            // 比参照数小的放到[p,i]区域,[p,i]区域就开端扩展了
            if (arr[j] < arr[r]) {
                swap(arr, i + 1, j);
                i++;
            }
        }
        // 再把参照数放到比它小的区域的后一个下标方位,这样 参照数左侧便是悉数小于参照数的数,右侧便是大于参照数的数,能够继续往下拆分左右2侧递归了
        swap(arr, i + 1, r);
        return i + 1;
    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

②测验用例

package arithmetic.ecut.com.排序.a_快速排序;
/**
 * 快速排序
 *
 * @author 起凤
 * @description: TODO
 * @date 2022/4/11
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {1, 5, 6, 2, 3, 4};
        print(quickSort(arr));
        int[] arr1 = {2, 3, 1, 4, -1, 8, -1};
        print(quickSort(arr1));
        int[] arr2 = {-1, 7, 1, 4, 5, 8, 7};
        print(quickSort(arr2));
    }
    private static int[] quickSort(int[] arr) {
        if (null != arr && arr.length > 0) {
            quickSortCore(arr, 0, arr.length - 1);
        }
        return arr;
    }
    private static void quickSortCore(int[] arr, int p, int r) {
        if (p >= r) {
            return;
        }
        int q = partition(arr, p, r);
        quickSortCore(arr, p, q - 1);
        quickSortCore(arr, q + 1, r);
    }
    /**
     * [p,i] 小于 arr[r]的区域
     * [i+1,j-1] 大于 arr[r]的区域
     * [j,r-1]  未处理的区域
     * r 下标默许是分区点参照的元素
     * p,j,i,r 均指下标,arr[r]指参照数
     *
     * @param arr
     * @param p
     * @param r
     * @return
     */
    private static int partition(int[] arr, int p, int r) {
        // 初始时设置i= -1,表明小于参照数的区域是空,i标识小于参照数的区域结尾方位
        int i = p - 1;
        // 扫描未处理区域和挨个和参照数进行比较
        for (int j = p; j < r; j++) {
            // 比参照数小的放到[p,i]区域,[p,i]区域就开端扩展了
            if (arr[j] < arr[r]) {
                swap(arr, i + 1, j);
                i++;
            }
        }
        // 再把参照数放到比它小的区域的后一个下标方位,这样 参照数左侧便是悉数小于参照数的数,右侧便是大于参照数的数,能够继续往下拆分左右2侧递归了
        swap(arr, i + 1, r);
        return i + 1;
    }
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    private static void print(int[] sort) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < sort.length; i++) {
            builder.append(sort[i]).append(",");
        }
        System.out.println(builder);
    }
}

撒花,2022Android最新面试专题:完结篇

5.Activity发动办法

①规范办法——standard

这个发动办法是最常见的,Activity 默许便是此发动办法。每发动一次 Activity,就会创立一个新 Activity 实例并置于栈顶。谁发动了这个 Activity,那么这个 Activity 就运转在发动它的那个 Activity 所在的栈中。

其实后边这句话挺重要,之前学习的时分并不是太了解这句话,但也是在不久前遇到了一个问题让我重新了解了:Android 中有多窗口办法,这个问题是在多窗口办法下发现的,咱们运用中有个当地会调用设置中的页面来挑选声响,在正常办法下是没有问题的,可是多窗口办法下就会重新发动一个使命栈,但咱们体系中束缚多窗口办法下只能有一个运用在前台,成果咱们自己的运用被干掉了。。。大家一定引以为戒,知识点的每一句话都有或许有用!

下面咱们来测验下规范办法,先一步一步来,先从第一个页面跳转到第二个页面,看下 log:

E/MainActivity: onCreate: 
E/MainActivity: onStart: 
E/MainActivity: onResume: 
E/MainActivity: onPause: 
E/TwoActivity: onCreate: 
E/TwoActivity: onStart: 
E/TwoActivity: onResume: 
E/MainActivity: onStop:

没什么问题,和料想的共同,然后回到桌面再翻开运用,看下 log:

E/TwoActivity: onPause: 
E/TwoActivity: onStop: 
E/TwoActivity: onStart: 
E/TwoActivity: onResume:

嗯,没问题,现在使命栈里有两个 Activity,点击回来键依次退出再来看下 log:

E/TwoActivity: onPause: 
E/MainActivity: onStart: 
E/MainActivity: onResume: 
E/TwoActivity: onStop: 
E/TwoActivity: onDestroy: 
E/MainActivity: onPause: 
E/MainActivity: onStop:

从第二个 Activity 回到第一个 Activity 能够了解,可是大家有没有发现第一个 Activity 并没有走 onDestroy ,这儿引证下一个凶猛的大哥文章中的描述吧:

Android 12 曾经,当咱们处于 Root Activity 时,点击回来键时,运用回来桌面, Activity 履行 onDestroy,程序完毕。Android 12 起同样场景下 Activity 只会 onStop,不再履行 onDestroy。

到这儿规范办法就差不多了,由于这是默许的发动办法,大家运用也最频频,也就不再烦琐。

栈顶办法——singleTop

栈顶办法其实很好了解,假如栈顶存在该activity的实例,则复用,不存在新建放入栈顶,它的体现简直和 上面刚说的规范办法一模相同,栈顶办法的 Activity 实例能够无限多,仅有的差异是假如在栈顶现已有一个相同类型的 Activity 实例,那么 Intent 则不会再去创立一个 Activity,而是经过 onNewIntent() 发送到现有的Activity。

比方运用现在在一个概况页面,而且这个页面发动办法为栈顶办法,这个时分来了一个告诉,点击告诉正好要跳转到概况页面,那么这个时分使命栈就不会为这个 Activity 再创立一个实例而用现已在栈顶的之前创立好的 Activity 实例。

③栈内复用——singleTask

这个办法之前真的没有了解透彻,之前我了解的便是假如栈内存在该 Activity 的实例则进行复用,假如不存在则创立。

接下来将方才的 Demo 中的主 Activity 的发动办法改为栈内复用,先来看下发动运用后点击跳转到第二个 Activity 的 log:

E/MainActivity: onCreate: 
E/MainActivity: onStart: 
E/MainActivity: onResume: 
E/MainActivity: onPause: 
E/TwoActivity: onCreate: 
E/TwoActivity: onStart: 
E/TwoActivity: onResume: 
E/MainActivity: onStop:

现在来看仍是比较正常的,接下来直接回到桌面,再来看下 log:

E/TwoActivity: onPause: 
E/TwoActivity: onStop:

也还对着呢,然后再次翻开运用再看 log:

E/TwoActivity: onDestroy: 
E/MainActivity: onStart: 
E/MainActivity: onResume:

是不是不对了,我本来想让运用回到第二个 Activity,但为什么第二个 Activity 直接毁掉了?

其实栈内复用中还有一点要留意,也正是我疏忽的重要一点:栈内复用办法会将该实例上边的 Activity 悉数出栈,将该实例置于栈顶,这也便是呈现文章最初我说的那个问题的根本原因。

④单例办法——singleInstance

单例办法,望文生义,便是新开一个使命栈,该栈内只寄存当时实例。比方说项目中语音通话功用,来电显现页面选用的就能够选用单例办法进行处理。

当然还有别的办法来新开使命栈,比方说发动 Activity 的时分加上 FLAG_ACTIVITY_NEW_TASK ,也会敞开一个新的使命栈。

这儿需求留意,即便将 Activity 的发动办法设置为单例办法或许增加了 flag,也不会呈现像上面某信那种作用,由于 Activity 的 taskAffinity 是相同的,但假如将 Activity 的 taskAffinity 修正下,就能够呈现类似于上面某信的作用,如下图所示:

撒花,2022Android最新面试专题:完结篇

6.Activity四大发动办法生命周期

①Standard 规范办法发动

android:launchMode=“standard”

概括:直接在栈顶新建一个Activity进行发动

生命周期:

===>敞开一个Activity

I/LifecycleActivity_A: onCreate: I/LifecycleActivity_A: onStart: I/LifecycleActivity_A: onResume:

===>退出

I/LifecycleActivity_A: onPause: I/LifecycleActivity_A: onStop: I/LifecycleActivity_A: onDestroy:

②SingleTop 栈顶发动

manifest:android:launchMode=“singleTop” FLAG:FLAG_ACTIVITY_SINGLE_TOP

概括:当发动的Activity在栈顶时,则进行复用,假如栈顶没有时运用默许的Standard办法

生命周期:

===> 发动A:

A: onCreate: A: onStart: A: onResume:

===> 栈顶发动:

A: onPause: A: onNewIntent: A: onResume:

FLAG_ACTIVITY_CLEAR_TOP发动

栈顶复用不走newIntent(), 将栈内的需求发动的activity移到最顶处发动,并将上面的activity悉数出栈毁掉。

===> 发动A FLAG_ACTIVITY_NEW_TASK

I/LifecycleActivity_A: onCreate: I/LifecycleActivity_A: onStart: I/LifecycleActivity_A: onResume: I/LifecycleActivity_A: onPause:

===> 发动B FLAG_ACTIVITY_NEW_TASK

I/LifecycleActivity_A: onPause: I/LifecycleActivity_B: onCreate: I/LifecycleActivity_B: onStart: I/LifecycleActivity_B: onResume: I/LifecycleActivity_A: onStop:

===> 发动A FLAG_ACTIVITY_CLEAR_TOP

I/LifecycleActivity_B: onPause: I/LifecycleActivity_A: onDestroy: I/LifecycleActivity_A: onCreate: I/LifecycleActivity_A: onStart: I/LifecycleActivity_A: onResume: I/LifecycleActivity_B: onStop: I/LifecycleActivity_B: onDestroy:

===>A退出

I/LifecycleActivity_A: onPause: I/LifecycleActivity_A: onStop: I/LifecycleActivity_A: onDestroy:

③SingleTask 栈内复用办法

android:launchMode=“singleTask”

概括:当栈内有需求发动的Activity时,则将该Activity移动到栈顶,并在栈中这个activity顶部的activity悉数出栈毁掉。

生命周期:

===> 发动A:

A: onCreate: A: onStart: A: onResume:

===> 发动B:

A: onPause: B: onCreate: B: onStart: B: onResume: A: onStop:

===> 栈内发动A:

B: onPause: A: onNewIntent: A: onRestart: A: onStart: A: onResume: B: onStop: B: onDestroy:

④FLAG_ACTIVITY_REORDER_TO_FRONT发动

===>发动A FLAG_ACTIVITY_NEW_TASK

I/LifecycleActivity_A: onCreate: I/LifecycleActivity_A: onStart: I/LifecycleActivity_A: onResume:

===> 发动B FLAG_ACTIVITY_NEW_TASK

I/LifecycleActivity_A: onPause: I/LifecycleActivity_B: onCreate: I/LifecycleActivity_B: onStart: I/LifecycleActivity_B: onResume: I/LifecycleActivity_A: onStop:

===> 发动A FLAG_ACTIVITY_REORDER_TO_FRONT

I/LifecycleActivity_B: onPause: I/LifecycleActivity_A: onNewIntent: I/LifecycleActivity_A: onRestart: I/LifecycleActivity_A: onStart: I/LifecycleActivity_A: onResume: I/LifecycleActivity_B: onStop:

===>A退出

I/LifecycleActivity_A: onPause: I/LifecycleActivity_B: onRestart: I/LifecycleActivity_B: onStart: I/LifecycleActivity_B: onResume: I/LifecycleActivity_A: onStop: I/LifecycleActivity_A: onDestroy:

===>B退出

I/LifecycleActivity_B: onPause: I/LifecycleActivity_B: onStop: I/LifecycleActivity_B: onDestroy:

⑤SingleInstance 单例发动办法

android:launchMode=“singleinstance”

概括:单独创立一个栈控件发动这个Activity,常用于发动壁纸,电话,闹钟等界面完结。

7.有序播送实例

有序播送:依照接收者的优先级接收,只有一个播送接收者能接收信息,在此播送接收者的逻辑履行完毕后,才会继续传递。

abortBroadcast();中止播送

有序播送功用概述:

撒花,2022Android最新面试专题:完结篇

播送类型:

撒花,2022Android最新面试专题:完结篇

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn"
        android:text="发送有序播送"
      />
</LinearLayout>

MainActivity.java

package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
 MyReceiverOne one ;
 MyReceiverTow two;
 MyReceiverThree three;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        registerReceiver();
        Button button =findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent();
                intent.setAction("Intercept_Stitch");
                sendOrderedBroadcast(intent,null);
            }
        });
    }
    private void registerReceiver() {
        one=new MyReceiverOne();
        IntentFilter filter1=new IntentFilter();
        filter1.setPriority(1000);
        filter1.addAction("Intercept_Stitch");
        registerReceiver(one,filter1);
        two=new MyReceiverTow();
        IntentFilter filter2=new IntentFilter();
        filter2.setPriority(900);
        filter2.addAction("Intercept_Stitch");
        registerReceiver(two,filter2);
        three=new MyReceiverThree();
        IntentFilter filter3=new IntentFilter();
        filter3.setPriority(600);
        filter3.addAction("Intercept_Stitch");
        registerReceiver(three,filter3);
    }
}

myReceiverone

package com.example.myapplication;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
class MyReceiverOne extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("test","自界说的播送接收者One,承受到了播送事情");
    }
}

MyRiverTwo

package com.example.myapplication;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
class MyReceiverTow extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("test","自界说的播送接收者Two,承受到了播送事情");
    }
}

MyReceiverThree

package com.example.myapplication;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
class MyReceiverThree extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("test","自界说的播送接收者Three,承受到了播送事情");
    }
}

撒花,2022Android最新面试专题:完结篇

撒花,2022Android最新面试专题:完结篇

撒花,2022Android最新面试专题:完结篇

8.SharedPreferences的详解

①SharedPreferences 首选项 介绍

存储软件的装备信息 存储的信息:很小,简略的数据;比方:主动登录,记住暗码,小说app(回来后再次进入仍是 原来看的页数),按钮的状况。 特色:当程序运转首选项里边的数据会悉数加载进内容。

②SharedPreferences的简略运用

1.布局文件中,写入两个按钮,保存到SP,和从SP中获取数据 布局代码的文件就不再写了。

2.看MainActivity的代码,里边有注释详解

package com.example.spdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    private Button btn_save;
    private Button btn_obtain;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_save=findViewById(R.id.btn_save);
        btn_obtain=findViewById(R.id.btn_obtain);
        //保存到SP
        btn_save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences sp = getSharedPreferences("onClick", MODE_PRIVATE);
                sp.edit().putString("SoundCode","测点代码").apply();//apply才会写入到xml装备文件里边
            }
        });
        //获取到SP中的数据
        btn_obtain.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences sp1 = getSharedPreferences("onClick", MODE_PRIVATE);
                //假如SoundCode,获取的值是空的,则会弹出后边的默许值
                String obtain = sp1.getString("SoundCode", "默许值");
                Toast.makeText(MainActivity.this, obtain, Toast.LENGTH_SHORT).show();
            }
        });
    }
    /**
     *
     *参数1:SP的名字
     *参数2:SP保存时,用的办法,MODE_PRIVATE惯例(每次保存都会更新),MODE_APPEND(每次保存都会追加到后边)
     * @Override
     *     public SharedPreferences getSharedPreferences(String name, int mode) {
     *         return mBase.getSharedPreferences(name, mode);
     *     }
     *
     */
}

③SharedPreferences的实战,完结记载暗码和主动登录功用

作用如下:

撒花,2022Android最新面试专题:完结篇

1.XML代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="用户名:"
            android:textSize="25sp"
            android:gravity="right"
            />
        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="4"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="暗码:"
            android:textSize="25sp"
            android:gravity="right"
            />
        <EditText
            android:id="@+id/et_password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="4"
            android:inputType="textPassword"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <CheckBox
            android:id="@+id/ck_password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="记住暗码"
            android:textSize="20sp"
            />
        <CheckBox
            android:id="@+id/ck_login"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="主动登录"
            android:textSize="20sp"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
        <Button
            android:id="@+id/btn_register"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="注册"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            />
        <Button
            android:id="@+id/btn_login"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="登录"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            />
    </LinearLayout>
</LinearLayout>

MainActivity代码如下:详解和注释都现已写好

package com.example.shareddemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    private EditText et_name;
    private EditText et_password;
    private CheckBox ck_password;
    private CheckBox ck_login;
    private Button btn_register;
    private Button btn_login;
    private SharedPreferences sp;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_name=findViewById(R.id.et_name);
        et_password=findViewById(R.id.et_password);
        ck_password=findViewById(R.id.ck_password);
        ck_login=findViewById(R.id.ck_login);
        btn_register=findViewById(R.id.btn_register);
        btn_login=findViewById(R.id.btn_login);
         sp = getSharedPreferences("Personal", MODE_PRIVATE);
        //登录办法
        LoginMethod();
        //程序再次进入获取SharedPreferences中的数据
        AgainInto();
    }
    private void AgainInto() {
        //假如获取为空就回来默许值
        boolean ck1 = sp.getBoolean("ck_password", false);
        boolean ck2 = sp.getBoolean("ck_login", false);
        //假如是记住暗码
        if (ck1){
            String name=sp.getString("name","");
            String password=sp.getString("password","");
            et_name.setText(name);
            et_password.setText(password);
            //记住暗码打上√
            ck_password.setChecked(true);
        }
        //假如是主动登录
        if (ck2){
            ck_login.setChecked(true);
            Toast.makeText(this, "我是主动登录!", Toast.LENGTH_SHORT).show();
        }
    }
    private void LoginMethod() {
        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name=et_name.getText().toString().trim();
                String password=et_password.getText().toString().trim();
                //判别用户名和暗码是否为空
                if (TextUtils.isEmpty(name)||TextUtils.isEmpty(password)){
                    Toast.makeText(MainActivity.this, "用户名和暗码不能为空", Toast.LENGTH_SHORT).show();
                }else {
                    //假如记载暗码是勾选的
                    if (ck_password.isChecked()){
                        //把用户名和暗码保存在SharedPreferences中
                        sp.edit().putString("name",name).apply();
                        sp.edit().putString("password",password).apply();
                        sp.edit().putBoolean("ck_password",true).apply();
                    }else {//没有勾选,保存空值
                        sp.edit().putString("name","").apply();
                        sp.edit().putString("password","").apply();
                        sp.edit().putBoolean("ck_password",false).apply();
                    }
                    //假如主动登录是勾选的
                    if (ck_login.isChecked()){
                        sp.edit().putBoolean("ck_login",true).apply();
                    }else {
                        sp.edit().putBoolean("ck_login",false).apply();
                    }
                }
            }
        });
    }
}

9.xml解析办法

①xml解析办法

解析:操作xml文档,将文档中的数据读取到内存中

  操作xml文档

    1.解析(读取):将文档中的数据读取到内存中

    2.写入:将内存中的数据保存到xml文档中。持久化的存储

  解析xml的办法:

    1.DOM:将符号言语文档一次性加载进内存,在内存中构成一棵树dom树

      优点:操作便利,能够对文档进行CRUD的所有操作

      缺陷:占内存

    2.SAX:逐行读取,基于事情驱动的。

      优点:不占内存

      缺陷:只能读取,不能增修正

②xml常见的解析器

  • JAXP:sun公司供给的解析器,支持dom和sax两种思想

  • DOM4J:一款十分优异的解析器

  • Jsoup:jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。

它供给了一套十分省力的API,可经过DOM,CSS以及类似于jQuery的操作办法来取出和操作数据

  • PULL:Android操作体系内置的解析器,sax办法的。

10.json与xml的差异,json为什么比xml更好

① JSON相比XML的不同之处

  • 没有完毕标签
  • 更短
  • 读写的速度更快
  • 能够运用内建的 JavaScript eval() 办法进行解析
  • 运用数组
  • 不运用保留字

总归: JSON 比 XML 更小、更快,更易解析。

②XML和JSON的差异:

XML的首要组成成分:

XML是element、attribute和element content。

JSON的首要组成成分

JSON是object、array、string、number、boolean(true/false)和null。

11.Android view绘制流程

(第一章第2题)

12.surfaceView的显现与躲藏

在安卓中假如有用最灵活的播映,則便是surfaceView完结的。

那么有这样一一个运用场景,便是我想要经过一个按钮比方叫做“挑选一个视频”,改按钮会调用出一个dialog去从一个视频列表挑选一个视频后,在当时界面载入。

像是如下这样

撒花,2022Android最新面试专题:完结篇

可是呢,一般的做法便是在当时界面先躲藏一个surfaceView,用setVisibility(View.GONE)或许直接在佈局上面写。

可是你会发现,当你视频文件挑选之后,让其重新显现的时分就不能正常显现出来了。

这儿要选用如:

sv_with_ds.getChildAt(0).setVisibility(View.VISIBLE);
sv_with_ds.setVisibility(View.VISIBLE);

这儿我的surfaceview尽管没有躲藏,可是其父级躲藏了,之前我直接让父级显现,可是surfaceView依旧不显现,关于这种状况下,要先让surfaceView先显现,尽管它自身没有设置躲藏。

当然朴实的直接在surfaceView中进行躲藏和显现,我没有试过,那样应该是能够的

那么关于surfaceView来説比较原生一些,常常很简单由于自己了解的不可透彻而导致各种问题的发生。

最常见的便是其播映视频有必要要在getHolder所取得的目标中增加一个回调,这个回调有必要是完结了SurfaceView

13.关于移动端适配机型

撒花,2022Android最新面试专题:完结篇

14.ANR 什么时分呈现,怎么排查

①ANR发生的场景:

  • activity内对事情 5秒无法完结处理
  • BroadcastReceiver 内对事情10秒无法完结处理
  • Service 的各个生命周期函数在特定时刻(20秒)内无法完结处理
  • 运用程序UI线程存在耗时操作,例如在UI线程中进行网络恳求,数据库操作或许文件操作等,或许会导致UI线程无法及时处理用户输入等。
  • 运用程序UI线程等待子线程开释某个锁,然后无法处理用户的恳求的输入
  • 耗时操作的动画需求大量的计算工作,或许导致CPU负载过重

② ANR发生的原因

主线程(UI线程)假如在规则时刻内没有处理完相应的工作,就会呈现ANR, 超时发生的原因一般有:

  • 主线程在做一些耗时使命
  • 主线程被其他线程锁
  • cpu被其他进程占用,该进程没被分配到足够的cpu资源
  • 自身服务堵塞
  • 体系堵塞
  • 内存严重

③ANR发生原因定位剖析

经过ANR 日志定位问题

当ANR发生时,咱们往往经过Logcat和traces文件(目录/data/anr/)的相关信息输出去定位问题。首要包含以下几方面:

  • 根本信息,包含进程名、进程号、包名、体系build号、ANR 类型等等;
  • CPU运用信息,包含活跃进程的CPU 均匀占用率、IO状况等等;
  • 线程仓库信息,所属进程包含发生ANR的进程、其父进程、最近有活动的3个进程等等。

测验进程发现ANR的现状

  • 在往常测验中,ANR有根本测验不到,由于ANR根本发生在废物设备中,弱网络,频频操作。
  • 问题不必现,即便看到了问题,定位麻烦:要去data/anr.txt 文件里边查找。有必要root,没有对应联系,剖析复杂,导出文件就有必要依靠手机零距离。

引入ANR检测东西

由于anr问题不必现,因此引入以下ANR检测东西,当anr问题呈现时,主动dump手机中的日志信息如trace文件、仓库信息等,根本原理如下:

撒花,2022Android最新面试专题:完结篇

检测到UI主线程卡顿时刻超过设定的时刻,如4s,即dump trace文件以及仓库信息,一同抛出反常,收集信息,依据这些文件信息即可定位到发生anr的原因 。

经过traces.txt的文件剖析

在APP触发ANR时, 体系默许会生成一个traces.txt的文件,并放在/data/anr/下(咱们能够借助第三方的MT办理器或许adb指令进行对traces.txt进行操作)。咱们就能够结合traces.txt文件进行剖析排查定位出是有app中的哪个类触发的ANR。

15.Android的几种动画界说与运用

Android动画的分类与运用

学习Android必不可少的便是动画的运用了,在Android版别迭代的进程中,呈现了许多动画结构,这儿做一个总结。

Android动画类型分类

逐帧动画【Frame Animation】,即次序播映事前预备的图片

补间动画【Tween Animation】,View的动画作用能够完结简略的平移、缩放、旋转。

特点动画【Property Animation】,补间动画增强版,支持对目标履行动画。

过渡动画【Transition Animation】,完结Activity或View过渡动画作用。包含5.0之后的MD过渡动画等。

动画的分类与版别

Android动画完结办法分类都能够分为xml界说和java界说。

Android 3.0之前版别,逐帧动画,补间动画 Android 3.0之后版别,特点动画 Android 4.4中,过渡动画 Android 5.0以上 MD的动画作用

下面一同看看简略的完结吧。

①逐帧动画

引荐运用一些小图片,它的功用不是很好,假如运用大图的帧动画,会呈现功用问题导致卡顿。

比较常用的办法,在res/drawable目录下新建动画XML文件:

撒花,2022Android最新面试专题:完结篇

设置或铲除动画代码:

  //开端动画
    mIvRefreshIcon.setImageResource(R.drawable.anim_loading);
    mAnimationDrawable = (AnimationDrawable) mIvRefreshIcon.getDrawable();
    mAnimationDrawable.start();
​
    //中止动画
    mIvRefreshIcon.clearAnimation();
    if (mAnimationDrawable != null){
        mAnimationDrawable.stop();
    }

设置Background和设置ImageResource是相同的作用:

撒花,2022Android最新面试专题:完结篇

 ImageView voiceIcon = new ImageView(CommUtils.getContext());
    voiceIcon.setBackgroundResource(message.isSelf() ? R.drawable.right_voice : R.drawable.left_voice);
    final AnimationDrawable frameAnim = (AnimationDrawable) voiceIcon.getBackground();
​
    frameAnimatio.start();
​
    MediaUtil.getInstance().setEventListener(new MediaUtil.EventListener() {
         @Override
         public void onStop() {
            frameAnimatio.stop();
            frameAnimatio.selectDrawable(0);
        }
    });

②补间动画

一句话阐明补间动画:只能给View加,不能给目标加,而且不会改动目标的真实特点。

无需重视每一帧,只需求界说动画开端与完毕两个关键帧,并指定动画改动的时刻与办法等 。首要有四种根本的作用

  • 透明度改动
  • 巨细缩放改动
  • 位移改动
  • 旋转改动

能够在xml中界说,也能够在代码中界说!

透明度的界说:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > 
    <alpha 
        android:duration="1000" 
        android:fromAlpha="0.0" 
        android:toAlpha="1.0" /> 
</set>

缩放的界说:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > 
    <scale 
        android:duration="1000" 
        android:fillAfter="false" 
        android:fromXScale="0.0" 
        android:fromYScale="0.0" 
        android:interpolator="@android:anim/accelerate_decelerate_interpolator" 
        android:pivotX="50%" 
        android:pivotY="50%" 
        android:toXScale="1.4" 
        android:toYScale="1.4" /> 
</set>

平移的界说:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > 
    <translate 
        android:duration="2000" 
        android:fromXDelta="30" 
        android:fromYDelta="30" 
        android:toXDelta="-80" 
        android:toYDelta="300" /> 
</set>

旋转的界说:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" > 
    <rotate 
        android:duration="3000" 
        android:fromDegrees="0" 
        android:interpolator="@android:anim/accelerate_decelerate_interpolator" 
        android:pivotX="50%" 
        android:pivotY="50%" 
        android:toDegrees="+350" /> 
</set>

Java代码中运用补间动画(引荐):

透明度界说:

AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500);          //设置继续时刻 
alpha.setFillAfter(true);                   //动画完毕后保留完毕状况 
alpha.setInterpolator(new AccelerateInterpolator());        //增加差值器 
ivImage.setAnimation(alpha);

缩放界说:

ScaleAnimation scale = new ScaleAnimation(1.0f, scaleXY, 1.0f, scaleXY, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 
scale.setDuration(durationMillis); 
scale.setFillAfter(true); 
ivImage.setAnimation(scale);

平移界说

TranslateAnimation translate = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta); 
translate.setDuration(durationMillis); 
translate.setFillAfter(true); 
ivImage.setAnimation(translate);

旋转界说:

RotateAnimation rotate = new RotateAnimation(fromDegrees, toDegrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); 
rotate.setDuration(durationMillis); 
rotate.setFillAfter(true); 
ivImage.setAnimation(rotate);

组合Set的界说:

 RelativeLayout rlRoot = (RelativeLayout) findViewById(R.id.rl_root);
	// 旋转动画
	RotateAnimation animRotate = new RotateAnimation(0, 360,
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
				0.5f);
	animRotate.setDuration(1000);// 动画时刻
	animRotate.setFillAfter(true);// 坚持动画完毕状况
	// 缩放动画
	ScaleAnimation animScale = new ScaleAnimation(0, 1, 0, 1,
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,0.5f);
	animScale.setDuration(1000);
	animScale.setFillAfter(true);// 坚持动画完毕状况
	// 突变动画
	AlphaAnimation animAlpha = new AlphaAnimation(0, 1);
	animAlpha.setDuration(2000);// 动画时刻
	animAlpha.setFillAfter(true);// 坚持动画完毕状况
	// 动画调集
	AnimationSet set = new AnimationSet(true);
	set.addAnimation(animRotate);
	set.addAnimation(animScale);
	set.addAnimation(animAlpha);
	// 发动动画
	rlRoot.startAnimation(set);
	set.setAnimationListener(new AnimationListener() {
		@Override
		public void onAnimationStart(Animation animation) {
		}
		@Override
		public void onAnimationRepeat(Animation animation) {
		}
		@Override
		public void onAnimationEnd(Animation animation) {
			// 动画完毕,跳转页面
			// 假如是第一次进入, 跳新手引导
			// 否则跳主页面
			boolean isFirstEnter = PrefUtils.getBoolean(
						SplashActivity.this, "is_first_enter", true);
			Intent intent;
			if (isFirstEnter) {
				// 新手引导
				intent = new Intent(getApplicationContext(),
						GuideActivity.class);
			} else {
				// 主页面
				intent = new Intent(getApplicationContext(),MainActivity.class);
			}
			startActivity(intent);
			finish();
			}
	});

③特点动画

补间动画增强版别。弥补补间动画的一些缺陷

作用目标:恣意 Java 目标,不再限制于 视图View目标

完结的动画作用:可自界说各种动画作用,不再限制于4种根本改换:平移、旋转、缩放 & 透明度

分为ObjectAnimator和ValueAnimator。

3.1 一个简略的特点动画

先用xml的办法完结

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> 
    <animator 
        android:valueFrom="0" 
        android:valueTo="100" 
        android:valueType="intType" 
        android:duration="3000" 
        android:startOffset ="1000" 
        android:fillBefore = "true" 
        android:fillAfter = "false" 
        android:fillEnabled= "true" 
        android:repeatMode= "restart" 
        android:repeatCount = "0" 
        android:interpolator="@android:anim/accelerate_interpolator"/> 
</set> 

运用:

Button b3 = (Button) findViewById(R.id.b3); 
Animator mAnim = AnimatorInflater.loadAnimator(this, R.animator.animator_1_0); 
mAnim.setTarget(b3); 
mAnim.start();

当然咱们能够直接运用Java代码完结:

public static ObjectAnimator setObjectAnimator(View view , String type , int start , int end , long time){
    ObjectAnimator mAnimator = ObjectAnimator.ofFloat(view, type, start, end); 
    // 设置动画重复播映次数 = 重放次数+1 
    // 动画播映次数 = infinite时,动画无限重复 
    mAnimator.setRepeatCount(ValueAnimator.INFINITE); 
    // 设置动画运转的时长 
    mAnimator.setDuration(time); 
    // 设置动画推迟播映时刻 
    mAnimator.setStartDelay(0); 
    // 设置重复播映动画办法 
    mAnimator.setRepeatMode(ValueAnimator.RESTART); 
    // ValueAnimator.RESTART(默许):正序重放 
    // ValueAnimator.REVERSE:倒序回放 
    //设置差值器 
    mAnimator.setInterpolator(new LinearInterpolator()); 
    return mAnimator; 
}

3.2 ValueAnimator与ObjectAnimator差异:

  • ValueAnimator 类是先改动值,然后手动赋值 给目标的特点然后完结动画;是间接对目标特点进行操作;
  • ObjectAnimator 类是先改动值,然后主动赋值 给目标的特点然后完结动画;是直接对目标特点进行操作;
   //不同的界说办法
        ValueAnimator animator = null;
        if (isOpen) {
            //要封闭
            if (longHeight > shortHeight) {
                isOpen = false;
                animator = ValueAnimator.ofInt(longHeight, shortHeight);
            }
        } else {
            //要翻开
            if (longHeight > shortHeight) {
                isOpen = true;
                animator = ValueAnimator.ofInt(shortHeight, longHeight);
            }
        }
        animator.start();
       //不同的界说办法
       ObjectAnimator animatorX = ObjectAnimator.ofFloat(mSplashImage, "scaleX", 1f, 2f);  
       animatorX.start();

3.3 监听动画的办法:

mAnim2.addListener(new AnimatorListenerAdapter() {
    // 向addListener()办法中传入适配器目标AnimatorListenerAdapter() 
    // 由于AnimatorListenerAdapter中现已完结好每个接口 
    // 所以这儿不完结悉数办法也不会报错 
    @Override 
    public void onAnimationCancel(Animator animation) { 
        super.onAnimationCancel(animation); 
        ToastUtils.showShort("动画完毕了"); 
    } 
});

3.4 组合动画AnimatorSet:

xml的组合

<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially" > 
    <!--表明Set调集内的动画按次序进行--> 
    <!--ordering的特点值:sequentially & together--> 
    <!--sequentially:表明set中的动画,依照先后次序逐步进行(a 完结之后进行 b )--> 
    <!--together:表明set中的动画,在同一时刻一同进行,为默许值--> 
    <set android:ordering="together" > 
        <!--下面的动画一同进行--> 
        <objectAnimator 
            android:duration="2000" 
            android:propertyName="translationX" 
            android:valueFrom="0" 
            android:valueTo="300" 
            android:valueType="floatType" > 
        </objectAnimator> 
        <objectAnimator 
            android:duration="3000" 
            android:propertyName="rotation" 
            android:valueFrom="0" 
            android:valueTo="360" 
            android:valueType="floatType" > 
        </objectAnimator> 
    </set> 
    <set android:ordering="sequentially" > 
        <!--下面的动画按序进行--> 
        <objectAnimator 
            android:duration="1500" 
            android:propertyName="alpha" 
            android:valueFrom="1" 
            android:valueTo="0" 
            android:valueType="floatType" > 
        </objectAnimator> 
        <objectAnimator 
            android:duration="1500" 
            android:propertyName="alpha" 
            android:valueFrom="0" 
            android:valueTo="1" 
            android:valueType="floatType" > 
        </objectAnimator> 
    </set>
</set> 

Java办法的组合

ObjectAnimator translation = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX);  // 平移动画 
ObjectAnimator rotate = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);  // 旋转动画 
ObjectAnimator alpha = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f);  // 透明度动画 // 创立组合动画的目标 
AnimatorSet animSet = new AnimatorSet();  // 依据需求组合动画 
animSet.play(translation).with(rotate).before(alpha);  
animSet.setDuration(5000);  //发动动画 
animSet.start();

常用的组合办法

  • AnimatorSet.play(Animator anim) :播映当时动画
  • AnimatorSet.after(long delay) :将现有动画推迟x毫秒后履行
  • AnimatorSet.with(Animator anim) :将现有动画和传入的动画一同履行
  • AnimatorSet.after(Animator anim) :将现有动画刺进到传入的动画之后履行
  • AnimatorSet.before(Animator anim) : 将现有动画刺进到传入的动画之前履行

3.5 Evaluator估值器

表明计算某个时刻点,动画需求更新 view 的值。

Evaluator.evaluate(float fraction, T startValue, T endValue) 是中心办法。其间,fraction 表明一个百分比。startValue 和 endValue 表明动画的开端值和完毕值。经过 fraction、startValue、endValue 计算 view 对应的特点方位。

常用的就那么几个:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(animationView, "X", 0, 500); 
objectAnimator.setInterpolator(new LinearInterpolator()); 
objectAnimator.setEvaluator(new FloatEvaluator()); 
objectAnimator.setDuration(5 * 1000); 
objectAnimator.start();

3.6 简略Demo,

完结开端躲藏在屏幕顶部,已动画的办法渐渐回来:

  text.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
		@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
		@Override
		public void onGlobalLayout() {
			text.getViewTreeObserver().removeOnGlobalLayoutListener(this);
			textHeight = text.getHeight();
			Log.e("tag", "textHeight: "+textHeight);
			//一开端需求先让text往上移动它自身的高度
			ViewHelper.setTranslationY(text, -textHeight);
			Log.e("tag", "top:"+text.getTop());
				//再以动画的办法渐渐滚动下拉
			text.animate(text).translationYBy(textHeight)
				.setDuration(500)
				.setStartDelay(1000)
				.start();

特点动画设置控件的高度,完结动画封闭和翻开的作用:

 private boolean isOpen = false;
    /**
     * 状况的开关。上下封闭的特点动画
     */
    private void toggle() {
        ValueAnimator animator = null;
        if (isOpen) {
            isOpen = false;
            //敞开特点动画
            animator = ValueAnimator.ofInt(mDesHeight, 0);
        } else {
            isOpen = true;
            animator = ValueAnimator.ofInt(0, mDesHeight);
        }
        //动画的进程监听
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Integer height = (Integer) valueAnimator.getAnimatedValue();
                mParams.height = height;
                llDesRoot.setLayoutParams(mParams);
            }
        });
        //设置动画的状况监听。给小箭头设置状况
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
            }
            @Override
            public void onAnimationEnd(Animator animator) {
                 //完毕的时分,更换小箭头的图片
                if (isOpen){
                    ivArrow.setImageResource(R.drawable.arrow_up);
                }else {
                    ivArrow.setImageResource(R.drawable.arrow_down);
                }
            }
            @Override
            public void onAnimationCancel(Animator animator) {
            }
            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });
        animator.setDuration(200);  //动画时刻
        animator.start();           //发动
    }

特点动画讲的好乱,太多了,比较复杂。后边会有更详细的代码!

④过渡动画

4.1 Android5.0曾经的过渡动画

同样能够在xml中界说 ,也能够运用java代码操控

咱们在style文件夹中界说

 <!--左右进进场的activity动画-->
    <style name="My_AnimationActivity" mce_bogus="1" parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">@anim/open_enter</item>
        <item name="android:activityCloseExitAnimation">@anim/close_exit</item>
    </style>
    <!--上下进进场的activity动画-->
    <style name="up_down_activity_anim" mce_bogus="1" parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">@anim/open_up</item>
        <item name="android:activityCloseExitAnimation">@anim/close_down</item>
    </style>

界说的文件如下,补间动画的办法:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="270"
        android:fromXDelta="100%p"
        android:toXDelta="0%p" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="270"
        android:fromXDelta="0%p"
        android:toXDelta="-100%p" />
</set>

对应的Activity完结指定的样式即可完结。

在Java文件中同样能够经过 overridePendingTransition 来完结。

大致完结如下:

startActivity(intent);
overridePendingTransition(R.anim.bottom_top_anim, R.anim.alpha_hide);
finish();
overridePendingTransition(R.anim.alpha_show, R.anim.top_bottom_anim);

4.2 Android5.0今后的过渡动画

5.0之后,Android就自带几种动画特效。 3种转场动画 ,1种同享元素。

三种转场动画如下:

 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void explode(View view) {
        intent = new Intent(this, TransitionActivity.class);
        intent.putExtra("flag", 0);
        startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void slide(View view) {
        intent = new Intent(this, TransitionActivity.class);
        intent.putExtra("flag", 1);
        startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
    }
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void fade(View view) {
        intent = new Intent(this, TransitionActivity.class);
        intent.putExtra("flag", 2);
        startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
    }

经过对面的页面来指定完结的办法:

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
         getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        int flag = getIntent().getExtras().getInt("flag");
        switch (flag) {
            case 0:
                //分化作用 上面的上面消失  下面的下面消失  分化掉了
                getWindow().setEnterTransition(new Explode());
                break;
            case 1:
                //滑动作用 默许上下滑动
                getWindow().setEnterTransition(new Slide());
                break;
            case 2:
                //淡出作用  透明度
                getWindow().setEnterTransition(new Fade());
                getWindow().setExitTransition(new Fade());
                break;
            case 3:
                break;
        }
        setContentView(R.layout.activity_transition);
    }

5.0的Share同享动画:

跳转的办法:

  @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void share(View view) {
        View fab = findViewById(R.id.fab_button);
        intent = new Intent(this, TransitionActivity.class);
        intent.putExtra("flag", 3);
        //创立单个同享
//        startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, view, "share")
//                .toBundle());
        //创立多个同享
        startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, Pair.create
                (view, "share"),
                Pair.create(fab,"fab"))
                .toBundle());
    }

share的办法,不需求对方页面接收设置过渡动画,而是需求在xml中装备transitionName特点:

   <View
        android:background="?android:colorPrimary"
        android:id="@+id/holder_view"
        android:transitionName="share"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>

那儿是一个button 同享名字叫“share” 那儿是拿到的view 不是button 转过来界说的是view

那儿同享的是button 同享名字叫tab 同享过来也界说的button。

假如Share动画 想Share一个ViewGroup怎样办?比方一个Item跳转到Detail页面 能够直接运用这种过渡作用。

private void toActivity(View sharedElement) {
        Intent intent = new Intent(getContext(), TimeTableAcivity.class);
        ActivityOptions options =
                ActivityOptions.makeSceneTransitionAnimation(getActivity(), sharedElement, "shared_element_end_root");
        startActivity(intent, options.toBundle());
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
        findViewById(android.R.id.content).setTransitionName("shared_element_end_root");
        setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
        getWindow().setSharedElementEnterTransition(buildContainerTransform(true));
        getWindow().setSharedElementReturnTransition(buildContainerTransform(false));
        super.onCreate(savedInstanceState);
    }
    private MaterialContainerTransform buildContainerTransform(boolean entering) {
        MaterialContainerTransform transform = new MaterialContainerTransform(this, entering);
        transform.setAllContainerColors(
                MaterialColors.getColor(findViewById(android.R.id.content), R.attr.colorSurface));
        transform.addTarget(android.R.id.content);
        //设置动画继续时刻(毫秒)
        transform.setDuration(666);
        return transform;
    }

5.0之后在MD中还有其他的动画,比方揭露动画,不知道是不是转场动画的一种。由于一般也是用于转场的时分运用,可是这个动画咱们运用的很少很少。

简略的运用如下:

 View myView = findView(R.id.awesome_card);
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;
    int dx = Math.max(cx, myView.getWidth() - cx);
    int dy = Math.max(cy, myView.getHeight() - cy);
    float finalRadius = (float) Math.hypot(dx, dy);
    Animator animator =
            ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
    animator.setInterpolator(new AccelerateDecelerateInterpolator());
    animator.setDuration(1500);
    animator.start();

这些动画尽管牛皮,可是记住5.0以上才收效的哦,一同咱们也不能看着什么动画炫酷都想上,转场动画也是在主线程履行的,假如界说不妥也会形成卡顿的。

⑤异步动画

在子线程中履行动画?我懂了,看我操作!

  Thread {
        val animatorscaleX = ObjectAnimator.ofFloat(mBinding.ivAnim, "scaleX", 2f)
        val animatorscaleY = ObjectAnimator.ofFloat(mBinding.ivAnim, "scaleY", 2f)
        val animatortranslationX = ObjectAnimator.ofFloat(mBinding.ivAnim, "translationX", 200f)
        val animatortranslationY = ObjectAnimator.ofFloat(mBinding.ivAnim, "translationY", 200f)
        val set = AnimatorSet()
        set.setDuration(1000).play(animatorscaleX).with(animatorscaleY).with(animatortranslationX).with(animatortranslationY)
        set.start()
        }.start()

开个线程,履行特点动画。 so easy! 等等,怎样写个特点动画这么多代码,修正一下,高雅一点,同样的作用一行代码处理。

  Thread {
          mBinding.ivAnim.animate().scaleX(2f).scaleY(2f).translationX(200f).translationY(200f).setDuration(1000).start()
        }.start()

运转!

撒花,2022Android最新面试专题:完结篇

竟然报错?不能运转在没有looper的子线程?哦…我懂了,子线程不能更新UI来着。

到此就引出一个经典面试题,子线程真的不能更新UI吗?当然能够更新UI了。看我操作!

public class MyLooperThread extends Thread {
    // 子线程的looper
    private Looper myLooper;
    // 子线程的handler
    private Handler mHandler;
    // 用于测验的textview
    private TextView testView;
    private Activity activity;
    public Looper getLooper() {
        return myLooper;
    }
    public Handler getHandler() {
        return mHandler;
    }
    public MyLooperThread(Context context, TextView view) {
        this.activity = (Activity) context;
        testView = view;
    }
    @Override
    public void run() {
        super.run();
        // 调用了此办法后,当时线程具有了一个looper目标
        Looper.prepare();
        YYLogUtils.w("音讯循环开端");
        if (myLooper == null) {
            while (myLooper == null) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 调用此办法获取当时线程的looper目标
                myLooper = Looper.myLooper();
            }
        }
        // 当时handler与当时线程的looper相关
        mHandler = new Handler(myLooper) {
            @Override
            public void handleMessage(Message msg) {
                YYLogUtils.w("处理音讯:" + msg.obj);
                //此线程,此Looper创立的ui能够随意修正
                addTextViewInChildThread().setText(String.valueOf(msg.obj));
                //发现跟ui创立的方位有关。假如ui是在main线程创立的,则在子线程中不能够更改此ui;
                // 假如在含有looper的子线程中创立的ui,则能够恣意修正
                // 这儿传进来的是主线程的ui,不能修正!低版别或许能够修正
                //CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
//                try {
//                    if (testView != null) {
//                        testView.setText(String.valueOf(msg.obj));
//                    }
//                } catch (Exception e) {
//                    e.printStackTrace();
//
//                }
            }
        };
        Looper.loop();
        YYLogUtils.w("looper音讯循环完毕,线程中止");
    }
    /**
     * 创立TextView
     */
    private TextView addTextViewInChildThread() {
        TextView textView = new TextView(activity);
        textView.setBackgroundColor(Color.GRAY);  //布景灰色
        textView.setGravity(Gravity.CENTER);  //居中展示
        textView.setTextSize(20);
        WindowManager windowManager = activity.getWindowManager();
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0,
                WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                WindowManager.LayoutParams.TYPE_TOAST,
                PixelFormat.TRANSPARENT);
        windowManager.addView(textView, params);
        return textView;
    }
}

咱们需求界说线程,然后预备Looper,并创立内部的Handler处理数据。咱们内部线程创立TextView,咱们发送handle音讯创立textview并赋值。

  val looperThread = MyLooperThread(this, mBinding.tvRMsg)
        looperThread.start()
        mBinding.ivAnim.click {
            looperThread.handler.obtainMessage(200, "test set tv'msg").sendToTarget()
        }

作用:

撒花,2022Android最新面试专题:完结篇

正常显现子线程创立的textview,可是咱们传入线程目标的tvRMsg是不能在子线程赋值的,会报错:

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

定论:假如ui是在main线程创立的,则在子线程中不能够更改此ui; 假如在含有looper的子线程中创立的ui,则能够恣意修正。

已然子线程都能够更新UI了,那么子线程履行动画行不可? 当然行!

咱们直接修正代码:

     val looperThread = MyLooperThread(this, mBinding.tvRMsg)
        looperThread.start()
        mBinding.ivAnim.click {
            //试试子线程履行动画看看
            looperThread.handler.post {
                mBinding.ivAnim.animate().scaleX(2f).scaleY(2f).translationX(200f).translationY(200f).setDuration(1000).start()
            }
        }

完美运转!

其实官方早有阐明,RenderThread 中运转动画。其实咱们上面的Thread类便是仿 HandlerThread 来写的。咱们能够运用 HandlerThread 很便利的完结子线程动画。详细的运用办法和咱们自界说的 Thread 类似。

咱们能够基于体系类 HandlerThread 封装一个异步动画东西类:

class AsynAnimUtil private constructor() : LifecycleObserver {
​
    private var mHandlerThread: HandlerThread? = HandlerThread("anim_run_in_thread")
​
    private var mHandler: Handler? = mHandlerThread?.run {
        start()
        Handler(this.looper)
    }
​
    private var mOwner: LifecycleOwner? = null
    private var mAnim: ViewPropertyAnimator? = nullcompanion object {
        val instance: AsynAnimUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            AsynAnimUtil()
        }
    }
​
    //发动动画
    fun startAnim(owner: LifecycleOwner?, animator: ViewPropertyAnimator) {
        try {
            if (mOwner != owner) {
                mOwner = owner
                addLoopLifecycleObserver()
            }
​
            if (mHandlerThread?.isAlive != true) {
                YYLogUtils.w("handlerThread restart")
                mHandlerThread = HandlerThread("anim_run_in_thread")
                mHandler = mHandlerThread?.run {
                    start()
                    Handler(this.looper)
                }
            }
​
            mHandler?.post {
                mAnim = animator.setListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator?) {
                        super.onAnimationEnd(animation)
                        destory()
                    }
​
                    override fun onAnimationCancel(animation: Animator?) {
                        super.onAnimationCancel(animation)
                        destory()
                    }
​
                    override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
                        super.onAnimationEnd(animation, isReverse)
                        destory()
                    }
                })
                mAnim?.start()
            }
​
        } catch (e: Exception) {
            e.printStackTrace()
        }
​
    }
​
    // 绑定当时页面生命周期
    private fun addLoopLifecycleObserver() {
        mOwner?.lifecycle?.addObserver(this)
    }
​
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        YYLogUtils.i("AsynAnimUtil Lifecycle -> onDestroy")
        mAnim?.cancel()
        destory()
    }
​
    private fun destory() {
        YYLogUtils.w("handlerThread quit")
​
        try {
            mHandlerThread?.quitSafely()
​
            mAnim = null
            mOwner = null
            mHandler = null
            mHandlerThread = null
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
​
}

运用的时分就能够直接拿东西类来进行异步动画

mBinding.ivAnim.click {
​
        //试试HandlerThread履行动画
        val anim = mBinding.ivAnim.animate()
            .scaleX(2f)
            .scaleY(2f)
            .translationXBy(200f)
            .translationYBy(200f)
            .setDuration(2000)
​
         AsynAnimUtil.instance.startAnim(this, anim)
​
    }

Ok,完美运转。这儿留意需求传入LifecycleOwner 为了在当时页面封闭的时分及时的中止动画开释资源。

总结与其他动画作用

网上也有许多开源的第三方的动画结构,如gif动画 lottie动画 mp4动画 Leonids粒子动画 SVGA动画 SurfaceView线程动画 Motion动画 VAP动画 等等太多了。这儿就不做过多的打开。假如大家有爱好能够自行查找哦!

16.startService与bindService的差异

差异:

  1. bindService绑定Activity能够和Service进行交互,而startService不能够
  2. startService发动的服务只能够经过stopService进行中止,bindService发动的无法用stopService中止
  3. bindService发动的服务只能经过unBindService办法进行中止

17.Service保活办法

最近开发了个内部即时通讯的app,能够说是真的蛋疼了,我简直把整个保活的文章悉数看了一遍,能够说android界真的是特别的鱼龙混杂。许多文章都写得很片面,简单构成很大的误导。我先说一个最近研究得出来的定论,在7.0或之后的版别,包含三星和国内的这些原生rom,假如不经过用户或厂家设置,至少service是肯定没有任何办法保活的,肯定,除非你还能找到未知的漏洞。尽管我也很头疼,但我真的很赞同谷歌这样的做法,否则天天收推送告诉真的是恶心得不可,IOS也得手动去关。现在android能够说是将全部统统杀掉,然后把仅有的一丝权限,给予用户去设置,乃至app中连弹出授权的api都不会给你。最终也供给下现在音讯实时push的处理方案。

  • jni保活,在5.0曾经,android体系自身是不办理jni层的,所以用linux那套fork机制,能够让进程和app分隔,就算封闭app也不会影响到。所以那时许多人说android十分的卡,幸运的是我那段时刻用的ios,这些进程连用户都没法关掉,真的特别恶心

  • jobservice和alarmmanager,在5.0之后,连native层也会受到体系束缚,比方之前可用的jni包活办法,也便是说无论是杀掉仍是冻结,都只和你发动的第一个进程有关,后边不再以父子的联系去看待,而是以前史同组的联系去看待。这个前史同组真的是一个很关键的改动,其实不止killgroup的作用性,无论怎样写代码,都脱离不了体系对你app的操控,比方让你何时休眠之类的。说了这么多,那么jobservice和alarmmanager到底有什么用呢,其实在整个体系下,就能够看做是一个智能省电的定时器,其他没有任何特殊功用,和线程相同会受到运转管控

  • doze,doze是在android6.0的时分呈现的,作用在锁屏之后,对app就行一系列的办理,能够说doze是一种底层机制。感觉doze仍是很友好的,比方说供给白名单api、推迟履行的操作等等。便是说你每个app都会给你机会去发告诉之类的,而且假如在app中授权ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,doze就不会再去管你,也便是假如即时通讯的话,加doze白名单后,假如没有monitor,就能够一向在后台刷数据

  • monitor,monitor能够看成是对doze的一种增强完结,也叫做厂商白名单,而且是对手机体系办理的一种完结,在绝大部分7.0的rom中都会有这东西,国产那简直便是100%有了,能够看做是一个root权限的app。许多app之前一进去就弹窗进doze白名单,这样后台运转的app多起来又卡得不可,android又得背上这大锅。monitor就默许只允许白名单中的app后台运转和自发动,就算在doze白名单中也不可,而且不供给任何弹窗api授权,有必要由用户在设置中手动参加白名单,或许找厂商给你加进去,比方QQ、微信、支付宝、搜狗这些

最终总结一下,那么从android7.0之后,到现在的android12,怎么完结后台音讯实时push呢?

  • 引导用户操作,一同参加doze白名单和monitor白名单,有必要两个都参加,app将一向在后台运转,那么就能够坚持tcp的连接,就能够实时获取到服务器数据。牢记发心跳,需求运用智能省电的定时器~~

  • 和厂商对接,用钱处理全部,让厂商给你加doze和monitor白名单

  • 经过厂商的api,对接推送,这种和ios思路相同,比方现在的华为、小米都有开发实时推送api,比ios恶心的便是需求对接多套。

这是现在实践过能用的方案,其他什么杂乱无章,利用漏洞和体系抢实间的这些就不说了,根本都被堵死了

18.泛型

①为什么需求泛型?

  • 具有不同参数类型却有相同的履行流程的办法,需求运用泛型;

  • 指定数据类型,能够在编译期间发现类型过错,也不需求进行强制类型转化;

②泛型类和泛型办法、泛型接口的界说

泛型类

public class A<T>{private T data; public A(T d){} public T getData(){return data;}……}

泛型接口:

public interface impl<T>{public T method();}//界说1
class impls<T> implements impl<T>{}//调用1,也是泛型
class impls implements impl<String>{public String method();}//调用2,指定了详细类型

泛型办法: (彻底独立,不一定要声明在泛型类和泛型接口中)

public <T> T method(T,……) {} <T>泛型办法的标志
class.<String>method();//调用1
class.method();//调用2

③泛型办法辨析

正确判别泛型办法: 最初

④限制类型

extends :指定泛型类派生于哪个类/接口

public class <T extends c&i>

public <T,V extends c&i> T method(T a,V b);

类和接口混用,类要放在最初,有且只能有1个类

⑤泛型中的束缚和限制性

  • 不能实例化类型变量:new T()// 不可

  • 静态域和静态办法里不能引证类型变量 private statc T instance;// 不可,由于在目标创立的时分,才知道T的详细类型

  • 静态办法自身能够是泛型办法

  • 泛型只能是类,不能用根底类型,能够用包装类

  • 泛型不支持instanceof

  • 泛型的原生类型,类型不会由于T的改动而改动:Test<T> 的目标 t(String)t(Float) 的类型是相同的

  • 泛型能够声明数组,却不能new:

    Test<T>
    Test<Float>[] arrays;//能够
    Test<Float>[] arrays = new Test<Float>[10];//不能够
    
  • 泛型类不能够extends ExceptionThrowable,try……catch不能够捕获泛型类目标,但能够捕获Throwable

    public  <T extends Throwable> void doWork(T x)
    {
          try{}catch(T x){}//不可
          try{} catch(Throwable e) throw T{throw t;}//能够
    }
    

⑥泛型类型的承继规则

  • class A extends B;C C和C没有任何联系,类型之间有承继联系不代表泛型之间有承继联系

  • 泛型类可承继自泛型类 class A extends B A a = new B//能够

⑦通配符类型

处理承继规则中C和C没有任何联系的限制。

class<T> A;
      method(A<? extends/super B>)
      ? extends B:首要用于安全的拜访数据,拜访类型B

限制了泛型类型的上限;有必要派生B的派生类;调用时增加

get一定是B;set不能用。

限制了泛型类型的下限;有必要是B的超类;

设置只能设置自身和子类,回来只能回来Object,首要用于安全的写入数据

⑧虚拟机怎么完结泛型

类型擦除 T 擦除成Object,T extends A,擦除成A(第一个)。完结接口时,在恰当的方位加强制类型转化

重载时,泛型参数的类型不经过。

19.重写equals办法需求重写hashCode办法吗

需求。

①咱们为什么需求重写hashCode()办法和equals()办法?(Why)

有时在咱们的事务体系中判别目标时有时分需求的不是一种严格意义上的持平,而是一种事务上的目标持平。在这种状况下,原生的equals办法就不能满足咱们的需求了.

咱们所知道的JavaBean的超类(父类)是Object类,JavaBean中的equals办法是承继自Object中的办法.Object类中界说的equals()办法是用来比较两个引证所指向的目标的内存地址是否共同.并不是比较两个目标的特点值是否共同,所以这时咱们需求重写equals()办法.

Object类中equals()办法的源码

public boolean equals(Object obj) {
       return (this == obj);
}
public class Demo {
 public static void main(String[] args) {
  Student stu1 = new Student("awu",22);
  Student stu2 = new Student("awu",22);
  System.out.println(stu1.equals(stu2));
                /*由于Student这个JavaBean没有重写关于特点值持平的equals()办法
                  ,所以默许比较的是地址值,然后输出成果为false*/    
 }
}

那么为什么在重写equals办法的时分需求重写hashCode办法呢?

首要是Object.hashCode的通用约好:

  • 在java运用程序运转时,无论何时屡次调用同一个目标时的hsahCode()办法,这个目标的hashCode()办法的回来值有必要是相同的一个int值.
  • 假如两个目标equals()回来值为true,则他们的hashCode()也有必要回来相同的int值.
  • 假如两个目标equals()回来值为false,则他们的hashCode()回来值也有必要不同.

以HashSet来阐明为什么要这么约好:HashSet寄存元素时,依据元素的hashCode值快速找到要存储的方位,假如这个方位有元素,两个目标经过equals()比较,假如回来值为true,则不放入;假如回来值为false,则这个时分会以链表的办法在同一个方位上寄存两个元素,这会使得HashSet的功用下降,由于不能快速定位了。

还有一种状况便是两个目标的hashCode()回来值不同,可是equals()回来true,这个时分HashSet会把这两个目标都存进去,这就和Set调集不重复的规则相悖了;所以,咱们重写了equals()办法时,要依照b,c规则重写hashCode()办法!(其实便是假如只重写了 equals 办法,两个目标 equals 回来了true,可是假如没有重写 hashCode 办法,调集仍是会刺进元素。这样调集中就呈现了重复元素了。)

②在什么状况下需求重写hashCode()办法和equals()办法? (When)

当咱们自界说的一个类,想要把它的实例保存在以Hash散列查找的调集中时,咱们就需求重写这两个办法;


public class Student {
 private String name;
 private Integer age;
 public Student(){
 }
 public Student(String name,Integer age){
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public Integer getAge() {
  return age;
 }
 public void setAge(Integer age) {
  this.age = age;
 }
 @Override  
    public int hashCode(){  
        final int prime = 31;  
        int result = 17;  
        result = prime * result + name.hashCode();  
        result = prime * result + age;  
        return result;  
    }  
    @Override  
    public boolean equals(Object obj){  
        if(this == obj)  
            return true;  
        if(obj == null)  
            return false;  
        if(getClass() != obj.getClass())  
            return false;  
        final Student other = (Student)obj;  
        if(name.equals(other.name)){  
            return false;  
        }  
        if(age.equals(other.age)){  
            return false;  
        }  
        return true;  
    }  
}
public class Demo {
 public static void main(String[] args) {
  Student stu1 = new Student("awu",22);
  Student stu3 = new Student("awu",33);
  Student stu2 = new Student("awu",22);
  Set set = new HashSet();
  set.add(stu1);
  set.add(stu2);
  set.add(stu3);
  System.out.println(set.size());
                /*输出成果为2*/
 }
}

假如不是以Hash散列查找的调集,即便重写HashCode也没多大实践用处.比方如下栗子:

public class Demo {
 public static void main(String[] args) {
  Student stu1 = new Student("awu",22);
  Student stu3 = new Student("awu",33);
  Student stu2 = new Student("awu",22);
  ArrayList list = new ArrayList();
  list.add(stu1);
  list.add(stu2);
  list.add(stu3);
  System.out.println(list .size());
                /*输出成果为3*/
 }
}

③怎么重写这两个办法?

public class Student {
 private String name;
 private Integer age;
 public Student(){
 }
 public Student(String name,Integer age){
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public Integer getAge() {
  return age;
 }
 public void setAge(Integer age) {
  this.age = age;
 }
     @Override  
        public int hashCode(){  
            final int prime = 31;  
            int result = 17;  
            result = prime * result + name.hashCode();  
            result = prime * result + age;  
            return result;  
        }  
        @Override  
        public boolean equals(Object obj){  
            if(this == obj)  
                return true;  
            if(obj == null)  
                return false;  
            if(getClass() != obj.getClass())  
                return false;  
                final Student other = (Student)obj;  
            if(name.equals(other.name)){  
                return false;  
            }  
            if(age.equals(other.age)){  
                return false;  
            }  
            return true;  
    }  
}

一共50W字的文档,面试专题12W字仅仅一小部分,字数束缚,分几篇更。

重视大众号:初一十五a

提前解锁 《整套50W字Android体系PDF》,让学习更靠近未来实战。

一共包含

1.腾讯Android开发笔记(33W字)

2.2022最新Android十一位大厂面试专题(12W字)

3.音视频经典面试题(6W字)

4.Jetpack全家桶

5.Android 功用监控结构Matrix

6.JVM

7.车载运用开发

《2022Android十一位大厂面试真题》 结合之前的 《腾讯Android开发笔记》 也算是左右开弓了!