This study guide series will be divided into three modules

  • First Java knowledge summary
  • Second Android basic knowledge summary (1) (2)
  • Third Android advanced knowledge summary

The subsequent NDK, cross-platform, underlying source code and other technologies will also take time to sort out the knowledge points for you. If you like it, I hope you will pay attention to it and give me a thumbs up. If you have contact information about your home page, please find me to get the PDF version of the study guide.

Activity

The life cycle

  • Activity A starts another Activity B with the following callback: OnPause () → onCreate() → onStart() → onResume() → onStop(); If B is A transparent topic or A DialogActivity, A’s onStop is not called;
  • Use onSaveInstanceState () to save simple, lightweight UI state
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) }Copy the code

Boot mode

LaunchMode instructions
standard The system creates a new instance of the activity in the task that starts it
singleTop If an instance of the activity already exists at the top of the current task, the system calls its onNewIntent(); otherwise, a new instance is created
singleTask The system creates a new task and instantiates the activity in the root directory of the task. But if an instance of the activity already exists in a separate task, its onNewIntent() method is called, and the instance above it is removed from the stack. Only one instance of an activity can exist at a time
singleInstance Like a singleTask, an activity is always the only member of its task; Any activity that starts from this is opened in a separate task
 

The boot process

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { //step 1: R.packageinfo = getPackageInfo(ainfo.applicationInfo, r.patInfo, context.context_include_code); }... / / component initialization process Java. Lang. This cl = r.p ackageInfo. GetClassLoader (); / / step 2: create the Activity object Activity Activity. = mInstrumentation newActivity (cl, component getClassName (), r.i ntent); . / / step 3: create the Application object Application app = r.p. AckageInfo makeApplication (false, mInstrumentation); if (activity ! = null) {/ / step 4: create ContextImpl object Context appContext = createBaseContextForActivity (r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); //step5: Attach (appContext, this, getInstrumentation(), R.toy, R.I.dent, 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: Implement the callback onCreate mInstrumentation. CallActivityOnCreate (activity, r.s Tate, r.p ersistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } r.activity = activity; r.stopped = true; if (! r.activity.mFinished) { activity.performStart(); // Perform the callback onStart R.survey = false; } if (! R.activity. MFinished) {// Execute callback onRestoreInstanceState if (R.isspersistable ()) {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; }Copy the code

Fragment

The characteristics of

  • Fragment Solve the switching between activities is not smooth, light switching
  • The return result can be received from startActivityForResult, but the View cannot
  • Commit () can only be used to commit a transaction before the Activity saves its state (the user leaves the Activity). If you try to commit after that point, an exception is thrown. This is because the state after submission may be lost if the Activity needs to be resumed. For lost commits that don’t matter, use commitAllowingStateLoss().

The life cycle

Communicate with the Activity

A good way to do this is to define a callback interface within the fragment and ask the host Activity to implement it.

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()); }}... }Copy the code

Service

Service has two working states. One is the start state, which is used to perform background computing. The other is the binding state, which is used for interactions between other components and services.

The boot process

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(); ...}}}Copy the code

The binding process

ActivityThread.java

private void handleBindService(BindServiceData data) { Service s = mServices.get(data.token); ... the 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(); }} ···}}Copy the code

The life cycle

value instructions
START_NOT_STICKY If the system terminates the service after onStartCommand() returns, the system will not rebuild the service unless there is a pending Intent to deliver. This is the safest option to avoid running the service when it is not necessary and when the application can easily restart all outstanding jobs
START_STICKY If the system terminates the service after onStartCommand() returns, the service is rebuilt and onStartCommand() is called, but the last Intent is not redelivered. Instead, onStartCommand() is called with an empty Intent unless there is a pending Intent to start the service (in which case those intents will be delivered). This applies to media players (or similar services) that do not execute commands but run indefinitely and wait for jobs
START_REDELIVER_INTENT If the system terminates the service after onStartCommand() returns, the service is rebuilt and onStartCommand() is called with the last Intent passed to the service. Any pending intents are delivered in sequence. This applies to services that actively perform jobs that should be restored immediately, such as downloading files

Enabling the Foreground Service

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
Copy the code
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);
Copy the code

BroadcastReceiver

After Target 26, the AndroidManifest display cannot declare most broadcasts, except for some necessary broadcasts, such as:

  • ACTION_BOOT_COMPLETED
  • ACTION_TIME_SET
  • ACTION_LOCALE_CHANGED
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(receiver, filter);
Copy the code

The registration process

ContentProvider

ContentProvider manages access to structured data sets. They encapsulate data and provide a mechanism for defining data security. Content providers are standard interfaces that connect data in one process to code running in another.

A ContentProvider cannot be perceived by users. For a ContentProvider component, its internal operations need to be added, deleted, and checked. It maintains a data set inside, which can be either a database implementation or any other type. Internal insert, DELETE, Update, and Query methods, such as List and Map, need to handle thread synchronization because they are called in Binder thread pools.

Contentproviders provide data to other components and even other applications through binders. When the ContentProvider process starts, the ContentProvider will start and publish to AMS at the same time. Note that The onCreate of the ContentProvider is executed before the onCreate of the Application.

The basic use

// 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
Copy the code
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; }}Copy the code

What is the implementation difference between ContentProvider and SQL?

  • The ContentProvider hides the details of the data store, implements internal transparency, and the user only cares about the URI (whether it matches)
  • ContentProvider can realize data sharing between different apps, SQL can only be accessed by their own programs
  • Contentprovider can also add and delete local files, XML and other information

Data is stored

storage instructions
SharedPreferences Store private raw data in key-value pairs
Internal storage Stores private data in device memory
External storage Store common data in shared external storage
SQLite database Store structured data in a private database

View

ViewRoot corresponds to the ViewRootImpl class, which is the link between Windows Manager and the DecorView, and all three of the View’s processes are done through ViewRoot. In ActivityThread, when the Activity object is created, the DecorView is added to the Window and the ViewRootImpl object is created, And associate the ViewRootImpl object with the DecorView

The entire drawing process of a View can be divided into the following three stages:

  • Measure: Determines whether the View size needs to be recalculated or calculated if necessary
  • Layout: Determine whether the View position needs to be recalculated, if necessary
  • Draw: Determines whether the View needs to be redrawn or redrawn if necessary

MeasureSpec

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec is a static inner class of the View class that says how the View should be measured

Mode instructions
UNSPECIFIED The measurement mode is not specified, and the parent view does not limit the size of the child view. The child view can be any size desired. It is usually used inside the system and rarely used in application development.
EXACTLY Precise measurement mode, which takes effect when the width and height of the View is match_parent or a specific value, indicating that the parent View has determined the exact size of the child View. In this mode, the measured value of the View is the value of SpecSize
AT_MOST Max measurement mode, which takes effect when the width and height of the view is specified as wrAP_content, where the size of the child view can be any size that does not exceed the maximum size allowed by the parent view

For a DecorView, its MeasureSpec is determined by the window size and its own LayoutParams; For a normal View, its MeasureSpec is determined by the parent’s MeasureSpec and its own 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)

A control that directly inherits from a View needs to override the onMeasure method and set its size to wrap_content. Since the View uses wrap_content in its layout, its specMode is AT_MOST. Its width/height is equal to the current amount of space left in the parent container, which is equivalent to using match_parent. The solution is as follows:

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); // If (widthSpecMode == MeasureSpec.AT_MOST &&heightSpecMode == MeasureSpec MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(widthSpecSize, mHeight); }}Copy the code

MotionEvent

The event instructions
ACTION_DOWN The finger just touched the screen
ACTION_MOVE Fingers move across the screen
ACTION_UP The moment the phone comes loose from the screen
ACTION_CANCEL Touch event cancellation

Click the screen to release, the event sequence is DOWN -> UP, click the screen to slide to release, the event sequence is DOWN -> MOVE ->… > MOVE -> UP.

GetX /getY returns coordinates relative to the top left corner of the current View, and getRawX/getRawY returns coordinates relative to the top left corner of the screen

TouchSlop is the minimum distance that the system recognizes as sliding. The value may vary from device to device, and can be obtained from viewConfiguration.get (getContext()).getScaledTouchSlop().

VelocityTracker

VelocityTracker can be used to track the speed of the finger in the slide:

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; }});Copy the code

GestureDetector

GestureDetector helps detect user behaviors such as clicking, swiping, long pressing, and double clicking:

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; }}); / / solve the problem of long press screen can't drag mGestureDetector. SetIsLongpressEnabled (false); imageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); }});Copy the code

It is recommended to implement onTouchEvent if you are listening for slides, and GestureDectector if you are listening for double-clicks.

Scroller

The elastic sliding object is used to realize the elastic sliding of View. Scroller itself cannot make the View elastic sliding, so it needs to be used together with the View’s computeScroll method. The startScroll method will not allow the View to slide, invalidate will cause the View to redraw, and then the draw method will call the computeScroll method again. The computeScroll method goes to Scroller to get the current scrollX and scrollY, then slides through the scrollTo method, then calls the postInvalidate method and so on.

Scroller mScroller = new Scroller(mContext); private void smoothScrollTo(int destX) { int scrollX = getScrollX(); int delta = destX - scrollX; Mscroll. startScroll(scrollX, 0, delta, 0, 1000); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); }}Copy the code

The View of sliding

  • scrollTo/scrollBy

    Suitable for swiping View contents.scrollByIt’s actually calledscrollToMethods:
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); }Copy the code

MScrollX is equal to the horizontal distance between the left edge of the View and the left edge of the View content, and mScrollY is equal to the vertical distance between the top edge of the View and the top edge of the View content. ScrollTo and scrollBy can only change the position of the contents of the View, not the position of the View in the layout.

  • Animation is simple to use and is mainly suitable for views without interaction and for complex animation effects.
  • Changing layout parameters is a slightly more complex operation and is suitable for interactive views.
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); params.width += 100; params.leftMargin += 100; view.requestLayout(); / / or the setLayoutParams (params);Copy the code

View event distribution

The dispatchTouchEvent method of the ViewGroup is called. If the top-level ViewGroup intercepts the event, that is, onInterceptTouchEvent returns true. The event is handled by the ViewGroup. If the ViewGroup mOnTouchListener is set, onTouch is called, otherwise onTouchEvent is called. That is, onTouch will block the onTouchEvent if both are provided. In onTouchEvent, onClick is called if mOnClickListenser is set. If the top-level ViewGroup does not intercept the event, the event is passed to the child View in the click event chain in which it is located, at which point the child View’s dispatchTouchEvent is called. And so on.

  • ViewGroup does not intercept any events by default. The onInterceptTouchEvent method of ViewGroup returns false by default.
  • The View does not have an onInterceptTouchEvent method; onTouchEvent is called whenever a hit event is passed to it.
  • OnTouchEvent consumes events by default when the View is clickable.
  • ACTION_DOWN is intercepted, onInterceptTouchEvent is executed once, leaving a mark (mFirstTouchTarget == NULL) so that all subsequent ACTION_MOVE and ACTION_UP will be intercepted. `

Get the width and height of a View in the Activity

  • Activity/View#onWindowFocusChanged
// The Activity window is called once when it gets focus and when it loses focus. // If onResume and onPause are used frequently, Public void onWindowFocusChanged(Boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasureWidth(); int height = view.getMeasuredHeight(); }}Copy the code
  • view.post(runnable)
// Post a runnable to the end of the message queue and wait for Looper to call runnable Protected void onStart() {super.onstart (); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); }}); }Copy the code
  • ViewTreeObserver
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(); }}); }Copy the code

The basic flow of Draw

Public void draw(Canvas Canvas) {... DrawBackground (canvas); drawBackground(canvas); . // Step 2: Keep the canvas layer if necessary, and prepare for fading saveCount = canvas.getSavecount (); . canvas.saveLayer(left, top, right, top + length, null, flags); . // Step 3: Draw the View's content onDraw(canvas); . // Step 4: Draw the child View dispatchDraw(canvas); . DrawRect (left, top, right, top + Length, p); drawRect(left, top, right, top + Length, p); . canvas.restoreToCount(saveCount); . OnDrawForeground (canvas)}Copy the code

The custom View

  • Inherit View overrideonDrawmethods

Mainly used to achieve some irregular effects, static or dynamic display of some irregular graphics, namely rewrite onDraw method. To do this, you need to support wrap_content yourself, and you need to handle the padding yourself.

  • Inherit from ViewGroup to derive a special Layout

It is mainly used to achieve custom layout. In this way, it needs to properly handle the measurement and layout of the ViewGroup, as well as the measurement and layout of child elements.

  • Inherit a particular View

To extend the functionality of an existing View

  • Inherits a specific ViewGroup

Function to extend an existing ViewGroup

process

Process is a running activity of a program on a data set in a computer. It is the basic unit of resource allocation and scheduling in the system and the basis of operating system structure.

When an application component is started and no other components are running, the Android system uses a single thread of execution to start a new Linux process for the application. By default, all components of the same application run in the same process and thread (called the “main” thread).

The manifest file entries for various component elements

,

, < Receiver >, and – all support the Android: Process property, which specifies which process the component should run in.

Process life cycle

1. Foreground processes

  • Hosting the Activity that the user is interacting with (that has called the ActivityonResume()Methods)
  • Hosts a Service that is bound to the Activity the user is interacting with
  • Hosting a Service that is running in the foreground (the Service has been invoked)startForeground())
  • Hosting a Service that is performing a lifecycle callback (onCreate(),onStart() 或 onDestroy())
  • The trustee is executing itonReceive()Methods the BroadcastReceiver

2, visible process

  • Host an Activity that is not in the foreground but is still visible to the user (invoked)onPause()Methods). For example, this might happen if the RE foreground Activity starts a dialog box that allows the previous Activity to be displayed after it.
  • Hosts a Service bound to a visible (or foreground) Activity

3. Service process

  • A process that is running a service started with the startService() method and does not belong to either of the higher categories of processes described above.

4. Background processes

  • The process that contains the Activity that is currently invisible to the user (that has called the Activity)onStop()Methods). There are usually many background processes running, so they are saved in the LRU (Least Recently used) list to ensure that the process containing the Activity the user recently viewed is the last to terminate.

5. Empty processes

  • A process that does not contain any active application components. The sole purpose of keeping such processes is to be used as a cache to reduce the startup time needed to run components in it the next time. To balance overall system resources between the process cache and the underlying kernel cache, systems often kill these processes. \

Multiple processes

If any of the four registered components uses multiple processes, a new Application object is created when the component is run. In the case of multiple processes creating repeated applications, you only need to determine the current process in the class.

public class MyApplication extends Application { @Override public void onCreate() { Log.d("MyApplication", getProcessName(android.os.Process.myPid())); super.onCreate(); } @param PID Process ID * @return Process name */ 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; }}Copy the code

In general, using multiple processes causes problems in the following areas:

  • Static members and singletons are completely invalidated
  • The thread synchronization mechanism has completely failed
  • The reliability of SharedPreferences decreases
  • Application will be created multiple times

Process of survival

OOM_ADJ

ADJ level The values explain
UNKNOWN_ADJ 16 It usually means that the process will be cached and cannot obtain a definite value
CACHED_APP_MAX_ADJ 15 Maximum adj for invisible processes
CACHED_APP_MIN_ADJ 9 Minimum adj for an invisible process
SERVICE_B_AD 8 Services in B List (older, less likely to be used)
PREVIOUS_APP_ADJ 7 Previous App process (usually by pressing the back key)
HOME_APP_ADJ 6 The Home process
SERVICE_ADJ 5 Service Process
HEAVY_WEIGHT_APP_ADJ 4 Background heavyweight process, set in the system/rootdir/init.rc file
BACKUP_APP_ADJ 3 The backup process
PERCEPTIBLE_APP_ADJ 2 Aware of processes, such as background music playback
VISIBLE_APP_ADJ 1 Visible Process
FOREGROUND_APP_ADJ 0 Foreground Process
PERSISTENT_SERVICE_ADJ – 11 Associated with systems or persistent processes
PERSISTENT_PROC_ADJ – 12 Persistent system processes, such as Telephony
SYSTEM_ADJ – 16 System processes
NATIVE_ADJ – 17 Native processes (not managed by the system)

The process was killed

Process survival scheme

  • Start an Activity with one pixel
  • Use the Front Desk service
  • Multiple processes wake each other up
  • JobSheduler awaken
  • Sticky services & bundled with system services

Parcelable interface

As long as the Parcelable interface is implemented, objects of a class can be serialized and delivered with intEnts and binders.

Use the sample

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; }}Copy the code

Method statement

Parcel internally wraps serializable data that can be transferred freely in Binder. Serialization is done by the writeToParcel method and ultimately through a series of write methods in the Parcel. The deserialization is done by CREATOR, through a series of Read methods on Parcel.

methods function
createFromParcel(Parcel in) Creates the original object from the serialized object
newArray(int size) Creates an array of raw objects of the specified length
User(Parcel in) Creates the original object from the serialized object
writeToParcel(Parcel dest, int flags) Writes the current object to a serialization structure where flags flags have two values: 0 or 1. A value of 1 indicates that the current object needs to be returned as a return value and that resources cannot be released immediately. In almost all cases, it is 0
describeContents Returns the content description of the current object. Returns 1 if there is a file descriptor, 0 otherwise, and 0 in almost all cases

Parcelable compared with Serializable

  • Serializable uses I/O read and write storage on the hard disk, while Parcelable is directly read and write in memory
  • Serializable will use reflection, serialization and deserialization process requires a lot of I/O operations, Parcelable own implementation of marshalling and unmarshalling (reflection) operation, data is also stored in Native memory, It’s much faster

IPC

IPC stands for inter-process Communication. Android is based on Linux, which, for security reasons, prevents different processes from manipulating each other’s data, a practice known as process isolation.

Virtual memory mechanism in Linux system, allocation of the linear continuous memory space for each process, the operating system will be this kind of virtual memory mapped to physical memory space, each process has its own virtual memory space, and cannot operate other processes memory space, only the operating system have permission to operate the physical memory space. Process isolation ensures memory safety for each process.

The IPC way

The name of the advantages disadvantages Applicable scenario
Bundle Simple and easy to use Only data types supported by the Bundle can be transferred Interprocess communication between the four components
File sharing Simple and easy to use It is not suitable for high concurrency scenarios and cannot achieve instant communication between processes In the case of no concurrent access, simple data exchange is not real-time
AIDL Powerful, support one – to – many concurrent communication, support real-time communication It’s a little more complicated to use and needs to handle thread synchronization One-to-many communication with RPC requirements
Messenger The function is general, support one to many serial communication, support real-time communication Does not handle high concurrency awareness very well, does not support RPC, and data is transmitted via Message, so only the data types supported by the Bundle can be transmitted Low concurrency one-to-many instant communication, no RPC requirements, or RPC requirements that do not return results
ContentProvider It is powerful for data source access, supports one-to-many concurrent data sharing, and extends other operations through the Call method You can think of it as a constrained AIDL that provides CRUD operations for the data source One-to-many interprocess data sharing
Socket The byte stream can be transmitted over the network, supporting one-to-many concurrent real-time communication The implementation details are a bit cumbersome and do not support direct RPC Network data exchange

Binder

Binder is a class in Android that implements the IBinder interface. Binder is an extended process communicator in Android from an IPC perspective. At the Android application layer, Binder is the communication medium between the client and server. When bindService is performed, the server returns a Binder object containing the service invocation.

Binder is more suitable for Android than traditional IPC for three reasons:

  • Binder itself is C/S architecture, which is more in line with the Android architecture
  • Performance benefits: Pipes, message queues, and sockets all require two copies of data, whereas binders require only one. It should be noted that for the underlying IPC form of the system, one less copy of data has a very large impact on overall performance
  • Better security: With Binder IPC, you can’t get the UID/GID of the other party, but with Binder IPC, these ids are automatically passed along with the call. The Server can easily know the identity of the Client, facilitating security checks

Example:

  • Create an AIDL interface file

RemoteService.aidl

package com.example.mystudyapplication3;

interface IRemoteService {

    int getUserId();

}
Copy the code

The system automatically generates 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; }Copy the code
methods meaning
DESCRIPTOR A unique identifier for a Binder, typically the current Binder class name
asInterface(IBinder obj) Binder objects on the server are converted into AIDL interface objects required by the client. The conversion process is process-specific. If the Binder objects are in the same process, the Stub object itself is returned; otherwise, the system encapsulated stub. proxy object is returned.
asBinder Returns the current Binder object
onTransact Running in a Binder thread pool on the server side, remote requests are processed by this method after being wrapped by the underlying system
Directional tag meaning
in Data can only flow from the client to the server. The server will receive the complete data of the client object. The client object will not change because the server modifies the parameter.
out Data can only flow from the server to the client. The server will receive the client object, which is not empty but has empty fields in it, but the client’s parameter object will change synchronously after the server makes any changes to the object.
inout The server will receive complete information about the object from the client, and the client will synchronize any changes the server makes to the object.

process

AIDL communication

Android Interface Definition Language

Example:

  • Create an AIDL interface file
// RemoteService.aidl
package com.example.mystudyapplication3;

interface IRemoteService {

    int getUserId();

}
Copy the code
  • Creating a Remote Service
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; }}Copy the code
  • Declare remote services
<service
    android:name=".RemoteService"
    android:process=":aidl" />
Copy the code
  • Bind remote Services
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); }}Copy the code

Messenger

Messenger can pass Message objects between different processes. By putting the data we need to pass in the Message, we can easily pass data between processes. Messenger is a lightweight IPC solution with an underlying implementation of AIDL.

Window / WindowManager

Window concept and classification

Window is an abstract class whose concrete implementation is PhoneWindow. WindowManager is the entrance for the outside world to access Windows. The concrete implementation of Windows is located in WindowManagerService. The interaction between WindowManager and WindowManagerService is an IPC process. All views in Android are rendered through Windows, so Windows is actually the direct manager of the View.

The Window type instructions The hierarchy
Application Window Corresponds to an Activity 1~99
Sub Window Cannot exist alone, can only be attached to the parent Window, such as Dialog, etc 1000 ~ 1999
System Window Permission declarations are required, such as Toast and system status bars 2000 ~ 2999

Windows internal mechanism

Window is an abstract concept, and each Window corresponds to a View and a ViewRootImpl. The Window doesn’t really exist; it exists as a View. Windows must be accessed through WindowManager, whose implementation class is 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);
}
Copy the code

WindowManagerImpl does not directly implement Window’s three operations. Instead, it hands over all of them to WindowManagerGlobal, which provides instances of its own in the form of factories:

WindowManagerGlobal.java

// Add public void addView(View View, ViewGroup.LayoutParams params, Display Display, Window parentWindow) {... / / child Window if need to adjust some parameters of the final layout WindowManager. LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow ! = null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else {···} ViewRootImpl root; View panelParentView = null; Synchronized (mLock) {// create a ViewRootImpl, Root = new ViewRootImpl(view.getContext(), display); 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); 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, 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); }}Copy the code

Windows are eventually added, updated, and deleted using WindowSession, a Binder object of type IWindowSession. The real implementation class is Session, which is an IPC process.

Window creation process

Window creation for the Activity

The Activity creation process is eventually started by the ActivityThread’s performLaunchActivity() method, which internally creates an instance of the Activity through the class loader. The attach method is called to associate a set of context variables. In the Attach method of the Activity, the system creates the attached Window object and sets the callback interface, and then attaches the view to the Window in the Activity’s setContentView method:

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(); }Copy the code

PhoneWindow.java

@override public void setContentView(int layoutResID) {if (mContentParent == null) {// If there is no DecorView, Create installDecor (); } else { mContentParent.removeAllViews(); } mLayoutInflater.inflate(layoutResID, mContentParent); final Callback cb = getCallback(); if (cb ! = null && ! Cb.oncontentchanged (); isDestroyed()) {// Call back the Activity's onContentChanged method to inform the Activity that the Activity view has changed. }}Copy the code

The DecorView has not been officially added by WindowManager at this time. In the ActivityThread’s handleResumeActivity method, the Activity’s onResume method is called, followed by the Activity’s makeVisible(), Complete the adding and displaying of the DecorView:

Activity.java

void makeVisible() { if (! mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }Copy the code

Dialog Window creation process

The creation of a Dialog Window is similar to that of an Activity in that it is created through the PolicyManager makeNewWindow method, and the resulting object is actually a PhoneWindow. When the Dialog is closed, will be removed by 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); }Copy the code

A normal Dialog must use the Activity Context. If the Application Context is used, an error is reported because the Application token is normally owned by only the Activity. Windows is special and does not require a token.

Toast Window creation process

Toast belongs to the system Window. Due to its scheduled cancellation function, the system uses Handler. Toast the internal process, there are two kinds of IPC of the first kind is access NotificationManagerService Toast, the second is NotificationManagerService callback interface of TN in the Toast.

There are two ways to View inside Toast. One is the default style of the system, and the other is setView to specify a custom View, which corresponds to mNextView, an internal member of Toast.

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 (); }Copy the code

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); }Copy the code

Bitmap

Configure information and compression mode

There are two internal enumerated classes in Bitmap:

  • Config is used to set the color configuration information
  • CompressFormat is used to set the compression method
Config Number of bytes per pixel parsing
Bitmap.Config.ALPHA_8 1 The color information consists only of transparency, which is 8 bits
Bitmap.Config.ARGB_4444 2 Color information is made up of four RGBA parts, each with 4 bits, for a total of 16 bits
Bitmap.Config.ARGB_8888 4 The color information is made up of four RGBA parts, each with 8 bits, for a total of 32 bits. Is the default Bitmap color configuration and is the most space-intensive configuration
Bitmap.Config.RGB_565 2 Color information is composed of RGB three parts, R for 5, G for 6, B for 5, a total of 16 bits
RGBA_F16 8 Android 8.0 added (Richer color display HDR)
HARDWARE Special New in Android 8.0 (Bitmap stored directly in Graphic Memory)

When we usually optimize Bitmap, when we need to optimize performance or prevent OOM, we usually use bitmap.config. RGB_565 configuration, because bitmap.config. ALPHA_8 only has transparency, it is meaningless to display general images. ARGB_4444 display image is not clear, bitmap.config. ARGB_8888 occupies the most memory.

CompressFormat parsing
Bitmap.CompressFormat.JPEG JPEG compression algorithm for image compression, the compressed format can be.jpgor.jpegIs a lossy compression
Bitmap.CompressFormat.PNG Color information is made up of four RGBA parts, each with 4 bits, for a total of 16 bits
Bitmap.Config.ARGB_8888 The color information is made up of four RGBA parts, each with 8 bits, for a total of 32 bits. Is the default Bitmap color configuration and is the most space-intensive configuration
Bitmap.Config.RGB_565 Color information is composed of RGB three parts, R for 5, G for 6, B for 5, a total of 16 bits

Common operations

Crop, scale, rotate, move

Matrix matrix = new Matrix(); // matrix. PostScale (0.8f, 0.9f); PostRotate (-45); // Left-rotate with regular right-rotate matrix. PostRotate (-45); // Left-rotate with regular right-rotate matrix. PostTranslate (100, 80); // Translate, modify set again after the last change. Each operation is the latest and overwrites the last operation. Bitmap Bitmap = bitmap.createBitmap (source, 0, 0, source.getwidth (), source.getheight (), matrix, true);Copy the code

While Matrix can also call the postSkew method for skewing, it cannot be used when creating a Bitmap at this point.

Bitmap and Drawable conversion

// 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; }Copy the code

Save and release

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(); } // Release bitmap resources, which is an irreversible operation bitmap.recycle();Copy the code

Image compression

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; }Copy the code

BitmapFactory

Bitmap creation process

The Option class

Commonly used method instructions
boolean inJustDecodeBounds If set to true, no image is retrieved, no memory is allocated, but the height and width of the image are returned
int inSampleSize Multiple of zoom in the image
int outWidth Gets the width value of the picture
int outHeight Gets the height value of the picture
int inDensity The pixel compression ratio used for bitmaps
int inTargetDensity Pixel compression ratio for target bitmap (bitmap to be generated)
byte[] inTempStorage Create a temporary file to store the image
boolean inScaled Image compression when set to true, from inDensity to inTargetDensity
boolean inDither If true, the decoder attempts dithering decoding
Bitmap.Config inPreferredConfig The default value is ARGB_8888. In this mode, a pixel takes up 4bytes of space. If transparency is not required, RGB_565 mode is used, and a pixel takes up 2bytes
String outMimeType Setting up decoded images
boolean inPurgeable Whether the memory used to store Pixel can be reclaimed when the system runs out of memory
boolean inInputShareable This parameter takes effect only when inPurgeable is true. Whether to share an InputStream
boolean inPreferQualityOverSpeed If it is true, the Bitmap quality is guaranteed first, followed by the decoding speed
boolean inMutable Configure whether the Bitmap can be changed, for example, by adding a line segment to the Bitmap several pixels apart
int inScreenDensity Pixel density of the current screen

The basic use

try { FileInputStream fis = new FileInputStream(filePath); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // After setting inJustDecodeBounds to true, use decodeFile() and other methods, which do not allocate real space (the decodeFile map is null, but can calculate the width and height of the original image). Namely the options. OutWidth and 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(); }Copy the code

Memory recovery

if(bitmap ! = null && ! IsRecycled ()){// Recycle and set it to null bitmap.recycle(); bitmap = null; }Copy the code

Bitmap class constructors are private, so developers cannot directly create a Bitmap object. Instead, they can instantiate a Bitmap using static methods of the BitmapFactory class. A close look at the BitmapFactory source code shows that generating Bitmap objects is ultimately done through JNI calls. So, after loading the Bitmap into memory, there are two parts of memory. In short, part of it is Java and part of it is C. This Bitmap object is allocated by the Java part, when not used, the system will automatically reclaim, but the corresponding C available memory area, virtual machine is not directly reclaimed, this can only call the underlying function release. So you need to call the recycle() method to free up memory in part C. As you can see from the Bitmap source code, the RECYCLE () method does call JNI methods.

Screen adaptation

unit

  • Dpi Number of pixels per inch
  • Dp density-independent pixel – an abstract unit based on the physical density of the screen. These units are relative to a 160 dpi screen, so a DP is a px on a 160 dpi screen. The ratio of DP to pixels will vary with screen density, but not necessarily directly. Provides consistency in the actual size of UI elements for different devices.
  • Sp scale independent pixels – this is similar to dp units, but it can also be scaled by the user’s font size preferences. It is recommended to use this unit when specifying font sizes so that they can be adjusted according to screen density and user preferences.
dpi = px / inch

density = dpi / 160

dp = px / density
Copy the code

Headline-matching scheme

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; / / to monitor font switch 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() { } }); } / / after a dpi will be unified to 360 dpi 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 }Copy the code

Bangs fit

  • Android P fringe screen adaptation scheme

Android P supports the latest full-screen and a concave screen with room for cameras and speakers. With the new DisplayCutout class, you can determine the position and shape of non-functional areas where content should not be displayed. To determine the presence and location of these notched screen areas, use the getDisplayCutout() function.

DisplayCutout class method instructions
getBoundingRects() Returns a list of Rects, each of which is a bounding rectangle for a non-functional area on the display
getSafeInsetLeft () Returns the distance from the safe zone to the left of the screen, in px
getSafeInsetRight () Returns the distance from the security zone to the right of the screen, in px
getSafeInsetTop () Returns the distance, in px, from the top of the screen to the security zone
getSafeInsetBottom() Returns the distance from the security zone to the bottom of the screen, in px

Android P the WindowManager. LayoutParams added a layout parameter properties layoutInDisplayCutoutMode:

model Model specification
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT Windows are allowed to extend into the DisplayCutout area only when DisplayCutout is fully contained in the system bar. Otherwise, the window layout does not overlap the DisplayCutout area.
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER This window should never be allowed to overlap with the DisplayCutout area.
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES The window is always allowed to extend to the DisplayCutout area on the short side of the screen.
  • Android P before the bangs screen adaptation

Different manufacturers of bangs screen adaptation scheme is not the same, need to consult their respective developer documentation.

Context

Context itself is an abstract class that encapsulates a series of system service interfaces, including the management of internal resources, packages, class loading, I/O operations, permissions, main threads, IPC, and component startup operations. ContextImpl, Activity, Service, and Application are all direct or indirect subclasses of Context as follows:

ContextWrapper is an implementation of the proxy Context, simply delegating all of its calls to another Context (mBase).

Application, Activity, and Service call attachBaseContext() of ContextWrapper by attach(), setting mBase as ContextImpl. The core work of ContextWrapper is left to mBase(ContextImpl) so that the Context can be subclassed to modify its behavior without changing the original Context.

SharedPreferences

SharedPreferences in the form of key-value pairs are mainly used for lightweight data storage, which is especially suitable for storing application configuration parameters. However, it is not recommended to use SharedPreferences to store large-scale data, which may degrade performance.

SharedPreferences XML file format is used to save data. The file is located in /data/data/ /shared_prefs, for example:

<? XML version='1.0' Encoding =' UTF-8 'standalone='yes'? > <map> <string name="blog">https://github.com/JasonWu1111/Android-Review</string> </map>Copy the code

Starting with Android N, the SP file mode created does not allow MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE modules, otherwise SecurityException will be thrown directly. MODE_MULTI_PROCESS is not recommended by Google and will not be supported in the future.

When MODE_MULTI_PROCESS mode is set, the getSharedPreferences process checks the last modification time and file size of the SP file and reloads the file from disk once all changes are made.

access

getPreferences

Activity.getpreferences (mode): Uses the name of the Activity class as the SP file name. Namely xxxActivity. XML Activity. Java

public SharedPreferences getPreferences(int mode) {
    return getSharedPreferences(getLocalClassName(), mode);
}
Copy the code

getDefaultSharedPreferences

PreferenceManager. GetDefaultSharedPreferences (Context) : with the package name plus _preferences as file name, in MODE_PRIVATE mode create SP file. Namely packgeName_preferences. XML.

public static SharedPreferences getDefaultSharedPreferences(Context context) {
    return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
           getDefaultSharedPreferencesMode());
}
Copy the code

getSharedPreferences

Direct call Context. GetSharedPreferences (name, mode), all methods are ultimately calls to the methods as follows:

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<>(); } file = mSharedPrefsPaths file = mSharedPrefsPaths. Get (name); If (file == null) {// If the file does not exist, create a new file file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } return getSharedPreferences(file, mode); }}Copy the code

architecture

SharedPreferences and Editor are just two interfaces. SharedPreferencesImpl and EditorImpl implement corresponding interfaces respectively. In addition, ContextImpl records important data for SharedPreferences.

The putxxx() operation writes data to editorImpl.mmodified;

Apply ()/commit() calls commitToMemory() to synchronize data to the SharedPreferencesImpl mMap, Save to mapToWriteToDisk of MemoryCommitResult, and then call enqueueDiskWrite() to write to disk file. The original data is saved to a file with the suffix.bak. The data can be recovered if any exception occurs during disk writing.

Getxxx () operation from SharedPreferencesImpl. MMap read data.

apply / commit

  • Apply has no return value, and commit has a return value to know if the change was committed successfully
  • Apply commits changes to memory and then asynchronously to disk files, while COMMIT commits to disk files synchronously
  • In the case of multiple concurrent COMMIT, the commit data being processed must be updated to disk files before the execution continues, which reduces the efficiency. However, apply is only atomic update to memory, and the subsequent call to apply directly overwrites the previous memory data, thus improving efficiency to a certain extent.

Pay attention to

  • It is strongly recommended not to store very large keys/values in sp to reduce the lag/ANR
  • Don’t use Apply too often and commit in batches whenever possible
  • Do not use MODE_MULTI_PROCESS
  • High frequency write keys and high frequency read keys can split files appropriately due to reduced synchronization lock contention
  • Instead of edit() multiple times in a row, you should get edit() once and then putxxx() multiple times to reduce memory fluctuations

Message mechanism

Handler mechanism

Handlers have two main uses :(1) scheduling messages and runnables to be executed at some point in the future; (2) Queue operations that will be performed on a thread other than your own. (Keep the UI safe while updating it concurrently in multiple threads.)

Android states that UI access can only be done in the main thread, because Android UI controls are not thread-safe and concurrent access by multiple threads can cause UI controls to be in an unexpected state. Why doesn’t the system lock access to UI controls? There are two disadvantages: locking complicates UI access logic; Secondly, locking mechanism will reduce the efficiency of UI access. If a child thread accesses the UI, the program throws an exception. The ViewRootImpl validates the UI operation by using the ViewRootImpl checkThread method:

ViewRootImpl.java

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
Copy the code
  • Message: The Message object received and processed by the Handler
  • MessageQueue: queue of messages, first in, first out, each thread can have a maximum of one
  • Looper: The message pump is the manager of the MessageQueue. It continuously retrieves messages from the MessageQueue and distributes the messages to the corresponding Handler. Each thread has only one Looper.

Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Since the default UI main thread is an ActivityThread, Looper is initialized when an ActivityThread is created, which is why we can use handlers in the main thread by default.