LinearLayout按照垂直和水平順序依次排列子元素,儘量少用layout_weight(子元素佔用剩餘空間的大小比例),會導致二次測量;FrameLayout所有子元素都疊放在左上角;RelativeLayout按照各個子元素之間相對位置完成佈局;TableLayout爲表格佈局,適用於N行N列的佈局格式;AbsoluteLayout是絕對位置佈局,在此佈局中的子元素的android:layout_x和android:layout_y屬性將生效,用於描述該子元素的座標位置
RelativeLayout和ConstrainLayout比較:ConstrainLayout功能更強大,通過各種約束,可以實現更加扁平化的頁面佈局
android四大組件、生命週期
Service生命週期:當我們既想綁定一個Service(實現Activity和Service交互)又想在 Activity停止時,Service不會停止,我們可以先StartService,然後再BindService(),這樣當Activity退出的時候,Service的onUnbind()方法就會被調用,但Sercvice並不會停止,然後我們可以再進入Activity重新綁定該Service,這個時候Service就會調用onRebind()方法。但是onRebind()方法被調用還有個前提是先前的onUnbind()方法返回值爲true,但是如果使用默認的 super.onUnbind(intent)是不行的,這時候我們要手動的使其返回true,再次綁定時onRebind()就會執行了
Activity生命週期(一定要提到activity棧),(onStart可見性、onResume可用性、onStop->onRestart->onStart),可見性和可用性在很多地方都有體現,dialog遮擋Activity時,Activity切換時
Activity異常生命週期:onSaveInstanceState在activity有可能被系統回收時調用,總結一下,調用onSaveInstanceState情況如下:當用戶按下HOME鍵、關閉屏幕、切換到新Activity、橫豎屏切換,前四種情況下生命週期如下:onPause->onSaveInstanceState->onStop->onRestart->onStart->onResume,第五種情況下生命週期如下:onPause->onSaveInstanceState->onStop->onDestory->onCreate->onStart->onRestoreInstanceState->onResume,另外,我們在onCreate中讀取bundle時需要判空,在onRestoreInstanceState中不需要判空
Activity之間通訊:Intent(Bundle)、類靜態變量,如果activity之間需要傳遞的數據量過大(1M - 8K),則應該使用靜態變量
Activity向Fragment傳遞數據:通過setArguments傳遞數據。在橫豎屏切換時,系統會調用默認構造方法來創建Fragment,但會恢復Arguments
public class FramentTest2Activity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().
add(R.id. container, TestFragment.newInstance("param")).commit();
}
}
public static class TestFragment extends Fragment {
private static final String ARG = "arg";
public TestFragment() {
Log. i("INFO", "TestFragment non-parameter constructor" );
}
public static Fragment newInstance(String arg){
TestFragment fragment = new TestFragment();
Bundle bundle = new Bundle();
bundle.putString( ARG, arg);
fragment.setArguments(bundle);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout. fragment_main, container, false);
TextView tv = (TextView) rootView.findViewById(R.id. tv);
tv.setText(getArguments().getString( ARG));
return rootView;
}
}
}
Fragment向Activity傳遞數據:Activity實現回調接口,在onAttach中保存Activity實例、onDetatch中置空
Activity與Service通訊:Service綁定、Intent(Bundle)
Handler:在Android異步消息機制中,最常用的就是Handler,它有四大功能類,分別是Handler、Looper、MessageQueue、Message,其中,Message用於承載線程之間傳遞的消息;MessageQueue是一個用鏈表實現的先進先出隊列,用來存儲Message;Handler用於發送Message到MessageQueue,並實現消息處理接口(handleMessage),其中,加入隊列的過程最終會調用到sendMessageAtTime,插入過程從隊列頭開始,判斷時間大小進行插入;Looper主要是爲每個線程開啓單獨的消息循環,原則就是隻有創建Looper的線程才能處理消息(通過ThreadLocal(線程副本)實現,prepare、myLooper、loop),其中Looper內部採用循環機制,每循環一次,從其內部的MessageQueue中取出一個消息,然後通過Message的target屬性獲取到Handler實例,進而調用到dispatchMessage函數,在該函數中,如果Message存在callback(手動設置,或者通過post(runnable)自動設置),會執行handleCallback方法,如果Handler的mCallback變量不爲空,則執行mCallback.handleMessage方法,否則執行Handler的handleMessage方法(實踐中我們經常重寫這個方法),Looper處理完成一個消息後則繼續循環。若消息隊列爲空,線程則會阻塞等待
ThreadLocal:每個線程中保存着ThreadLocalMap對象,調用ThreadLocal的get和set方法時,先通過當前Thread拿到ThreadLocalMap對象,Map的key爲ThreadLocal,value爲要保存的值,現在想爲什麼Map不用Thread作爲key值呢,因爲這樣就無法在一個線程中保存多個值了,關鍵點是get和set會拿到當前線程的ThreadLocalMap的概念,以及TheadLocal是key值
handler.sendEmptyMessage(1);
handler.sendEmptyMessageDelayed(2, 2);
handler.sendEmptyMessage(3);
handler.sendEmptyMessage(6);
handler.sendEmptyMessageDelayed(4, 12);
handler.sendEmptyMessageDelayed(5, 1);
//執行順序 1 3 6 5 2 4
Looper主要有三個方法,Looper#prepare、Looper#getLooper、Looper#loop,Looper#prepare會創建當前線程對應的Looper實例(ThreadLocal#put),Looper#getLooper會返回當前線程對應的Looper實例,Looper#loop會通過Looper#getLooper獲取當前線程對應的Looper,並執行消息隊列循環(msg.target.dispatchMessage)
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
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;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
}
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
}
public class ThreadLocal<T> {
public T get() {
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
Handler內存泄漏:Runnable延時消息、消息過多,原因:Activity <— Handler <— Message <— MessageQueue <— Looper <— Thread(ThreadLocalMap),消息被放到主線程的Looper中,主線程的Looper是一直存在的,解決辦法(靜態內部類+弱引用、removeCallbacks、removemessages)
Android異步處理:線程、線程池(線程重用、可以對線程進行簡單管理)、HandlerThread(實現了Handler功能的Thread)、AsyncTask(異步執行的任務,擅長UI交互)、IntentService(異步執行的任務,由於是Service,優先級較高,適用於操作比較固定,順序執行的任務)
Service和Thread的區別:Service是系統組件,具有生命週期,生命週期函數運行在主線程;Thread是程序運行的最小單元,不由系統管理
//HandlerThread主要是在線程中創建Looper
public class HandlerThread extends Thread {
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
@Override
public void run() {
Looper.prepare();
Synchronized(this) {
mLooper = Looper.myLooper();
notifyAll();
}
Looper.loop();
}
public Looper getLooper() {
Synchronized(this) {
while(mLooper == null) {
try {
wait();
} catch (Exception e) {
}
}
}
return mLooper;
}
}
IntentService在onStart中把Intent參數post到Handler中處理,Handler通過HandlerThread實現(創建HandlerThread實例,拿到Looper對象,繼而創建的Handler),Handler相當於維護一個隊列,在Service沒銷燬的情況下,每次startService都會向Handler中post一個消息,在消息處理結束後調用stopSelf(int)來關閉當前Service,如果當前Handler中還有別的消息要處理,自然無法關閉service,因爲每次調用startService時Service都會傳入一個startId值,從1開始,如果IntentService服務沒有被關閉,那這個值會順序增加,只有傳入的startId和當前Service的id相等的時候才能成功關閉Service,也就是說當前處理的Intent是最後一個
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
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 IntentService(String name) {
super();
mName = name;
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
protected abstract void onHandleIntent(@Nullable Intent intent);
}
AsyncTask封裝了Handler和線程池(避免不必要線程創建和銷燬的開銷)、回調方法有onPreExecute、doInBackground(內部調用publishProgress)、onProgressUpdate 、onPostExecute
AsyncTask要點:實例必須在主線程中創建、execute方法必須在主線程中調用、實例只能執行一次execute方法
AsyncTask原理:AsyncTask實例只能執行一次execute方法,在execute方法中會判斷AsyncTask實例的狀態,如果不是Pending狀態,則拋出異常,然後調用onPreExecute方法,最後AsyncTask通過SerialExecutor來執行任務,SerialExecutor實現了executor接口,SerialExecutor內部通過隊列保存FutureTask(Runnable任務,在構造方法中初始化,其中,FutureTask封裝了WorkerRunnable,WorkerRunnable實現了Callable接口),並順序傳入線程池,因此AsyncTask模式是串型執行任務的。在FutureTask中的Callable任務執行結束後,會調用done回調方法,AsyncTask在創建FutureTask時實現了done方法,在該方法中會給Handler發送MESSAGE_POST_RESULT消息,從而執行onPostExecute。另外,FutureTask實現了操縱Runnable所在線程的api,包括通過get方法獲取執行結果,cancel方法取消執行線程,如果調用cancel,AsyncTask則不調用onPostExecute回調方法,而是調用onCancelled回調方法
public abstract class AsyncTask<Params, Progress, Result> {
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
public AsyncTask() {
this((Looper) null);
}
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null ||
callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
Result result = doInBackground(mParams);
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
postResultIfNotInvoked(get());
}
};
}
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:the task has already been
executed a task can be executed only once");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
}
public class FutureTask<V> implements RunnableFuture<V> {
public void run() {
if (state != NEW) return;
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
result = c.call();
set(result);
finishCompletion();
}
}
public V get(long timeout, TimeUnit unit) throws TimeoutException {
int s = state;
if (s <= COMPLETING && (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
public boolean cancel(boolean mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null) {
t.interrupt();
}
} finally {
finishCompletion();
}
return true;
}
private void finishCompletion() {
done();
}
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
}
//new UpdateTextTask(MainActivity.this, "1").execute();
//new UpdateTextTask(MainActivity.this, "2").execute();
//new UpdateTextTask2(MainActivity.this, "3").execute();
//驗證結果,三個都是順序執行的
如果執行AsyncTask的execute方法,默認是採用DefaultExecutor(同步線程池),各個任務順序執行,我們也可以調用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),該執行器爲AsyncTask提供的異步線程池,其中,核心線程數等於cpu數量+1,最小值是2個,最大值是4個,最大線程數量等於cpu數量*2+1,另外我們也可以自己實現Executor
Android主線程主要用來繪製界面、響應事件
異步消息:Handler、AsyncTask
Android中主要的工作線程:AsyncTask、IntentService、HandlerThread
五種不同類型的進程:前臺進程(與用戶正在交互的Activity或者Activity用到的Service)、可見進程(處於暫停狀態的Activity或者綁定在其上的Service)、服務進程(其中運行着使用startService方法啓動的Service,例如音樂播放程序)、後臺進程(運行着執行onStop方法而停止的程序)、空進程(不包含任何組件的進程)
靜態廣播和動態廣播存活時間不同,動態廣播需要在onResume中註冊,在onPause中反註冊,LocalBroadCastManager只在同一進程內有效,因此效率較高,BroadcastReceiver是全局廣播,需要使用系統API,效率較低,另外LocalBroadCastManager相對安全,因爲廣播數據僅在進程範圍內傳播,不用擔心隱私數據泄露的問題(傳統廣播也可以在發送廣播時設置Permission來規避,或通過Intent.setPackage設置廣播僅對某個程序有效), 另外,不用擔心別的應用僞造廣播,造成安全隱患(可以在動態註冊廣播接收器時設置Permission,靜態廣播可以設置android:exported="false")
廣播類型:普通廣播、系統廣播、有序廣播(通過設置Receiver的優先級來控制接收順序,sendOrderedBroadcast、abortBroadcast,短信通知廣播)、Local廣播
事件分發:事件是從上向下傳遞,從下往上消費,最開始是從Activity的dispatchTouchEvent開始,傳遞給ViewGroup的dispatchTouchEvent,相比於Activity和View,ViewGroup多了一個攔截功能,如果ViewGroup攔截該事件,那麼直接從當前層開始消費事件,如果當前層不攔截,則最終會傳遞給View,從而進行事件消費,如果View不消費,則依次向上傳遞。如果事件被某一層消費,那麼後續事件都不會再向該層之下傳遞。對於攔截功能,如果子類調用了disallow方法,則父類不能攔截,down事件除外,因爲每個事件序列都會進行重置
TouchDelegate在onTouch之後,在onTouchEvent之前,TouchDelegate還是沒有破壞事件傳遞機制,事件還是會直接傳遞給父類,然後在由父類繼續dispatch
父View在消費事件時會判斷是否設置了mTouchDelegate,如果設置了mTouchDelegate,會調用mTouchDelegate的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
// ...
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// ...
}
public class TouchDelegate {
// 該方法會判斷點擊事件是否在擴展的範圍內:如果在範圍內,則修改點擊事件位置,
// 確保事件一定在View範圍內,調用對應View的dispatch方法;如果不在範圍內,
// 也會修改事件位置,確保點擊事件一定不在View範圍內,調用對應View的dispatch方法,
// 最終返回disPatch結果。題外話,只要當前層消費了DOWN事件,其餘事件是否消費都會傳遞給該層
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
boolean sendToDelegate = false;
boolean hit = true;
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Rect bounds = mBounds;
if (bounds.contains(x, y)) {
mDelegateTargeted = true;
sendToDelegate = true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
sendToDelegate = mDelegateTargeted;
if (sendToDelegate) {
Rect slopBounds = mSlopBounds;
if (!slopBounds.contains(x, y)) {
hit = false;
}
}
break;
case MotionEvent.ACTION_CANCEL:
sendToDelegate = mDelegateTargeted;
mDelegateTargeted = false;
break;
}
if (sendToDelegate) {
final View delegateView = mDelegateView;
if (hit) {
// Offset event coordinates to be inside the target view
event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
} else {
// Offset event coordinates to be outside the target view (in case it does
// something like tracking pressed state)
int slop = mSlop;
event.setLocation(-(slop * 2), -(slop * 2));
}
handled = delegateView.dispatchTouchEvent(event);
}
return handled;
}
}
//com.lede.train.commondemo E/offer: CustomRelativeLayout dispatchTouchEvent 0
//com.lede.train.commondemo E/offer: CustomButton dispatchTouchEvent 0
//com.lede.train.commondemo E/offer: CustomButton onTouchEvent 0
//com.lede.train.commondemo E/offer: CustomRelativeLayout dispatchTouchEvent 1
//com.lede.train.commondemo E/offer: CustomButton dispatchTouchEvent 1
//com.lede.train.commondemo E/offer: CustomButton onTouchEvent 1
//com.lede.train.commondemo E/offer: CustomRelativeLayout dispatchTouchEvent 0
//com.lede.train.commondemo E/offer: CustomRelativeLayout onTouchEvent 0
//com.lede.train.commondemo E/offer: CustomButton dispatchTouchEvent 0
//com.lede.train.commondemo E/offer: CustomButton onTouchEvent 0
//com.lede.train.commondemo E/offer: CustomRelativeLayout dispatchTouchEvent 1
//com.lede.train.commondemo E/offer: CustomRelativeLayout onTouchEvent 1
//com.lede.train.commondemo E/offer: CustomButton dispatchTouchEvent 1
//com.lede.train.commondemo E/offer: CustomButton onTouchEvent 1
Activity啓動流程:系統啓動時通過android.intent.action.MAIN和android.intent.category.HOME啓動Launcher App,Launcher App通過PackageManager遍歷已安裝應用,拿到所有入口Activity(android.intent.action.MAIN和android.intent.category.LAUNCHER),用戶點擊圖標時通過Activity#startActivity啓動應用,Activity#startActivity最終會調用Activity#startActivityForResult,對應於RequestCode小於零。在啓動Activity時,用戶進程最終會調用Instrumentation#execStartActivity方法,該方法會通過ActivityManagerNative與AMS通訊,AMS保存請求參數後,通過ApplicationThread通知當前棧頂Activity執行Instrumentation#callActivityOnPause方法,進而執行Activity#onPause方法,客戶端執行完onPause方法後,會通過ActivityManagerNative#activityPaused方法通知系統,這時繼續啓動Activity邏輯。首先判斷是否創建了應用進程,如果進程沒創建,AMS會通過Socket和Zygote通訊,讓Zygote進程fork出一個應用進程,並執行ActivityThread#Main方法,該方法會初始化消息隊列等操作,接下來應用進程會和AMS通訊,最終執行Instrumentation#newAcvitity方法來創建Activity,執行Instrumentation#callActivityOnCreate、Instrumentation#callActivityOnStart、Instrumentation#callActivityOnResume方法,最終執行Activity的onCreate、onStart、onResume方法,ActivityThread在執行完onResume方法之後,會通過willActivityBeVisible方法告知AMS,這時系統會通知棧頂Activity執行onStop方法
public final class ActivityThread {
private class ApplicationThread extends ApplicationThreadNative {
}
}
public abstract class ApplicationThreadNative extends Binder implements IApplicationThread {
}
public abstract class ActivityManagerNative extends Binder implements IActivityManager {
}
package name和application id:package name聲明在manifest中,application id聲明在build.gradle中,應用最終的包名是由application id決定的,package name主要用來替換類路徑
透明度0是完全透明,256是不透明
java繼承關係:BootStrapClassLoader(啓動類) --> ExtClassLoader(擴展類) --> AppClassLoader(系統類) --> CustomClassLoader
android繼承關係:BaseDexClassLoader --> DexClassLoader --> PathClassLoader
視圖狀態:Enable表示當前視圖是否可用,調用setEnable()方法可以改變視圖的可用狀態,傳入true表示可用,傳入false表示不可用。它們之間最大的區別在於,不可用的視圖無法響應onTouch事件和onClick事件,無法獲取焦點,enable屬性不會影響onTouchEvent的返回值,只要View是clickable或者long clickable的,onTouchEvent就會返回true;Focus用來表示當前視圖是否獲取焦點,需要注意touch模式下的FOCUS_IN_TOUCH_MODE;還有selected和pressed狀態等
Context:Android程序不像Java程序一樣,隨便寫個main()方法就能跑了,而是要有一個完整的工程環境,也就是我們這裏討論的Context,在絕大多數場景下,Activity、Service和Application這三種類型的Context都是繼承ContextWrapper,是可以通用的,不過有幾種場景比較特殊,比如啓動Activity,還有彈出Dialog,出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啓動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會出錯。getApplication()方法是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到,如果我們也想在BroadcastReceiver中獲得Application的實例,這時就可以藉助getApplicationContext()方法。getBaseContext()方法得到的是一個ContextImpl對象,ContextImpl正是上下文功能的實現類。Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是做了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的,Application中在onCreate()方法裏去初始化各種全局的變量數據是一種比較推薦的做法,但是如果你想把初始化的時間點提前到極致,也可以去重寫attachBaseContext()方法
Listview與ScrollView都可以做滾動效果,但ScrollView裏面只能放一個元素,當然這個元素能裏面可以擺放很多,當這個元素的高度超過ScrollView的高度的話就可以滾動了,當然ScrollView裏面的東西是初始化好就存在了,就已經在內存中了,ListView 是隻顯示listview面積內的東西,不再面積內的是不存在的,只有滑動到這個面積內纔會加載新的東西
View.post和Handler.post的區別:View.post方法想在非UI線程有效工作,必須保證該View已經被添加至窗口
ButterKnife:利用Java註解機制來實現輔助代碼生成(Annotation Processing)的框架,經典應用(@BindView)
gradle3.0及以上 | gradle3.0之前 | 說明 | 作用 |
---|---|---|---|
implementation | compile | compile目前是deprecated狀態 | 編譯依賴不存在傳遞性,運行時可見,提高編譯速度 |
api | compile | 同上 | 傳遞依賴,編譯時和運行時對其他module可見 |
compileOnly | provided | provided在3.0以後不可用 | 編譯時可用,不打包到apk中 |
runtimeOnly | apk | apk在3.0以後不可用 | 編譯時不可用,運行時添加到build的output中 |
SharedPreferences兩種提交方式的區別:
- commit返回boolean值驗證是否提交成功 ;apply第二種沒有返回值
- commit同步提交硬盤,容易造成線程堵塞;apply先提交到內存,然後異步提交到磁盤
SharedPreferences數據是常駐內存的,且數據寫入是全量數據寫入磁盤,因此不建議大數據寫到SharedPreferences,且建議SharedPreferences分類存放,另外SharedPreferences每次讀取都是從內存緩存中讀取,即使加了MULTI_PROCESS標記位之後,每次都從文件中讀取,但由於多進程訪問文件也是不安全的,因此SharedPreferences不適用於跨進程通訊
Android常用的數據加密方式:MD5(數據校驗)、RSA(非對稱加密)、AES(對稱加密)
Android數字簽名:計算MD5值,通過數字證書的私鑰加密,連同數字證書一起放到應用中發佈
如何判斷是否是同一個應用:數字證書是否相同
Android中的五種存儲方式:SharedPreferences、文件、Sqlite數據庫、ContentProvider、網絡
SharedPreferences是十分常用的數據持久化方式,開發人員應該避免使用null作爲Key,即便這樣做合法
Drawable代表圖片資源(可以是GIF(可以解析成多張位圖)、PNG、JPG、BMP等),Bitmap代表位圖,Canvas代表畫布,可以看成是一種處理過程,Paint是畫筆,可以設置字體、顏色等
GestureDetector gestureDetector = new GestureDetector(getContext(),
new GestureDetector.OnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
return false;
}
});
NestedScrollingChild:如果一個View想要能夠產生嵌套滑動事件,這個View必須實現NestedScrollChild接口,從Android 5.0開始,View實現了這個接口,不需要我們手動實現
NestedScrollingParent:這個接口通常用來被ViewGroup來實現,表示能夠接收從子View發送過來的嵌套滑動事件
NestedScrollingChildHelper:這個類通常在實現NestedScrollChild接口的View裏面使用,他通常用來負責將子View產生的嵌套滑動事件報告給父View。也就是說,如果一個子View想要將產生的嵌套滑動事件交給父View,這個過程不需要我們來實現,而是交給NestedScrollingChildHelper來幫助我們處理
NestedScrollingParentHelper:這個類跟NestedScrollingChildHelper差不多,也是幫助來傳遞事件的,不過這個類通常用在實現NestedScrollingParent接口的View。如果一個父View不想處理一個事件,通過NestedScrollingParentHelper類幫助我們傳遞就行了
子View事件處理:整個事件傳遞過程中,首先能保證傳統的事件能夠到達子View,當一個事件序列開始時,首先會調用startNestedScroll方法來告訴父View,馬上就要開始一個滑動事件了,請問爸爸需要處理,如果處理的話,會返回true,不處理返回fasle。跟傳統的事件傳遞一樣,如果不處理的話,那麼該事件序列的其他事件都不會傳遞到父View裏面。然後就是調用dispatchNestedPreScroll方法,這個方法調用時,子View還未真正滑動,所以這個方法的作用是子View告訴它的爸爸,此時滑動的距離已經產生,爸爸你看看能消耗多少,然後父View會根據情況消耗自己所需的距離,如果此時距離還未消耗完,剩下的距離子View來消耗,子View滑動完畢之後,會調用dispatchNestedScroll方法來告訴父View,爸爸,我已經滑動完畢,你看看你有什麼要求沒?這個過程裏面可能有子View未消耗完的距離。其次就是fling事件產生,過程跟上面也是一樣,也是先調用dispatchNestedPreFling方法來詢問父View是否有所行動,然後調用dispatchNestedFling告訴父View,子View已經fling完畢。最後就是調用stopNestedScroll表示本次事件序列結束。整個過程中,我們會發現子View開始一個動作時,會詢問父View是否有所表示,結束一個動作時,也會告訴父View,自己的動作結束了,父View是否有所指示
父View事件處理:在系統中,沒有特定ViewGroup用來接收和消耗子View傳遞的事件。因此,只能自己動手了。在整個實現過程中,我們發現,我們只對onStartNestedScroll方法和onNestedPreScroll方法做了我們自己的實現,其他的要麼空着,要麼就是通過NestedScrollingParentHelper來幫助我們來實現
總結:
1、跟傳統的事件分發不同,嵌套滑動是由子View傳遞給父View,是從下到上的,傳統事件的分發是從上到下的
2、如果一個View想要傳遞嵌套滑動的事件,有兩個前提:實現NestedScrollingChild接口;setNestedScrollingEnabled方法設置爲true。如果一個ViewGroup想要接收和消耗子View傳遞過來的事件,必須實現NestedScrollingParent接口
ListView和RecyclerView對比:
編碼方式:
RecyclerView編碼更規範,支持LayoutManager、ItemDecoration、ItemAnimation
RecyclerView自動完成item複用,不需要setTag和getTag操作
佈局效果:
ListView佈局單一,只支持縱向佈局
RecyclerView佈局效果豐富,可以在LayoutManager中設置:縱向、橫向、線性、表格、瀑布流,另外,我們也可以自定義LayoutManager
數據處理:
ListView中存在setEmptyView(處理Adapter中數據爲空的情況)、addHeaderView(不會影響Adapter的編寫)、addFooterView(不會影響Adapter的編寫)
RecyclerView中需要在Adapter中自己處理(可以通過ViewHolder的Type類型)
列表刷新:
ListView通過notifyDataSetChanged刷新數據,但是這種刷新是全局的
RecyclerView可以通過notifyItemChanged實現局部刷新
動畫效果:
RecyclerView的notifyItemChanged、notifyItemInserted、notifyItemRemoved實現了動畫效果,我們可以也通過ItemAnimator實現自己的動畫效果
ListView並沒有實現動畫效果,但我們可以在Adapter自己實現item的動畫效果
點擊事件:
ListView中有onItemClickListener,但是如果有HeaderView和FooterView,這是postion會發生變化,可能造成數組越界問題,在工程實現時,我們要判斷是否存在HeaderView(getHeaderViewsCount),然後position動態減1
在RecyclerView中有onItemTouchListener,我們不需要考慮header和footer的問題,因爲所有item都是adapter中通過position生成的
嵌套滾動:
RecyclerView實現了NestedScrollingChild,可以實現嵌套滾動
ListView沒有實現嵌套滾動
滑動列表卡頓問題:解決辦法(滑動時停止圖片加載),在SCROLL_STATE_IDLE時加載圖片
Glide.with(getContext()).resumeRequests();
Glide.with(getContext()).pauseRequests();
minSdkVersion:應用支持的系統最低版本;
targetSdkVersion:應用已經兼容從minSdkVersion至targetSdkVersion之間的所有api變化,用於運行時判斷,該屬性是Android提供向前兼容的主要依據;
compileSdkVersion:編譯時兼容的api版本;
三者的關係:minSdkVersion <= targetSdkVersion <= compileSdkVersion,正常情況下targetSdkVersion和compileSdkVersion應該相等(既然編譯時已經適配了最新的api,那爲啥不改targetSdkVersion,理由是更改compileSdkVersion會提示那些api過時了,那我們儘量少用,但是有些問題我們通過版本判斷屏蔽了,所以還不能改targetSdkVersion)
git常用命令:init、status、diff、add、remove、commit、clone、branch、checkout
.9圖規則:左上黑邊爲拉伸區域,右下黑邊爲顯示區域
//忽略字段id
private Gson getSkipIdGson() {
Gson gson = new GsonBuilder().setExclusionStrategies(
new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
//過濾掉字段名包含"id","address"的字段
return f.getName().equals("id");
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
// 過濾掉 類名包含 Bean的類
return false;
}
}).create();
return gson;
}
private Gson getSkipIdAndGroupIdGson() {
Gson gson = new GsonBuilder().setExclusionStrategies(
new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
//過濾掉字段名包含"id","address"的字段
return f.getName().equals("id") | f.getName().equals("groupGuid");
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
// 過濾掉 類名包含 Bean的類
return false;
}
}).create();
return gson;
}
Android彈泡主要有兩種實現方式:PopupWindow、動態添加View。適用場景:PopupWindow適用於固定位置的提示、動態添加View適用於Feed流、ViewPager
PopupWindow可以在窗口的任意位置顯示,相對於其它窗口組件顯示的位置更靈活,Popupwindow不會給頁面的其它部分添加蒙層,可以自定義動畫,但想動態變更位置,只能手動調用update方法,容易出bug
showAtLocation與showAsDropDown:https://www.jianshu.com/p/12f53c931eda
下拉刷新(背景拉大+頂部轉球)實現:
mAppBar.addOnOffsetChangedListener { _, offset ->
var maxOffset = mHeaderView.height
var maxDisplayOffset = mHeaderView.height
mIsHeadFold = maxOffset + offset == 0
if (!mIsHeadFold) checkCollapsingToolbarUpdate()
if (mIsRefreshing) {
maxOffset += mLoadingPullEndOffset
maxDisplayOffset += mLoadingPullEndOffset
}
onAppBarCollapsing(offset, maxOffset, maxDisplayOffset)
}
mZoomLayout.setZoomReadyProxy(object : PullZoomExtendLayout.IZoomReadyProxy {
override fun isReadyZoom(): Boolean {
return mAppBarOffset >= 0 && mRefreshEnable && mAppBarMaxOffset > 0 && !isRefreshTagList
}
})
mZoomLayout.addOnPullZoomListener(object : PullZoomBaseView.OnPullZoomListener {
override fun onPullZooming(newScrollValue: Float) {
val offset = Math.abs(newScrollValue).toInt()
onPullZooming(true, if (mIsRefreshing) offset + mLoadingPullEndOffset else offset)
}
override fun onPullStart() {}
override fun onPullZoomEnd(newScrollValue: Float) {
val offset = Math.abs(newScrollValue).toInt()
onPullZoomEnd(if (mIsRefreshing) offset + mLoadingPullEndOffset else offset)
}
})
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!isZoomEnable){
return false;
}
if (event.getAction() == MotionEvent.ACTION_MOVE && isPullStart){
return true;
}
performInterceptAction(event);
return isPullStart;
}
private void performInterceptAction(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
if (isReadyZoom()){
onZoomReadyActionDown(event);
}
break;
case MotionEvent.ACTION_MOVE:
if (isReadyZoom()) {
onZoomReadyActionMove(event);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// do nothing
// the reset action will be done in the function onTouchEvent
break;
default:
break;
}
}
private void onZoomReadyActionDown(MotionEvent event) {
mInitTouchY = mLastTouchY = event.getY();
mInitTouchX = mLastTouchX = event.getX();
isPullStart = false;
}
private void onZoomReadyActionMove(MotionEvent event) {
float mCurrentX = event.getX();
float mCurrentY = event.getY();
float xDistance = mCurrentX - mLastTouchX;
float yDistance = mCurrentY - mLastTouchY;
if (mMode == ZOOM_HEADER && yDistance > mTouchSlop && yDistance > Math.abs(xDistance)
|| mMode == ZOOM_FOOTER && -yDistance > mTouchSlop && -yDistance > Math.abs(xDistance)){
mLastTouchY = mCurrentY;
mLastTouchX = mCurrentX;
if (!mZoomListenerList.isEmpty()) {
for (OnPullZoomListener listener : mZoomListenerList) {
listener.onPullStart();
}
}
if (!isPullStart) {
mInitTouchY = mCurrentY;
}
isPullStart = true;
}
}
下拉刷新(背景不動+頂部轉球)實現:開源控件(PullToRefresh),控件通過負padding和scroll實現UI效果,通過僞造TOUCH_DOWN事件來實現上下拖動(父視圖攔截事件後還可以讓子視圖重新獲取MOVE事件),該控件不會動態拉伸頭部背景,也是通過回調來判斷是否開始下拉刷新
class TabPullToRefreshView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null)
: AppBarPullToRefreshLayout(context, attrs) {
private lateinit var coordinatorLayout: CoordinatorLayout
private lateinit var headerLoadingView: BollLoadingLayout
override fun initLoadingLayout(): LoadingLayout? {
headerLoadingView = BollLoadingLayout(context)
return headerLoadingView
}
override fun getAppBarLayout(): AppBarLayout {
return coordinatorLayout.findViewById(R.id.appbar_header)
}
override fun initRootChild(): CoordinatorLayout {
coordinatorLayout = LayoutInflater.from(context).inflate(R.layout.peppa_tab_main_content_layout, null) as CoordinatorLayout
return coordinatorLayout
}
}
override fun getPullToRefreshScrollDirection(): Orientation {
return Orientation.VERTICAL
}
override fun createLoadingLayout(context: Context, mode: Mode?, attrs: TypedArray?): LoadingLayout {
return initLoadingLayout() ?: super.createLoadingLayout(context, mode, attrs)
}
override fun createRefreshableView(p0: Context?, p1: AttributeSet?): CoordinatorLayout? {
val childRoot = initRootChild()
getAppBarLayout()?.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
appBarOffset = verticalOffset
}
return childRoot
}
override fun isReadyForPullStart(): Boolean {
val isNoOffset = appBarOffset >= 0L
return isNoOffset && (pullReadyProxy?.isReadyForPullStart() ?: true)
}
override fun isReadyForPullEnd(): Boolean {
return false
}