Android面试题总结——持续更新

文章目录

Android

四大组件

Activity

Activity是一个Android应用程序组件(也称为Android四大组件之一),它提供了一个屏幕,用户可以通过该屏幕进行交互以执行某些操作,例如拨打电话,拍照,发送电子邮件或查看地图。每个活动都有一个窗口,用于绘制其用户界面。窗口通常填满屏幕,但可能比屏幕小,并漂浮在其他窗口的顶部。

生命周期

Activity 应用场景
  • Activity A启动:A->onCreate()->onStart()->onResume();
  • 返回桌面(home键):A->onPause()->onStop();
  • 桌面重新打开:A->onRestart()->onStart()->onResume();
  • 退出Activity:A->onPause()->onStop()->onDestroy();
  • Activity A退居后台,且系统内存不足,系统会杀死这个后台状态的Activity,若再次回到这个Activity,则会走A->onCreate()->onStart()->onResume()
  • 锁屏时只会调用onPause(),而不会调用onStop方法,开屏后则调用onResume()
Activity 中启动一个新的 Activity 生命周期变化
  • Activity A 启动 B
  • A->onPause();
  • B->onCreate()->onStart()->onResume();
  • 若A此时不可见了,A->onStop()

四种启动方式

  1. standard
    标准模式:创建一个Activity然后放入任务栈中。

  2. singleTop
    栈顶复用模式:先判断栈顶Activity是否是当前需要创建的Activity,是的话就重用栈顶,否则创建再插入。

  3. singleTask
    任务栈单例模式:判断任务栈中是否有相同的Activity,有则移出任务栈上面的所有任务栈,使当前Activity回到栈顶,没有则重新创建。

  4. singleInstance
    单任务栈模式:为Activity新开一个任务栈,且只有一个实例,使该Activity独享一个任务栈,如果已经启动该实例,再次启动将会回调onNewIntent方法。

如何给Activity指定启动模式
  1. AndroidManifest中为Activity指定启动模式 android:launchMode=“singleTask”
  2. 通过在Intent中设置标记位来指定Activity的启动模式 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  • Intent.FLAG_ACTIVITY_NEW_TASK,是为Activity指定“singleTask”启动模式

  • Intent.FLAG_ACTIVITY_SINGLE_TOP,是为Activity指定“singleTop”启动模式

为什么需要onNewIntent()

因为Activity启动可能会通过Intent传递参数

  1. 如果第一次创建,可在onCreate()内接收数据
  2. 如果不是第一次创建(singleTop、singleTask、singleInstance模式下),由于再次启动可能不经过onCreate(),可在onNewIntent()接收参数。

onNewIntent()方法应用场景

  1. Activity A从后台onStop()状态重新唤醒
  • A->onStop()->onNewIntent()->onRestart()->onStart()->onResume();
  1. 当Activity A处于singleTop模式,此时要再次启动A:
  • Activity A中启动Activity A
  • A->onResume()->onPause()->onNewIntent()->onResume();
  1. 当Activity A处于singleTask或者singleInstance模式时:
  • Activity A中启动Activity B
  • Activity B中启动Activity A
  • A->onStop()->onNewIntent()->onRestart()->onStart()->onResume();

onSaveInstanceState() 和 onRestoreInstanceState()

  1. 如果系统由于系统约束(而不是正常的应用程序行为)而破坏了Activity,那么尽管实际 Activity实例已经消失,但是系统还是会记住它已经存在,这样如果用户导航回到它,系统会创建一个新的实例的Activity使用一组保存的数据来描述Activity在被销毁时的状态。系统用于恢复以前状态的已保存数据称为“实例状态”,是存储在Bundle对象中的键值对的集合。
  2. 每次用户旋转屏幕时,您的Activity将被破坏并重新创建。当屏幕改变方向时,系统会破坏并重新创建前台Activity,因为屏幕配置已更改,您的Activity可能需要加载替代资源(例如布局)。即会执行onSaveInstanceState()和onRestoreInstanceState()的。
  3. 该方法调用在onStop之前,但和onPause没有时序关系。
  4. 系统onCreated()方法也可进行数据恢复,但是要判断是否Bundle为空;系统onRestoreInstanceState()只有在存在保存状态的情况下才会恢复,因此不需要检查是否Bundle为空,但是onRestoreInstanceState()在onStart()方法之后才会被调用。

注意:如果是用户自动按下返回键,或程序调用finish()退出程序,是不会触发onSaveInstanceState()和onRestoreInstanceState()的。

执行场景
  1. 当用户按下HOME键时
  2. 长按HOME键,选择运行其他的程序时
  3. 锁屏时
  4. 从activity A中启动一个新的activity时
  5. 屏幕方向切换时
屏幕旋转时发生什么
  1. Activity启动
I/System.out: onCreate
I/System.out: onStart
I/System.out: onResume
  1. 屏幕旋转
I/System.out: onConfigurationChanged
I/System.out: onPause
I/System.out: onSaveInstanceState
I/System.out: onStop
I/System.out: onDestroy
I/System.out: onCreate
I/System.out: onStart
I/System.out: onRestoreInstanceState
I/System.out: onResume
  1. 在manifest文件中加入configChanges参数
    为了避免由于配置改变导致Activity重建,可在AndroidManifest.xml中对应的Activity中设置android:configChanges=“orientation|screenSize”。此时再次旋转屏幕时,该Activity不会被系统杀死和重建,只会调用onConfigurationChanged。因此,当配置程序需要响应配置改变,指定configChanges属性,重写onConfigurationChanged方法即可。
android:configChanges="orientation|screenSize"

屏幕旋转时

I/System.out: onConfigurationChanged

如何将一个 Activity 设置成窗口的样式?

android:theme="@android:style/Theme.Dialog"

如何退出APP并关闭所有Activity

  1. 发送特定广播
  2. 递归退出,就调用 finish()方法 把当前的在打开新的 Activity 时使用 startActivityForResult,然后自己加标志,在 onActivityResult 中处理,递归关闭。

Activity 怎么和 Service 通信

  1. 利用BroadcastReceive
  2. 通过Binder对象,Activity通过bindService方法开启服务,得到一个Service的对象,通过该对象可以直接和Service进行交互

Service

服务是Android中实现程序后台运行的解决方案,他非常适合是去执行那些不需要和用户交互而且还要长期运行的任务。 Service并不是运行在单独线程中,而是主线程中。所以尽量要避免一些ANR的操作。

Service的两种形式

启动本地服务用的是显式启动; 远程服务的启动要用到隐式启动

本地服务
  • 该服务依附于主进程上
  • 节省资源
  • 当主进程被kill,服务也随之停止
远程服务
  • 处于独立的线程
  • 当Activity所在进程被Kill时,服务仍在运行
  • 占用一定的资源,一般用于系统的Service
  • 进程名格式为所在包名加上你指定的android:process字符串。一般定义方式:
android:process=":service" 

Service 的两种状态

后台服务

应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。

如何保证Service不被杀死

后台服务在系统内存不足的时候,可能会被系统杀死,如何让服务不被杀死:

  1. 设置优先级(1-1000)
  2. 把service写成远程服务
  3. 把service写成前台服务
  4. 利用广播实时检查
  5. 在Service的onStartCommand()中设置flages值为START_STICKY,使得Service被杀死后尝试再次启动Service
前台服务

前台服务在手机状态栏会显示一个状态,类似于通知的效果,这样可以保证不会被系统轻易杀死。

服务的三种启动方式

startService()

生命周期是这样:调用startService()–>onCreate()–>onStartConmon()–> onDestroy()。

  • 当我们通过startService被调用以后,多次在调用startService(),onCreate()方法也只会被调用一次,而onStartConmon()会被多次调用当我们调用stopService()的时候,onDestroy()就会被调用,从而销毁服务。
  • 当我们通过startService启动时候,通过intent传值,在onStartConmon()方法中获取值的时候,一定要先判断intent是否为null。
bindService()

生命周期走法:bindService()–>onCreate()–>onBind()–>unBind()–>onDestroy()

  • 此时的Service处于“绑定”状态
  • 调用bindService()会返回一个Binder对象,通过Binder对象使Activity完成于Service的交互
startForegroundService()

启动前台服务

IntentService

IntentService,可以看做是Service和HandlerThread的结合体,在完成了使命之后会自动停止,适合需要在工作线程处理UI无关任务的场景。

  1. IntentService 是继承自 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作。
  2. 当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。
  3. 如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,使用串行的方式,执行完自动结束。
IntentService 的特点
  1. 本质上是服务,优先级要高于线程,不容易被系统杀死,适合执行一些高优先级的任务
  2. IntentService可自动创建子线程来执行任务,且任务执行完毕后自动退出
为何不用bindService方式创建IntentService?

IntentService的工作原理是,在IntentService的onCreate()里会创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage()方法会去调用IntentService的onHandleIntent(),这也是为什么可在该方法中处理后台任务的逻辑;当有Intent任务请求时会把Intent封装到Message,然后ServiceHandler会把消息发送出,而发送消息是在onStartCommand()完成的,只能通过startService()才可走该生命周期方法,因此不能通过bindService创建IntentService。

关闭服务

  • 需要手动调用 stopService去停止Service。
  • IntentService不需要,任务执行完自动释放。

系统服务

[外链图片转存失败(img-esldEOKN-1566482432857)(https://upload-images.jianshu.io/upload_images/5494434-2588ec70045e4ab2?imageMogr2/auto-orient/)]

ActivityManagerService

ActivityManagerService是Android中最核心的服务 , 主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。

BroadcastReceive

广播(Broadcast)是一种广泛运用的在应用程序之间传输信息的机制。

  1. 广播(Broadcast) - 用于发送广播。
  2. 广播接收器(BroadcastReceiver) - 用于接收广播;
  3. 意图(Intent)-用于保存广播相关信息的媒介。

广播类型

  1. 普通广播
    异步发送,传递速度快,可以被所有广播接收者接收,缺点是接收者不能将广播处理结果传递给下一个接收者,也无法终止广播。
  2. 有序广播
    按照广播接收者的优先级发送广播。有序广播可以被接收器截断使得后面的接收器无法收到它。
  3. 系统广播
    系统广播是Android内置的广播,发送系统状态。
  4. 本地广播
    广播只在应用程序内部传递。
  5. 动态注册广播
    动态注册就是在程序中使用Context.registerReceiver注册。可以自由的控制注册和注销,必须程序启动以后才开启。
  6. 静态注册广播
    在AndroidManifest.xml中注册广播称之为静态注册,静态注册是常驻型 ,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
  7. 粘性广播
    粘性消息在发送后就一直存在于系统的消息容器里面,是指广播接收器一注册马上就能接收到广播的一种机制。

ContentProvider

ContentProvider主要负责存储和共享数据。与文件存储、SharedPreferences存储、SQLite数据库存储这几种数据存储方法不同的是,后者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。

  • 是一个抽象类
  • 利用URI来访问资源

URI

URI(Uniform Resource Identifier)即统一资源标识符,是一个用于标识某一互联网资源名称的字符串

Android 进程优先级

优先级从高到低

  • 前台进程:指正在与用户进行交换的进程(进程包括当前APP的activity、service等)
  • 可视进程:能被用户看见,但是不能根据用户动作做成响应的进程
  • 服务进程:没有可视界面但是仍在不断执行的进程(一般是service)
  • 后台进程:后台进程,例如进入onStop()的activity
  • 空进程:为提高整体系统性能,系统会保存已经完成生命周期的应用程序 ,存在与内存当中,也就是缓存,为下次的启动更加迅速而设计。通常会被定期地终止

Android有main方法吗

有main方法,main方法在ActivityThread类中的第 6041行 main(String[] args)。

Manifest.xml文件中主要包括哪些信息?

  1. manifest:根节点,描述了package中所有的内容。
  2. uses-permission:请求你的package正常运作所需赋予的安全许可。
  3. permission: 声明了安全许可来限制哪些程序能你package中的组件和功能。
  4. instrumentation:声明了用来测试此package或其他package指令组件的代码。
  5. application:包含package中application级别组件声明的根节点。
  6. activity:Activity是用来与用户交互的主要工具。
  7. receiver:IntentReceiver能使的application获得数据的改变或者发生的操作,即使它当前不在运行。
  8. service:Service是能在后台运行任意时间的组件。
  9. provider:ContentProvider是用来管理持久化数据并发布给其他应用程序使用的组件。

总结:

  • 四大组件
  • 运行权限
  • APP相关

View相关

SurfaceView和View的区别是什么?

SurfaceView中采用了双缓存技术,在单独的线程中更新界面;View在UI线程中更新界面

  • SurfaceView是从View基类中派生出来的显示类
  • View在UI线程对画面进行更新,而SurfaceView在子线程对画面进行刷新
  • View适用于主动更新的情况,而SurfaceView适应于被动更新,如频繁的刷新,这是因为如果使用View频繁的刷新会阻塞主线程,导致界面卡顿
  • SurfaceView在底层已实现双缓冲机制,而View没有,因此SurfaceView更适用于需要频繁刷新、刷新时数据处理量很大的页面

invalidate()和postInvalidate()的区别?

invalidate()与postInvalidate()都用于刷新View,主要区别是invalidate()在主线程中调用,若在子线程中使用需要配合handler;而postInvalidate()可在子线程中直接调用。

onTouch()、onTouchEvent()和onClick()关系?

优先度onTouch()>onTouchEvent()>onClick()。因此onTouchListener的onTouch()方法会先触发;如果onTouch()返回false才会接着触发onTouchEvent(),同样的,内置诸如onClick()事件的实现等等都基于onTouchEvent();如果onTouch()返回true,这些事件将不会被触发。

dp、dpi、px的区别

  • px像素
  • dpi每英寸的像素密度
  • dp相对密度单位,便于不同手机适配
    dp 单位转换为 px 很简单: px = dp * (dpi / 160)

Activity、View、Window三者之间的关系?

在Activity启动过程其中的attach()方法中初始化了PhoneWindow,而PhoneWindow是Window的唯一实现类,然后Activity通过setContentView将View设置到了PhoneWindow上,而View通过WindowManager的addView()、removeView()、updateViewLayout()对View进行管理。

  • Activity相当于一个容器
  • Window相当于开了一个窗口
  • View在窗口上显示各种东西

Android中内存泄漏的原因

  1. 广播没有unregisterrecevier
  2. 静态集合Vector、HashMap
    后面再补充

JNI

JNI,全称为Java Native Interface,即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。同时,这个特性使我们可以复用以前用C/C++写的大量代码JNI是一种在Java虚拟机机制下的执行代码的标准机制。代码被编写成汇编程序或者C/C++程序,并组装为动态库。也就允许非静态绑定用法。这提供了一个在Java平台上调用C/C++的一种途径,反之亦然。

NDK

Android NDK 就是一套工具集合,允许你使用C/C++语言来实现应用程序的部分功能。

优点

  1. C/C++效率高
  2. 便于加密
  3. 有些优秀的C/C++第三方库
  4. 方便平台移植

so文件

因为C语言的不跨平台,使用NDK编译在Linux下能执行的函数库——so文件。

JNI的命名规则

  • JNIEnv 对应Java环境,通过JNIEnv的函数表来操作Java数据或者调用Java方法
  • jobject 调用的对象
JNIExport 返回值 JNICALL 包名_类名_方法名(JNIEnv* env,jobject thiz,...参数)

Android 中的内存泄漏

发生内存泄漏是由于长周期对象持有对短周期对象的引用,使得短周期对象不能被及时回收。

Handle(AsyncTask/Runnable)造成的内存泄漏

使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象的引用。如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束 解决:1.将Handler声明为静态类静态类不持有外部类的对象,所以你的Activity可以随意被回收,但是由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用

资源未关闭导致的内存泄漏

未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。

Fragment

生命周期


Fragment从创建到销毁整个生命周期中涉及到的方法依次为:onAttach()->onCreate()-> onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestroyView()->onDestroy()->onDetach(),其中和Activity有不少名称相同作用相似的方法,而不同的方法有:

onAttach():当Fragment和Activity建立关联时调用

onCreateView():当Fragment创建视图时调用

onActivityCreated():当与Fragment相关联的Activity完成onCreate()之后调用

onDestroyView():在Fragment中的布局被移除时调用

onDetach():当Fragment和Activity解除关联时调用

Activity 和 Fragment 的异同?

  • Fragment和Activity相同在于,它们都包含布局,右子节的生命周期,Fragment可以看成迷你的Activity
  • Fragment依附于Activity,多了一些与宿主相关的方法onAttach()、onActivityCreated()、onDetach()。
  • Fragment的生命周期由Activity调用而不是操作系统。
  • 正如Fragment的名字“碎片”,它的出现是为了解决Android碎片化 ,它可作为Activity界面的组成部分,可在Activity运行中实现动态地加入、移除和交换。
  • 一个Activity中可同时出现多个Fragment,一个Fragment也可在多个Activity中使用。

Fragment的优点

  • 模块化(Modularity):我们不必把所有代码全部写在Activity中,而是把代码写在各自的Fragment中。
  • 可重用(Reusability):多个Activity可以重用一个Fragment。
  • 可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,能够方便地实现不同的布局,这样用户体验更好。
  • 使用Fragment能实现一个界面的多次使用,能加快效率

Android 中的数据存储技术

  • File 文件存储:写入和读取文件的方法和 Java中实现I/O的程序一样
    public void saveData(byte[] data,String path){
        try {
            File file = new File(path);
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public byte[] readData(String path){
        byte[] data = null;
        try {
            File file = new File(path);
            FileInputStream fis = new FileInputStream (file);
            data = new byte[fis.available()];
            fis.read(data);
        }catch (Exception e){
            e.printStackTrace();
        }
        
        return data;
    }
  • SharedPreferences存储:一种轻型的数据存储方式,常用来存储一些简单的配置信息,本质是基于XML文件存储key-value键值对数据。SharedPreferences的读/写有一定的缓存策略,即在内存中有一份该文件的缓存,因此在多进程模式下,其读/写会变得不可靠,甚至丢失数据。
    public static void saveSongData(Context context,String database,String key,String data){
        SharedPreferences.Editor editor = context.getSharedPreferences(database,Context.MODE_PRIVATE)
                .edit();
        editor.putString(key,data);
        editor.apply();
    }

    public static String getSongData(Context context,String database,String key){
        SharedPreferences sharedPreferences = context.getSharedPreferences(database,Context.MODE_PRIVATE);
        return sharedPreferences.getString(key,"");
    }
  • SQLite数据库存储:一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,在存储大量复杂的关系型数据的时可以使用。
  • ContentProvider:四大组件之一,用于数据的存储和共享,不仅可以让不同应用程序之间进行数据共享,还可以选择只对哪一部分数据进行共享,可保证程序中的隐私数据不会有泄漏风险。

Android 多进程通信(ICP)

如何开启多进程

  1. 在Manifest文件中给(Activity,Service,Receiver,ContentProvider)指定android:process属性,
  2. android:process属性内容以":"开头的进程属于私有的,其他应用组件不能和它跑在同一个进程
  3. 不以":"开头的属于全局进程,其他应用可以通过ShareUID和它跑在同一个进程
  4. Android会为每个应用分配一个UID,相同UID的应用才能共享数据。只有2个应用ShareUID相同并且签名相同,才能跑在同一个进程

多进程造成的影响

  • 静态变量和单例模式失效:由独立的虚拟机造成
  • 线程同步机制失效:由独立的虚拟机造成
  • SharedPreference的不可靠下降:不支持两个进程同时进行读写操作,即不支持并发读写,有一定几率导致数据丢失
  • Application多次创建: Android系统会为新的进程分配独立虚拟机,相当于系统又把这个应用重新启动了一次。

Binder

优点

  • 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高

从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,如图:


而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:

  • 实现C/S架构方便:Linux的众IPC方式除了Socket以外都不是基于C/S架构,而Socket主要用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。

  • 安全性高:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。

C/S架构

C/S架构的优点

  1. C/S架构的界面和操作可以很丰富。(客户端操作界面可以随意排列,满足客户的需要)

  2. 安全性能可以很容易保证。(因为只有两层的传输,而不是中间有很多层。

  3. 由于只有一层交互,因此响应速度较快。(直接相连,中间没有什么阻隔或岔路,比如QQ,每天那么多人在线,也不觉得慢)

C/S架构的缺点:

  1. 适用面窄,通常用于局域网中。

  2. 用户群固定。由于程序需要安装才可使用,因此不适合面向一些不可知的用户。

  3. 维护成本高,发生一次升级,则所有客户端的程序都需要改变。

Android中的应用

Service以bindService()方式启动。

为什么不能在子线程访问UI

系统不建议在子线程访问UI的原因:UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。

为什么不对子线程上锁

  • 效率变低
  • 上锁后容易造成ANR

Handle

  • Message(消息):需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。

  • MessageQueue(消息队列):用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。

  • Handler(处理者):负责Message的发送及处理。

    • Handler.sendMessage():向消息池发送各种消息事件。
    • Handler.handleMessage() :处理相应的消息事件。
  • Looper(消息泵):通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。


Looper

    public void test() {
        new Thread(){
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                Handler handler = new Handler();//默认为当前线程Looper
                Looper.loop();
            }
        }.start();
    }
  • 每个线程只能有一个Looper
  • 如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用 Looper.loop();之后就进入了消息循环(无限循环),这个之后就可以发消息、取消息、和处理消息。
  • 通过Looper.prepare()可将一个Thread线程转换成Looper线程。Looper线程和普通Thread不同,它通过MessageQueue来存放消息和事件、Looper.loop()进行消息轮询。

Message创建

  • Message msg = new Message();
  • Message msg = Message.obtain();
  • Message msg = handler1.obtainMessage();
    后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message

Looper无限循环为什么没有阻塞ANR

Acitvity中所有的生命周期方法都是由handler和looper和消息队列构成的。代码都是由事件触发的,也就不会产生阻塞问题。

AsyncTask

底层封装了线程池和Handler,便于执行后台任务以及在子线程中进行UI操作。

为什么要用AsyncTask

  • Handler机制存在的问题:多任务同时执行时不易精确控制线程。
  • 引入AsyncTask的好处:创建异步任务更简单,直接继承它可方便实现后台异步任务的执行和进度的回调更新UI,而无需编写任务线程和Handler实例就能完成相同的任务。

注意事项

  • 不要直接调用onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()和onCancelled()方法
  • 一个异步对象只能调用一次execute()方法

线程通信

  1. Handle
  2. AsyncTask
  3. HandlerThread

HandlerThread

HandlerThread是一个线程类,它继承自Thread。与普通Thread不同,HandlerThread具有消息循环的效果,这是因为它内部HandlerThread.run()方法中有Looper,能通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。

  • 实例化一个HandlerThread对象,参数是该线程的名称;
  • 通过 HandlerThread.start()开启线程;
  • 实例化一个Handler并传入HandlerThread中的looper对象,使得与HandlerThread绑定;
  • 利用Handler即可执行异步任务;
  • 当不需要HandlerThread时,通过HandlerThread.quit()/quitSafely()方法来终止线程的执行。

Bitmap

  • 直接加载大容量的高清Bitmap很容易出现显示不完整、内存溢出OOM的问题,所以最好按一定的采样率将图片缩小后再加载进来
  • 为减少流量消耗,可对图片采用内存缓存策略,又为了避免图片占用过多内存导致内存溢出,最好以软引用方式持有图片
  • 如果还需要网上下载图片,注意要开子线程去做下载的耗时操作

View

View的绘制流程

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

OnMeasure()

测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

OnLayout()

确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

OnDraw()

绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:
① 绘制视图的背景
② 保存画布的图层(Layer)
③ 绘制View的内容
④ 绘制View子视图,如果没有就不用(一般由ViewGroup调用);
⑤ 还原图层(Layer)
⑥ 绘制滚动条

ViewGroup

View和ViewGroup

  • ViewGroup继承于View
  • View是绘制在屏幕上的用户能与之交互的一个对象。而ViewGroup则是一个用于存放其他View(和ViewGroup)对象的布局容器。

ViewGroup绘制流程

ViewGroup相当于一个放置View的容器,主要负责给childView计算出建议的宽高和测量模式;决定childView的位置。主要用到的方法有:

  • onMesure() ——计算childView的测量值以及模式,以及设置自己的宽和高。
  • onLayout()——通过getChildCount()获取子view数量,getChildAt获取所有子View,分别调用layout(int l, int t, int r, int b)确定每个子View的摆放位置。
  • onSizeChanged()——在onMeasure()后执行,只有大小发生了变化才会执行onSizeChange。
  • onDraw()——默认不会触发,需要手动触发。

margin和padding的区别

  • margin是控件到父控件之间的距离
  • padding是控件到自身边界的距离

ListView和RecycleView

  • ListView可用ViewHolder或者不用,RecycleView强制使用ViewHolder
  • ListView对Item的缓存每次取出需要对View进行重新绘制,而RecycleView缓存以后不需要进行重新绘制
  • ListView没有方法对单个Item进行View更新(可自行写方法),RecycleView提供了局部刷新的接口
  • Android 默认提供的 RecyclerView 就能支持线性布局、网格布局、瀑布流布局,由LayoutManager控制(因此RecyclerView比ListView多一步设置LayoutManager的过程),还能支持控件横向滚动

ListView优化

  • convertView重用;
    利用好 convertView 来重用 View,切忌每次 getView() 都新建。ListView 的核心原理就是重用 View,如果重用 view 不改变宽高,重用View可以减少重新分配缓存造成的内存频繁分配/回收;
  • ViewHolder优化;
    使用ViewHolder的原因是findViewById方法耗时较大,如果控件个数过多,会严重影响性能,而使用ViewHolder主要是为了可以省去这个时间。通过setTag,getTag直接获取View。
  • 图片加载优化 ;
    如果ListView需要加载显示网络图片,我们尽量不要在ListView滑动的时候加载图片,那样会使ListView变得卡顿,所以我们需要在监听器里面监听ListView的状态,如果ListView滑动(SCROLL_STATE_TOUCH_SCROLL)或者被猛滑(SCROLL_STATE_FLING)的时候,停止加载图片,如果没有滑动(SCROLL_STATE_IDLE),则开始加载图片。

ANR

Bundle

Android性能优化

发布了63 篇原创文章 · 获赞 73 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/jjwwwww/article/details/99717428