关于Android基础面试,你需要知道的(学习笔记 一)
关于
前段时间找了两周工作(天太冷了不想跑路去公司面试,所以没怎么上心找),最终也是有三家公司给了我offer(一家上司外包公司(工资是12.5k)没去)、(上市公司做自研项目(9k)还是要的太少了,所以没去)、(自研公司行业项目(工资保密))。
应届生找工作的话基础很重要,尽量找自研公司。外包公司(中软、润和等)不建议去,虽然工资高,但是基本打杂。
以上仅代表个人想法,勿喷
Android四大组件
Activity
Activity的生命周期。
一个简单的activity生命周期:onCreate()->onStart()【此时activity不可见】->onResume()【activity处于运行状态】->onPause()【有其他活动实例在该实例前面,此时处于暂停状态】->onStop()【activity不可见,activity处于停滞状态】->onRestart()【重新回到前面】或者-onDestroy()【activity生命周期结束加粗样式】
- Activity跳转到了新的Activity界面或者进入后台、锁屏等,系统会调用onPause(),然后新的Activity会onCreate()->onStart()->onResume(),然后旧的Activity会onStop(),使Activity进入停滞状态,如果此时重新回到该Activity界面(上一个Activity回来、从后台回到前台、解开锁屏),系统会调用onRestrat()->onStart()->onResume(),上一个界面onStop().
- Activity被其他Activity覆盖的时候,系统会调用onPause()方法,暂停当前Activity的运行。
- 当用户退出Activity时会调用onPause()->onStop()->onDestroy()结束生命周期。
- 当Activity中弹出dialog对话框的时候,不会调用onPause()方法,只有当Activity启动了dialog风格的Activity时才会调用。
- 当Activity由于异常情况下终止的,系统会调用onSaveInstance来保存当前Activity的状态,这个方法是在onStop()之前调用的,当Activity重新创建后,系统会调用onRestoreInstanceState(),并把Activity销毁时的onSaveInstance里面保存的bundle对象作为参数同时传递给onRestoreInstanceState和onCreate()方法。
Activity的四种启动模式
Activity有四种启动模式 standard,singleTop,singleTask,singleInstance,这四种模式我们可以在清单文件的activity节点下中的android:launchMode配置。
- standard模式:在这种模式下,activity会默认进入启动它的activity任务栈中,这是默认模式。
- singleTop模式:在这种模式下(栈顶复用模式)。如果新activity位于任务栈的栈顶时,activity不会被重新创建,会调用onNewIntent方法。
- singleTask模式:栈内复用模式。只要activity在一个栈中存在,activity就不会被重新创建,同时回调onNewIntent方法。可以称为单例模式,只会存在一种,有会直接调用。
- singleInstance模式:单实例模式。这种模式的activity只能单独的位于一个任务栈中,并让多个应用共享这个Activity实例,一旦该模式的Activity实例已经存在,其他激活该Activity时都会重用该栈中的实例(onNewIntent)。
Service
Service分为两种,一种是Service(这一种是运行在主线程中的,如果要执行耗时操作,可以在service中创建一个异步或线程来执行),一种是IntentService(这是异步服务,是继承于Service的子类)。
- IntentService:service+thread模式,用来进行处理异步请求的服务,内部有一个工作线程,所有发送给服务的请求都会在这个工作线程中按序执行,在处理完所有请求后服务会自动停止。通过在子类onHandleIntent(Intent)接口中进行实际请求。当IntentService第一次启动的时候会调用其onCreate来完成一些初始化操作:
- 1.创建一个HandlerThread对象,内部有一个消息循环队列的线程。
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
- 2.利用已创建的HandlerThread内部的消息循环创建一个ServiceHandler对象,这样就可以在对象的线程中执行了。
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
- 3.通过执行onStartCommand回调调用oonStart方法对发送过来的请求并通过mServiceHandler进行处理,在handleMessage中通过onHandleIntent进行实际处理,然后调用stopSelf()来尝试停止当前服务。、
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
public final void stopSelf() {
stopSelf(-1);
}
/**
* Old version of {@link #stopSelfResult} that doesn't return a result.
*
* @see #stopSelfResult
*/
public final void stopSelf(int startId) {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
}
/**
* Stop the service if the most recent time it was started was
* <var>startId</var>. This is the same as calling {@link
* android.content.Context#stopService} for this particular service but allows you to
* safely avoid stopping if there is a start request from a client that you
* haven't yet seen in {@link #onStart}.
*
* <p><em>Be careful about ordering of your calls to this function.</em>.
* If you call this function with the most-recently received ID before
* you have called it for previously received IDs, the service will be
* immediately stopped anyway. If you may end up processing IDs out
* of order (such as by dispatching them on separate threads), then you
* are responsible for stopping them in the same order you received them.</p>
*
* @param startId The most recent start identifier received in {@link
* #onStart}.
* @return Returns true if the startId matches the last start request
* and the service will be stopped, else false.
*
* @see #stopSelf()
*/
public final boolean stopSelfResult(int startId) {
if (mActivityManager == null) {
return false;
}
try {
return mActivityManager.stopServiceToken(
new ComponentName(this, mClassName), mToken, startId);
} catch (RemoteException ex) {
}
return false;
}
- 4.在onDestroy中调用,mServiceLooper.quit()方法,停止工作线程的消息循环,等待线程退出。
@Override
public void onDestroy() {
mServiceLooper.quit();
}`
Service的两种启动方式
- 通过start方法开启服务
创建一个类继承Service,其中会有一个IBinder onBind()方法。
public class TestHandlerIntentService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
还要再清单文件中注册这个服务:
<service
android:name="xxService"
android:enable="true"
android:exported="true"/>
启动方法startService(intent);以及停止服务方法:stopService(intent);(service的生命周期和调用者无关)
当Service首次启动,先调用onCreate()->onStartConmmand()->onDestroy()方法,多次调用会执行onStartCommand()方法。
- 通过bind的方式启动服务
当Service首次启动,先调用onCreate()->onBind()->unBind()->onDestroy()方法,在activity中调用需要获取ServiceConnection对象,然后货哦渠道service中内部类的类对象,然后通过类对象调用类中的方法,这个类需要继承Binder对象。
public class MyServiceBinder extends Binder{
public myservice getService(){
return myservice.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyServiceBinder();
}
在Activity中调用,不再使用的时候,通过unbindService(connection)方法
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//活动与服务连接成功是调用
myservice.MyServiceBinder binder =(myservice.MyServiceBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
//断开调用
}
};
bindService()也可以调用多次,当发起对象与Service已经成功绑定后,不会多次返回ServiceConnection中的回调方法。
BroadcastReceiver
广播分为两种,一种是是无序广播,另一种是有序广播。
- 静态注册即在清单文件中喂BroadcastReceiver进行注册,使用标签声明,并在标签内用标签设置过滤器。不受页面生命周期影响,但是会占用CPU资源。继承BroadcastReceiver,在onReceive()方法中取出Intent传递的字符。
public class NormalReceiver extends BroadcastReceiver {
private static final String TAG = "NormalReceiver";
public NormalReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("Msg");
Log.e(TAG, msg);
}
}
在Activity中调用sendBroadcast(Intent)方法
Intent intent = new Intent(NORMAL_ACTION);
intent.putExtra("Msg", "Hi");
sendBroadcast(intent);
清单中配置
<receiver android:name="broadcastReceiver">
<intent-filter>
<action android:name="xxx.receiver"/>
</intent-filter>
</receiver>
可以通过BroadcastReceiver也能调用abortBroadcast()方法截断广播。
- 动态注册即在代码中定义并设置好一个IntentFilter对象,然后再需要注册的地方调用Context.registerReceiver()方法。调用Context.unregisterReceiver()方法取消注册。
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.receiver");
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d("服务器","接受到了广播发送数据");
}
};
registerReceiver(receiver,intentFilter);
- 本地广播只能在应用内部进行传递,而且广播接收器也只能接受本应用内自身发出的广播,本地广播使用LocalBroadcastManager来对广播进行管理。
private LocalBroadcastManager localBroadcastManager;
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localReceiver = new LocalReceiver();
IntentFilter filter = new IntentFilter(LOCAL_ACTION);
localBroadcastManager.registerReceiver(localReceiver, filter);
动态注册广播不是常驻型广播,它跟随activity的生命周期,静态注册是常驻型,即使程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
同优先级的广播接收器,动态优先于静态。
- 小结:
在Android中如果要发送一个广播必须使用sendBroadcast向系统发送对其感兴趣的广播接收器中。
使用广播必须要有一个intent对象必设置其action动作对象。
如果在AndroidManifest.xml中注册,当应用程序关闭的时候,也会接收到广播。在应用程序中注册就不产生这种情况了。
ContentProvider
contentprovider是Android四大组件之一的内容提供器,主要作用就是将程序的内部的数据和外部进行共享,为数据提供外部访问接口。是Android中一种跨程序共享数据的重要组件。
- android:exported=“true”,运行跨进程访问。
- grantUriPermissions=“true”,允许临时权限访问
<provider
android:name=".gassend.util.MyFileProvider"
android:authorities="com.xiaoduo.xiangkang.gas.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file1_paths" />
</provider>
Android的事件分发机制学习
在手指触摸屏幕后产生的一系列事件中,典型的事件类型如下:
- ACTION_DOWN 在屏幕按下时;
- ACTION_MOVE在屏幕上滑动时;
- ACTION_UO手指在屏幕抬起时;
- ACTION_CANCEL 取消;
什么是MotionEvent
MotionEvent就是Android对上面触摸事件相关信息的封装,View的事件分发中的事件就是这个MotionEvent,当这个MotionEvent产生后,那么系统就会将这个MotionEvent传递给View的层级,MotionEvent在View的层级传递的过程就是事件分发。MotionEvent封装了事件类型和坐标两类信息。
事件类型可以通过motionEvent.getAction()方法获取,返回一个常量,对应一个事件类型,事件类型主要有以下四种:
//MotionEvent.java
public final class MotionEvent extends InputEvent implements Parcelable {
//按下(down)
public static final int ACTION_DOWN = 0;
//抬起(up)
public static final int ACTION_UP = 1;
//移动(move)
public static final int ACTION_MOVE = 2;
//取消(cancel)
public static final int ACTION_CANCEL = 3;
//...
}
坐标也是通过MotionEvent获取,motionEvent.getRawX()和motionEvent.getRawY()可以获得以屏幕作为参考系的坐标值,motionEvent.getX()、motionEvent.getY() 可以获得以被触摸的 View 作为参考系的坐标值。
一个事件序列
从手指按下屏幕到抬起,在这个过程中所产生的一系列事件,就是一个事件序列,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。所以可能会有下面两种事件序列:
- ACTION_DOWN->ACTION_UP手指按下屏幕后又抬起
- ACTION_DOWN->ACTION_MOVE->…ACTION_MOVE_ACTION_UP手指按下屏幕,滑动一会后抬起
在Activity的实现中,它会把事件交给PhoneWindow来分发,然后PhoneWindow又会把这个事件分发给顶级View,然后顶级View就调用super.dispatchTouchEvent方法,把这个输入事件在View树中层层分发下去,直到找到合适的View来处理这个事件。
事件分发的三个重要方法:
-
dispatchTouchEvent(MotionEvent event)调度触摸事件
用来进行事件的分发,如果事件能传递给当前View,那么此方法一定能被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消费当前事件。 -
onInterceptTouchEvent(MotionEvent ev)在拦截触摸事件上
用来进行事件的拦截,在dispatchTouchEvent()方法中调用,如果当前View拦截了某个事件,那么在同个事件序列中,此方法不会被再次调用,返回结果表示是否拦截当前事件。 -
onTouchEvent(MotionEvent ev)
用来处理点击事件,在dispatchTouchEvent()方法中进行调用。返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
分发事件的组件
ViewGroup就比如LinearLayout或者RelativeLayout,View就比如Button等控件。
子View可以通过requestDsiallowInterceptTouchEvent(bollen)方法设置是否允许ViewGroup的拦截事件。
结论
下面给出事件传递机制的一些结论,参考自《Android开发艺术探索》,根据这些结论可以更好地理解整个传递机制。
- 同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程产生的一系列事件。这个事件义down开始,中间含有数量不定的move事件,最终以up事件结束。
- 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。 这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列的其他方法都直接交给他来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。
- 正常情况下,一个事件序列只能被一个View拦截消耗。 因为一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理。(通过其他手段可以,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理)
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false)那么同一事件序列中的其他事件(ACTION_MOVE,ACTON_UP…)都不会再交给他处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent将会被调用。 即事件一旦交给一个View处理,那么它就必须消耗掉,否则同一个事件序列中剩下的事件就不再交给它来处理。
- 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false)那么同一事件序列中的其他事件(ACTION_MOVE,ACTON_UP…)都不会再交给他处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent将会被调用。 即事件一旦交给一个View处理,那么它就必须消耗掉,否则同一个事件序列中剩下的事件就不再交给它来处理。
- 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
- ViewGroup默认不拦截任何事件。 Andorid源码中ViewGroup的onInterceptTouchEvent方法默认返回false。
- View没有onInterceptTouchEvent方法, 一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用
- View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时返回false)。
- 事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素分发过程,但是ACTION_DOWN事件除外。
- 在ViewGroup中ACTION_DOWN 事件负责寻找 target,即寻找能够消费ACTION_DOWN事件的子View,如果找到,那么接下来同一事件序列内的所有事件都会交给这个子View处理,不再交给ViewGroup;如果没有找到,有两种情况:1、ViewGroup没有子View,2、子View处理了ACTION_DOWN事件,但是在dispatchTouchEvent()返回了false,那么接下来同一事件序列下的所有事件都是ViewGroup自己处理。