Android面试题
Posted lxn_李小牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android面试题相关的知识,希望对你有一定的参考价值。
1.ListView的优化策略和原理
2.Activity和Fragment的生命周期
3.View和ViewGroup的关系
android的UI界面都是View和ViewGroup及其子类组合而成的。View是所有UI组件的父类,其子类称为组件(Widget);ViewGroup是布局管理器,本身也是继承自View类,其子类称为布局(Layout)
4.常用布局类型,使用时如何取舍
5.机型如何适配
6.Activity之间如何传递复杂的对象
Intent intent = new Intent(this,SecondActivity.class);
//创建Bundle对象
Bundle bundle = new Bundle();
//传递对象,对象需要实现Serializable接口
bundle.putSerializable("1", new MovieInfo());
//传递字符串,CharSequence是String的父接口
bundle.putCharSequence("2","aaa");
//传递CharSequence数组
String[] str = "a","b";
bundle.putCharSequenceArray("3",str);
//传递CharSequence集合
ArrayList arrayList = new ArrayList();
bundle.putCharSequenceArrayList("4",arrayList);
//bundle保存到intent中
intent.putExtras(bundle);
startActivity(intent);
7.Android有哪些Spannable
Spannable是一个接口,有两个实现类,SpannableString和SpannableStringBuilder,
SpannableStringBuilder通常配合下面的Span
- BackgroundColorSpan
- ForegroundColorSpan
- AbsoluteSizeSpan
- ImageSpan
- StyleSpan
8.Android有哪些Drawable
常用的Drawable有BitmapDrawable,ColorDrawable,ShapeDrawable,LayerDrawable,StateListDrawable
9.FragmentTranscation的commit和commitAllowingStateLoss的区别
10.如何处理多个旧版本的数据库升级
11.反射机制使用的场景
加载数据库驱动,动态调用函数,获取泛型
12.DeadObjectException
我们先看看它的定义
/**
* The object you are calling has died, because its hosting process
* no longer exists.
*/
public class DeadObjectException extends RemoteException
public DeadObjectException()
super();
public DeadObjectException(String message)
super(message);
意思是说你调用的对象死了,因为它所在的进程不存在了,转载一篇博客,给出链接
13.Java的值传递和引用传递
基本类型:按值传递,方法的实参是原值的一个副本,
引用类型:传递的是地址值,在方法内对这个对象的修改是会反映到原来的对象,但是String例外,看下面的例子
public static void main(String[] args)
String s = new String("A");
change(s);
System.out.println(s);
public static void change(String s)
s = "B";
因为String是final修饰的,它的值不能被修改,所以当我们修改引用所指向的内容时,会重新开辟一个控件,所以最后打印的结果 是B
14.Looper.prepare主要做了哪些工作
(1)创建Looper对象并放入到ThreadLocal中
(2)创建消息队列
Looper.loop主要做了哪些工作
(1)从ThreadLocal中取出Looper
(2)获取Looper对应的消息队列
(3)在for循环中调用MessageQueue的next方法取出一个消息
一个线程只能由一个Looper
Handler的构造函数中做了什么工作
(1)调用Looper.myLooper获取looper对象
(2)获取Looper对应的MessageQueue
Handler的sendMessage方法有个返回值,true代表消息成功放到了消息对垒,否则是false,通常是因为Looper退出了,
注意,返回值true不代表消息一定会被处理,如果在时间处理消息的时间到达之前Looper退出了,那消息就会被丢弃。
sendMessage方法最终会调用到下面的方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
MessageQueue queue = mQueue;
if (queue == null)
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
//将消息插入到消息队列中等待处理
return enqueueMessage(queue, msg, uptimeMillis);
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
msg.target = this;//msg的target属性是一个Handler,此时Handler就和消息对应起来了
if (mAsynchronous)
msg.setAsynchronous(true);
return queue.enqueueMessage(msg, uptimeMillis);
boolean enqueueMessage(Message msg, long when) //把消息插入到消息队列中
if (msg.target == null)
throw new IllegalArgumentException("Message must have a target.");
if (msg.isInUse())
throw new IllegalStateException(msg + " This message is already in use.");
synchronized (this)
if (mQuitting) //消息队列退出了
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
msg.recycle();
return false;
msg.markInUse();
msg.when = when;
Message p = mMessages;//mMessages代表消息队列的第一个元素,第一次mMessages是空的
boolean needWake;
if (p == null || when == 0 || when < p.when)
// 消息队列空闲,此时还没有消息
msg.next = p;
mMessages = msg;//把我们要处理的消息赋值给mMessages
needWake = mBlocked;
else
//把消息插入到消息队列中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;)
prev = p;
p = p.next;//next代表下一个消息
if (p == null || when < p.when) //p==null代表消息队列中已经没有消息了,break跳出当前循环
break;
if (needWake && p.isAsynchronous())
needWake = false;
msg.next = p; // invariant: p == prev.next
prev.next = msg;
// We can assume mPtr != 0 because mQuitting is false.
if (needWake)
nativeWake(mPtr);
return true;
Message next() //从消息队列读取消息
....
for (;;)
if (nextPollTimeoutMillis != 0)
Binder.flushPendingCommands();
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this)
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;//mMessages是当前要处理的消息
if (msg != null && msg.target == null)
// Stalled by a barrier. Find the next asynchronous message in the queue.
do
prevMsg = msg;
msg = msg.next;
while (msg != null && !msg.isAsynchronous());
if (msg != null)
if (now < msg.when)
// 消息还没准备好,设置一个时间等它准备好
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
else
// 获取到一个消息
mBlocked = false;
if (prevMsg != null)
prevMsg.next = msg.next;
else
mMessages = msg.next;
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
return msg;
else
// No more messages.
nextPollTimeoutMillis = -1;
// Process the quit message now that all pending messages have been handled.
if (mQuitting)
dispose();
return null;
获取到消息以后,然后我们看消息是如何被处理的
public static void loop()
final Looper me = myLooper();
if (me == null)
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
final MessageQueue queue = me.mQueue;//获取消息队列
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;)
Message msg = queue.next(); // 从消息队列中获取消息
if (msg == null)
// 没有消息说明消息队列退出了
return;
...//调用Handler的dispatchMessage方法处理消息
msg.target.dispatchMessage(msg);
public void dispatchMessage(Message msg)
if (msg.callback != null)
handleCallback(msg);
else
if (mCallback != null)
if (mCallback.handleMessage(msg))
return;
handleMessage(msg);//空方法,需要我们实现去处理消息
MessageQueue的存储结构是单链表
最后我们看看Looper的quit方法
public void quit() //终止loop方法的执行,不在处理消息队列中剩余的消息
mQueue.quit(false);
public void quitSafely() //处理完消息队列的消息之后终止loop方法的执行
mQueue.quit(true);
15.HandlerThread
handlerThread = new HandlerThread("ht");//创建HandlerThread对象
handlerThread.start();//启动HandlerThread,在run方法中创建了Looper对象
mHandler = new Handler(handlerThread.getLooper())
@Override
public void handleMessage(Message msg)
super.handleMessage(msg);
//这里执行在子线程,线程名称使我们在创建HandlerThread是传入的参数
;
public void send(View view)
mHandler.sendEmptyMessage(0);
HandlerThread自带Looper,使它可以通过消息队列,来重复使用当前线程,节省系统资源开销,这是它的优点也是缺点,
每一个任务将以队列的方式逐个被执行到,一旦任务中有某个任务执行时间太长,那么就会导致后续的任务都会被延迟处理。
16.Service生命周期
通过startService: onCreate--- onStartCommand(onStart过时)---onDestory
通过bindService: onCreate--onBind--onUnBind--onDestory
onStartCommand我们可以指定一个返回值,START_STICKY,意思如下
如果Service在启动之后(onStartCommand返回值执行了),被系统杀死了,然后会保留在开启状态,但是不保留intent,
然后系统会重建Service,但是注意此时的intent是null.
17.通过bindService启动intentService会有什么结果
intentService源码中bindService返回的是null,而且通过上面的生命周期我们可以看出,通过bindService启动的Service不会走onStartCommand方法,而onStartCommand放啊中利用Handler发送了消息到handleMessage方法中执行,所以bindService启动intentService不会触发onHandleIntent方法。
18.为什么多次启动IntentService会顺序执行事件,停止服务后,后续的时间得不到执行。
因为IntentService是使用Handler,Looper,MessageQueue机制把消息发送到线程中去执行的,所以多次启动IntentService不会创建新的服务和新的线程,只是把消息放入到消息队列中等待执行,如果服务停止
@Override
public void onDestroy()
mServiceLooper.quit();//调用了Looper的quit方法,通过之前的分析,我们知道,此时消息队列中的任务是得不到执行的
19.Context的结构
Context可以理解为上下文,环境,通过Context我们可以访问资源,和其他组件进行交互,接下来我们看看Context是何时创建的
Application:
在ApplicationThread的scheduleLaunchActivity方法中,有下面的代码
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo)
.....
sendMessage(H.LAUNCH_ACTIVITY, r);
然后到了ActivityThread中的handleMessage方法
public void handleMessage(Message msg)
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what)
case LAUNCH_ACTIVITY:
...
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent)
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
if (r.profilerInfo != null)
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
//创建一个Activity
Activity a = performLaunchActivity(r, customIntent);
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent)
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null)
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
ComponentName component = r.intent.getComponent();
if (component == null)
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
if (r.activityInfo.targetActivity != null)
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
Activity activity = null;
try
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(//创建Activity
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null)
r.state.setClassLoader(cl);
...
try //创建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) //创建Activity的Context
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
if (customIntent != null)
activity.mIntent = customIntent;
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0)
activity.setTheme(theme);
activity.mCalled = false;
if (r.isPersistable())
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
else
mInstrumentation.callActivityOnCreate(activity, r.state);
if (!activity.mCalled)
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished)
activity.performStart();
r.stopped = false;
if (!r.activity.mFinished)
if (r.isPersistable())
if (r.state != null || r.persistentState != null)
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
else if (r.state != null)
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
if (!r.activity.mFinished)
activity.mCalled = false;
if (r.isPersistable())
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
else
mInstrumentation.callActivityOnPostCreate(activity, r.state);
if (!activity.mCalled)
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
r.paused = true;
mActivities.put(r.token, r);
...
return activity;
我们看看makeApplication方法,这里的packageInfo是一个loadedApk对象,
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation)
if (mApplication != null)
return mApplication;
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null))
appClass = "android.app.Application";
try
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android"))
initializeJavaContextClassLoader();
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(//创建Application
cl, appClass, appContext);
appContext.setOuterContext(app);
catch (Exception e)
if (!mActivityThread.mInstrumentation.onException(app, e))
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
mActivityThread.mAllApplications.add(app);
mApplication = app;
if (instrumentation != null)
try
instrumentation.callApplicationOnCreate(app);
catch (Exception e)
if (!instrumentation.onException(app, e))
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
// Rewrite the R 'constants' for all library apks.
SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
.getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++)
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f)
continue;
rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
return app;
20.ListView优化
(1)复用ConvertView,避免每次填充布局,保证了只填充屏幕内看到的条目
(2)使用ViewHolder,保存控件的引用,避免每次查找控件
(3)在getView中不要做复杂逻辑
(4)滑动的时候不加载图片
(5)异步加载图片
(6)ItemView的布局层次减少
(7)数据分批和分页加载
(8)局部刷新
(9)使用内存和本地缓存
(10)设置下面的属性
//是否允许条目的绘制缓存
listView.setScrollingCacheEnabled(false);
//5.0以后废弃了,通过setLayerType可以控制子View的缓存行为
listView.setAnimationCacheEnabled(false);
21.Android语言切换
22.Sqlite数据库面试题
产生的journal文件的作用:用于事务的回滚,如果没有操作异常或者不需要回滚,那么这个文件大小为0.如果程序crash了,该文件会被保留,下次打开数据库文件时,系统会检查有没有journal文件存在,有则用它来恢复数据。
如何插入大量的数据,使用事务+SqliteStatement,代码如下
MyDatabase myDatabase = new MyDatabase(this);
SQLiteDatabase db = myDatabase.getReadableDatabase();
try
db.beginTransaction();//开启事务
//预编译Sql语句
SQLiteStatement sqLiteStatement = db.compileStatement("");
for (int i = 0; i < 10000; i++)
//绑定数据
sqLiteStatement.bindLong(1,22);
sqLiteStatement.bindString(2,"test");
sqLiteStatement.executeInsert();//执行插入
db.setTransactionSuccessful();
catch(Exception e)
e.printStackTrace();
finally
db.endTransaction();
db.close();
其他优化
1.为表创建索引,语句如下
create index index_name on table_name
注意:索引不应该使用在比较小的表上,索引会增加查询的性能,降低插入和更新的性能,所以如果查询多,可以用索引。
2.语句拼接使用Stringbuilder代替String
3.查询时返回更少的结果
4.少用cursor.getColumnIndex,可以在建表的时候用Static变量记住列的index
Sqlite多线程访问
23.线程池
public static void main(String[] args)
//线程同时执行
ExecutorService service = Executors.newCachedThreadPool();
//线程依次执行
ExecutorService service2 = Executors.newSingleThreadExecutor();
//每次最多同时执行固定数量的线程
ExecutorService service3 = Executors.newFixedThreadPool(2);
//
ScheduledExecutorService service4 = Executors.newScheduledThreadPool(2);
//执行下面的会发生RejectedExecutionException
// service.shutdown();
// service.submit(new Task());
//以固定间隔执行任务
// service4.scheduleAtFixedRate(new Task(),0,2, TimeUnit.SECONDS);
// //固定延迟执行任务
// service4.scheduleWithFixedDelay(new Task(),0,2,TimeUnit.SECONDS);
// service3.execute(new Task());
// service.submit(new Task());
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,3,30,
TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(1));
for (int i = 0; i < 4; i++)
executor.execute(new Task());
static class Task implements Runnable
@Override
public void run()
try
Thread.sleep(2000);
System.out.println("===========run");
catch (InterruptedException e)
e.printStackTrace();
接下来看看每种线程池内部的实现
newCachedThreadPool
public static ExecutorService newCachedThreadPool()
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
核心线程数为0,最大线程数为整数最大值,任务队列为SynchronousQueue,这个队列在接收到任务的时候,会直接提交给线程池处理。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor()
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
这个线程池很有意思,没有直接返回ThreadPoolExecutor,而是包装了一个FinalizableDelegatedExecutorService,它的源码如下
private static class FinalizableDelegatedExecutorService
extends DelegatedExecutorService
FinalizableDelegatedExecutorService(ExecutorService executor)
super(executor);
protected void finalize()
super.shutdown();
我们知道,在当前对象要被垃圾回收器回收之前,会调用它的finalize方法
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads)
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
核心线程数等于最大线程数
ScheduledThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize)
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
核心线程树由我们指定,最大线程数是整数最大值,
24.Java中程序何时终止
当程序中的所有非守护线程终止的时候,程序就终止了。
public static void main(String[] args)
Thread t1 = new Thread()
@Override
public void run()
try
Thread.sleep(2000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("run");
;
t1.start();
System.out.println("main");
运行上面的程序,会立即打印出main,然后隔2秒打印出run,程序结束。加入我们把t1设置为守护线程呢。
public static void main(String[] args)
Thread t1 = new Thread()
@Override
public void run()
try
Thread.sleep(2000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("run");
;
//设置为守护线程,当程序中只剩下守护线程时,JVM就退出了
t1.setDaemon(true);
t1.start();
System.out.println("main");
再次运行上面的程序,结果是打印出main之后程序就退出了,因为main线程执行完以后,就剩下一个守护线程了,JVM就退出了。
以上是关于Android面试题的主要内容,如果未能解决你的问题,请参考以下文章
备战2022一二线互联网公司Android面试题汇总,48份(2010-2021)大厂面试题整理分享
备战2022一二线互联网公司Android面试题汇总,48份(2010-2021)大厂面试题整理分享