记一次面试题

编程入门 行业动态 更新时间:2024-10-23 21:37:36

1.常用的单例模式
单例模式——顾名思义即在既定的业务场景下某一实体类只需存在一个对象,就能充分的处理所有的业务需求。而且在某种现场环境下,创建这样的对象对系统性能的开销非常大。正因为这种特性,单利模式通常具有节省系统开销的效果。
1.饿汉式

类加载的时候就会初始化,他是线程安全的,但是类加载的时候就初始化这样会预先消耗一部分资源。

/**
 * Created by lijiayi on 2017/3/1.
 * 饿汉式单例
 */
public class Singleton {
    //生成单例对象
    private static final Singleton mSingleton = new Singleton();

    //私有化构造方法
    private Singleton() {
    }

    //获取单例对象
    public static Singleton getInstance() {
        return mSingleton;
    }
}

2.懒汉式

当第一次使用的时候才创建对象,合理占用资源,但是当该对象已经创建的时候,调用getInstance()方法会产生不必要的开销,这是懒汉式的一个缺点。

/**
 * Created by lijiayi on 2017/3/1.
 * 懒汉式单例
 */
public class Singleton {
    //声明单例对象
    private static Singleton mSingleton;

    //私有化构造方法
    private Singleton() {
    }

    //同步该方法获取单例对象
    public static synchronized Singleton getInstance() {
        //当该对象为空的时候创建该对象
        if (mSingleton == null) {
            mSingleton = new Singleton();
        }
        //返回该对象实例
        return mSingleton;
    }
}

3.DCL式

该方式能保证在需要的时候才初始化单例,又能够保证线程安全,而且单例初始化后调用getInstance()不会进行同步锁。

/**
 * Created by lijiayi on 2017/3/1.
 * DCL单例
 */
public class Singleton {
    //声明单例对象
    private static Singleton mSingleton;

    //私有化构造方法
    private Singleton() {
    }

    //同步该方法获取单例对象
    public static synchronized Singleton getInstance() {
        //当该对象为空的时候先同步这个对象
        if (mSingleton == null) {
            synchronized (Singleton.class) {
                //再判断是否为空
                if (mSingleton == null) {
                    //如果还空的话 就创建对象
                    mSingleton = new Singleton();
                }
            }
        }
        //返回该对象实例
        return mSingleton;
    }
}

4.静态内部类式

该方式保证了资源预先消耗、不必要的同步、线程的安全问题,还避免了DCL模式在某些情况下失效的问题。所以笔者建议使用该单例模式。

/**
 * Created by lijiayi on 2017/3/1.
 * 静态内部类单例
 */
public class Singleton {

    //私有化构造方法
    private Singleton() {
    }

    //获取单例对象 
    public static Singleton getInstance() {
        //返回内部类中的singleton对象 
        return SingletonHolder.singleton;
    }

    /**
     * 静态内部类
     * 第一次加载类的时候不用调用该类,创建singleton对象,
     * 只有调用getInstance()方法时才会创建该对象。
     */
    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

}

5.枚举式

枚举是线程安全的,而且任何情况下都是一个实例,他不能被序列化,也不能被反射,所以枚举单例也是一个不错的选择。

/**
 * Created by lijiayi on 2017/3/1.
 * 枚举式单例
 * 枚举单例时线程安全的,在任何情况下都是一个单例
 */
public enum SingletonEnum {
    //枚举的类型
    INSTANCE;

    // 单例中的函数
    public void todoSomethings() {
        System.out.println("do somethings");
    }
}

//枚举单例的使用
SingletonEnum.INSTANCE.todoSomethings();```
***
###6.容器实现单例
它的好处是可以统一的管理单例,安卓中getSystemService(String name)就是用这种方式实现的。
```java 
/**
 * Created by lijiayi on 2017/3/1.
 * 容器模式单例
 * 实现了程序中单例的统一管理
 */
public class SingletonManager {
    private static Map<String, Object> singletonManagerMap = new HashMap<String, Object>();

    //私有化构造方法
    private SingletonManager() {

    }

    //注入程序中的单例
    public static void registerSingleton(String key, Object instance) {
        if (!singletonManagerMap.containsKey(key)) {
            singletonManagerMap.put(key, instance);
        }
    }

    //获取对于key值的单例对象
    public static Object getInstance(String key) {
        return singletonManagerMap.get(key);
    }
}

参考文章:https://www.jianshu/p/8fe210e6aeb9
2.如何正确的覆盖equals()方法?为什么要这么做?
equals()方法是所有类超类Object中的方法,但是该方法是用来比较2个对象变量是否引用同一个地址,在大多数情况下 我们并不需要这种比较方式,而是需要比较2个对象的状态是否一致,即对象中的实例域是否对应相等。

 因此我们需要将equals方法进行覆盖

注意:public boolean equals(Object other) 而不是public boolean equals(ClassName other)

前者是覆盖重写,后者只是方法名一致

public class A{
public boolean equals(Object other){//类A覆盖了equals方法,只要是类A的实例,都返回true
return true;
}
}

参考文章:https://blog.csdn/yyyyyu_cool/article/details/78168216
3.线程池的使用.线程池的几种类型,为什么要使用线程池?

使用线程池的好处主要有以下几点:
(1)因为在请求时,线程已经存在,从而消除了线程创建所带来的延迟
(2)通过调整线程池中的线程数目,可以有效防止资源不足
常用的几种线程池:
1.newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:
(1)工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程(2)将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
(3)在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for(int i = 0; i < 10; i++){
            final int index = i;
            try{
                Thread.sleep(index * 1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable(){
                public void run(){
                    System.out.println(index);
                }
            });
        }

2.newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for(int i = 0; i < 10; i++){
            final int index = i;
            fixedThreadPool.execute(new Runnable(){
                public void run(){
                    try{
                        System.out.println(index);
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }

因为线程池大小为3,每个任务输出index后sleep 1秒,所以每两秒打印1个数字。
定长线程池的大小最好根据系统资源进行设置如Runtime.getRuntime().availableProcessors()。

3.newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
        for(int i = 0; i < 10; i++){
            final int index = i;
            singleThreadPool.execute(new Runnable(){
                public void run(){
                    try{
                        System.out.println(index);
                        Thread.sleep(1000);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }

4.newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

延迟3秒执行,延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.schedule(new Runnable(){
            public void run(){
                System.out.println("延迟3秒");
            }
        }, 3, TimeUnit.SECONDS);

参考文章:https://blog.csdn/freezeriver/article/details/82827302
4.快排(快速排序)
快速排序是冒泡排序的改进版,也是最好的一种内排序

package quickSort;
 
public class QuickSort {
	private static int count;
	/**
	 * 测试
	 * @param args
	 */
	public static void main(String[] args) {
		int[] num = {3,45,78,64,52,11,64,55,99,11,18};
		System.out.println(arrayToString(num,"未排序"));
		QuickSort(num,0,num.length-1);
		System.out.println(arrayToString(num,"排序"));
		System.out.println("数组个数:"+num.length);
		System.out.println("循环次数:"+count);
		
	}
	/**
	 * 快速排序
	 * @param num	排序的数组
	 * @param left	数组的前针
	 * @param right 数组后针
	 */
	private static void QuickSort(int[] num, int left, int right) {
		//如果left等于right,即数组只有一个元素,直接返回
		if(left>=right) {
			return;
		}
		//设置最左边的元素为基准值
		int key=num[left];
		//数组中比key小的放在左边,比key大的放在右边,key值下标为i
		int i=left;
		int j=right;
		while(i<j){
			//j向左移,直到遇到比key小的值
			while(num[j]>=key && i<j){
				j--;
			}
			//i向右移,直到遇到比key大的值
			while(num[i]<=key && i<j){
				i++;
			}
			//i和j指向的元素交换
			if(i<j){
				int temp=num[i];
				num[i]=num[j];
				num[j]=temp;
			}
		}
		num[left]=num[i];
		num[i]=key;
		count++;
		QuickSort(num,left,i-1);
		QuickSort(num,i+1,right);
	}
	/**
	 * 将一个int类型数组转化为字符串
	 * @param arr
	 * @param flag
	 * @return
	 */
	private static String arrayToString(int[] arr,String flag) {
		String str = "数组为("+flag+"):";
		for(int a : arr) {
			str += a + "\t";
		}
		return str;
	}
 }

参考文章:https://blog.csdn/qq_36186690/article/details/82470451
6.Android 5.0到9.0,各版本的区别是什么?
Android5.0

1、android 5.0 Hello 5.0

  5.0的版本号就能告诉我们,这是一个大的升级版本。

2、基于Linux内核3.0

  Linux刚刚发布Linux 3.0内核没多久,后来又更新到了3.0.1 。

3、通过Android Market购买音乐

谷歌已经试水性质的推出了Google Music Beta ,允许上传2万首音乐到Google的云端服务器,自动创建播放列表在任何支持flash的设备上回放,我们有理由相信这种在Appstore影响下的数字视频、音频服务内容的提供也将会出现在Android上。

4、多核处理器优化

现在各厂商都在推双核的智能手机,实际的效果如何我们不予评说,但Android 4.0将特别为使用双核乃至多核处理器的手机进行专门的优化。

5、运行速度比3.1提高1.8倍

  Google的人员只是这么一说,并没有提及这个性能具体体现在什么方面,因此我们不知道这是关于浏览器、GPU还是整体性能,只有等待。

6、集成Google电视和Chrome OS的智能停放

7、为OEM提供了官方的主题引擎

有了这个引擎,便可通过Google的升级程序更新到这些第三方的主题。

8、苹果游戏中心的一个真正的竞争者

9、支持现有的智能手机

据称现行所有运行Android 2.3的手机都有可能升级到4.0版本,这条消息是我喜闻乐见的,而Google官方的Nexus One和Nexus S将是第一批升级到4.0的手机。

10、新的摄影技巧以及虚拟摄像机

这将为开发者提供更丰富的摄像头控制API。

android 6.0

锁屏下语音搜索

用户可以直接在锁屏状态下进行语音搜索,虽然现在的一些安卓手机支持语音唤醒功能,但这些语音唤醒都是第三方厂商开发的,而此次的Android 6.0在系统层面加入锁屏下语音搜索,这无疑会在体验上有一个明显的提升。

指纹识别

说到指纹识别,很多用户都会觉得现在的中高端安卓手机都支持,但事实上这些安卓手机的指纹识别都是各个厂商自行开发的并没有系统底层的支持。Android 6.0则在系统层面加入指纹识别,能提供原生指纹识别API,这不但降低了厂商开发指纹识别模块的成本,最重要的是原生指纹识别将会大大提升安卓手机的指纹识别支付安全性。

更完整的应用权限管理

在此前的原生安卓系统中有应用通知管理功能,但更为深入的应用权限管理只能靠第三方应用实现。Android 6.0进一步强化应用权限管理,应用权限管理也成为系统级的功能,不过这对于那些权限管理软件来说并不是什么好消息。

Doze电量管理

Android 6.0自带Doze电量管理功能,在“Doze”模式下,手机会在一段时间未检测到移动时,让应用休眠清杀后台进程减少功耗,谷歌表示,当屏幕处于关闭状态,平均续航时间提高30%。

Now on Tap功能

Now on Tap功能是和Google搜索紧密结合的功能,它可以让谷歌从任何应用中进行搜索。例如,在微信中聊天的时候提到餐馆,那么就可以在不跳转的情况下进行谷歌搜索。

App Links

通过App Links功能,Android平台能够向网络服务器提出申请,自主识别链接内容。直接跳转到App客户端中,改善用户体验,有利于让用户在体验更完善的App客户端完成更多操作。

此外,在Android 6.0谷歌还加入了Android Pay进一步强化移动支付,同时也是为了对抗Apple Pay。在发布会上谷歌表示Android 6.0将在下周开始推送,Nexus5/6/7/9以及Nexus Player将能够在第一时间得到升级。

Android7.0

分屏多任务

进入后台多任务管理页面,然后按住其中一个卡片,然后向上拖动至顶部即可开启分屏多任务,支持上下分栏和左右分栏,允许拖动中间的分割线调整两个APP所占的比例。目前,安卓7.0开发者预览版支持全部第三方应用尝试分屏操作,但个别应用适配可能存在问题,分屏后可能导致界面显示不全等问题.

全新下拉快捷开关页

在安卓7.0中,下拉打开通知栏顶部即可显示5个用户常用的快捷开关,支持单击开关以及长按进入对应设置。如果继续下拉通知栏即可显示全部快捷开关,此外在快捷开关页右下角也会显示一个“编辑”按钮,点击之后即可自定义添加/删除快捷开关,或拖动进行排序。

通知消息快捷回复

安卓7.0加入了全新的API,支持第三方应用通知的快捷操作和回复,例如来电会以横幅方式在屏幕顶部出现,提供接听/挂断两个按钮;信息/社交类应用通知,还可以直接打开键盘,在输入栏里进行快捷回复。

通知消息归拢

安卓7.0会将同一应用的多条通知提示消息归拢为一项,点击该项即可展开此前的全部通知,允许用户对每个通知执行单独操作。

夜间模式

安卓7.0中重新加入了夜间深色主题模式,该功能依然需要在系统调谐器中开启,从顶部下划打开快捷设置页,然后长按其中的设置图标,齿轮旋转10秒钟左右即可提示已开启系统调谐器,之后用户在设置中即可找到“系统调谐器”设置项。点开其中的“色彩和外观”,即可找到夜间模式,开启后即可使用全局的深色主题模式,同时亮度和色彩也会进行一定的调整,该功能可以基于时间或地理位置自动开启。另外,系统调谐器中也提供了RGB红绿蓝三色调节滑动条,允许用户手动精细调节,例如减少蓝色或增加红色以提供类似护眼模式的效果。

流量保护模式

安卓7.0新增的流量保护模式不仅可以禁止应用在后台使用流量,还会进一步减少该应用在前台时的流量使用。其具体实现原理目前尚不清楚,推测其有可能使用了类似Chrome浏览器的数据压缩技术。此外,谷歌还扩展了ConnectivityManager API的能力,使得应用可以检测系统是否开启了流量保护模式,或者检测自己是否在白名单中。安卓7.0允许用户单独针对每个应用,选择是否开启数据保护模式。

全新设置样式

安卓7.0启用了全新的设置样式,首先每个分类下各个子项之间的分割线消失了,只保留分类之间的分割线。全新的设置菜单还提供了一个绿色的顶栏,允许用户通过后方的下拉箭头,快速设定勿扰模式等。除了勿扰模式外,顶栏菜单还可以显示诸多其他的设置状态,例如数据流量的使用情况,自动亮度是否开启等。谷歌也在安卓7.0的设置中加入了汉堡菜单,在二级设置界面中的左上角,你就会看到这个汉堡菜单,点击后即可看到所有设置项,方便用户快速跳转。

改进的Doze休眠机制

谷歌在安卓7.0中对Doze休眠机制做了进一步的优化,在此前的安卓6.0中,Doze深度休眠机制对于改善安卓的续航提供了巨大的作用。而在安卓7.0中,谷歌对Doze进行了更多的优化,休眠机制的使用规则和场景有所扩展,例如只要手动在后台删掉应用卡片,关屏后该应用就会被很快深度休眠。

系统级电话黑名单功能

安卓7.0将电话拦截功能变成了一个系统级功能。其它应用可以调用这个拦截名单,但只有个别应用可以写入,包括拨号应用、默认的短信应用等。被拦截号码将不会出现在来电记录中,也不会出现通知。另外用户也可以通过账户体系备份和恢复这个拦截名单,以便快速导入其它设备或账号。

菜单键快速应用切换

双击菜单键,就能自动切换到上一个应用。此外,如果你不停地点击菜单键的话,就会在所有应用中不间断地轮换,应用窗口会自动放大,顶部还会出现倒计时条,停止点击且倒计时结束后,当前应用会自动放大并返回到前台。
参考链接:https://blog.csdn/u012758803/article/details/54844903
8.0—自适应图标

由于不同的厂商对应用图标的形状有了一定的规范(圆角、圆形等),如果不遵循它们的规范,您的应用图标可能就会被强制改成它们要求的形状,有时候改过的图标可能你的期望落差太大 。所以从Android 8.0系统开始,google对应用图标进行统一的规范。在8.0版本之后,应用程序的图标被分为了两层:前景层和背景层。前景层是一个背景透明的logo,背景层一般是一张纯色或带纹理的图片。前景层和背景层组合之后,会被盖上一层mask,这层mask是厂商决定的,这样一来,不管这一层mask是圆角还是圆形,都可以完整地显示您的logo了。

Android 8.0对通知栏进行了比较大的改动,引入了通知渠道的概念,就好像为每种不同的消息分类别一样,例如,某个新闻app,将通知分为两种,一种为新闻类通知,一种为广告类通知。这样,就可以创建两个不同的渠道,发出通知的时候,可以根据渠道来发送,这样做的好处是便于用户去管理每个渠道的通知(选择他们感兴趣的内容)。同时,Android用户具有管理每个渠道的权限(设置声音、震动等),并且可以关闭某个渠道的通知,
android 9.0适配
限制访问通话记录

Android 9 引入 CALL_LOG 权限组并将 READ_CALL_LOG、WRITE_CALL_LOG 和 PROCESS_OUTGOING_CALLS 权限移入该组。 在之前的 Android 版本中,这些权限位于 PHONE 权限组。

如果应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG 权限组明确请求这些权限。 否则会发生 SecurityException

限制访问电话号码

在未首先获得 READ_CALL_LOG 权限的情况下,除了应用的用例需要的其他权限之外,运行于 Android 9 上的应用无法读取电话号码或手机状态。

与来电和去电关联的电话号码可在手机状态广播(比如来电和去电的手机状态广播)中看到,并可通过 PhoneStateListener 类访问。 但是,如果没有 READ_CALL_LOG 权限,则 PHONE_STATE_CHANGED 广播和 PhoneStateListener 提供的电话号码字段为空

要从手机状态中读取电话号码,请根据您的用例更新应用以请求必要的权限:

要通过 PHONE_STATE Intent 操作读取电话号码,同时需要 READ_CALL_LOG 权限和 READ_PHONE_STATE 权限。
要从 onCallStateChanged() 中读取电话号码,只需要 READ_CALL_LOG 权限。 不需要 READ_PHONE_STATE 权限。
限制了明文流量的网络(HTTP)请求,非加密的流量请求都会被系统禁止掉,要求使用HTTPS
解决方案:

1:在 res 下新建一个 xml 目录,然后创建一个名为:network_security_config.xml 文件 ,该文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后在 AndroidManifest.xml application 标签内应用上面的xml配置:

        android:name=".App"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:theme="@style/AppTheme"></application>

2.服务器和本地应用都改用 https (推荐)

3.targetSdkVersion 降级回到 27
参考链接:https://blog.csdn/handbaby_gril/article/details/88227989
6.Android布局优化技巧有哪些
需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。这个流程的表现性能取决于View的复杂程度,View的状态变化以及渲染管道的执行性能。
  所以我们需要尽量减少Overdraw。
  Overdraw(过度绘制):描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,就会导致某些像素区域被绘制了多次,浪费大量的CPU以及GPU资源。(可以通过开发者选项,打开Show GPU Overdraw的选项,观察UI上的Overdraw情况)。

蓝色、淡绿、淡红,深红代表了4种不同程度的Overdraw的情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
1、首先是善用相对布局Relativelayout
在RelativeLayout和LinearLayout同时能够满足需求时,尽量使用RelativeLayout,这一点可以从我们MainActivity默认布局就可以看出,默认是RelativeLayout,因为可以通过扁平的RelativeLayout降低LinearLayout嵌套所产生布局树的层级。
  Android提供了几种方便的布局管理器,大多数时候,你只需要这些布局的一部分基本特性去实现UI。 一般情况下用LinearLayout的时候总会比RelativeLayout多一个View的层级。而每次往应用里面增加一个View,或者增加一个布局管理器的时候,都会增加运行时对系统的消耗,因此这样就会导致界面初始化、布局、绘制的过程变慢。
2、布局优化的另外一种手段就是使用抽象布局标签include、merge、ViewStub
2.1、首先是include标签

include标签常用于将布局中的公共部分提取出来,比如我们要在activity_main.xml中需要上述LinearLayout的数据,那么就可以直接include进去了。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android/apk/res/android"
    xmlns:tools="http://schemas.android/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jared.layoutoptimise.MainActivity">
 
    <include layout="@layout/item_test_linear_layout" />
     
</RelativeLayout>

2.2、merge标签:

merge标签是作为include标签的一种辅助扩展来使用,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。
  Android渲染需要消耗时间,布局越复杂,性能就越差。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher" />
 
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="16dp"
        android:layout_toRightOf="@+id/iv_image"
        android:text="这个是MergeLayout"
        android:textSize="16sp" />
 
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_title"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@+id/iv_image"
        android:text="这个是MergeLayout,这个是MergeLayout"
        android:textSize="12sp" />
 
</merge>

2.3、viewstub标签:

viewstub是view的子类。他是一个轻量级View, 隐藏的,没有尺寸的View。他可以用来在程序运行时简单的填充布局文件。接着简单试用下viewstub吧。首先修改activity_main.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android/apk/res/android"
    xmlns:tools="http://schemas.android/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jared.layoutoptimise.MainActivity">
 
    <include
        android:id="@+id/layout_merge"
        layout="@layout/item_merge_layout" />
 
    <Button
        android:id="@+id/btn_view_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="显示ViewStub"
        android:textAllCaps="false"
        android:layout_below="@+id/tv_content"/>
 
    <Button
        android:id="@+id/btn_view_hide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="50dp"
        android:layout_toRightOf="@+id/btn_view_show"
        android:text="隐藏ViewStub"
        android:textAllCaps="false"
        android:layout_below="@+id/tv_content"/>
 
    <ViewStub
        android:id="@+id/vs_test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/item_test_linear_layout"
        android:layout_below="@+id/btn_view_show"
        android:layout_marginTop="10dp" />
 
</RelativeLayout>

这里的ViewStub控件的layout指定为item_test_linear_layout。当点击button隐藏的时候不会显示item_test_linear_layout,而点击button显示的时候就会用item_test_linear_layout替代ViewStub。
3、Android最新的布局方式ConstaintLayout  
 ConstraintLayout允许你在不适用任何嵌套的情况下创建大型而又复杂的布局。它与RelativeLayout非常相似,所有的view都依赖于兄弟控件和父控件的相对关系。但是,ConstraintLayout比RelativeLayout更加灵活,目前在AndroidStudio中使用也十分方便,就和以前的拖拉控件十分相似。那么怎么使用呢?
  首先是安装Constaintlayout了。Android SDK -> SDK Tools -> Support Repository中的ConstrainLayout for Android和Solver for ConstaintLayout。
  
  然后build.gradle中添加:

compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'

然后同步下就可以正常使用ConstaintLayout了。
4、利用Android Lint工具寻求可能优化布局的层次

一些Lint规则如下:
  1、使用组合控件: 包含了一个ImageView以及一个TextView控件的LinearLayout如果能够作为一个组合控件将会被更有效的处理。
  2、合并作为根节点的帧布局(Framelayout) :如果一个帧布局时布局文件中的根节点,而且它没有背景图片或者padding等,更有效的方式是使用merge标签替换该Framelayout标签 。
  3、无用的叶子节点:通常来说如果一个布局控件没有子视图或者背景图片,那么该布局控件时可以被移除(由于它处于 invisible状态)。
  4、无用的父节点 :如果一个父视图即有子视图,但没有兄弟视图节点,该视图不是ScrollView控件或者根节点,并且它没有背景图片,也是可以被移除的,移除之后,该父视图的所有子视图都直接迁移至之前父视图的布局层次。同样能够使解析布局以及布局层次更有效。
  5、过深的布局层次:内嵌过多的布局总是低效率地。考虑使用一些扁平的布局控件,例如 RelativeLayout、GridLayout ,来改善布局过程。默认最大的布局深度为10 。
  参考链接:https://wwwblogs/hoolay/p/6248514.html
7.造成ANR的原因有哪些?如何优化?
ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现anr,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。

默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

第一:什么会引发ANR?

在Android里,应用程序的响应性是由Activity Manager和WindowManager系统服务监视的 。当它监测到以下情况中的一个时,Android就会针对特定的应用程序显示ANR:

1.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)
2.BroadcastReceiver在10秒内没有执行完毕

造成以上两点的原因有很多,比如在主线程中做了非常耗时的操作,比如说是下载,io异常等。

潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是Thread.sleep()。替代的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。

第二:如何避免ANR?

1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)

2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)

3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

总结:anr异常也是在程序中自己经常遇到的问题,主要的解决办法自己最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现,比如采用Handler+mesage的方式,或者是有时候需要做一些和网络相互交互的耗时操作就采用asyntask异步任务的方式(它的底层其实Handler+mesage有所区别的是它是线程池)等,在主线程中更新UI。
参考链接:https://blog.csdn/licong0716/article/details/51062871
8.APP代码编写过程中,有哪些显而易见会导致内存泄漏的错误编程方式?
 Android中的内存泄漏:

先说一下为什么会出现内存泄漏:

Android程序开发中,如果一个对象已经不需要被使用了,本该被回收时,而这时另一个对象还在持有对该对象的引用,这样就会导致无法被GC回收,就会出现内存泄漏的情况。

内存泄漏时Android程序中出现OOM问题的主要原因之一。所以我们在编写代码时,一定要细心处理好这一类的问题。

下面说一下Android开发中最常见的5个内存泄漏问题:

一:单例设计模式造成的内存泄漏:

单例设计模式我就不多说了,这个是最基本的设计模式,相信大家都会使用,但是时候我们在使用单例设计模式时没有注意到其中的细节,就会造成内存泄漏。

单例设计模式的静态特性会使他的生命周期和应用程序的生命周期一样长,这就说明了如果一个对象不在使用了,而这时单例对象还在持有该对象的引用,这时GC就会无法回收该对象,造成了内存泄露的情况。
下面是错误的单例设计模式的代码:

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

上面的代码是一个最普通的单例模式,但是需要注意两个问题:
1、如果我们传入的Context是Application的Context的话,就没有任何问题,因为Application的Context生命周期和应用程序生命周期一样长。

2、如果我们传入的Context是Activity的Context的话,这时如果我们因为需求销毁了该Activity的话,Context也会随着Activity被销毁,但是单例还在持有对该类对象的引用,这时就会造成内存泄漏。

所以,正确的单例模式写法应该是这样的:

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

这样的话不管我们传入什么样的Context,最终使用的都是Application的Context,单例的生命周期和应用一样长,这样就不会造成内存泄漏了。

二、非静态内部类创建的静态实例造成的内存泄漏

有时候因为需求我们会去频繁的启动一个Activity,这时为了避免频繁的创建相同的数据源,我们通常会做如下处理:

public class MainActivity extends AppCompatActivity {
 
    private static TestResource mResource = null;
 
    @Override
 
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_main);
 
        if(mManager == null){
 
            mManager = new TestResource();
 
        }
 
        //...
 
    }
 
    class TestResource {
 
        //...
 
    }
 
}

这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:

1、将内部类改为静态内部类。

2、将这个内部类封装成一个单例,Context使用Application的Context

三、Handler造成的内存泄漏:

先看一下不规范的Handler写法:

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

这里的handler也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。正确的做法是:

public class MainActivity extends AppCompatActivity {
//new一个自定义的Handler
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
 
//自定义静态内部类继承自Handler
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
//在构造函数中使用弱引用来引用context对象
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }
  
    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
 
@Override
  protected void onDestroy() {
      super.onDestroy();
//移除队列中所有的Runable和消息
//这里也可以使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable
      mHandler.removeCallbacksAndMessages(null);
  }
}

创建一个静态内部类继承自handler,然后再在构造参数中对handler持有的对象做弱引用,这样在回收时就会回收了handler持有的对象,这里还做了一处修改,就是当我
们的回收了handler持有的对向,即销毁了该Activity时,这时如果handler中的还有未处理的消息,我们就需要在OnDestry方法中移除消息队列中的消息。

四、线程造成的内存泄漏
线程使用不恰当造成的内存泄漏也是很常见的,下面举两个例子:

//——————test1
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();
//——————test2
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();

上面是两个内部类,当我们的Activity销毁时,这两个任务没有执行完毕,就会使Activity的内存资源无法被回收,造成了内存泄漏。
正确的做法是使用静态内部类:如下

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;
  
        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }
  
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
  
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }
//——————
    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

这样就避免了内存泄漏,当然在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。

五、资源未关闭造成的内存泄漏
在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存,
否则这些资源不会被GC回收,就会造成内存泄漏。
参考文章:https://blog.csdn/qq_35373333/article/details/74909811
9.内存优化策略
一、为什么要内存优化?

因为Android平台和Java语言本身的某些特性的缘故,在开发过程中,如果不注意这些特性。可能会导致内存消耗,比其它平台(IOS)和开发语言(C/C++)多得多。所以,我们需要最大化的去避免,额外的内存开销和泄露。所以,通过了解这些特性,并合理的组织你的代码,来减少APP的内存开销和避免内存泄露,写出高质量的代码,提高APP的运行性能。

二、如何进行内存优化(方法有哪些)?

1.关闭不必要的Service。
Service会消耗较多的内存和CPU开销,如果不是很必要,并不建议创建服务。如果只是要在后台跑一个任务,考虑用线程处理或JobScheduler。如果必需要创建一个Service,建议用IntentService来创建。这样,当启动Service的Intent被销毁时,Service也会被销毁。

2.使用Android定制的容器。
当某一容器,只需要存储少量的数据时,建议用Android定制的容器,代替Java提供的容器。比如ArrayMap代替HashMap。因为,通常情况下,APP的Map或Array并不会存储,大量的数据。多数情况下,大部分情况下,都是量少于1000的。所以,在存储量不超过1000的地方,建议用安卓定制的容器去替换。 其它的容器,还有SparseArray, SparseBooleanArray等等。(超过1000的,有必要思考一下,是不是真的需要这么多…)

3.避免使用抽象类,除非确实有显著的作用。
因为,在代码架构时,许多有些经验和学过Java设计模式和EffectiveJava的人都知道。好的抽象类设计,可以让你的代码更灵活,维护起来也容易。但是,抽象类,会产生额外的代码。执行这些额外的代码,需要消耗额外的大量内存和CPU资源。所以,在使用抽象类之前,得衡量一下,是否值得花费这些开销。

4.避免内存抖动。
所谓的内存抖动,即是在短时间内,大量的创建、销毁对象。因为这样会导致系统,不断的进行GC操作。少量的GC操作,并不影响性能。但大量的GC操作,就另当别论。比较常见的例子就是,不要在onDraw()方法里面创建对象,因为onDraw()方法会被频繁的调用(这个,AS也自带提示了…)

5.使用编译时注解,代替运行时注解。比如,Dagger2。
我个人是不倾向于使用注解的。当然,注解可以在很大程度上,简化开发的复杂度,提高开发的速度。但多数情况下,除非真的特别新(没有可复用的资源)、急、赶的项目,否则,我是不建议使用的,注解,或多或少的增加运行时的开销或额外的apk的体积的消耗。但如果两者必要一个,Google的建议是,牺牲空间换时间。这里的空间是指apk体积占用,而不是内存占用。这一条,仁者见仁了。但从内存优化角度来说,编译时注解[占体积]是优于运行时[占内存和运行时速度]注解的。

6.谨慎使用第三方库。
不少第三方库,处于Demo形态。或者,只顾实现,不管优化。导致内存泄露,额外的内存开销。有的第三方库,里面又嵌套其它第三方库。导致Apk体积的增加和运行时的额外开销(初始化等操作)。在挑选第三方库时,应该谨慎,尽量挑那些star值多的。

7.使用约束布局,减少层次嵌套。
对于复杂的布局,建议使用约束布局代替。因为约束布局,使用的是相对约束,控件与控件,Layout与Layout之间仅存在约束,不存在层次关系。学过findViewById原理的同学可能知道,Android在FindView时,会层层获取,层次越多,查找的就越慢,在绘制UI时,也会更消耗更多的资源和时间。所以,尽量不要嵌套布局。

8.使用ViewStub,延时加载布局。
对于一些一开始不必要展示的布局,使用ViewStub来加载。或者动态添加也行。而不要写死在xml里面,然后设置成GONE。哪怕你设置成GONE,你的布局同样也会占用内存,消耗资源, 可以考虑用动态添加的方式实现。

9.使用动态创建View,取代xml绘制View。
一些简点的界面,可以考虑用动态创建的方式来实现,可以减少Android系统将xml转换成View的这一层开销。这取决于界面的复杂度、实现的复杂度和个人的能力。

10.慎用回调,尤其是将Activity做为参数。
为什么说要慎用回调?对Java新手来说,回调是个摸不着头脑的事情。对Java有点熟的同志来说,回调是个处理异步事件的好帮手。有些新手或半熟手,在对一些异步处理需求的实现上面,为了方便或是没有其它更好的办法处理的时候,就倾向于将Activity做为参数传递过去,等异步处理完后,再直接调用Activity对象的方法,来实现这样的需求。殊不知,这样做,容易导致,Activity的对象在另一个地方被持有,导致页面一直无法被释放,进而导致内存泄露。So,除非万不得已,请别将Activity对象做为参数传递。异处回调处理,可以考虑用EventBus事件通知。万不得已而用之时,则需要关注一下生命周期和对象的释放。
参考文章:https://blog.csdn/l_o_s/article/details/80888619
以上是一次面试的题目总结,不会的太多了,这边做一下笔记,方便以后的查看。

更多推荐

记一次面试题

本文发布于:2023-06-14 05:52:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1444574.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:面试题

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!