接觸 Android 開發也有一段時間了,前段時間便開始想抽空整理一些知識點,經過筆記整理的方式減小本身重複學習的時間成本和提升自身的效率。php
目前先是總結了部分 Android 的知識點,這就是本文的主要分享內容。想特地申明的一點是,這個總結更多的是從本人本身的編程基礎和側重點出發,因此在內容上會有選擇性的忽略以及側重點,參考的博客和圖文有不少,沒辦法一一列出,若是有引用不當的部分會當即刪除,望你們見諒。 知識點目錄能夠再右邊側邊欄查看跳轉。java
另外,以後會整理的知識點還會有 java、Android 源碼、其餘的一些計算機基礎以及常見的面試題等幾個部分,日後的一個月時間裏會陸續補充更新,在 Github 上建立了項目,想關注的歡迎 star 。android
當某個應用組件啓動且該應用沒有運行其餘任何組件時,Android 系統會使用單個執行線程爲應用啓動新的 Linux 進程。默認狀況下,同一應用的全部組件在相同的進程和線程(稱爲「主」線程)中運行。git
各種組件元素的清單文件條目<activity>
、<service>
、<receiver>
和 <provider>
—均支持 android:process 屬性,此屬性能夠指定該組件應在哪一個進程運行。github
一、前臺進程面試
onResume()
方法)startForeground()
)onCreate()
、onStart()
或 onDestroy()
)onReceive()
方法的 BroadcastReceiver二、可見進程算法
onPause()
方法)。例如,若是 re前臺 Activity 啓動了一個對話框,容許在其後顯示上一 Activity,則有可能會發生這種狀況。三、服務進程數據庫
四、後臺進程編程
onStop()
方法)。一般會有不少後臺進程在運行,所以它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。五、空進程canvas
若是註冊的四大組件中的任意一個組件時用到了多進程,運行該組件時,都會建立一個新的 Application 對象。對於多進程重複建立 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;
}
}
複製代碼
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 | 關聯着系統或persistent進程 |
PERSISTENT_PROC_ADJ | -12 | 系統persistent進程,好比telephony |
SYSTEM_ADJ | -16 | 系統進程 |
NATIVE_ADJ | -17 | native進程(不被系統管理) |
應用啓動時,系統會爲應用建立一個名爲「主線程」的執行線程( UI 線程)。 此線程很是重要,由於它負責將事件分派給相應的用戶界面小部件,其中包括繪圖事件。 此外,它也是應用與 Android UI 工具包組件(來自 android.widget
和 android.view
軟件包的組件)進行交互的線程。
系統不會爲每一個組件實例建立單獨的線程。運行於同一進程的全部組件均在 UI 線程中實例化,而且對每一個組件的系統調用均由該線程進行分派。 所以,響應系統回調的方法(例如,報告用戶操做的 onKeyDown() 或生命週期回調方法)始終在進程的 UI 線程中運行。
Android 的單線程模式必須遵照兩條規則:
爲解決此問題,Android 提供了幾種途徑來從其餘線程訪問 UI 線程:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
IPC 即 Inter-Process Communication (進程間通訊)。Android 基於 Linux,而 Linux 出於安全考慮,不一樣進程間不能之間操做對方的數據,這叫作「進程隔離」。
在 Linux 系統中,虛擬內存機制爲每一個進程分配了線性連續的內存空間,操做系統將這種虛擬內存空間映射到物理內存空間,每一個進程有本身的虛擬內存空間,進而不能操做其餘進程的內存空間,只有操做系統纔有權限操做物理內存空間。 進程隔離保證了每一個進程的內存安全。
名稱 | 優勢 | 缺點 | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸Bundle支持的數據類型 | 四大組件間的進程間通訊 |
文件共享 | 簡單易用 | 不適合高併發場景,而且沒法作到進程間即時通訊 | 無併發訪問情形,交換簡單的數據實時性不高的場景 |
AIDL | 功能強大,支持一對多併發通訊,支持實時通訊 | 使用稍複雜,須要處理好線程同步 | 一對多通訊且有RPC需求 |
Messenger | 功能通常,支持一對多串行通訊,支持實時通訊 | 不能很處理高併發清醒,不支持RPC,數據經過Message進行傳輸,所以只能傳輸Bundle支持的數據類型 | 低併發的一對多即時通訊,無RPC需求,或者無需返回結果的RPC需求 |
ContentProvider | 在數據源訪問方面功能強大,支持一對多併發數據共享,可經過Call方法擴展其餘操做 | 能夠理解爲受約束的AIDL,主要提供數據源的CRUD操做 | 一對多的進程間數據共享 |
Socket | 功能請打,能夠經過網絡傳輸字節流,支持一對多併發實時通訊 | 實現細節稍微有點煩瑣,不支持直接的RPC | 網絡數據交換 |
Android Interface Definition Language
// RemoteService.aidl
package com.example.mystudyapplication3;
interface IRemoteService {
int getUserId();
}
複製代碼
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能夠在不一樣進程中傳遞Message對象,在Message中放入咱們須要傳遞的數據,就能夠輕鬆地實現數據的進程間傳遞了。Messenger是一種輕量級的IPC方案,底層實現是AIDL。
Context自己是一個抽象類,是對一系列系統服務接口的封裝,包括:內部資源、包、類加載、I/O操做、權限、主線程、IPC和組件啓動等操做的管理。ContextImpl, Activity, Service, Application這些都是Context的直接或間接子類, 關係以下:
ContextWrapper是代理Context的實現,簡單地將其全部調用委託給另外一個Context(mBase)。
Application、Activity、Service經過attach()
調用父類ContextWrapper的attachBaseContext()
, 從而設置父類成員變量mBase爲ContextImpl對象;, ontextWrapper的核心工做都是交給mBase(即ContextImpl)來完成.
LaunchMode | 說明 |
---|---|
standard | 系統在啓動它的任務中建立activity的新實例 |
singleTop | 若是activity的實例已存在於當前任務的頂部,則系統經過調用其onNewIntent() |
singleTask | 系統建立新task並在task的根目錄下實例化activity。但若是activity的實例已存在於單獨的任務中,則調用其onNewIntent()方法。一次只能存在一個activity實例 |
singleInstance | 相同"singleTask",activity始終是其task的惟一成員; 任何由此開始的activity都在一個單獨的task中打開 |
使用Intent標誌 | 說明 |
---|---|
FLAG_ACTIVITY_NEW_TASK | 同singleTask |
FLAG_ACTIVITY_SINGLE_TOP | 同singleTop |
FLAG_ACTIVITY_CLEAR_TOP | 若是正在啓動的activity已在當前task中運行,則不會啓動該activity的新實例,而是銷燬其上的activity,並調用其onNewIntent() |
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
//step 1: 建立LoadedApk對象
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
... //component初始化過程
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//step 2: 建立Activity對象
Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...
//step 3: 建立Application對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
//step 4: 建立ContextImpl對象
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//step5: 將Application/ContextImpl都attach到Activity對象 [見小節4.1]
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: 執行回調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(); //執行回調onStart
r.stopped = false;
}
if (!r.activity.mFinished) {
//執行回調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;
}
複製代碼
執行此操做的一個好方法是,在片斷內定義一個回調接口,並要求宿主 Activity 實現它。
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());
}
}
...
}
複製代碼
值 | 說明 |
---|---|
START_NOT_STICKY | 若是系統在 onStartCommand() 返回後終止服務,則除非有掛起 Intent 要傳遞,不然系統不會重建服務。這是最安全的選項,能夠避免在沒必要要時以及應用可以輕鬆重啓全部未完成的做業時運行服務 |
START_STICKY | 若是系統在 onStartCommand() 返回後終止服務,則會重建服務並調用 onStartCommand(),但不會從新傳遞最後一個 Intent。相反,除非有掛起 Intent 要啓動服務(在這種狀況下,將傳遞這些 Intent ),不然系統會經過空 Intent 調用 onStartCommand()。這適用於不執行命令、但無限期運行並等待做業的媒體播放器(或相似服務 |
START_REDELIVER_INTENT | 若是系統在 onStartCommand() 返回後終止服務,則會重建服務,並經過傳遞給服務的最後一個 Intent 調用 onStartCommand()。任何掛起 Intent 均依次傳遞。這適用於主動執行應該當即恢復的做業(例以下載文件)的服務 |
<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);
複製代碼
存儲方式 | 說明 |
---|---|
SharedPreferences | 在鍵值對中存儲私有原始數據 |
內部存儲 | 在設備內存中存儲私有數據 |
外部存儲 | 在共享的外部存儲中存儲公共數據 |
SQLite 數據庫 | 在私有數據庫中存儲結構化數據 |
SharedPreferences採用key-value(鍵值對)形式, 主要用於輕量級的數據存儲, 尤爲適合保存應用的配置參數, 但不建議使用SharedPreferences來存儲大規模的數據, 可能會下降性能.
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開始, 建立的SP文件模式, 不容許MODE_WORLD_READABLE
和MODE_WORLD_WRITEABLE
模塊, 不然會直接拋出異常SecurityException。MODE_MULTI_PROCESS
這種多進程的方式也是Google不推薦的方式, 後續一樣會再也不支持。
當設置MODE_MULTI_PROCESS模式, 則每次getSharedPreferences過程, 會檢查SP文件上次修改時間和文件大小, 一旦全部修改則會從新從磁盤加載文件.
Activity.getPreferences(mode): 以當前Activity的類名做爲SP的文件名. 即xxxActivity.xml Activity.java
public SharedPreferences getPreferences(int mode) {
return getSharedPreferences(getLocalClassName(), mode);
}
複製代碼
PreferenceManager.getDefaultSharedPreferences(Context): 以包名加上_preferences做爲文件名, 以MODE_PRIVATE模式建立SP文件. 即packgeName_preferences.xml.
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
複製代碼
直接調用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) {
//若是文件不存在, 則建立新的文件
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
}
複製代碼
SharedPreferences與Editor只是兩個接口. SharedPreferencesImpl和EditorImpl分別實現了對應接口. 另外, ContextImpl記錄着SharedPreferences的重要數據。
putxxx()
操做把數據寫入到EditorImpl.mModified;
apply()/commit()
操做先調用commitToMemory(`, 將數據同步到SharedPreferencesImpl的mMap, 並保存到MemoryCommitResult的mapToWriteToDisk,再調用enqueueDiskWrite(), 寫入到磁盤文件; 先以前把原有數據保存到.bak爲後綴的文件,用於在寫磁盤的過程出現任何異常可恢復數據;
getxxx()
操做從SharedPreferencesImpl.mMap讀取數據.
View的整個繪製流程能夠分爲如下三個階段:
MeasureSpec表示的是一個32位的整形值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize。MeasureSpec是View類的一個靜態內部類,用來講明應該如何測量這個View
Mode | 說明 |
---|---|
UNSPECIFIED | 不指定測量模式, 父視圖沒有限制子視圖的大小,子視圖能夠是想要的任何尺寸,一般用於系統內部,應用開發中不多用到。 |
EXACTLY | 精確測量模式,視圖寬高指定爲match_parent或具體數值時生效,表示父視圖已經決定了子視圖的精確大小,這種模式下View的測量值就是SpecSize的值 |
AT_MOST | 最大值測量模式,當視圖的寬高指定爲wrap_content時生效,此時子視圖的尺寸能夠是不超過父視圖容許的最大尺寸的任何尺寸 |
對於DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;對於普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定
直接繼承View的控件須要重寫onMeasure方法並設置wrap_content時的自身大小,不然在佈局中使用wrap_content就至關於使用match_parent。解決方式以下:
protected void onMeasure(int widthMeasureSpec, int height MeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// 在wrap_content的狀況下指定內部寬/高(mWidth和mHeight)
int heightSpecSize = 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);
}
}
複製代碼
// 此時View已經初始化完畢
// 當Activity的窗口獲得焦點和失去焦點時均會被調用一次
// 若是頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地調用
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasureWidth();
int height = view.getMeasuredHeight();
}
}
複製代碼
// 經過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();
}
});
}
複製代碼
// 當View樹的狀態發生改變或者View樹內部的View的可見// 性發生改變時,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();
}
});
}
複製代碼
// 繪製基本上能夠分爲六個步驟
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)
}
複製代碼
Bitmap中有兩個內部枚舉類:
Config | 單位像素所佔字節數 | 解析 |
---|---|---|
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 新增(更豐富的色彩表現HDR) |
HARDWARE | Special | Android 8.0 新增 (Bitmap直接存儲在graphic memory) |
一般咱們優化Bitmap時,當須要作性能優化或者防止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);
// 裁剪並執行以上操做
Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
複製代碼
雖然Matrix還能夠調用postSkew方法進行傾斜操做,可是卻不能夠在此時建立Bitmap時使用。
// 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;
}
複製代碼
經常使用方法 | 說明 |
---|---|
boolean inJustDecodeBounds | 若是設置爲true,不獲取圖片,不分配內存,但會返回圖片的高度寬度信息 |
int inSampleSize | 圖片縮放的倍數 |
int outWidth | 獲取圖片的寬度值 |
int outHeight | 獲取圖片的高度值 |
int inDensity | 用於位圖的像素壓縮比 |
int inTargetDensity | 用於目標位圖的像素壓縮比(要生成的位圖) |
byte[] inTempStorage | 建立臨時文件,將圖片存儲 |
boolean inScaled | 設置爲true時進行圖片壓縮,從inDensity到inTargetDensity |
boolean inDither | 若是爲true,解碼器嘗試抖動解碼 |
Bitmap.Config inPreferredConfig | 設置解碼器這個值是設置色彩模式,默認值是ARGB_8888,在這個模式下,一個像素點佔用4bytes空間,通常對透明度不作要求的話,通常採用RGB_565模式,這個模式下一個像素點佔用2bytes |
String outMimeType | 設置解碼圖像 |
boolean inPurgeable | 當存儲Pixel的內存空間在系統內存不足時是否能夠被回收 |
boolean inInputShareable | inPurgeable爲true狀況下才生效,是否能夠共享一個InputStream |
boolean inPreferQualityOverSpeed | 爲true則優先保證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類的構造方法都是私有的,因此開發者不能直接new出一個Bitmap對象,只能經過BitmapFactory類的各類靜態方法來實例化一個Bitmap。仔細查看BitmapFactory的源代碼能夠看到,生成Bitmap對象最終都是經過JNI調用方式實現的。因此,加載Bitmap到內存裏之後,是包含兩部份內存區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap對象是由Java部分分配的,不用的時候系統就會自動回收了,可是那個對應的C可用的內存區域,虛擬機是不能直接回收的,這個只能調用底層的功能釋放。因此須要調用recycle()方法來釋放C部分的內存。從Bitmap類的源代碼也能夠看到,recycle()方法裏也的確是調用了JNI方法了的。
Handler有兩個主要用途:(1)安排Message和runnables在未來的某個時刻執行; (2)將要在不一樣於本身的線程上執行的操做排入隊列。(在多個線程併發更新UI的同時保證線程安全。)
Handler建立的時候會採用當前線程的Looper來構造消息循環系統,須要注意的是,線程默認是沒有Looper的,若是須要使用Handler就必須爲線程建立Looper,由於默認的UI主線程,也就是ActivityThread,ActivityThread被建立的時候就會初始化Looper,這也是在主線程中默承認以使用Handler的緣由。
import android.os.AsyncTask;
public class DownloadTask extends AsyncTask<String, Integer, Boolean> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Boolean doInBackground(String... strings) {
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
}
}
複製代碼
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;
// 監聽字體切換
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將統一爲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
}
複製代碼
本次的分享就到這啦,喜歡的話能夠點個贊👍或關注。若有錯誤的地方歡迎你們在評論裏指出。
本文爲我的原創,轉載請註明出處。