用于面试参考,不做深究。答案整理自互联网,也会加上我自己的理解。
说说Activity的启动流程
Activity启动有几种方式?一种是写一个startActivity,第二种是点击手机App,通过手机系统里的Launcher机制,启动App里默认的Activity。
IActivityManager IApplicationThread 是AIDL接口文件.
在API26中: ActivityManagerNative类被弃用,代理类ActivityManagerProxy已经被删除了。这里直接使用aidl的方式来通信了,不在使用远端服务的中间类了。而是直接使用IActivityManager.Stub.asInterface(b); 来获取AMS。
同样的ApplicationThreadNative和代理类ApplicationThreadProxy,改用IApplicationThread.Stub代替。
base/core/java/android/app/Instrumentation.java
//api 25
int result = ActivityManagerNative.getDefault() int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
//api 26
int result = ActivityManager.getService()//注意这一行
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
在App进程中:
sytem_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求。
App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息。
主线程在收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。
代码调用大致如下:
//ams中
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
...
只有当系统启动完,或者app允许启动过程允许,则会true
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
thread.bindApplication(...);
if (normalMode) {
if (mStackSupervisor.attachApplicationLocked(app)) {
didSomething = true;
}
}
...
}
//ASS.attachApplicationLocked
boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
...
try {
//真正的启动Activity
if (realStartActivityLocked(hr, app, true, true)) {
didSomething = true;
}
} catch (RemoteException e) {
throw e;
}
if (!didSomething) {
//启动Activity不成功,则确保有可见的Activity
ensureActivitiesVisibleLocked(null, 0);
}
return didSomething;
}
//ASS.realStartActivityLocked
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
......
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global and
// override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, !andResume,
mService.isNextTransitionForward(), profilerInfo);
}
// ApplicationThread.scheduleLaunchActivity
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
...
}
接着看下代码创建Activity与开始回调生命周期
ActivityThread.handleLaunchActivity
// 启动Activity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
// Make sure we are running with the most recent config.
// 最终回调目标Activity的onConfigurationChanged()
handleConfigurationChanged(null, null);
...
// 首先调用performLaunchActivity方法将目标Activity组件启动起来,最终调用目标Activity的onCreate方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
// 最终回调目标Activity的onStart,onResume.
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
...
performPauseActivityIfNeeded(r, reason);
...
} else {
...
}
}
ActivityThread.performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
// 将目标Activity的类文件加载到内存中,并创建一个实例。由于所有Activity组件都是从Activity类
// 继承下来的,因此,我们就可以将前面创建的Activity组件保存在Activity对象activity中
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
try {
// 创建Application对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
...
// 使用ContextImpl对象appContext和ActivityClientRecord对象r来初始化Activity对象activity
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
// 调用成员变量mInstrumentation的callActivityOnCreate方法将Activity对象activity启动起来,
// 会调用Activity的onCreate方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
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);
}
}
...
}
...
return activity;
}
理解 Instrumentation 类:
1.Instrumentation是用来监控程序与系统之间的交互操作的
2.mMainThread的类型为ActivityThread,是用来描述应用程序进程的,系统每当启动一个应用程序进程时,都会在它里面加载一个ActivityThread类实体,并且这个类实体会保存在每一个在该进程中启动的Activity组件的父类Activity的成员变量mMainThread中。
3.ActivityThread的成员函数getApplicationThread用来获取它内部一个类型为ApplicationThread对象作为参数传递给变量mInstrumentation的成员函数execStartActivity,以便将它传递给AMS,这样就可以通过ApplicationThread与Activity交流了。
4.Activity的成员变量mToken的类型为IBinder,它是Binder代理对象,指向AMS中一个类型为ActivityRecord对象,用来维护对应的Activity组件的运行状态以及信息。此处传入也是为了传递给AMS,这样AMS就可以得到Activity的详细信息了。
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
总结上面两个问题
第一种:
1.Activity1调用startActivity,实际会调用Instrumentation类的execStartActivity方法,Instrumentation是系统用来监控Activity运行的一个类,Activity的整个生命周期都有它的影子。
2.通过跨进程的binder调用,进入到ActivityManagerService(AMS)中,其内部(ActivityStackSupervisor)会处理Activity栈。之后又通过跨进程调用进入到Activity2所在的进程中。
3.ApplicationThread是一个binder对象,其运行在binder线程池中,内部包含一个H类,该类继承于Handler。ApplicationThread将启动Activity2的信息通过H对象发送给主线程。
4.主线程拿到Activity2的信息后,调用Instrumentation类的newAcitivity方法,其内部通过ClassLoader创建Activity2实例。
第二种:
1.点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
2.system_server进程接收到请求后,向zygote进程发送创建进程的请求;
3.Zygote进程fork出新的子进程,即App进程;
说一说service
首先服务是一种即使用户未与应用交互也可在后台运行的组件。
1.分为前台服务和后台服务与绑定服务。
绑定服务指:当应用组件通过调用 bindService() 绑定到服务时,服务即处于绑定状态。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
2.启动服务与绑定服务可以同时运行。唯一的问题在于您是否实现一组回调方法:onStartCommand()(让组件启动服务)和 onBind()(实现服务绑定)
3.应用组件(如 Activity)可通过调用 startService() 方法并传递 Intent 对象(指定服务并包含待使用服务的所有数据)来启动服务。服务会在 onStartCommand() 方法接收此 Intent。
讲讲Binder的原理
1.Binder是一种进程间通信(IPC)方式,Android常见的进程中通信方式有文件共享,Bundle,AIDL,Messenger,ContentProvider,Socket。其中AIDL,Messenger,ContentProvider都是基于Binder。Linux系统通过Binder驱动来管理Binder机制。
2.Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。
通信过程:
Binder 通信过程:
1.首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
2.Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
3.Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。
- A 进程想要 B 进程中某个对象(object)是如何实现的呢?(Binder)
- 跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。
- 当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。
- 当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。
额外的代码理解:
/**
* 这个类用来定义服务端 RemoteService 具备什么样的能力
*/
public interface BookManager extends IInterface {
void addBook(Book book) throws RemoteException;
}
public abstract class Stub extends Binder implements BookManager {
...
public static BookManager asInterface(IBinder binder) {
if (binder == null)
return null;
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof BookManager)
return (BookManager) iin;
return new Proxy(binder);
}
...
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSAVTION_addBook:
data.enforceInterface(DESCRIPTOR);
Book arg0 = null;
if (data.readInt() != 0) {
arg0 = Book.CREATOR.createFromParcel(data);
}
this.addBook(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
...
}
public class Proxy implements BookManager {
...
public Proxy(IBinder remote) {
this.remote = remote;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
replay.readException();
} finally {
replay.recycle();
data.recycle();
}
}
...
}
Handler的实现方式
RecyclerView的缓存模式
View的绘制流程
View的整个绘制流程可以分为以下三个阶段:
measure: 判断是否需要重新计算View的大小,需要的话则计算。
测量表示值:
1.EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize);
2.AT_MOST: 子View的大小不得超过SpecSize;
3.UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部。
//这段代码表示了
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// spec为父View的MeasureSpec
// padding为父View在相应方向的已用尺寸加上父View的padding和子View的margin
// childDimension为子View的LayoutParams的值
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 现在size的值为父View相应方向上的可用大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 表示子View的LayoutParams指定了具体大小值(xx dp)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子View想和父View一样大
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想自己决定其尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// 子View指定了具体大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子View想跟父View一样大,但是父View的大小未固定下来
// 所以指定约束子View不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想要自己决定尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
. . .
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
layout: 判断是否需要重新计算View的位置,需要的话则计算。
draw: 判断是否需要重新绘制View,需要的话则重绘制。
Android的事件分发流程
概念:
Android的TouchEvent通常包含三个动作,ACTION_DOWN,ACTION_MOVE与ACTION_UP。发出的顺序是DOWN->MOVE->MOVE->…->UP(注意MOVE事件是否能够被触发取决于操作手势里面是否包含了移动的动作)。
简单的流程:
1.消息分发流程,从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View
2.消息响应流程,从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity
具体的分析:
public boolean dispatchTouchEvent(MotionEvent ev);
事件分发处理函数,通常会在Activity层根据UI的显示情况,把事件传递给相应的ViewGroup。
public boolean onInterceptTouchEvent(MotionEvent ev);
对分发的事件进行拦截,注意拦截ACION_DOWN与其他ACTION的差异。
第1种情况:如果ACTION_DOWN的事件没有被拦截,顺利找到了TargetView,那么后续的MOVE与UP都能够下发。如果后续的MOVE与UP下发时还有继续拦截的话,事件只能传递到拦截层,并且发出ACTION_CANCEL。
第2种情况:如果ACITON_DOWN的事件下发时被拦截,导致没有找到TargetView,那么后续的MOVE与UP都无法向下派发了,在Activity层就终止了传递。
public boolean onTouchEvent(MotionEvent ev);
响应处理函数,如果有设置对应listener的话,这里还会与onTouch,onClick,onLongClick有关联。具体执行顺序是onTouch()->onTouchEvent()->onClick()->onLongClick()。是否能够顺序执行,取决于每个方法的返回值是true还是false。
- 如何优化View的绘制
大方向:
1.减少 View 层级。这样会加快 View 的循环遍历过程。
2.去除不必要的背景。由于 在 draw 的步骤中,会单独绘制背景。因此去除不必要的背景会加快 View 的绘制。
3.尽可能少的使用 margin、padding。在测量和布局的过程中,对有 margin 和 padding 的 View 会进行单独的处理步骤,这样会花费时间。我们可以在父 View 中设置 margin 和 padding,从而避免在子 View 中每个单独设置和配置。
具体的方向:
1.在onDraw()方法是同步的主线程的所以尽量不要用for循环,可以用path去装载要绘制的点,然后drawPath()来代替for循环。
2.减少局部对象的创建。
3.当内存不足时或者对象重创建的时候我们自己的view 保存自己的属性 ,onSaveInstanceState()与onRestoreInstanceState(Parcelable state)。
解释一下数据库的事务
理论:
事务是恢复和并发控制的基本单位。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
1.原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
2.一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
3.隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4.持久性(durability)。指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
//最后使用sqlite3查看数据库person表中的数据,转账没有成功,张三和王五的钱都没有变化
public void testTransaction() throws Exception {
PersonSQLiteOpenHelper helper = new PersonSQLiteOpenHelper(getContext());
SQLiteDatabase db = helper.getReadableDatabase();
// 开始数据库的事务
db.beginTransaction();
try {
db.execSQL("update person set account=account-1000 where name=?",
new Object[] { "zhangsan" });
int i = 2/0; //制造异常测试事务操作
db.execSQL("update person set account=account+1000 where name=?",
new Object[] { "wangwu" });
// 标记数据库事务执行成功
db.setTransactionSuccessful();
} catch (Exception e) {
} finally {
db.endTransaction();
db.close();
}
}
说一下Android组件化开发
说说clickable enable 在事件分发里面的作用
clickable 与 enable的源码解析:
//只有clickable为true的时候,才会执行下面的手势状态
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//...
if (!post(mPerformClick)) {
performClickInternal();
}
//...
break;
}
// 如果clickable之前设置了false 那么重新设置监听之后,会重新置为true。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
//enable
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
......
if (onFilterTouchEventForSecurity(event)) {
//如果设置了false 那么就无法滑动thumb
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//如果enable设置了false 后面的ontouch不会执行短路了
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result 为false的时候,会进入onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
//虽然进去了onTouchEvent 但是也会直接return出去 返回值取决于clickable的设置
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
讲一讲Android的ANR
无论是四大组件或者进程等只要发生ANR,最终都会调AMS.appNotResponding()方法.
实现的条件:
1.Service Timeout:比如前台服务在20s内未执行完成;
2.BroadcastQueue Timeout:比如前台广播在10s内未执行完成
3.InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。
如何分析:
1.分析这个下面的文件/data/anr/traces
2.当前发生ANR的进程,system_server进程以及所有persistent进程
audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程
CPU使用率排名前5的进程
2.将发生ANR的reason以及CPU使用情况信息输出到main log
将traces文件和CPU使用情况信息保存到dropbox,即data/system/dropbox目录
3.对用户可感知的进程则弹出ANR对话框告知用户,对用户不可感知的进程发生ANR则直接杀掉
说一下你如何应对内存泄露,你是怎么检测的
概念:
内存空间使用完毕之后未回收,会出现oom
出现的场景:
1.静态的变量,如view视图
2.内部类持有静态变量的引用,内部类的优势就是方遍的访问外部类,但是内部类会持有外部类实例的强引用。
3.handler在activity 的使用,使用匿名的handler与匿名的runnable对象来执行任务,极可能发生内存泄漏。
4.线程的使用
5.引用的Context对象
6.cursor未关闭
分析:
可以使用 adb shell dumpsys meminfo 来查看内存使用状况
使用lint检测
使用LeakCanary检测
在activity的生命周期里面即使去销毁定时任务,handler的任务 ,按照场景使用WeakReference来控制对象的内存释放,
说一下Android 7.0,8.0,9.0,10对于应用层开发大的变动有哪些?
Android10主要变动:
1.如果应用以 Android 10 或更高版本为目标平台并使用涉及全屏 intent 的通知,则必须在应用的清单文件中请求 USE_FULL_SCREEN_INTENT 权限。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。
2.如果用户从屏幕边缘向内滑动,系统会将该手势解读为“返回”导航,除非应用针对屏幕的相应部分明确替换该手势。
为了确保您的应用与手势导航兼容,您需要将应用内容扩展到屏幕边缘,并适当地处理存在冲突的手势。
Android9主要变动:
1.后台对传感器的访问受限
2.限制访问通话记录
3.限制访问 Wi-Fi 位置和连接信息;
Android8主要变动:
1.Android 8.0(API 级别 26)添加了对通知渠道的支持(NotificationChannels),即应用可以将其通知整理划分到不同的主题类别中。每种类别都有自己的提醒类型,用户可以根据自己的兴趣选择性地启用或停用这些类别。
2.在 Android 8.0 之前,创建前台 Service 的方式通常是先创建一个后台 Service,然后将该 Service 推到前台。 Android 8.0 有一项复杂功能:系统不允许后台应用创建后台 Service。 因此,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 如果应用在此时间限制内未调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR。
3.如果您的清单为显式广播声明了接收器,您必须予以替换。 可能的解决方法包括:通过调用 Context.registerReceiver() 而不是在清单中声明接收器的方式在运行时创建接收器。
Android7.0主要变动:
1.多窗口的支持,通知的变更
2.优化了后台,同时删除了三个常用隐式广播 — CONNECTIVITY_ACTION、ACTION_NEW_PICTURE 和ACTION_NEW_VIDEO
3.私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 和/或 MODE_WORLD_WRITEABLE 而进行的此类尝试将触发 SecurityException。
4.传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。
Android6.0主要变动:
1.权限模式。对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。
2.Android 6.0 版移除了对 Apache HTTP 客户端的支持。
3.修改adb shell dumpsys notification --noredact 命令打印输出 notification 对象中的文本。
想知道更多更详细的版本变更点击这里。
讲一讲aidl的具体使用
这里就不说具体的代码了,上面已经介绍了。补充一点知识:
1.aidl支持7种数据类型(除去short类型),String,CharSequence,实现了Parcelable接口的数据类型,List类型,Map类型。
2.in 表示数据只能由客户端流向服务端。
3.out 表示数据只能由服务端流向客户端。
4.inout 则表示数据可在服务端与客户端之间双向流通。
5.如果AIDL方法接口的参数值类型是:基本数据类型(除去short类型)、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in。
原理:
AIDL 使用 Binder 内核驱动程序进行调用。当您发出调用时,系统会将方法标识符和所有对象打包到某个缓冲区中,然后将其复制到某个远程进程,该进程中有一个 Binder 线程正在等待读取数据。Binder 线程收到某个事务的数据后,该线程会在本地进程中查找原生存根对象,然后此类会解压缩数据并调用本地接口对象。此本地接口对象正是服务器进程所创建和注册的对象。当在同一进程和同一后端中进行调用时,不存在代理对象,因此直接调用即可,无需执行任何打包或解压缩操作。
如何优化app的启动
讲讲你对MVP模式的理解
1.就是 Activity 或者 Fragment 直接与数据层交互,activity 通过 apiProvider 进行网络访问,或者通过 CacheProvider 读取本地缓存,然后在返回或者回调里对 Activity 的界面进行响应刷新。
2.视图层(View):
负责绘制UI元素、与用户进行交互,对应于XML、Activity、Fragment。
3.模型层(Model):
负责存储、检索、操纵数据,一般包含网络请求,数据库处理,I/O流。
4.控制层(Presenter):
Presenter是整个MVP体系的控制中心,作为View与Model交互的中间纽带,处理View于Model间的交互和业务逻辑。
为什么okHttp3 好用呢?
1.支持HTTP2/SPDY(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验)
2.socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享Socket,减少对服务器的请求次数
3.基于Headers的缓存策略减少重复的网络请求
4.拥有Interceptors轻松处理请求与响应(自动处理GZip压缩)
书面回答,意义不大。可以查阅源码分析。
retrofit的动态代理中是如何处理接口返回类型的(因为接口申明的泛型在运行时会被擦除)
这个不知道,有知道的可以留言告知。
乐观锁与悲观锁
概念与使用场景:
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
代码例子:
// ------------------------- 悲观锁的调用方式 -------------------------
// synchronized
public synchronized void testMethod() {
// 操作同步资源
}
// ReentrantLock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
public void modifyPublicResources() {
lock.lock();
// 操作同步资源
lock.unlock();
}
// ------------------------- 乐观锁的调用方式 -------------------------
private AtomicInteger atomicInteger = new AtomicInteger(); // 需要保证多个线程使用的是同一个AtomicInteger
atomicInteger.incrementAndGet(); //执行自增1
想了解更多请点击。
单例模式的缺点
这个问题可能是要讲清楚各种单例写法的优缺点。
1.明确饿汉式写法的优缺点:
类加载的时候,就会初始化。不管需不需要,消耗内存,但是线程安全,因为一个类在一个ClassLoader中只会被加载一次,单例模式的mInstance在加载时就已经初始化了,这可以确定对象的唯一性。同时也保证了在多线程并发情况下获取到的对象是唯一的。
2.明确懒汉式加载的优缺点:
可以实现懒加载,根据需要实例化代码,但是多数情况下线程不是安全的。
代码举例:列出了Android开发中比较常用的方式
//饿汉式
public class SingletonHungry {
private static SingletonHungry mInstance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return mInstance;
}
}
//懒汉式非线程安全
public class SingletonLazy {
private static SingletonLazy mInstance;
private SingletonLazy() {
}
public static SingletonLazy getInstanceUnLocked() {
if (mInstance == null) {
mInstance = new SingletonLazy();
}
return mInstance;
}
}
//懒汉式线程安全,但是损耗性能
public class SingletonLazy {
//关键字volatile 实现原子性
private volatile static SingletonLazy mInstance;
private SingletonLazy() {
}
public static SingletonLazy getInstance() {
//第一次检测
if (mInstance == null) {
//加锁
synchronized (SingletonLazy.class) {
//第二次检测
if (mInstance == null) {
//new 对象
mInstance = new SingletonLazy();
}
}
}
return mInstance;
}
}
2.面对项目中大量的单例模式,我们可以使用生成器来让单例少一些。
static class SingletonManager {
private static Map<String, Object> objectMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object ins) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, ins);
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}
线程死锁出现的条件,如何避免
java 死锁产生的四个必要条件:
1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4.循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
Java线程池的运用
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
//corePoolSize 核心池的大小
//maximumPoolSize 线程池最大线程数
//keepAliveTime 表示线程没有任务执行时最多保持多久时间会终止
//unit 时间单位
// workQueue ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue 阻塞队列
说一下Java二维数组
TCP三次握手与四次挥手
三次握手:
首先Client端发送连接请求报文(SYN = 1,seq = client_isn)
server接受连接后回复ACK报文,并为这次连接分配资源。(SYN = 1,seq=client_isn ack = client_isn +1 )
Client端接收到ACK报文后也向Server端发送ack报文,并分配资源。(SYN = 0,seq=client_isn ack = client_isn +1 )
四次挥手:
第一次:Client端发起中断连接请求,发送FIN报文。
第二次:Server端接到FIN报文后,会把剩下得数据连同ACK一同发送给Client端。
第三次:Client端就进入FIN_WAIT状态,等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文。
Client端收到FIN报文后,发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。
Server端收到ACK后,断开连接。
第四次:Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,Client端也可以关闭连接了
在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.
SYN表示建立连接,
FIN表示关闭连接,
ACK表示响应,
PSH表示有 DATA数据传输,
RST表示连接重置。
蛋:
上面是我面试一家2线不知名网络公司的面试题。(浙江宁波的某网络公司)今年成立的网络公司要14个人。面了1小时,很多问题我都不知道该怎么回答。对方也不给我解答任何一个面试问题,面完了人力资源也不会给你任何答复(毁三观的面试),这种公司摆明了就是完成招聘任务而设套,大家要小心这种套子公司。