Android面试笔记

Android面试

JAVA基础杂记

  • String相关
String longString = "aa......a"; 

存储在栈内存中,编译时知道大小,编译成字节码时字节码中数组最多存储65535个字节(如果为拉丁字符,小于但不包含,如果为非拉丁字符,包含),最终存在虚拟机方法区的常量池中。

byte[] bytes = loadFromFile(new File("text.txt"));
String superLongString = new String(bytes);

存储在堆内存中,编译时未知大小

  • 匿名内部类
    匿名内部类有名字,是内部类+$N
    匿名内部类构造方法由编译器定义,构造方法有以下几种情况:
  1. 父类为非静态内部类:
public class Client {
	public void run() {
	InnerClass innerClass = new OuterClass().new InnerClass() {...};
	}
}

public class OuterClass {
	public abstract class InnerClass {
		abstract void test();
	}
}

编译器构造函数包括父类外部类实例引用以及所在方法外部类实例的引用

public class Client$1 {
	public Client$1(Client client, OuterClass outerClass){...}
}
  1. 父类为静态类或接口
public class Client {
	public void run() {
	InnerClass innerClass = new OuterClass.InnerClass() {...};
	}
}

public class OuterClass {
	public interface class InnerClass {
		abstract void test();
	}
}

public class Client$1 {
	public Client$1(Client client){...}
}
  1. 匿名内部类存在于静态方法中
public class Client {
	public static void run() {
	InnerClass innerClass = new OuterClass.InnerClass() {...};
	}
}

public class Client$1 {
	public Client$1(){...}
}
  1. 捕获外部作用域的局部变量
public class Client {
	public static void run() {
	final Object object = new Object();
	InnerClass innerClass = new OuterClass.InnerClass() {
		@Override
		void test() {
			System.out.println(object.toString());
		}
	};
	}
}

public class Client$1 {
	public Client$1(Object object){...}
}

局部变量需为final,匿名内部类捕获的是一个该局部变量的快照。
总结:
匿名内部类只能继承一个父类或实现一个接口,构造方法为编译器生成,参数列表包括外部对象(定义在非静态域内)、父类的外部对象(父类非静态)、父类的构造方法参数(父类有构造方法且参数列表不为空)、外部捕获的变量(方法体内有引用外部final变量)

  • Java方法分派

静态分派-方法重载分派:编译器确定,依据调用者的声明类型和方法参数类型
动态分派-方法覆写分派:运行是确定,依据调用者的实际类型分派

  • 泛型擦出带来的问题
    基本类型无法作为泛型实参,带来装箱开箱的开销

泛型类型无法用过方法重载

public void printList(List<Integer> list){}
public void printList(List<String> list){}//error

泛型类型无法当作真实类型使用

T newInstance = new T();//错误,未知对象的类型
Class c = T.class;//错误 无法获取类型
List<T> list = new ArrayList<T>();//可以用于创建其他泛型类型
if(list instanceof List<String>){}//错误,非法的类型判断,将String去掉则合法

静态方法无法引用类泛型参数

class GenericClass<T> {
	public static T max(T a, T b){}//错误,静态方法无法引用类泛型参数
	public static <R> R max(R a, R b){}//正确,静态方法可以声明泛型参数
}

类型强转时有类型开销

Android基础杂记

Handler相关

面试题:Android中非UI线程为什么不能更新UI?
  • UI线程是什么?

在这里插入图片描述

AntivityThread#main

public static void main(String[] args) {
	Environment.initForCurrentUser();

	Looper.prepareMainLooper();

	ActivityThread thread = new ActivityThread();
	thread.attach(false, startSeq);

	if(sMainThreadHandler == null) {
		sMainThreadHandler = thread.getHandler();
	}

	Looper.loop();

	throw new RuntimeException("Main thread loop unexpectedly exited");
}

由此可看出,如果looper退出会抛出异常。
UI线程就是主线程,其他线程就是非UI线程。

  • 主线程如何工作
    在这里插入图片描述
  • UI为什么不能设计成线程安全的

UI具有可变性,甚至是高频可变性
UI对相应时间的敏感性要求UI操作必须高效
UI组件必须批量绘制来保证效率

  • Handler发送消息的Delay靠谱吗?

MessageQueue如何处理消息

boolean enqueueMessage(Message msg, long when){
	...
	Message p = mMessages;
	boolean needWake;
	if(p == null || when == 0 || when < p.when) {
		...//put msg at the head of the queue
	} else {
		...//loop messages to insert
	}
	if(needWake) nativeWake(mPtr);
	...
}

Message next() {
	...
	for(;;) {
		...
		nativePollOnce(ptr, nextPollTimeoutMillis);

		synchronized(this) {
			Message msg = mMessages;
			...
			if(msg != null) {
				nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
			} else {
				...
				return msg;
			}
		}
	}
}

消息队列优化:重复消息过滤、互斥消息取消、复用消息
总结:
大于Handler Looper的周期时基本可靠
Looper负载越高,任务越容易积压,进而导致卡顿

  • Looper在ThreadLocal中的存储

回到Looper.prepareMainLooper()

public static void prepareMainLooper() {
	prepare(false);
	synchronized (Looper.class) {
		if (sMainLooper != null) {
			throw new IllegalStateException("The main Looper has already been prepared.");
		}
		sMainLooper = nyLooper();
	}
}

private static void prepare(boolean quitAllowed) {
	if (sThreadLocal.get() != null) {
		throw new RuntimeException("Only one Looper may be created per thread");
	}
	sThreadLocal.set(new Looper(quitAllowed));
}

由此可知,prepare()在同一个线程中只能调用一次,否则会抛异常。
下面来看ThreadLocal中是如何存储和获取Looper的。

public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		map.set(this, value);
	}else {
		createMap(t, value);
	}
}

public void get(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		ThreadLocalMap.Entry e = map.getEntry(this);
		if(e != null) {
			@SuppressWarnings("unchecked");
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

ThreadLocalsetget方法首先拿到类当前线程,并获取到一个ThreadLocalMap对象。当前线程作为keyLooper作为参数被存储在了ThreadLocalMap里。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值