- Activity
- Fragment
- Service
- BroadcastReceiver
- ContentProvider
- 數據存儲
- View
- 進程
- Parcelable 接口
- IPC
- Window / WindowManager
- Bitmap
- 屏幕適配
- Context
- SharedPreferences
- 消息機制
- 線程異步
- RecyclerView 優(yōu)化
- Webview
Activity
生命周期
Activity A 啟動另一個Activity B遂黍,回調如下:
Activity A 的onPause() → Activity B的onCreate() → onStart() → onResume() → Activity A的onStop()抹缕;如果B是透明主題又或則是個DialogActivity,則不會回調A的onStop趟径;使用onSaveInstanceState()保存簡單瘪吏,輕量級的UI狀態(tài)
lateinit var textView: TextView
var gameState: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameState = savedInstanceState?.getString(GAME_STATE_KEY)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.text_view)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}
override fun onSaveInstanceState(outState: Bundle?) {
outState?.run {
putString(GAME_STATE_KEY, gameState)
putString(TEXT_VIEW_KEY, textView.text.toString())
}
super.onSaveInstanceState(outState)
}
啟動模式
LaunchMode | 說明 |
---|---|
standard | 系統(tǒng)在啟動它的任務中創(chuàng)建 activity 的新實例 |
singleTop | 如果activity的實例已存在于當前任務的頂部,則系統(tǒng)通過調用其onNewIntent()蜗巧,否則會創(chuàng)建新實例 |
singleTask | 系統(tǒng)創(chuàng)建新 task 并在 task 的根目錄下實例化 activity掌眠。但如果 activity 的實例已存在于單獨的任務中,則調用其 onNewIntent() 方法幕屹,其上面的實例會被移除棧蓝丙。一次只能存在一個 activity 實例 |
singleInstance | 相同 singleTask级遭,activity始終是其task的唯一成員; 任何由此開始的activity 都在一個單獨的 task 中打開 |
?
啟動過程
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
//step 1: 創(chuàng)建LoadedApk對象
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
... //component初始化過程
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//step 2: 創(chuàng)建Activity對象
Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
//step 3: 創(chuàng)建Application對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
//step 4: 創(chuàng)建ContextImpl對象
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//step5: 將Application/ContextImpl都attach到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);
...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
//step 6: 執(zhí)行回調onCreate
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart(); //執(zhí)行回調onStart
r.stopped = false;
}
if (!r.activity.mFinished) {
//執(zhí)行回調onRestoreInstanceState
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);
}
}
...
r.paused = true;
mActivities.put(r.token, r);
}
return activity;
}
Fragment
特點
- Fragment 解決 Activity 間的切換不流暢,輕量切換
- 可以從 startActivityForResult 中接收到返回結果渺尘,但是View不能
- 只能在 Activity 保存其狀態(tài)(用戶離開 Activity)之前使用 commit() 提交事務挫鸽。如果您試圖在該時間點后提交,則會引發(fā)異常鸥跟。 這是因為如需恢復 Activity丢郊,則提交后的狀態(tài)可能會丟失。 對于丟失提交無關緊要的情況医咨,請使用 commitAllowingStateLoss()枫匾。
生命周期
與Activity通信
執(zhí)行此操作的一個好方法是,在片段內定義一個回調接口腋逆,并要求宿主 Activity 實現(xiàn)它婿牍。
public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}
public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString());
}
}
...
}
Service
Service 分為兩種工作狀態(tài),一種是啟動狀態(tài)惩歉,主要用于執(zhí)行后臺計算等脂;另一種是綁定狀態(tài),主要用于其他組件和 Service 的交互撑蚌。
啟動過程
ActivityThread.java
@UnsupportedAppUsage
private void handleCreateService(CreateServiceData data) {
···
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
}
···
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
···
}
綁定過程
ActivityThread.java
private void handleBindService(BindServiceData data) {
Service s = mServices.get(data.token);
···
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
data.intent.prepareToEnterProcess();
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManager.getService().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
···
}
}
生命周期
值 | 說明 |
---|---|
START_NOT_STICKY | 如果系統(tǒng)在 onStartCommand() 返回后終止服務上遥,則除非有掛起 Intent 要傳遞,否則系統(tǒng)不會重建服務争涌。這是最安全的選項粉楚,可以避免在不必要時以及應用能夠輕松重啟所有未完成的作業(yè)時運行服務 |
START_STICKY | 如果系統(tǒng)在 onStartCommand() 返回后終止服務,則會重建服務并調用 onStartCommand()亮垫,但不會重新傳遞最后一個 Intent模软。相反,除非有掛起 Intent 要啟動服務(在這種情況下饮潦,將傳遞這些 Intent )燃异,否則系統(tǒng)會通過空 Intent 調用 onStartCommand()。這適用于不執(zhí)行命令继蜡、但無限期運行并等待作業(yè)的媒體播放器(或類似服務 |
START_REDELIVER_INTENT | 如果系統(tǒng)在 onStartCommand() 返回后終止服務回俐,則會重建服務,并通過傳遞給服務的最后一個 Intent 調用 onStartCommand()稀并。任何掛起 Intent 均依次傳遞仅颇。這適用于主動執(zhí)行應該立即恢復的作業(yè)(例如下載文件)的服務 |
啟用前臺服務
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Notification notification = new Notification(icon, text, System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, title, mmessage, pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
BroadcastReceiver
target 26 之后,無法在 AndroidManifest 顯示聲明大部分廣播碘举,除了一部分必要的廣播忘瓦,如:
- ACTION_BOOT_COMPLETED
- ACTION_TIME_SET
- ACTION_LOCALE_CHANGED
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(receiver, filter);
注冊過程
ContentProvider
ContentProvider 管理對結構化數據集的訪問。它們封裝數據殴俱,并提供用于定義數據安全性的機制政冻。 內容提供程序是連接一個進程中的數據與另一個進程中運行的代碼的標準界面枚抵。
ContentProvider 無法被用戶感知,對于一個 ContentProvider 組件來說明场,它的內部需要實現(xiàn)增刪該查這四種操作汽摹,它的內部維持著一份數據集合,這個數據集合既可以是數據庫實現(xiàn)苦锨,也可以是其他任何類型逼泣,如 List 和 Map,內部的 insert舟舒、delete拉庶、update、query 方法需要處理好線程同步秃励,因為這幾個方法是在 Binder 線程池中被調用的氏仗。
ContentProvider 通過 Binder 向其他組件乃至其他應用提供數據。當 ContentProvider 所在的進程啟動時夺鲜,ContentProvider 會同時啟動并發(fā)布到 AMS 中皆尔,需要注意的是,這個時候 ContentProvider 的 onCreate 要先于 Application 的 onCreate 而執(zhí)行币励。
基本使用
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table
mProjection, // The columns to return for each row
mSelectionClause // Selection criteria
mSelectionArgs, // Selection criteria
mSortOrder); // The sort order for the returned rows
public class Installer extends ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
ContentProvider 和 sql 在實現(xiàn)上有什么區(qū)別?
- ContentProvider 屏蔽了數據存儲的細節(jié)慷蠕,內部實現(xiàn)透明化,用戶只需關心 uri 即可(是否匹配)
- ContentProvider 能實現(xiàn)不同 app 的數據共享食呻,sql 只能是自己程序才能訪問
- Contentprovider 還能增刪本地的文件,xml等信息
數據存儲
存儲方式 | 說明 |
---|---|
SharedPreferences | 在鍵值對中存儲私有原始數據 |
內部存儲 | 在設備內存中存儲私有數據 |
外部存儲 | 在共享的外部存儲中存儲公共數據 |
SQLite 數據庫 | 在私有數據庫中存儲結構化數據 |
View
ViewRoot 對應于 ViewRootImpl 類流炕,它是連接 WindowManager 和 DecorView 的紐帶,View 的三大流程均是通過 ViewRoot 來完成的仅胞。在 ActivityThread 中每辟,當 Activity 對象被創(chuàng)建完畢后,會將 DecorView 添加到 Window 中干旧,同時會創(chuàng)建 ViewRootImpl 對象影兽,并將 ViewRootImpl 對象和 DecorView 建立關聯(lián)
View 的整個繪制流程可以分為以下三個階段:
- measure: 判斷是否需要重新計算 View 的大小,需要的話則計算
- layout: 判斷是否需要重新計算 View 的位置莱革,需要的話則計算
- draw: 判斷是否需要重新繪制 View,需要的話則重繪制
MeasureSpec
MeasureSpec表示的是一個32位的整形值讹开,它的高2位表示測量模式SpecMode盅视,低30位表示某種測量模式下的規(guī)格大小SpecSize。MeasureSpec 是 View 類的一個靜態(tài)內部類旦万,用來說明應該如何測量這個 View
Mode | 說明 |
---|---|
UNSPECIFIED | 不指定測量模式, 父視圖沒有限制子視圖的大小闹击,子視圖可以是想要的任何尺寸,通常用于系統(tǒng)內部成艘,應用開發(fā)中很少用到赏半。 |
EXACTLY | 精確測量模式贺归,視圖寬高指定為 match_parent 或具體數值時生效,表示父視圖已經決定了子視圖的精確大小断箫,這種模式下 View 的測量值就是 SpecSize 的值 |
AT_MOST | 最大值測量模式拂酣,當視圖的寬高指定為 wrap_content 時生效,此時子視圖的尺寸可以是不超過父視圖允許的最大尺寸的任何尺寸 |
對于 DecorView 而言仲义,它的MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同決定婶熬;對于普通的 View,它的 MeasureSpec 由父視圖的 MeasureSpec 和其自身的 LayoutParams 共同決定
childLayoutParams/parentSpecMode | EXACTLY | AT_MOST |
---|---|---|
dp/px | EXACTLY(childSize) | EXACTLY(childSize) |
match_parent | EXACTLY(childSize) | AT_MOST(parentSize) |
wrap_content | AT_MOST(parentSize) | AT_MOST(parentSize) |
直接繼承 View 的控件需要重寫 onMeasure 方法并設置 wrap_content 時的自身大小埃撵,因為 View 在布局中使用 wrap_content赵颅,那么它的 specMode 是 AT_MOST 模式,在這種模式下暂刘,它的寬/高等于父容器當前剩余的空間大小饺谬,就相當于使用 match_parent。這解決方式如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 在 wrap_content 的情況下指定內部寬/高(mWidth 和 mHeight`)
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasureDimension(widthSpecSize, mHeight);
}
}
MotionEvent
事件 | 說明 |
---|---|
ACTION_DOWN | 手指剛接觸到屏幕 |
ACTION_MOVE | 手指在屏幕上移動 |
ACTION_UP | 手機從屏幕上松開的一瞬間 |
ACTION_CANCEL | 觸摸事件取消 |
點擊屏幕后松開,事件序列為 DOWN -> UP遂填,點擊屏幕滑動松開贝或,事件序列為 DOWN -> MOVE -> ...> MOVE -> UP。
getX/getY
返回相對于當前View左上角的坐標绪商,getRawX/getRawY
返回相對于屏幕左上角的坐標
TouchSlop是系統(tǒng)所能識別出的被認為滑動的最小距離,不同設備值可能不相同辅鲸,可通過 ViewConfiguration.get(getContext()).getScaledTouchSlop()
獲取格郁。
VelocityTracker
VelocityTracker 可用于追蹤手指在滑動中的速度:
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
velocityTracker.clear();
velocityTracker.recycle();
return false;
}
});
GestureDetector
GestureDetector 輔助檢測用戶的單擊、滑動独悴、長按例书、雙擊等行為:
final GestureDetector mGestureDetector = new GestureDetector(this, 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; }
});
mGestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) { return false; }
@Override
public boolean onDoubleTap(MotionEvent e) { return false; }
@Override
public boolean onDoubleTapEvent(MotionEvent e) { return false; }
});
// 解決長按屏幕后無法拖動的問題
mGestureDetector.setIsLongpressEnabled(false);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
如果是監(jiān)聽滑動相關,建議在 onTouchEvent
中實現(xiàn)刻炒,如果要監(jiān)聽雙擊决采,那么就使用 GestureDectector
。
Scroller
彈性滑動對象坟奥,用于實現(xiàn) View 的彈性滑動树瞭,Scroller 本身無法讓 View 彈性滑動,需要和 View 的 computeScroll
方法配合使用爱谁。startScroll
方法是無法讓 View 滑動的晒喷,invalidate
會導致 View 重繪,重回后會在 draw
方法中又會去調用 computeScroll
方法访敌,computeScroll
方法又會去向 Scroller 獲取當前的 scrollX 和 scrollY凉敲,然后通過 scrollTo
方法實現(xiàn)滑動,接著又調用 postInvalidate
方法如此反復。
Scroller mScroller = new Scroller(mContext);
private void smoothScrollTo(int destX) {
int scrollX = getScrollX();
int delta = destX - scrollX;
// 1000ms 內滑向 destX爷抓,效果就是慢慢滑動
mScroller.startScroll(scrollX, 0 , delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
View 的滑動
-
scrollTo/scrollBy
適合對 View 內容的滑動势决。scrollBy
實際上也是調用了scrollTo
方法:
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
mScrollX的值等于 View 的左邊緣和 View 內容左邊緣在水平方向的距離,mScrollY的值等于 View 上邊緣和 View 內容上邊緣在豎直方向的距離蓝撇。scrollTo
和 scrollBy
只能改變 View 內容的位置而不能改變 View 在布局中的位置果复。
- 使用動畫
操作簡單,主要適用于沒有交互的 View 和實現(xiàn)復雜的動畫效果唉地。 - 改變布局參數
操作稍微復雜据悔,適用于有交互的 View.
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
view.requestLayout();
//或者 view.setLayoutParams(params);
View 的事件分發(fā)
點擊事件達到頂級 View(一般是一個 ViewGroup),會調用 ViewGroup 的 dispatchTouchEvent 方法耘沼,如果頂級 ViewGroup 攔截事件即 onInterceptTouchEvent 返回 true极颓,則事件由 ViewGroup 處理,這時如果 ViewGroup 的 mOnTouchListener 被設置群嗤,則 onTouch 會被調用菠隆,否則 onTouchEvent 會被調用。也就是說如果都提供的話狂秘,onTouch 會屏蔽掉 onTouchEvent骇径。在 onTouchEvent 中,如果設置了 mOnClickListenser者春,則 onClick 會被調用破衔。如果頂級 ViewGroup 不攔截事件,則事件會傳遞給它所在的點擊事件鏈上的子 View钱烟,這時子 View 的 dispatchTouchEvent 會被調用晰筛。如此循環(huán)。
[圖片上傳失敗...(image-70c964-1589948368199)]
[圖片上傳失敗...(image-bb4892-1589948368199)]
ViewGroup 默認不攔截任何事件拴袭。ViewGroup 的 onInterceptTouchEvent 方法默認返回 false读第。
View 沒有 onInterceptTouchEvent 方法,一旦有點擊事件傳遞給它拥刻,onTouchEvent 方法就會被調用怜瞒。
View 在可點擊狀態(tài)下,onTouchEvent 默認會消耗事件般哼。
ACTION_DOWN 被攔截了吴汪,onInterceptTouchEvent 方法執(zhí)行一次后,就會留下記號(mFirstTouchTarget == null)那么往后的 ACTION_MOVE 和 ACTION_UP 都會攔截蒸眠。`
在 Activity 中獲取某個 View 的寬高
- Activity/View#onWindowFocusChanged
// 此時View已經初始化完畢
// 當Activity的窗口得到焦點和失去焦點時均會被調用一次
// 如果頻繁地進行onResume和onPause浇坐,那么onWindowFocusChanged也會被頻繁地調用
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasureWidth();
int height = view.getMeasuredHeight();
}
}
- view.post(runnable)
// 通過post可以將一個runnable投遞到消息隊列的尾部,// 然后等待Looper調用次runnable的時候黔宛,View也已經初
// 始化好了
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
- ViewTreeObserver
// 當View樹的狀態(tài)發(fā)生改變或者View樹內部的View的可見// 性發(fā)生改變時,onGlobalLayout方法將被回調
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
Draw 的基本流程
// 繪制基本上可以分為六個步驟
public void draw(Canvas canvas) {
...
// 步驟一:繪制View的背景
drawBackground(canvas);
...
// 步驟二:如果需要的話,保持canvas的圖層臀晃,為fading做準備
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步驟三:繪制View的內容
onDraw(canvas);
...
// 步驟四:繪制View的子View
dispatchDraw(canvas);
...
// 步驟五:如果需要的話觉渴,繪制View的fading邊緣并恢復圖層
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步驟六:繪制View的裝飾(例如滾動條等等)
onDrawForeground(canvas)
}
自定義 View
- 繼承 View 重寫
onDraw
方法
主要用于實現(xiàn)一些不規(guī)則的效果,靜態(tài)或者動態(tài)地顯示一些不規(guī)則的圖形徽惋,即重寫 onDraw
方法案淋。采用這種方式需要自己支持 wrap_content,并且 padding 也需要自己處理险绘。
- 繼承 ViewGroup 派生特殊的 Layout
主要用于實現(xiàn)自定義布局踢京,采用這種方式需要合適地處理 ViewGroup 的測量、布局兩個過程宦棺,并同時處理子元素的測量和布局過程瓣距。
- 繼承特定的 View
用于擴張某種已有的View的功能
- 繼承特定的 ViewGroup
用于擴張某種已有的ViewGroup的功能
進程
當某個應用組件啟動且該應用沒有運行其他任何組件時,Android 系統(tǒng)會使用單個執(zhí)行線程為應用啟動新的 Linux 進程代咸。默認情況下蹈丸,同一應用的所有組件在相同的進程和線程(稱為“主”線程)中運行。
各類組件元素的清單文件條目<activity>
呐芥、<service>
逻杖、<receiver>
和 <provider>
—均支持 android:process 屬性,此屬性可以指定該組件應在哪個進程運行思瘟。
進程生命周期
1荸百、前臺進程
- 托管用戶正在交互的 Activity(已調用 Activity 的
onResume()
方法) - 托管某個 Service,后者綁定到用戶正在交互的 Activity
- 托管正在“前臺”運行的 Service(服務已調用
startForeground()
) - 托管正執(zhí)行一個生命周期回調的 Service(
onCreate()
滨攻、onStart()
或onDestroy()
) - 托管正執(zhí)行其
onReceive()
方法的 BroadcastReceiver
2够话、可見進程
- 托管不在前臺、但仍對用戶可見的 Activity(已調用其
onPause()
方法)铡买。例如更鲁,如果 re前臺 Activity 啟動了一個對話框,允許在其后顯示上一 Activity奇钞,則有可能會發(fā)生這種情況澡为。 - 托管綁定到可見(或前臺)Activity 的 Service
3、服務進程
- 正在運行已使用 startService() 方法啟動的服務且不屬于上述兩個更高類別進程的進程景埃。
4媒至、后臺進程
- 包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的
onStop()
方法)。通常會有很多后臺進程在運行谷徙,因此它們會保存在 LRU (最近最少使用)列表中拒啰,以確保包含用戶最近查看的 Activity 的進程最后一個被終止。
5完慧、空進程
- 不含任何活動應用組件的進程谋旦。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間。 為使總體系統(tǒng)資源在進程緩存和底層內核緩存之間保持平衡册着,系統(tǒng)往往會終止這些進程拴孤。\
多進程
如果注冊的四大組件中的任意一個組件時用到了多進程,運行該組件時甲捏,都會創(chuàng)建一個新的 Application 對象演熟。對于多進程重復創(chuàng)建 Application 這種情況,只需要在該類中對當前進程加以判斷即可司顿。
public class MyApplication extends Application {
@Override
public void onCreate() {
Log.d("MyApplication", getProcessName(android.os.Process.myPid()));
super.onCreate();
}
/**
* 根據進程 ID 獲取進程名
* @param pid 進程id
* @return 進程名
*/
public String getProcessName(int pid){
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processInfoList = am.getRunningAppProcesses();
if (processInfoList == null) {
return null;
}
for (ActivityManager.RunningAppProcessInfo processInfo : processInfoList) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return null;
}
}
一般來說芒粹,使用多進程會造成以下幾個方面的問題:
- 靜態(tài)成員和單例模式完全失效
- 線程同步機制完全失效
- SharedPreferences 的可靠性下降
- Application 會多次創(chuàng)建
進程存活
OOM_ADJ
ADJ級別 | 取值 | 解釋 |
---|---|---|
UNKNOWN_ADJ | 16 | 一般指將要會緩存進程,無法獲取確定值 |
CACHED_APP_MAX_ADJ | 15 | 不可見進程的adj最大值 |
CACHED_APP_MIN_ADJ | 9 | 不可見進程的adj最小值 |
SERVICE_B_AD | 8 | B List 中的 Service(較老的大溜、使用可能性更谢帷) |
PREVIOUS_APP_ADJ | 7 | 上一個App的進程(往往通過按返回鍵) |
HOME_APP_ADJ | 6 | Home進程 |
SERVICE_ADJ | 5 | 服務進程(Service process) |
HEAVY_WEIGHT_APP_ADJ | 4 | 后臺的重量級進程,system/rootdir/init.rc 文件中設置 |
BACKUP_APP_ADJ | 3 | 備份進程 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知進程猎提,比如后臺音樂播放 |
VISIBLE_APP_ADJ | 1 | 可見進程(Visible process) |
FOREGROUND_APP_ADJ | 0 | 前臺進程(Foreground process) |
PERSISTENT_SERVICE_ADJ | -11 | 關聯(lián)著系統(tǒng)或persistent進程 |
PERSISTENT_PROC_ADJ | -12 | 系統(tǒng) persistent 進程获三,比如telephony |
SYSTEM_ADJ | -16 | 系統(tǒng)進程 |
NATIVE_ADJ | -17 | native進程(不被系統(tǒng)管理) |
進程被殺情況
進程保活方案
- 開啟一個像素的 Activity
- 使用前臺服務
- 多進程相互喚醒
- JobSheduler 喚醒
- 粘性服務 & 與系統(tǒng)服務捆綁
Parcelable 接口
只要實現(xiàn)了 Parcelable 接口锨苏,一個類的對象就可以實現(xiàn)序列化并可以通過 Intent 和 Binder 傳遞疙教。
使用示例
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
private int userId;
protected User(Parcel in) {
userId = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
}
public int getUserId() {
return userId;
}
}
方法說明
Parcel 內部包裝了可序列化的數據,可以在 Binder 中自由傳輸伞租。序列化功能由 writeToParcel
方法完成贞谓,最終是通過 Parcel 中的一系列 write 方法完成。反序列化功能由 CREATOR 來完成葵诈,通過 Parcel 的一系列 read 方法來完成反序列化過程裸弦。
方法 | 功能 |
---|---|
createFromParcel(Parcel in) | 從序列化后的對象中創(chuàng)建原始對象 |
newArray(int size) | 創(chuàng)建指定長度的原始對象數組 |
User(Parcel in) | 從序列化后的對象中創(chuàng)建原始對象 |
writeToParcel(Parcel dest, int flags) | 將當前對象寫入序列化結構中,其中 flags 標識有兩種值:0 或者 1作喘。為 1 時標識當前對象需要作為返回值返回理疙,不能立即釋放資源,幾乎所有情況都為 0 |
describeContents | 返回當前對象的內容描述泞坦。如果含有文件描述符窖贤,返回 1,否則返回 0贰锁,幾乎所有情況都返回 0 |
Parcelable 與 Serializable 對比
- Serializable 使用 I/O 讀寫存儲在硬盤上赃梧,而 Parcelable 是直接在內存中讀寫
- Serializable 會使用反射,序列化和反序列化過程需要大量 I/O 操作豌熄, Parcelable 自已實現(xiàn)封送和解封(marshalled &unmarshalled)操作不需要用反射授嘀,數據也存放在 Native 內存中,效率要快很多
IPC
IPC 即 Inter-Process Communication (進程間通信)锣险。Android 基于 Linux蹄皱,而 Linux 出于安全考慮览闰,不同進程間不能之間操作對方的數據,這叫做“進程隔離”巷折。
在 Linux 系統(tǒng)中焕济,虛擬內存機制為每個進程分配了線性連續(xù)的內存空間,操作系統(tǒng)將這種虛擬內存空間映射到物理內存空間盔几,每個進程有自己的虛擬內存空間,進而不能操作其他進程的內存空間掩幢,只有操作系統(tǒng)才有權限操作物理內存空間逊拍。 進程隔離保證了每個進程的內存安全。
IPC方式
名稱 | 優(yōu)點 | 缺點 | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸 Bundle 支持的數據類型 | 四大組件間的進程間通信 |
文件共享 | 簡單易用 | 不適合高并發(fā)場景际邻,并且無法做到進程間即時通信 | 無并發(fā)訪問情形芯丧,交換簡單的數據實時性不高的場景 |
AIDL | 功能強大,支持一對多并發(fā)通信世曾,支持實時通信 | 使用稍復雜缨恒,需要處理好線程同步 | 一對多通信且有 RPC 需求 |
Messenger | 功能一般,支持一對多串行通信轮听,支持實時通信 | 不能很處理高并發(fā)清醒骗露,不支持 RPC,數據通過 Message 進行傳輸血巍,因此只能傳輸 Bundle 支持的數據類型 | 低并發(fā)的一對多即時通信萧锉,無RPC需求,或者無需返回結果的RPC需求 |
ContentProvider | 在數據源訪問方面功能強大述寡,支持一對多并發(fā)數據共享柿隙,可通過 Call 方法擴展其他操作 | 可以理解為受約束的 AIDL,主要提供數據源的 CRUD 操作 | 一對多的進程間數據共享 |
Socket | 可以通過網絡傳輸字節(jié)流鲫凶,支持一對多并發(fā)實時通信 | 實現(xiàn)細節(jié)稍微有點煩瑣禀崖,不支持直接的RPC | 網絡數據交換 |
Binder
Binder 是 Android 中的一個類,實現(xiàn)了 IBinder 接口螟炫。從 IPC 角度來說波附,Binder 是 Android 中的一種擴進程通信方方式。從 Android 應用層來說不恭,Binder 是客戶端和服務器端進行通信的媒介叶雹,當 bindService 的時候,服務端會返回一個包含了服務端業(yè)務調用的 Binder 對象换吧。
Binder 相較于傳統(tǒng) IPC 來說更適合于Android系統(tǒng)折晦,具體原因的包括如下三點:
- Binder 本身是 C/S 架構的,這一點更符合 Android 系統(tǒng)的架構
- 性能上更有優(yōu)勢:管道沾瓦,消息隊列满着,Socket 的通訊都需要兩次數據拷貝谦炒,而 Binder 只需要一次。要知道风喇,對于系統(tǒng)底層的 IPC 形式宁改,少一次數據拷貝,對整體性能的影響是非常之大的
- 安全性更好:傳統(tǒng) IPC 形式魂莫,無法得到對方的身份標識(UID/GID)还蹲,而在使用 Binder IPC 時,這些身份標示是跟隨調用過程而自動傳遞的耙考。Server 端很容易就可以知道 Client 端的身份谜喊,非常便于做安全檢查
示例:
- 新建AIDL接口文件
RemoteService.aidl
package com.example.mystudyapplication3;
interface IRemoteService {
int getUserId();
}
系統(tǒng)會自動生成 IRemoteService.java
:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.mystudyapplication3;
// Declare any non-default types here with import statements
//import com.example.mystudyapplication3.IUserBean;
public interface IRemoteService extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.mystudyapplication3.IRemoteService {
private static final java.lang.String DESCRIPTOR = "com.example.mystudyapplication3.IRemoteService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.mystudyapplication3.IRemoteService interface,
* generating a proxy if needed.
*/
public static com.example.mystudyapplication3.IRemoteService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.mystudyapplication3.IRemoteService))) {
return ((com.example.mystudyapplication3.IRemoteService) iin);
}
return new com.example.mystudyapplication3.IRemoteService.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getUserId: {
data.enforceInterface(descriptor);
int _result = this.getUserId();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.mystudyapplication3.IRemoteService {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int getUserId() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUserId, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getUserId = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int getUserId() throws android.os.RemoteException;
}
方法 | 含義 |
---|---|
DESCRIPTOR | Binder 的唯一標識,一般用當前的 Binder 的類名表示 |
asInterface(IBinder obj) | 將服務端的 Binder 對象成客戶端所需的 AIDL 接口類型對象倦始,這種轉換過程是區(qū)分進程的斗遏,如果位于同一進程,返回的就是 Stub 對象本身鞋邑,否則返回的是系統(tǒng)封裝后的 Stub.proxy 對象诵次。 |
asBinder | 用于返回當前 Binder 對象 |
onTransact | 運行在服務端中的 Binder 線程池中,遠程請求會通過系統(tǒng)底層封裝后交由此方法來處理 |
定向 tag | 含義 |
---|---|
in | 數據只能由客戶端流向服務端枚碗,服務端將會收到客戶端對象的完整數據逾一,客戶端對象不會因為服務端對傳參的修改而發(fā)生變動。 |
out | 數據只能由服務端流向客戶端视译,服務端將會收到客戶端對象嬉荆,該對象不為空,但是它里面的字段為空酷含,但是在服務端對該對象作任何修改之后客戶端的傳參對象都會同步改動鄙早。 |
inout | 服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務端對該對象的任何變動椅亚。 |
AIDL 通信
Android Interface Definition Language
使用示例:
- 新建AIDL接口文件
// RemoteService.aidl
package com.example.mystudyapplication3;
interface IRemoteService {
int getUserId();
}
- 創(chuàng)建遠程服務
public class RemoteService extends Service {
private int mId = -1;
private Binder binder = new IRemoteService.Stub() {
@Override
public int getUserId() throws RemoteException {
return mId;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
mId = 1256;
return binder;
}
}
- 聲明遠程服務
<service
android:name=".RemoteService"
android:process=":aidl" />
- 綁定遠程服務
public class MainActivity extends AppCompatActivity {
public static final String TAG = "wzq";
IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iRemoteService = IRemoteService.Stub.asInterface(service);
try {
Log.d(TAG, String.valueOf(iRemoteService.getUserId()));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iRemoteService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(MainActivity.this, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);
}
}
Messenger
Messenger可以在不同進程中傳遞 Message 對象限番,在Message中放入我們需要傳遞的數據,就可以輕松地實現(xiàn)數據的進程間傳遞了呀舔。Messenger 是一種輕量級的 IPC 方案弥虐,底層實現(xiàn)是 AIDL。
Window / WindowManager
Window 概念與分類
Window 是一個抽象類媚赖,它的具體實現(xiàn)是 PhoneWindow霜瘪。WindowManager 是外界訪問 Window 的入口,Window 的具體實現(xiàn)位于 WindowManagerService 中惧磺,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程颖对。Android 中所有的視圖都是通過 Window 來呈現(xiàn),因此 Window 實際是 View 的直接管理者磨隘。
Window 類型 | 說明 | 層級 |
---|---|---|
Application Window | 對應著一個 Activity | 1~99 |
Sub Window | 不能單獨存在缤底,只能附屬在父 Window 中顾患,如 Dialog 等 | 1000~1999 |
System Window | 需要權限聲明,如 Toast 和 系統(tǒng)狀態(tài)欄等 | 2000~2999 |
Window 的內部機制
Window 是一個抽象的概念个唧,每一個 Window 對應著一個 View 和一個 ViewRootImpl江解。Window 實際是不存在的,它是以 View 的形式存在徙歼。對 Window 的訪問必須通過 WindowManager犁河,WindowManager 的實現(xiàn)類是 WindowManagerImpl:
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerImpl 沒有直接實現(xiàn) Window 的三大操作,而是全部交給 WindowManagerGlobal 處理魄梯,WindowManagerGlobal 以工廠的形式向外提供自己的實例:
WindowManagerGlobal.java
// 添加
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
···
// 子 Window 的話需要調整一些布局參數
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
···
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 新建一個 ViewRootImpl呼股,并通過其 setView 來更新界面完成 Window 的添加過程
···
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
// 刪除
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
···
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
···
}
}
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
// 更新
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
···
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
在 ViewRootImpl 中最終會通過 WindowSession 來完成 Window 的添加、更新画恰、刪除工作,mWindowSession 的類型是 IWindowSession吸奴,是一個 Binder 對象允扇,真正地實現(xiàn)類是 Session,是一個 IPC 過程则奥。
Window 的創(chuàng)建過程
Activity 的 Window 創(chuàng)建過程
在 Activity 的創(chuàng)建過程中考润,最終會由 ActivityThread 的 performLaunchActivity() 來完成整個啟動過程,該方法內部會通過類加載器創(chuàng)建 Activity 的實例對象读处,并調用 attach 方法關聯(lián)一系列上下文環(huán)境變量糊治。在 Activity 的 attach 方法里,系統(tǒng)會創(chuàng)建所屬的 Window 對象并設置回調接口罚舱,然后在 Activity 的 setContentView 方法中將視圖附屬在 Window 上:
Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
···
}
···
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) { // 如果沒有 DecorView井辜,就創(chuàng)建
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 回調 Activity 的 onContentChanged 方法通知 Activity 視圖已經發(fā)生改變
cb.onContentChanged();
}
}
這個時候 DecorView 還沒有被 WindowManager 正式添加。在 ActivityThread 的 handleResumeActivity 方法中管闷,首先會調用 Activity 的 onResume 方法粥脚,接著調用 Activity 的 makeVisible(),完成 DecorView 的添加和顯示過程:
Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
Dialog 的 Window 創(chuàng)建過程
Dialog 的 Window 的創(chuàng)建過程和 Activity 類似包个,創(chuàng)建同樣是通過 PolicyManager 的 makeNewWindow 方法完成的刷允,創(chuàng)建后的對象實際就是 PhoneWindow。當 Dialog 被關閉時碧囊,會通過 WindowManager 來移除 DecorView:mWindowManager.removeViewImmediate(mDecor)树灶。
Dialog.java
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
···
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
普通 Dialog 必須采用 Activity 的 Context,采用 Application 的 Context 就會報錯糯而,是因為應用 token 所導致天通,應用 token 一般只有 Activity 擁有。系統(tǒng) Window 比較特殊歧蒋,不需要 token土砂。
Toast 的 Window 創(chuàng)建過程
Toast 屬于系統(tǒng) Window 州既,由于其具有定時取消功能,所以系統(tǒng)采用了 Handler萝映。Toast 的內部有兩類 IPC 過程吴叶,第一類是 Toast 訪問 NotificationManagerService,第二類是 NotificationManagerService 回調 Toast 里的 TN 接口序臂。
Toast 內部的視圖由兩種方式蚌卤,一種是系統(tǒng)默認的樣式,另一種是 setView 指定一個自定義 View奥秆,它們都對應 Toast 的一個內部成員 mNextView逊彭。
Toast.java
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
···
public void cancel() {
mTN.cancel();
}
NotificationManagerService.java
private void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
record.callback.show();
scheduleTimeoutLocked(record, false);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
···
private void scheduleTimeoutLocked(ToastRecord r, boolean immediate)
{
Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);
mHandler.removeCallbacksAndMessages(r);
mHandler.sendMessageDelayed(m, delay);
}
Bitmap
配置信息與壓縮方式
Bitmap 中有兩個內部枚舉類:
- Config 是用來設置顏色配置信息
- CompressFormat 是用來設置壓縮方式
Config | 單位像素所占字節(jié)數 | 解析 |
---|---|---|
Bitmap.Config.ALPHA_8 | 1 | 顏色信息只由透明度組成,占8位 |
Bitmap.Config.ARGB_4444 | 2 | 顏色信息由rgba四部分組成构订,每個部分都占4位侮叮,總共占16位 |
Bitmap.Config.ARGB_8888 | 4 | 顏色信息由rgba四部分組成,每個部分都占8位悼瘾,總共占32位囊榜。是Bitmap默認的顏色配置信息,也是最占空間的一種配置 |
Bitmap.Config.RGB_565 | 2 | 顏色信息由rgb三部分組成亥宿,R占5位卸勺,G占6位,B占5位烫扼,總共占16位 |
RGBA_F16 | 8 | Android 8.0 新增(更豐富的色彩表現(xiàn)HDR) |
HARDWARE | Special | Android 8.0 新增 (Bitmap直接存儲在graphic memory) |
通常我們優(yōu)化 Bitmap 時曙求,當需要做性能優(yōu)化或者防止 OOM,我們通常會使用 Bitmap.Config.RGB_565 這個配置映企,因為 Bitmap.Config.ALPHA_8 只有透明度悟狱,顯示一般圖片沒有意義,Bitmap.Config.ARGB_4444 顯示圖片不清楚堰氓, Bitmap.Config.ARGB_8888 占用內存最多芽淡。
CompressFormat | 解析 |
---|---|
Bitmap.CompressFormat.JPEG | 表示以 JPEG 壓縮算法進行圖像壓縮胧砰,壓縮后的格式可以是 .jpg 或者 .jpeg 混巧,是一種有損壓縮 |
Bitmap.CompressFormat.PNG | 顏色信息由 rgba 四部分組成面氓,每個部分都占 4 位终畅,總共占 16 位 |
Bitmap.Config.ARGB_8888 | 顏色信息由 rgba 四部分組成熏兄,每個部分都占 8 位名扛,總共占 32 位箱沦。是 Bitmap 默認的顏色配置信息谈秫,也是最占空間的一種配置 |
Bitmap.Config.RGB_565 | 顏色信息由 rgb 三部分組成抚岗,R 占 5 位或杠,G 占 6 位,B 占 5 位宣蔚,總共占 16 位 |
常用操作
裁剪向抢、縮放认境、旋轉、移動
Matrix matrix = new Matrix();
// 縮放
matrix.postScale(0.8f, 0.9f);
// 左旋挟鸠,參數為正則向右旋
matrix.postRotate(-45);
// 平移, 在上一次修改的基礎上進行再次修改 set 每次操作都是最新的 會覆蓋上次的操作
matrix.postTranslate(100, 80);
// 裁剪并執(zhí)行以上操作
Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
雖然Matrix還可以調用postSkew方法進行傾斜操作叉信,但是卻不可以在此時創(chuàng)建Bitmap時使用。
Bitmap與Drawable轉換
// Drawable -> Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight();
drawable.draw(canvas);
return bitmap;
}
// Bitmap -> Drawable
public static Drawable bitmapToDrawable(Resources resources, Bitmap bm) {
Drawable drawable = new BitmapDrawable(resources, bm);
return drawable;
}
保存與釋放
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
File file = new File(getFilesDir(),"test.jpg");
if(file.exists()){
file.delete();
}
try {
FileOutputStream outputStream=new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,90,outputStream);
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//釋放bitmap的資源艘希,這是一個不可逆轉的操作
bitmap.recycle();
圖片壓縮
public static Bitmap compressImage(Bitmap image) {
if (image == null) {
return null;
}
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream isBm = new ByteArrayInputStream(bytes);
Bitmap bitmap = BitmapFactory.decodeStream(isBm);
return bitmap;
} catch (OutOfMemoryError e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
BitmapFactory
Bitmap創(chuàng)建流程
Option類
常用方法 | 說明 |
---|---|
boolean inJustDecodeBounds | 如果設置為true硼身,不獲取圖片,不分配內存覆享,但會返回圖片的高度寬度信息 |
int inSampleSize | 圖片縮放的倍數 |
int outWidth | 獲取圖片的寬度值 |
int outHeight | 獲取圖片的高度值 |
int inDensity | 用于位圖的像素壓縮比 |
int inTargetDensity | 用于目標位圖的像素壓縮比(要生成的位圖) |
byte[] inTempStorage | 創(chuàng)建臨時文件佳遂,將圖片存儲 |
boolean inScaled | 設置為true時進行圖片壓縮,從inDensity到inTargetDensity |
boolean inDither | 如果為true,解碼器嘗試抖動解碼 |
Bitmap.Config inPreferredConfig | 設置解碼器這個值是設置色彩模式撒顿,默認值是ARGB_8888丑罪,在這個模式下,一個像素點占用4bytes空間凤壁,一般對透明度不做要求的話巍糯,一般采用RGB_565模式,這個模式下一個像素點占用2bytes |
String outMimeType | 設置解碼圖像 |
boolean inPurgeable | 當存儲Pixel的內存空間在系統(tǒng)內存不足時是否可以被回收 |
boolean inInputShareable | inPurgeable為true情況下才生效客扎,是否可以共享一個InputStream |
boolean inPreferQualityOverSpeed | 為true則優(yōu)先保證Bitmap質量其次是解碼速度 |
boolean inMutable | 配置Bitmap是否可以更改,比如:在Bitmap上隔幾個像素加一條線段 |
int inScreenDensity | 當前屏幕的像素密度 |
基本使用
try {
FileInputStream fis = new FileInputStream(filePath);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 設置inJustDecodeBounds為true后罚斗,再使用decodeFile()等方法徙鱼,并不會真正的分配空間,即解碼出來的Bitmap為null针姿,但是可計算出原始圖片的寬度和高度袱吆,即options.outWidth和options.outHeight
BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > height || srcWidth > width) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / height);
} else {
inSampleSize = Math.round(srcWidth / width);
}
}
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
} catch (Exception e) {
e.printStackTrace();
}
內存回收
if(bitmap != null && !bitmap.isRecycled()){
// 回收并且置為null
bitmap.recycle();
bitmap = null;
}
Bitmap 類的構造方法都是私有的,所以開發(fā)者不能直接 new 出一個 Bitmap 對象距淫,只能通過 BitmapFactory 類的各種靜態(tài)方法來實例化一個 Bitmap绞绒。仔細查看 BitmapFactory 的源代碼可以看到,生成 Bitmap 對象最終都是通過 JNI 調用方式實現(xiàn)的榕暇。所以蓬衡,加載 Bitmap 到內存里以后,是包含兩部分內存區(qū)域的彤枢。簡單的說狰晚,一部分是Java 部分的,一部分是 C 部分的缴啡。這個 Bitmap 對象是由 Java 部分分配的壁晒,不用的時候系統(tǒng)就會自動回收了,但是那個對應的 C 可用的內存區(qū)域业栅,虛擬機是不能直接回收的秒咐,這個只能調用底層的功能釋放谬晕。所以需要調用 recycle() 方法來釋放 C 部分的內存。從 Bitmap 類的源代碼也可以看到携取,recycle() 方法里也的確是調用了 JNI 方法了的攒钳。
屏幕適配
單位
dpi
每英寸像素數(dot per inch)dp
密度無關像素 - 一種基于屏幕物理密度的抽象單元。 這些單位相對于 160 dpi 的屏幕歹茶,因此一個 dp 是 160 dpi 屏幕上的一個 px夕玩。 dp 與像素的比率將隨著屏幕密度而變化,但不一定成正比惊豺。為不同設備的 UI 元素的實際大小提供了一致性燎孟。sp
與比例無關的像素 - 這與 dp 單位類似,但它也可以通過用戶的字體大小首選項進行縮放尸昧。建議在指定字體大小時使用此單位揩页,以便根據屏幕密度和用戶偏好調整它們。
dpi = px / inch
density = dpi / 160
dp = px / density
頭條適配方案
private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sNoncompatDensity == 0) {
sNoncompatDensity = appDisplayMetrics.density;
sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
// 監(jiān)聽字體切換
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
// 適配后的dpi將統(tǒng)一為360dpi
final float targetDensity = appDisplayMetrics.widthPixels / 360;
final float targetScaledDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
final int targetDensityDpi = (int)(160 * targetDensity);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi
}
劉海屏適配
- Android P 劉海屏適配方案
Android P 支持最新的全面屏以及為攝像頭和揚聲器預留空間的凹口屏幕烹俗。通過全新的 DisplayCutout 類爆侣,可以確定非功能區(qū)域的位置和形狀,這些區(qū)域不應顯示內容幢妄。要確定這些凹口屏幕區(qū)域是否存在及其位置兔仰,使用 getDisplayCutout() 函數。
DisplayCutout 類方法 | 說明 |
---|---|
getBoundingRects() | 返回Rects的列表蕉鸳,每個Rects都是顯示屏上非功能區(qū)域的邊界矩形 |
getSafeInsetLeft () | 返回安全區(qū)域距離屏幕左邊的距離乎赴,單位是px |
getSafeInsetRight () | 返回安全區(qū)域距離屏幕右邊的距離,單位是px |
getSafeInsetTop () | 返回安全區(qū)域距離屏幕頂部的距離潮尝,單位是px |
getSafeInsetBottom() | 返回安全區(qū)域距離屏幕底部的距離榕吼,單位是px |
Android P 中 WindowManager.LayoutParams 新增了一個布局參數屬性 layoutInDisplayCutoutMode:
模式 | 模式說明 |
---|---|
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有當DisplayCutout完全包含在系統(tǒng)欄中時,才允許窗口延伸到DisplayCutout區(qū)域勉失。 否則羹蚣,窗口布局不與DisplayCutout區(qū)域重疊。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 該窗口決不允許與DisplayCutout區(qū)域重疊乱凿。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 該窗口始終允許延伸到屏幕短邊上的DisplayCutout區(qū)域顽素。 |
- Android P 之前的劉海屏適配
不同廠商的劉海屏適配方案不盡相同,需分別查閱各自的開發(fā)者文檔徒蟆。
Context
Context 本身是一個抽象類戈抄,是對一系列系統(tǒng)服務接口的封裝,包括:內部資源后专、包划鸽、類加載、I/O操作、權限裸诽、主線程嫂用、IPC 和組件啟動等操作的管理。ContextImpl, Activity, Service, Application 這些都是 Context 的直接或間接子類, 關系如下:
ContextWrapper是代理Context的實現(xiàn)丈冬,簡單地將其所有調用委托給另一個Context(mBase)嘱函。
Application、Activity埂蕊、Service通過attach()
調用父類ContextWrapper的attachBaseContext()
, 從而設置父類成員變量 mBase 為 ContextImpl 對象, ContextWrapper 的核心工作都是交給 mBase(ContextImpl) 來完成往弓,這樣可以子類化 Context 以修改行為而無需更改原始 Context。
SharedPreferences
SharedPreferences 采用key-value(鍵值對)形式, 主要用于輕量級的數據存儲, 尤其適合保存應用的配置參數, 但不建議使用 SharedPreferences 來存儲大規(guī)模的數據, 可能會降低性能.
SharedPreferences采用xml文件格式來保存數據, 該文件所在目錄位于 /data/data/<package name>/shared_prefs
蓄氧,如:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="blog">https://github.com/JasonWu1111/Android-Review</string>
</map>
從Android N開始, 創(chuàng)建的 SP 文件模式, 不允許 MODE_WORLD_READABLE
和 MODE_WORLD_WRITEABLE
模塊, 否則會直接拋出異常 SecurityException函似。 MODE_MULTI_PROCESS
這種多進程的方式也是 Google 不推薦的方式, 后續(xù)同樣會不再支持。
當設置 MODE_MULTI_PROCESS 模式, 則每次 getSharedPreferences 過程, 會檢查 SP 文件上次修改時間和文件大小, 一旦所有修改則會重新從磁盤加載文件喉童。
獲取方式
getPreferences
Activity.getPreferences(mode): 以當前 Activity 的類名作為 SP 的文件名. 即 xxxActivity.xml
Activity.java
public SharedPreferences getPreferences(int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
getDefaultSharedPreferences
PreferenceManager.getDefaultSharedPreferences(Context): 以包名加上 _preferences 作為文件名, 以 MODE_PRIVATE 模式創(chuàng)建 SP 文件. 即 packgeName_preferences.xml.
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
getSharedPreferences
直接調用 Context.getSharedPreferences(name, mode)撇寞,所有的方法最終都是調用到如下方法:
class ContextImpl extends Context {
private ArrayMap<String, File> mSharedPrefsPaths;
public SharedPreferences getSharedPreferences(String name, int mode) {
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//先從mSharedPrefsPaths查詢是否存在相應文件
file = mSharedPrefsPaths.get(name);
if (file == null) {
//如果文件不存在, 則創(chuàng)建新的文件
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
}
架構
SharedPreferences 與 Editor 只是兩個接口. SharedPreferencesImpl 和 EditorImpl 分別實現(xiàn)了對應接口。另外, ContextImpl 記錄著 SharedPreferences 的重要數據堂氯。
putxxx()
操作把數據寫入到EditorImpl.mModified蔑担;
apply()/commit()
操作先調用 commitToMemory(), 將數據同步到 SharedPreferencesImpl 的 mMap, 并保存到 MemoryCommitResult 的 mapToWriteToDisk,再調用 enqueueDiskWrite(), 寫入到磁盤文件; 先之前把原有數據保存到 .bak 為后綴的文件,用于在寫磁盤的過程出現(xiàn)任何異逞拾祝可恢復數據;
getxxx()
操作從 SharedPreferencesImpl.mMap 讀取數據.
apply / commit
- apply 沒有返回值, commit 有返回值能知道修改是否提交成功
- apply 是將修改提交到內存啤握,再異步提交到磁盤文件,而 commit 是同步的提交到磁盤文件
- 多并發(fā)的提交 commit 時晶框,需等待正在處理的 commit 數據更新到磁盤文件后才會繼續(xù)往下執(zhí)行排抬,從而降低效率; 而 apply 只是原子更新到內存,后調用 apply 函數會直接覆蓋前面內存數據三妈,從一定程度上提高很多效率。
注意
- 強烈建議不要在 sp 里面存儲特別大的 key/value莫绣,有助于減少卡頓 / anr
- 不要高頻地使用 apply畴蒲,盡可能地批量提交
- 不要使用 MODE_MULTI_PROCESS
- 高頻寫操作的 key 與高頻讀操作的 key 可以適當地拆分文件,由于減少同步鎖競爭
- 不要連續(xù)多次 edit()对室,應該獲取一次獲取 edit()模燥,然后多次執(zhí)行 putxxx(),減少內存波動