diff --git a/.gitbook/assets/ComposeShader (1).jpeg b/.gitbook/assets/ComposeShader (1).jpeg new file mode 100644 index 00000000..a95f2971 Binary files /dev/null and b/.gitbook/assets/ComposeShader (1).jpeg differ diff --git a/.gitbook/assets/ComposeShader (2) (1).jpeg b/.gitbook/assets/ComposeShader (2) (1).jpeg new file mode 100644 index 00000000..a95f2971 Binary files /dev/null and b/.gitbook/assets/ComposeShader (2) (1).jpeg differ diff --git a/.gitbook/assets/ComposeShader (2).jpeg b/.gitbook/assets/ComposeShader (2).jpeg new file mode 100644 index 00000000..a95f2971 Binary files /dev/null and b/.gitbook/assets/ComposeShader (2).jpeg differ diff --git a/.gitbook/assets/ComposeShader.jpeg b/.gitbook/assets/ComposeShader.jpeg new file mode 100644 index 00000000..a95f2971 Binary files /dev/null and b/.gitbook/assets/ComposeShader.jpeg differ diff --git a/.gitbook/assets/LinearGradient-1 (1) (1) (1).jpeg b/.gitbook/assets/LinearGradient-1 (1) (1) (1).jpeg new file mode 100644 index 00000000..28174bf7 Binary files /dev/null and b/.gitbook/assets/LinearGradient-1 (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-1 (1) (1).jpeg b/.gitbook/assets/LinearGradient-1 (1) (1).jpeg new file mode 100644 index 00000000..28174bf7 Binary files /dev/null and b/.gitbook/assets/LinearGradient-1 (1) (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-1 (1) (2) (1).jpeg b/.gitbook/assets/LinearGradient-1 (1) (2) (1).jpeg new file mode 100644 index 00000000..28174bf7 Binary files /dev/null and b/.gitbook/assets/LinearGradient-1 (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-1 (1) (2).jpeg b/.gitbook/assets/LinearGradient-1 (1) (2).jpeg new file mode 100644 index 00000000..28174bf7 Binary files /dev/null and b/.gitbook/assets/LinearGradient-1 (1) (2).jpeg differ diff --git a/.gitbook/assets/LinearGradient-1 (1).jpeg b/.gitbook/assets/LinearGradient-1 (1).jpeg new file mode 100644 index 00000000..28174bf7 Binary files /dev/null and b/.gitbook/assets/LinearGradient-1 (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-2 (1) (1) (1).jpeg b/.gitbook/assets/LinearGradient-2 (1) (1) (1).jpeg new file mode 100644 index 00000000..28b8e599 Binary files /dev/null and b/.gitbook/assets/LinearGradient-2 (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-2 (1) (1).jpeg b/.gitbook/assets/LinearGradient-2 (1) (1).jpeg new file mode 100644 index 00000000..28b8e599 Binary files /dev/null and b/.gitbook/assets/LinearGradient-2 (1) (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-2 (1) (2) (1).jpeg b/.gitbook/assets/LinearGradient-2 (1) (2) (1).jpeg new file mode 100644 index 00000000..28b8e599 Binary files /dev/null and b/.gitbook/assets/LinearGradient-2 (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/LinearGradient-2 (1) (2).jpeg b/.gitbook/assets/LinearGradient-2 (1) (2).jpeg new file mode 100644 index 00000000..28b8e599 Binary files /dev/null and b/.gitbook/assets/LinearGradient-2 (1) (2).jpeg differ diff --git a/.gitbook/assets/LinearGradient-2 (1).jpeg b/.gitbook/assets/LinearGradient-2 (1).jpeg new file mode 100644 index 00000000..28b8e599 Binary files /dev/null and b/.gitbook/assets/LinearGradient-2 (1).jpeg differ diff --git a/.gitbook/assets/RadialGradient (1) (1).jpeg b/.gitbook/assets/RadialGradient (1) (1).jpeg new file mode 100644 index 00000000..d05f54e7 Binary files /dev/null and b/.gitbook/assets/RadialGradient (1) (1).jpeg differ diff --git a/.gitbook/assets/RadialGradient (1).jpeg b/.gitbook/assets/RadialGradient (1).jpeg new file mode 100644 index 00000000..d05f54e7 Binary files /dev/null and b/.gitbook/assets/RadialGradient (1).jpeg differ diff --git a/.gitbook/assets/RadialGradient (2) (1).jpeg b/.gitbook/assets/RadialGradient (2) (1).jpeg new file mode 100644 index 00000000..d05f54e7 Binary files /dev/null and b/.gitbook/assets/RadialGradient (2) (1).jpeg differ diff --git a/.gitbook/assets/RadialGradient (2).jpeg b/.gitbook/assets/RadialGradient (2).jpeg new file mode 100644 index 00000000..d05f54e7 Binary files /dev/null and b/.gitbook/assets/RadialGradient (2).jpeg differ diff --git a/.gitbook/assets/RadialGradient.jpeg b/.gitbook/assets/RadialGradient.jpeg new file mode 100644 index 00000000..d05f54e7 Binary files /dev/null and b/.gitbook/assets/RadialGradient.jpeg differ diff --git a/.gitbook/assets/RequestBody.png b/.gitbook/assets/RequestBody.png new file mode 100644 index 00000000..b4c4d367 Binary files /dev/null and b/.gitbook/assets/RequestBody.png differ diff --git a/.gitbook/assets/SweepGradient (1).jpeg b/.gitbook/assets/SweepGradient (1).jpeg new file mode 100644 index 00000000..72bf6e7b Binary files /dev/null and b/.gitbook/assets/SweepGradient (1).jpeg differ diff --git a/.gitbook/assets/SweepGradient (2) (1).jpeg b/.gitbook/assets/SweepGradient (2) (1).jpeg new file mode 100644 index 00000000..72bf6e7b Binary files /dev/null and b/.gitbook/assets/SweepGradient (2) (1).jpeg differ diff --git a/.gitbook/assets/SweepGradient (2).jpeg b/.gitbook/assets/SweepGradient (2).jpeg new file mode 100644 index 00000000..72bf6e7b Binary files /dev/null and b/.gitbook/assets/SweepGradient (2).jpeg differ diff --git a/.gitbook/assets/SweepGradient.jpeg b/.gitbook/assets/SweepGradient.jpeg new file mode 100644 index 00000000..72bf6e7b Binary files /dev/null and b/.gitbook/assets/SweepGradient.jpeg differ diff --git a/.gitbook/assets/activity_lifecycle.png b/.gitbook/assets/activity_lifecycle.png new file mode 100644 index 00000000..879f51f6 Binary files /dev/null and b/.gitbook/assets/activity_lifecycle.png differ diff --git a/.gitbook/assets/android (1).png b/.gitbook/assets/android (1).png new file mode 100644 index 00000000..33016532 Binary files /dev/null and b/.gitbook/assets/android (1).png differ diff --git a/.gitbook/assets/android (2) (1).png b/.gitbook/assets/android (2) (1).png new file mode 100644 index 00000000..33016532 Binary files /dev/null and b/.gitbook/assets/android (2) (1).png differ diff --git a/.gitbook/assets/android (2).png b/.gitbook/assets/android (2).png new file mode 100644 index 00000000..33016532 Binary files /dev/null and b/.gitbook/assets/android (2).png differ diff --git a/.gitbook/assets/android.png b/.gitbook/assets/android.png new file mode 100644 index 00000000..33016532 Binary files /dev/null and b/.gitbook/assets/android.png differ diff --git a/.gitbook/assets/animation-linear.png b/.gitbook/assets/animation-linear.png new file mode 100644 index 00000000..08bd9fc3 Binary files /dev/null and b/.gitbook/assets/animation-linear.png differ diff --git a/.gitbook/assets/animation-nonlinear.png b/.gitbook/assets/animation-nonlinear.png new file mode 100644 index 00000000..31c17120 Binary files /dev/null and b/.gitbook/assets/animation-nonlinear.png differ diff --git a/.gitbook/assets/bitmapshader-circle (1) (1).jpeg b/.gitbook/assets/bitmapshader-circle (1) (1).jpeg new file mode 100644 index 00000000..849bc154 Binary files /dev/null and b/.gitbook/assets/bitmapshader-circle (1) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-circle (1) (2) (1).jpeg b/.gitbook/assets/bitmapshader-circle (1) (2) (1).jpeg new file mode 100644 index 00000000..849bc154 Binary files /dev/null and b/.gitbook/assets/bitmapshader-circle (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-circle (1) (2).jpeg b/.gitbook/assets/bitmapshader-circle (1) (2).jpeg new file mode 100644 index 00000000..849bc154 Binary files /dev/null and b/.gitbook/assets/bitmapshader-circle (1) (2).jpeg differ diff --git a/.gitbook/assets/bitmapshader-circle (1).jpeg b/.gitbook/assets/bitmapshader-circle (1).jpeg new file mode 100644 index 00000000..849bc154 Binary files /dev/null and b/.gitbook/assets/bitmapshader-circle (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-clamp (1).jpeg b/.gitbook/assets/bitmapshader-clamp (1).jpeg new file mode 100644 index 00000000..ac7e71c5 Binary files /dev/null and b/.gitbook/assets/bitmapshader-clamp (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-clamp (2) (1).jpeg b/.gitbook/assets/bitmapshader-clamp (2) (1).jpeg new file mode 100644 index 00000000..ac7e71c5 Binary files /dev/null and b/.gitbook/assets/bitmapshader-clamp (2) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-clamp (2).jpeg b/.gitbook/assets/bitmapshader-clamp (2).jpeg new file mode 100644 index 00000000..ac7e71c5 Binary files /dev/null and b/.gitbook/assets/bitmapshader-clamp (2).jpeg differ diff --git a/.gitbook/assets/bitmapshader-clamp.jpeg b/.gitbook/assets/bitmapshader-clamp.jpeg new file mode 100644 index 00000000..ac7e71c5 Binary files /dev/null and b/.gitbook/assets/bitmapshader-clamp.jpeg differ diff --git a/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (1).jpeg b/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..dcdec8b7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..dcdec8b7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (2).jpeg b/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..dcdec8b7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-mirror (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/bitmapshader-mirror (1) (1) (1).jpeg b/.gitbook/assets/bitmapshader-mirror (1) (1) (1).jpeg new file mode 100644 index 00000000..dcdec8b7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-mirror (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-repeat (1) (1) (1).jpeg b/.gitbook/assets/bitmapshader-repeat (1) (1) (1).jpeg new file mode 100644 index 00000000..f236b2f7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-repeat (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-repeat (1) (1).jpeg b/.gitbook/assets/bitmapshader-repeat (1) (1).jpeg new file mode 100644 index 00000000..f236b2f7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-repeat (1) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-repeat (1) (2) (1).jpeg b/.gitbook/assets/bitmapshader-repeat (1) (2) (1).jpeg new file mode 100644 index 00000000..f236b2f7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-repeat (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/bitmapshader-repeat (1) (2).jpeg b/.gitbook/assets/bitmapshader-repeat (1) (2).jpeg new file mode 100644 index 00000000..f236b2f7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-repeat (1) (2).jpeg differ diff --git a/.gitbook/assets/bitmapshader-repeat (1).jpeg b/.gitbook/assets/bitmapshader-repeat (1).jpeg new file mode 100644 index 00000000..f236b2f7 Binary files /dev/null and b/.gitbook/assets/bitmapshader-repeat (1).jpeg differ diff --git a/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..c7e0fa37 Binary files /dev/null and b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..c7e0fa37 Binary files /dev/null and b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (2).jpeg b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..c7e0fa37 Binary files /dev/null and b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..c7e0fa37 Binary files /dev/null and b/.gitbook/assets/canvas-arc (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..ef930074 Binary files /dev/null and b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..ef930074 Binary files /dev/null and b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..ef930074 Binary files /dev/null and b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (2).jpeg b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..ef930074 Binary files /dev/null and b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1).jpeg b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1).jpeg new file mode 100644 index 00000000..ef930074 Binary files /dev/null and b/.gitbook/assets/canvas-bitmap1 (2) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..b9e2622d Binary files /dev/null and b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..b9e2622d Binary files /dev/null and b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (2).jpeg b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..b9e2622d Binary files /dev/null and b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..b9e2622d Binary files /dev/null and b/.gitbook/assets/canvas-bitmap2 (1) (1) (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..97ba75f6 Binary files /dev/null and b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..97ba75f6 Binary files /dev/null and b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..97ba75f6 Binary files /dev/null and b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..97ba75f6 Binary files /dev/null and b/.gitbook/assets/canvas-circle (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/canvas-line (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-line (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..faa1f859 Binary files /dev/null and b/.gitbook/assets/canvas-line (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-line (1) (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-line (1) (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..faa1f859 Binary files /dev/null and b/.gitbook/assets/canvas-line (1) (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-line (1) (1) (1) (1) (2).jpeg b/.gitbook/assets/canvas-line (1) (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..faa1f859 Binary files /dev/null and b/.gitbook/assets/canvas-line (1) (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-line (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-line (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..faa1f859 Binary files /dev/null and b/.gitbook/assets/canvas-line (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-oval (1) (1).jpeg b/.gitbook/assets/canvas-oval (1) (1).jpeg new file mode 100644 index 00000000..b95463ad Binary files /dev/null and b/.gitbook/assets/canvas-oval (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-oval (1).jpeg b/.gitbook/assets/canvas-oval (1).jpeg new file mode 100644 index 00000000..b95463ad Binary files /dev/null and b/.gitbook/assets/canvas-oval (1).jpeg differ diff --git a/.gitbook/assets/canvas-oval (2) (1).jpeg b/.gitbook/assets/canvas-oval (2) (1).jpeg new file mode 100644 index 00000000..b95463ad Binary files /dev/null and b/.gitbook/assets/canvas-oval (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-oval (2).jpeg b/.gitbook/assets/canvas-oval (2).jpeg new file mode 100644 index 00000000..b95463ad Binary files /dev/null and b/.gitbook/assets/canvas-oval (2).jpeg differ diff --git a/.gitbook/assets/canvas-oval.jpeg b/.gitbook/assets/canvas-oval.jpeg new file mode 100644 index 00000000..b95463ad Binary files /dev/null and b/.gitbook/assets/canvas-oval.jpeg differ diff --git a/.gitbook/assets/canvas-point (1).jpeg b/.gitbook/assets/canvas-point (1).jpeg new file mode 100644 index 00000000..06b30ab3 Binary files /dev/null and b/.gitbook/assets/canvas-point (1).jpeg differ diff --git a/.gitbook/assets/canvas-point (2) (1).jpeg b/.gitbook/assets/canvas-point (2) (1).jpeg new file mode 100644 index 00000000..06b30ab3 Binary files /dev/null and b/.gitbook/assets/canvas-point (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-point (2).jpeg b/.gitbook/assets/canvas-point (2).jpeg new file mode 100644 index 00000000..06b30ab3 Binary files /dev/null and b/.gitbook/assets/canvas-point (2).jpeg differ diff --git a/.gitbook/assets/canvas-point.jpeg b/.gitbook/assets/canvas-point.jpeg new file mode 100644 index 00000000..06b30ab3 Binary files /dev/null and b/.gitbook/assets/canvas-point.jpeg differ diff --git a/.gitbook/assets/canvas-rect (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-rect (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..b2acdc8d Binary files /dev/null and b/.gitbook/assets/canvas-rect (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-rect (1) (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-rect (1) (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..b2acdc8d Binary files /dev/null and b/.gitbook/assets/canvas-rect (1) (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-rect (1) (1) (1) (1) (2).jpeg b/.gitbook/assets/canvas-rect (1) (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..b2acdc8d Binary files /dev/null and b/.gitbook/assets/canvas-rect (1) (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-rect (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-rect (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..b2acdc8d Binary files /dev/null and b/.gitbook/assets/canvas-rect (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-rotate1 (1) (1).jpeg b/.gitbook/assets/canvas-rotate1 (1) (1).jpeg new file mode 100644 index 00000000..493aa15d Binary files /dev/null and b/.gitbook/assets/canvas-rotate1 (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-rotate1 (1).jpeg b/.gitbook/assets/canvas-rotate1 (1).jpeg new file mode 100644 index 00000000..493aa15d Binary files /dev/null and b/.gitbook/assets/canvas-rotate1 (1).jpeg differ diff --git a/.gitbook/assets/canvas-rotate1 (2) (1).jpeg b/.gitbook/assets/canvas-rotate1 (2) (1).jpeg new file mode 100644 index 00000000..493aa15d Binary files /dev/null and b/.gitbook/assets/canvas-rotate1 (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-rotate1 (2).jpeg b/.gitbook/assets/canvas-rotate1 (2).jpeg new file mode 100644 index 00000000..493aa15d Binary files /dev/null and b/.gitbook/assets/canvas-rotate1 (2).jpeg differ diff --git a/.gitbook/assets/canvas-rotate1.jpeg b/.gitbook/assets/canvas-rotate1.jpeg new file mode 100644 index 00000000..493aa15d Binary files /dev/null and b/.gitbook/assets/canvas-rotate1.jpeg differ diff --git a/.gitbook/assets/canvas-rotate2 (1).jpeg b/.gitbook/assets/canvas-rotate2 (1).jpeg new file mode 100644 index 00000000..1b78e517 Binary files /dev/null and b/.gitbook/assets/canvas-rotate2 (1).jpeg differ diff --git a/.gitbook/assets/canvas-rotate2 (2) (1).jpeg b/.gitbook/assets/canvas-rotate2 (2) (1).jpeg new file mode 100644 index 00000000..1b78e517 Binary files /dev/null and b/.gitbook/assets/canvas-rotate2 (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-rotate2 (2).jpeg b/.gitbook/assets/canvas-rotate2 (2).jpeg new file mode 100644 index 00000000..1b78e517 Binary files /dev/null and b/.gitbook/assets/canvas-rotate2 (2).jpeg differ diff --git a/.gitbook/assets/canvas-rotate2.jpeg b/.gitbook/assets/canvas-rotate2.jpeg new file mode 100644 index 00000000..1b78e517 Binary files /dev/null and b/.gitbook/assets/canvas-rotate2.jpeg differ diff --git a/.gitbook/assets/canvas-roundrect (1) (1).jpeg b/.gitbook/assets/canvas-roundrect (1) (1).jpeg new file mode 100644 index 00000000..cd7f2f29 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect (1) (2) (1).jpeg b/.gitbook/assets/canvas-roundrect (1) (2) (1).jpeg new file mode 100644 index 00000000..cd7f2f29 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect (1) (2).jpeg b/.gitbook/assets/canvas-roundrect (1) (2).jpeg new file mode 100644 index 00000000..cd7f2f29 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect (1).jpeg b/.gitbook/assets/canvas-roundrect (1).jpeg new file mode 100644 index 00000000..cd7f2f29 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect (1).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect2 (1) (1).jpeg b/.gitbook/assets/canvas-roundrect2 (1) (1).jpeg new file mode 100644 index 00000000..148dd319 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect2 (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect2 (1).jpeg b/.gitbook/assets/canvas-roundrect2 (1).jpeg new file mode 100644 index 00000000..148dd319 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect2 (1).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect2 (2) (1).jpeg b/.gitbook/assets/canvas-roundrect2 (2) (1).jpeg new file mode 100644 index 00000000..148dd319 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect2 (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect2 (2).jpeg b/.gitbook/assets/canvas-roundrect2 (2).jpeg new file mode 100644 index 00000000..148dd319 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect2 (2).jpeg differ diff --git a/.gitbook/assets/canvas-roundrect2.jpeg b/.gitbook/assets/canvas-roundrect2.jpeg new file mode 100644 index 00000000..148dd319 Binary files /dev/null and b/.gitbook/assets/canvas-roundrect2.jpeg differ diff --git a/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (1).jpg b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (1).jpg new file mode 100644 index 00000000..72d40547 Binary files /dev/null and b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (1).jpg differ diff --git a/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (2) (1).jpg b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (2) (1).jpg new file mode 100644 index 00000000..72d40547 Binary files /dev/null and b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (2).jpg b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (2).jpg new file mode 100644 index 00000000..72d40547 Binary files /dev/null and b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1) (2).jpg differ diff --git a/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..72d40547 Binary files /dev/null and b/.gitbook/assets/canvas-scale1 (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..69c5191b Binary files /dev/null and b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..69c5191b Binary files /dev/null and b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..69c5191b Binary files /dev/null and b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..69c5191b Binary files /dev/null and b/.gitbook/assets/canvas-scale2 (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/canvas-scale3 (1) (1).jpeg b/.gitbook/assets/canvas-scale3 (1) (1).jpeg new file mode 100644 index 00000000..98460202 Binary files /dev/null and b/.gitbook/assets/canvas-scale3 (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-scale3 (1) (2) (1).jpeg b/.gitbook/assets/canvas-scale3 (1) (2) (1).jpeg new file mode 100644 index 00000000..98460202 Binary files /dev/null and b/.gitbook/assets/canvas-scale3 (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-scale3 (1) (2).jpeg b/.gitbook/assets/canvas-scale3 (1) (2).jpeg new file mode 100644 index 00000000..98460202 Binary files /dev/null and b/.gitbook/assets/canvas-scale3 (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-scale3 (1).jpeg b/.gitbook/assets/canvas-scale3 (1).jpeg new file mode 100644 index 00000000..98460202 Binary files /dev/null and b/.gitbook/assets/canvas-scale3 (1).jpeg differ diff --git a/.gitbook/assets/canvas-skew (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-skew (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..149e8067 Binary files /dev/null and b/.gitbook/assets/canvas-skew (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-skew (1) (1) (1).jpeg b/.gitbook/assets/canvas-skew (1) (1) (1).jpeg new file mode 100644 index 00000000..149e8067 Binary files /dev/null and b/.gitbook/assets/canvas-skew (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-skew (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-skew (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..149e8067 Binary files /dev/null and b/.gitbook/assets/canvas-skew (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-skew (1) (1) (2).jpeg b/.gitbook/assets/canvas-skew (1) (1) (2).jpeg new file mode 100644 index 00000000..149e8067 Binary files /dev/null and b/.gitbook/assets/canvas-skew (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-skew (1) (1).jpeg b/.gitbook/assets/canvas-skew (1) (1).jpeg new file mode 100644 index 00000000..149e8067 Binary files /dev/null and b/.gitbook/assets/canvas-skew (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..0c4cb11e Binary files /dev/null and b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..0c4cb11e Binary files /dev/null and b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (2).jpeg b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..0c4cb11e Binary files /dev/null and b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1).jpeg b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..0c4cb11e Binary files /dev/null and b/.gitbook/assets/canvas-text (1) (1) (1) (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/canvas-text2 (1).jpeg b/.gitbook/assets/canvas-text2 (1).jpeg new file mode 100644 index 00000000..cc24b014 Binary files /dev/null and b/.gitbook/assets/canvas-text2 (1).jpeg differ diff --git a/.gitbook/assets/canvas-text2 (2) (1).jpeg b/.gitbook/assets/canvas-text2 (2) (1).jpeg new file mode 100644 index 00000000..cc24b014 Binary files /dev/null and b/.gitbook/assets/canvas-text2 (2) (1).jpeg differ diff --git a/.gitbook/assets/canvas-text2 (2).jpeg b/.gitbook/assets/canvas-text2 (2).jpeg new file mode 100644 index 00000000..cc24b014 Binary files /dev/null and b/.gitbook/assets/canvas-text2 (2).jpeg differ diff --git a/.gitbook/assets/canvas-text2.jpeg b/.gitbook/assets/canvas-text2.jpeg new file mode 100644 index 00000000..cc24b014 Binary files /dev/null and b/.gitbook/assets/canvas-text2.jpeg differ diff --git a/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (1) (1).jpg b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (1) (1).jpg new file mode 100644 index 00000000..77b45454 Binary files /dev/null and b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (1) (1).jpg differ diff --git a/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..77b45454 Binary files /dev/null and b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..77b45454 Binary files /dev/null and b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..77b45454 Binary files /dev/null and b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..77b45454 Binary files /dev/null and b/.gitbook/assets/canvas-translate (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/constraintlayout-2.jpeg b/.gitbook/assets/constraintlayout-2.jpeg new file mode 100644 index 00000000..57298150 Binary files /dev/null and b/.gitbook/assets/constraintlayout-2.jpeg differ diff --git a/.gitbook/assets/constraintlayout-3.jpeg b/.gitbook/assets/constraintlayout-3.jpeg new file mode 100644 index 00000000..f72bf715 Binary files /dev/null and b/.gitbook/assets/constraintlayout-3.jpeg differ diff --git a/.gitbook/assets/content-provider-interaction.png b/.gitbook/assets/content-provider-interaction.png new file mode 100644 index 00000000..17c6cb9a Binary files /dev/null and b/.gitbook/assets/content-provider-interaction.png differ diff --git a/.gitbook/assets/content-provider-tech-stack.png b/.gitbook/assets/content-provider-tech-stack.png new file mode 100644 index 00000000..210cf080 Binary files /dev/null and b/.gitbook/assets/content-provider-tech-stack.png differ diff --git a/.gitbook/assets/diagram_backstack_singletask_multiactivity.png b/.gitbook/assets/diagram_backstack_singletask_multiactivity.png new file mode 100644 index 00000000..b9e5ed48 Binary files /dev/null and b/.gitbook/assets/diagram_backstack_singletask_multiactivity.png differ diff --git a/.gitbook/assets/diagram_multiple_instances.png b/.gitbook/assets/diagram_multiple_instances.png new file mode 100644 index 00000000..606f5409 Binary files /dev/null and b/.gitbook/assets/diagram_multiple_instances.png differ diff --git a/.gitbook/assets/diagram_multitasking.png b/.gitbook/assets/diagram_multitasking.png new file mode 100644 index 00000000..ffe5c651 Binary files /dev/null and b/.gitbook/assets/diagram_multitasking.png differ diff --git a/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..e3486de5 Binary files /dev/null and b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..e3486de5 Binary files /dev/null and b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..e3486de5 Binary files /dev/null and b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..e3486de5 Binary files /dev/null and b/.gitbook/assets/even-odd-winding (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (1).png b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (1).png new file mode 100644 index 00000000..9c9b57b0 Binary files /dev/null and b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (2) (1).png b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..9c9b57b0 Binary files /dev/null and b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (2).png b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (2).png new file mode 100644 index 00000000..9c9b57b0 Binary files /dev/null and b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2).png b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2).png new file mode 100644 index 00000000..9c9b57b0 Binary files /dev/null and b/.gitbook/assets/exoplayer-1 (2) (2) (2) (1) (2) (2) (2).png differ diff --git a/.gitbook/assets/fragment_lifecycle (1).png b/.gitbook/assets/fragment_lifecycle (1).png new file mode 100644 index 00000000..fcaa63b6 Binary files /dev/null and b/.gitbook/assets/fragment_lifecycle (1).png differ diff --git a/.gitbook/assets/fragment_lifecycle.png b/.gitbook/assets/fragment_lifecycle.png new file mode 100644 index 00000000..fcaa63b6 Binary files /dev/null and b/.gitbook/assets/fragment_lifecycle.png differ diff --git a/.gitbook/assets/fragments (1) (1) (1) (1) (1) (1).png b/.gitbook/assets/fragments (1) (1) (1) (1) (1) (1).png new file mode 100644 index 00000000..cb7204b4 Binary files /dev/null and b/.gitbook/assets/fragments (1) (1) (1) (1) (1) (1).png differ diff --git a/.gitbook/assets/fragments (1) (1) (1) (1) (1).png b/.gitbook/assets/fragments (1) (1) (1) (1) (1).png new file mode 100644 index 00000000..cb7204b4 Binary files /dev/null and b/.gitbook/assets/fragments (1) (1) (1) (1) (1).png differ diff --git a/.gitbook/assets/glide1.svg b/.gitbook/assets/glide1.svg new file mode 100644 index 00000000..75ef2c2b --- /dev/null +++ b/.gitbook/assets/glide1.svg @@ -0,0 +1,3 @@ + + +
String
String
InputStream
InputStream
DataUrlLoader.StreamFactory
DataUrlLoader.StreamFactory
StringLoader.StreamFactory
StringLoader.StreamFactory
ParcelFileDescriptor
ParcelFileDescriptor
StringLoader.FileDescriptorFactory
StringLoader.FileDescriptorFactory
AssetFileDescriptor
AssetFileDescriptor
StringLoader.AssetFileDescriptorFactory
StringLoader.AssetFileDescriptorFactory
Uri
Uri
InputStream
InputStream
UriLoader.StreamFactory
UriLoader.StreamFactory
HttpUriLoader.Factory
HttpUriLoader.Factory
ParcelFileDescriptor
ParcelFileDescriptor
UriLoader.FileDescriptorFactory
UriLoader.FileDescriptorFactory
AssetFileDescriptor
AssetFileDescriptor
UriLoader.AssetFileDescriptorFactory
UriLoader.AssetFileDescriptorFactory
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..75cc95e1 Binary files /dev/null and b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..75cc95e1 Binary files /dev/null and b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..75cc95e1 Binary files /dev/null and b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..75cc95e1 Binary files /dev/null and b/.gitbook/assets/heads-up (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/hello (1).svg b/.gitbook/assets/hello (1).svg new file mode 100644 index 00000000..9c2b9237 --- /dev/null +++ b/.gitbook/assets/hello (1).svg @@ -0,0 +1,56 @@ +FrameLayoutGSYTextureRenderViewGSYVideoViewGSYVideoViewBridge getGSYVideoManager();包含一个GSYVideoViewBridge当通过UI来改变播放器状态通过调用GSYVideoViewBridge对应的方法GSYVideoControlViewGSYBaseVideoPlayerGSYVideoPlayerStandardGSYVideoPlayerGSYVideoViewBridge负责UI和播放器之间的交互GSYVideoBaseManagerGSYVideoManagerIPlayerManagervoid initVideoPlayer()//初始化视频播放器ICacheManagerIjkPlayerManagerSystemPlayerManagerExo2PlayerManagerProxyCacheManagerGSYMediaPlayerListener根据播放器的状态来刷新UI比如Prepare完成后加载进度条消失 \ No newline at end of file diff --git a/.gitbook/assets/hello.svg b/.gitbook/assets/hello.svg new file mode 100644 index 00000000..86e5a3af --- /dev/null +++ b/.gitbook/assets/hello.svg @@ -0,0 +1,52 @@ +FrameLayoutGSYTextureRenderViewGSYVideoViewGSYVideoViewBridge getGSYVideoManager();包含一个GSYVideoViewBridge当通过UI来改变播放器状态通过调用GSYVideoViewBridge对应的方法GSYVideoControlViewGSYBaseVideoPlayerGSYVideoPlayerStandardGSYVideoPlayerGSYVideoViewBridge负责UI和播放器之间的交互GSYVideoBaseManagerGSYVideoManagerIPlayerManagervoid initVideoPlayer()//初始化视频播放器ICacheManagerIjkPlayerManagerSystemPlayerManagerExo2PlayerManagerProxyCacheManager \ No newline at end of file diff --git a/.gitbook/assets/hierarchy-linearlayout.png b/.gitbook/assets/hierarchy-linearlayout.png new file mode 100644 index 00000000..cac4caef Binary files /dev/null and b/.gitbook/assets/hierarchy-linearlayout.png differ diff --git a/.gitbook/assets/hierarchy-relativelayout.png b/.gitbook/assets/hierarchy-relativelayout.png new file mode 100644 index 00000000..b3408e58 Binary files /dev/null and b/.gitbook/assets/hierarchy-relativelayout.png differ diff --git a/.gitbook/assets/httpstack.png b/.gitbook/assets/httpstack.png new file mode 100644 index 00000000..2c721517 Binary files /dev/null and b/.gitbook/assets/httpstack.png differ diff --git a/.gitbook/assets/image (1).png b/.gitbook/assets/image (1).png new file mode 100644 index 00000000..1cded626 Binary files /dev/null and b/.gitbook/assets/image (1).png differ diff --git a/.gitbook/assets/image (10).png b/.gitbook/assets/image (10).png new file mode 100644 index 00000000..d9f70332 Binary files /dev/null and b/.gitbook/assets/image (10).png differ diff --git a/.gitbook/assets/image (100).png b/.gitbook/assets/image (100).png new file mode 100644 index 00000000..1804a4b4 Binary files /dev/null and b/.gitbook/assets/image (100).png differ diff --git a/.gitbook/assets/image (101).png b/.gitbook/assets/image (101).png new file mode 100644 index 00000000..4b7d2782 Binary files /dev/null and b/.gitbook/assets/image (101).png differ diff --git a/.gitbook/assets/image (105) (1) (1).png b/.gitbook/assets/image (105) (1) (1).png new file mode 100644 index 00000000..a0a8f356 Binary files /dev/null and b/.gitbook/assets/image (105) (1) (1).png differ diff --git a/.gitbook/assets/image (105) (1).png b/.gitbook/assets/image (105) (1).png new file mode 100644 index 00000000..a0a8f356 Binary files /dev/null and b/.gitbook/assets/image (105) (1).png differ diff --git a/.gitbook/assets/image (106) (1) (1) (1).png b/.gitbook/assets/image (106) (1) (1) (1).png new file mode 100644 index 00000000..88c047af Binary files /dev/null and b/.gitbook/assets/image (106) (1) (1) (1).png differ diff --git a/.gitbook/assets/image (106) (1) (1).png b/.gitbook/assets/image (106) (1) (1).png new file mode 100644 index 00000000..88c047af Binary files /dev/null and b/.gitbook/assets/image (106) (1) (1).png differ diff --git a/.gitbook/assets/image (107) (1) (1) (1) (1) (1).png b/.gitbook/assets/image (107) (1) (1) (1) (1) (1).png new file mode 100644 index 00000000..376a3063 Binary files /dev/null and b/.gitbook/assets/image (107) (1) (1) (1) (1) (1).png differ diff --git a/.gitbook/assets/image (107) (1) (1) (1) (1).png b/.gitbook/assets/image (107) (1) (1) (1) (1).png new file mode 100644 index 00000000..376a3063 Binary files /dev/null and b/.gitbook/assets/image (107) (1) (1) (1) (1).png differ diff --git a/.gitbook/assets/image (11).png b/.gitbook/assets/image (11).png new file mode 100644 index 00000000..98412950 Binary files /dev/null and b/.gitbook/assets/image (11).png differ diff --git a/.gitbook/assets/image (12).png b/.gitbook/assets/image (12).png new file mode 100644 index 00000000..8a5b438d Binary files /dev/null and b/.gitbook/assets/image (12).png differ diff --git a/.gitbook/assets/image (13).png b/.gitbook/assets/image (13).png new file mode 100644 index 00000000..ac64ac91 Binary files /dev/null and b/.gitbook/assets/image (13).png differ diff --git a/.gitbook/assets/image (14).png b/.gitbook/assets/image (14).png new file mode 100644 index 00000000..0c6d17bb Binary files /dev/null and b/.gitbook/assets/image (14).png differ diff --git a/.gitbook/assets/image (15).png b/.gitbook/assets/image (15).png new file mode 100644 index 00000000..d7816c31 Binary files /dev/null and b/.gitbook/assets/image (15).png differ diff --git a/.gitbook/assets/image (16).png b/.gitbook/assets/image (16).png new file mode 100644 index 00000000..f29fe1cd Binary files /dev/null and b/.gitbook/assets/image (16).png differ diff --git a/.gitbook/assets/image (17).png b/.gitbook/assets/image (17).png new file mode 100644 index 00000000..81132c0d Binary files /dev/null and b/.gitbook/assets/image (17).png differ diff --git a/.gitbook/assets/image (18).png b/.gitbook/assets/image (18).png new file mode 100644 index 00000000..db95000d Binary files /dev/null and b/.gitbook/assets/image (18).png differ diff --git a/.gitbook/assets/image (19).png b/.gitbook/assets/image (19).png new file mode 100644 index 00000000..d048729a Binary files /dev/null and b/.gitbook/assets/image (19).png differ diff --git a/.gitbook/assets/image (2).png b/.gitbook/assets/image (2).png new file mode 100644 index 00000000..06adbe4e Binary files /dev/null and b/.gitbook/assets/image (2).png differ diff --git a/.gitbook/assets/image (20).png b/.gitbook/assets/image (20).png new file mode 100644 index 00000000..fbf59c33 Binary files /dev/null and b/.gitbook/assets/image (20).png differ diff --git a/.gitbook/assets/image (21).png b/.gitbook/assets/image (21).png new file mode 100644 index 00000000..93125626 Binary files /dev/null and b/.gitbook/assets/image (21).png differ diff --git a/.gitbook/assets/image (22).png b/.gitbook/assets/image (22).png new file mode 100644 index 00000000..d82da7e1 Binary files /dev/null and b/.gitbook/assets/image (22).png differ diff --git a/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..d61b179b Binary files /dev/null and b/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..d61b179b Binary files /dev/null and b/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..d61b179b Binary files /dev/null and b/.gitbook/assets/image (23) (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/image (23) (2) (2) (2) (2) (2).png b/.gitbook/assets/image (23) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..d61b179b Binary files /dev/null and b/.gitbook/assets/image (23) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/image (23).png b/.gitbook/assets/image (23).png new file mode 100644 index 00000000..9220b8d9 Binary files /dev/null and b/.gitbook/assets/image (23).png differ diff --git a/.gitbook/assets/image (24) (1) (1).png b/.gitbook/assets/image (24) (1) (1).png new file mode 100644 index 00000000..c5a3ac7c Binary files /dev/null and b/.gitbook/assets/image (24) (1) (1).png differ diff --git a/.gitbook/assets/image (24) (1).png b/.gitbook/assets/image (24) (1).png new file mode 100644 index 00000000..c5a3ac7c Binary files /dev/null and b/.gitbook/assets/image (24) (1).png differ diff --git a/.gitbook/assets/image (25).png b/.gitbook/assets/image (25).png new file mode 100644 index 00000000..acd1da6b Binary files /dev/null and b/.gitbook/assets/image (25).png differ diff --git a/.gitbook/assets/image (27) (1).png b/.gitbook/assets/image (27) (1).png new file mode 100644 index 00000000..f5655c3b Binary files /dev/null and b/.gitbook/assets/image (27) (1).png differ diff --git a/.gitbook/assets/image (27).png b/.gitbook/assets/image (27).png new file mode 100644 index 00000000..f5655c3b Binary files /dev/null and b/.gitbook/assets/image (27).png differ diff --git a/.gitbook/assets/image (28) (1) (1).png b/.gitbook/assets/image (28) (1) (1).png new file mode 100644 index 00000000..791f63bf Binary files /dev/null and b/.gitbook/assets/image (28) (1) (1).png differ diff --git a/.gitbook/assets/image (28) (1).png b/.gitbook/assets/image (28) (1).png new file mode 100644 index 00000000..791f63bf Binary files /dev/null and b/.gitbook/assets/image (28) (1).png differ diff --git a/.gitbook/assets/image (29).png b/.gitbook/assets/image (29).png new file mode 100644 index 00000000..35d4ecbb Binary files /dev/null and b/.gitbook/assets/image (29).png differ diff --git a/.gitbook/assets/image (3).png b/.gitbook/assets/image (3).png new file mode 100644 index 00000000..b85d0bab Binary files /dev/null and b/.gitbook/assets/image (3).png differ diff --git a/.gitbook/assets/image (31).png b/.gitbook/assets/image (31).png new file mode 100644 index 00000000..1958e664 Binary files /dev/null and b/.gitbook/assets/image (31).png differ diff --git a/.gitbook/assets/image (34).png b/.gitbook/assets/image (34).png new file mode 100644 index 00000000..b33c26ea Binary files /dev/null and b/.gitbook/assets/image (34).png differ diff --git a/.gitbook/assets/image (35).png b/.gitbook/assets/image (35).png new file mode 100644 index 00000000..a959db51 Binary files /dev/null and b/.gitbook/assets/image (35).png differ diff --git a/.gitbook/assets/image (36).png b/.gitbook/assets/image (36).png new file mode 100644 index 00000000..576ab7f9 Binary files /dev/null and b/.gitbook/assets/image (36).png differ diff --git a/.gitbook/assets/image (37).png b/.gitbook/assets/image (37).png new file mode 100644 index 00000000..6e9f19ae Binary files /dev/null and b/.gitbook/assets/image (37).png differ diff --git a/.gitbook/assets/image (38) (1).png b/.gitbook/assets/image (38) (1).png new file mode 100644 index 00000000..44d8f6f7 Binary files /dev/null and b/.gitbook/assets/image (38) (1).png differ diff --git a/.gitbook/assets/image (38).png b/.gitbook/assets/image (38).png new file mode 100644 index 00000000..44d8f6f7 Binary files /dev/null and b/.gitbook/assets/image (38).png differ diff --git a/.gitbook/assets/image (39).png b/.gitbook/assets/image (39).png new file mode 100644 index 00000000..999fc505 Binary files /dev/null and b/.gitbook/assets/image (39).png differ diff --git a/.gitbook/assets/image (4).png b/.gitbook/assets/image (4).png new file mode 100644 index 00000000..e52d2dab Binary files /dev/null and b/.gitbook/assets/image (4).png differ diff --git a/.gitbook/assets/image (42).png b/.gitbook/assets/image (42).png new file mode 100644 index 00000000..abdf0a96 Binary files /dev/null and b/.gitbook/assets/image (42).png differ diff --git a/.gitbook/assets/image (43).png b/.gitbook/assets/image (43).png new file mode 100644 index 00000000..661f7d6f Binary files /dev/null and b/.gitbook/assets/image (43).png differ diff --git a/.gitbook/assets/image (44).png b/.gitbook/assets/image (44).png new file mode 100644 index 00000000..cd2fa804 Binary files /dev/null and b/.gitbook/assets/image (44).png differ diff --git a/.gitbook/assets/image (45).png b/.gitbook/assets/image (45).png new file mode 100644 index 00000000..0ba9ae41 Binary files /dev/null and b/.gitbook/assets/image (45).png differ diff --git a/.gitbook/assets/image (47) (1) (1).png b/.gitbook/assets/image (47) (1) (1).png new file mode 100644 index 00000000..c08de77e Binary files /dev/null and b/.gitbook/assets/image (47) (1) (1).png differ diff --git a/.gitbook/assets/image (47) (1).png b/.gitbook/assets/image (47) (1).png new file mode 100644 index 00000000..c08de77e Binary files /dev/null and b/.gitbook/assets/image (47) (1).png differ diff --git a/.gitbook/assets/image (48).png b/.gitbook/assets/image (48).png new file mode 100644 index 00000000..dde5b7af Binary files /dev/null and b/.gitbook/assets/image (48).png differ diff --git a/.gitbook/assets/image (49).png b/.gitbook/assets/image (49).png new file mode 100644 index 00000000..1b6a2aaf Binary files /dev/null and b/.gitbook/assets/image (49).png differ diff --git a/.gitbook/assets/image (5).png b/.gitbook/assets/image (5).png new file mode 100644 index 00000000..b8a65846 Binary files /dev/null and b/.gitbook/assets/image (5).png differ diff --git a/.gitbook/assets/image (50).png b/.gitbook/assets/image (50).png new file mode 100644 index 00000000..5174fec0 Binary files /dev/null and b/.gitbook/assets/image (50).png differ diff --git a/.gitbook/assets/image (52).png b/.gitbook/assets/image (52).png new file mode 100644 index 00000000..cf7a5ace Binary files /dev/null and b/.gitbook/assets/image (52).png differ diff --git a/.gitbook/assets/image (53).png b/.gitbook/assets/image (53).png new file mode 100644 index 00000000..c2fe9c39 Binary files /dev/null and b/.gitbook/assets/image (53).png differ diff --git a/.gitbook/assets/image (54).png b/.gitbook/assets/image (54).png new file mode 100644 index 00000000..96ba52b7 Binary files /dev/null and b/.gitbook/assets/image (54).png differ diff --git a/.gitbook/assets/image (55).png b/.gitbook/assets/image (55).png new file mode 100644 index 00000000..89c87973 Binary files /dev/null and b/.gitbook/assets/image (55).png differ diff --git a/.gitbook/assets/image (56).png b/.gitbook/assets/image (56).png new file mode 100644 index 00000000..c3ab3a4d Binary files /dev/null and b/.gitbook/assets/image (56).png differ diff --git a/.gitbook/assets/image (57) (1).png b/.gitbook/assets/image (57) (1).png new file mode 100644 index 00000000..d4d65ce6 Binary files /dev/null and b/.gitbook/assets/image (57) (1).png differ diff --git a/.gitbook/assets/image (57).png b/.gitbook/assets/image (57).png new file mode 100644 index 00000000..d4d65ce6 Binary files /dev/null and b/.gitbook/assets/image (57).png differ diff --git a/.gitbook/assets/image (58).png b/.gitbook/assets/image (58).png new file mode 100644 index 00000000..202d19be Binary files /dev/null and b/.gitbook/assets/image (58).png differ diff --git a/.gitbook/assets/image (59).png b/.gitbook/assets/image (59).png new file mode 100644 index 00000000..14fe37af Binary files /dev/null and b/.gitbook/assets/image (59).png differ diff --git a/.gitbook/assets/image (6).png b/.gitbook/assets/image (6).png new file mode 100644 index 00000000..b4691c82 Binary files /dev/null and b/.gitbook/assets/image (6).png differ diff --git a/.gitbook/assets/image (60).png b/.gitbook/assets/image (60).png new file mode 100644 index 00000000..755e245b Binary files /dev/null and b/.gitbook/assets/image (60).png differ diff --git a/.gitbook/assets/image (61).png b/.gitbook/assets/image (61).png new file mode 100644 index 00000000..cffc6bb9 Binary files /dev/null and b/.gitbook/assets/image (61).png differ diff --git a/.gitbook/assets/image (62).png b/.gitbook/assets/image (62).png new file mode 100644 index 00000000..3fcb8b4a Binary files /dev/null and b/.gitbook/assets/image (62).png differ diff --git a/.gitbook/assets/image (63).png b/.gitbook/assets/image (63).png new file mode 100644 index 00000000..a72de204 Binary files /dev/null and b/.gitbook/assets/image (63).png differ diff --git a/.gitbook/assets/image (64).png b/.gitbook/assets/image (64).png new file mode 100644 index 00000000..534e73a5 Binary files /dev/null and b/.gitbook/assets/image (64).png differ diff --git a/.gitbook/assets/image (65).png b/.gitbook/assets/image (65).png new file mode 100644 index 00000000..8a74ddbf Binary files /dev/null and b/.gitbook/assets/image (65).png differ diff --git a/.gitbook/assets/image (66).png b/.gitbook/assets/image (66).png new file mode 100644 index 00000000..bb551f95 Binary files /dev/null and b/.gitbook/assets/image (66).png differ diff --git a/.gitbook/assets/image (67).png b/.gitbook/assets/image (67).png new file mode 100644 index 00000000..ef6a51d2 Binary files /dev/null and b/.gitbook/assets/image (67).png differ diff --git a/.gitbook/assets/image (68).png b/.gitbook/assets/image (68).png new file mode 100644 index 00000000..d4fb2705 Binary files /dev/null and b/.gitbook/assets/image (68).png differ diff --git a/.gitbook/assets/image (69).png b/.gitbook/assets/image (69).png new file mode 100644 index 00000000..8ae6ec61 Binary files /dev/null and b/.gitbook/assets/image (69).png differ diff --git a/.gitbook/assets/image (7).png b/.gitbook/assets/image (7).png new file mode 100644 index 00000000..e8700f60 Binary files /dev/null and b/.gitbook/assets/image (7).png differ diff --git a/.gitbook/assets/image (71).png b/.gitbook/assets/image (71).png new file mode 100644 index 00000000..e1902bd2 Binary files /dev/null and b/.gitbook/assets/image (71).png differ diff --git a/.gitbook/assets/image (72) (1).png b/.gitbook/assets/image (72) (1).png new file mode 100644 index 00000000..de93c7d0 Binary files /dev/null and b/.gitbook/assets/image (72) (1).png differ diff --git a/.gitbook/assets/image (72).png b/.gitbook/assets/image (72).png new file mode 100644 index 00000000..de93c7d0 Binary files /dev/null and b/.gitbook/assets/image (72).png differ diff --git a/.gitbook/assets/image (73).png b/.gitbook/assets/image (73).png new file mode 100644 index 00000000..7331d089 Binary files /dev/null and b/.gitbook/assets/image (73).png differ diff --git a/.gitbook/assets/image (74).png b/.gitbook/assets/image (74).png new file mode 100644 index 00000000..cfdff0d9 Binary files /dev/null and b/.gitbook/assets/image (74).png differ diff --git a/.gitbook/assets/image (75).png b/.gitbook/assets/image (75).png new file mode 100644 index 00000000..f4296a28 Binary files /dev/null and b/.gitbook/assets/image (75).png differ diff --git a/.gitbook/assets/image (76).png b/.gitbook/assets/image (76).png new file mode 100644 index 00000000..9ab15dcc Binary files /dev/null and b/.gitbook/assets/image (76).png differ diff --git a/.gitbook/assets/image (77).png b/.gitbook/assets/image (77).png new file mode 100644 index 00000000..9bb9a264 Binary files /dev/null and b/.gitbook/assets/image (77).png differ diff --git a/.gitbook/assets/image (78).png b/.gitbook/assets/image (78).png new file mode 100644 index 00000000..9a8dfb97 Binary files /dev/null and b/.gitbook/assets/image (78).png differ diff --git a/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (1).png b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (1).png new file mode 100644 index 00000000..7285c175 Binary files /dev/null and b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (2) (1).png b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..7285c175 Binary files /dev/null and b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (2).png b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (2).png new file mode 100644 index 00000000..7285c175 Binary files /dev/null and b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2).png b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2).png new file mode 100644 index 00000000..7285c175 Binary files /dev/null and b/.gitbook/assets/image (79) (2) (2) (1) (2) (2) (2).png differ diff --git a/.gitbook/assets/image (8).png b/.gitbook/assets/image (8).png new file mode 100644 index 00000000..5e1372f1 Binary files /dev/null and b/.gitbook/assets/image (8).png differ diff --git a/.gitbook/assets/image (80).png b/.gitbook/assets/image (80).png new file mode 100644 index 00000000..96f94f59 Binary files /dev/null and b/.gitbook/assets/image (80).png differ diff --git a/.gitbook/assets/image (81).png b/.gitbook/assets/image (81).png new file mode 100644 index 00000000..8280b01a Binary files /dev/null and b/.gitbook/assets/image (81).png differ diff --git a/.gitbook/assets/image (84) (1) (1).png b/.gitbook/assets/image (84) (1) (1).png new file mode 100644 index 00000000..e2b399b9 Binary files /dev/null and b/.gitbook/assets/image (84) (1) (1).png differ diff --git a/.gitbook/assets/image (84) (1).png b/.gitbook/assets/image (84) (1).png new file mode 100644 index 00000000..e2b399b9 Binary files /dev/null and b/.gitbook/assets/image (84) (1).png differ diff --git a/.gitbook/assets/image (86).png b/.gitbook/assets/image (86).png new file mode 100644 index 00000000..268b3649 Binary files /dev/null and b/.gitbook/assets/image (86).png differ diff --git a/.gitbook/assets/image (87).png b/.gitbook/assets/image (87).png new file mode 100644 index 00000000..70528860 Binary files /dev/null and b/.gitbook/assets/image (87).png differ diff --git a/.gitbook/assets/image (88).png b/.gitbook/assets/image (88).png new file mode 100644 index 00000000..0f593969 Binary files /dev/null and b/.gitbook/assets/image (88).png differ diff --git a/.gitbook/assets/image (89).png b/.gitbook/assets/image (89).png new file mode 100644 index 00000000..53fe5b37 Binary files /dev/null and b/.gitbook/assets/image (89).png differ diff --git a/.gitbook/assets/image (9) (1).png b/.gitbook/assets/image (9) (1).png new file mode 100644 index 00000000..b0756fb1 Binary files /dev/null and b/.gitbook/assets/image (9) (1).png differ diff --git a/.gitbook/assets/image (9).png b/.gitbook/assets/image (9).png new file mode 100644 index 00000000..b0756fb1 Binary files /dev/null and b/.gitbook/assets/image (9).png differ diff --git a/.gitbook/assets/image (90).png b/.gitbook/assets/image (90).png new file mode 100644 index 00000000..72b63357 Binary files /dev/null and b/.gitbook/assets/image (90).png differ diff --git a/.gitbook/assets/image (91).png b/.gitbook/assets/image (91).png new file mode 100644 index 00000000..f9bfee3c Binary files /dev/null and b/.gitbook/assets/image (91).png differ diff --git a/.gitbook/assets/image (92).png b/.gitbook/assets/image (92).png new file mode 100644 index 00000000..61de25e9 Binary files /dev/null and b/.gitbook/assets/image (92).png differ diff --git a/.gitbook/assets/image (93).png b/.gitbook/assets/image (93).png new file mode 100644 index 00000000..2a21e106 Binary files /dev/null and b/.gitbook/assets/image (93).png differ diff --git a/.gitbook/assets/image (94).png b/.gitbook/assets/image (94).png new file mode 100644 index 00000000..a122d821 Binary files /dev/null and b/.gitbook/assets/image (94).png differ diff --git a/.gitbook/assets/image (95).png b/.gitbook/assets/image (95).png new file mode 100644 index 00000000..cd339228 Binary files /dev/null and b/.gitbook/assets/image (95).png differ diff --git a/.gitbook/assets/image (96).png b/.gitbook/assets/image (96).png new file mode 100644 index 00000000..e4989818 Binary files /dev/null and b/.gitbook/assets/image (96).png differ diff --git a/.gitbook/assets/image (97).png b/.gitbook/assets/image (97).png new file mode 100644 index 00000000..0c364f98 Binary files /dev/null and b/.gitbook/assets/image (97).png differ diff --git a/.gitbook/assets/image (98).png b/.gitbook/assets/image (98).png new file mode 100644 index 00000000..1ba9307d Binary files /dev/null and b/.gitbook/assets/image (98).png differ diff --git a/.gitbook/assets/image (99).png b/.gitbook/assets/image (99).png new file mode 100644 index 00000000..0ba002d5 Binary files /dev/null and b/.gitbook/assets/image (99).png differ diff --git a/.gitbook/assets/image.png b/.gitbook/assets/image.png new file mode 100644 index 00000000..2a26362d Binary files /dev/null and b/.gitbook/assets/image.png differ diff --git a/.gitbook/assets/intent-chooser (1).png b/.gitbook/assets/intent-chooser (1).png new file mode 100644 index 00000000..8a8d3393 Binary files /dev/null and b/.gitbook/assets/intent-chooser (1).png differ diff --git a/.gitbook/assets/intent-chooser.png b/.gitbook/assets/intent-chooser.png new file mode 100644 index 00000000..8a8d3393 Binary files /dev/null and b/.gitbook/assets/intent-chooser.png differ diff --git a/.gitbook/assets/intent-filters@2x (1) (1).png b/.gitbook/assets/intent-filters@2x (1) (1).png new file mode 100644 index 00000000..5b1e38bc Binary files /dev/null and b/.gitbook/assets/intent-filters@2x (1) (1).png differ diff --git a/.gitbook/assets/intent-filters@2x (1).png b/.gitbook/assets/intent-filters@2x (1).png new file mode 100644 index 00000000..5b1e38bc Binary files /dev/null and b/.gitbook/assets/intent-filters@2x (1).png differ diff --git a/.gitbook/assets/interceptor.png b/.gitbook/assets/interceptor.png new file mode 100644 index 00000000..5f0f7085 Binary files /dev/null and b/.gitbook/assets/interceptor.png differ diff --git a/.gitbook/assets/inverse_winding-1 (1) (1).jpeg b/.gitbook/assets/inverse_winding-1 (1) (1).jpeg new file mode 100644 index 00000000..a073e658 Binary files /dev/null and b/.gitbook/assets/inverse_winding-1 (1) (1).jpeg differ diff --git a/.gitbook/assets/inverse_winding-1 (1) (2) (1).jpeg b/.gitbook/assets/inverse_winding-1 (1) (2) (1).jpeg new file mode 100644 index 00000000..a073e658 Binary files /dev/null and b/.gitbook/assets/inverse_winding-1 (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/inverse_winding-1 (1) (2).jpeg b/.gitbook/assets/inverse_winding-1 (1) (2).jpeg new file mode 100644 index 00000000..a073e658 Binary files /dev/null and b/.gitbook/assets/inverse_winding-1 (1) (2).jpeg differ diff --git a/.gitbook/assets/inverse_winding-1 (1).jpeg b/.gitbook/assets/inverse_winding-1 (1).jpeg new file mode 100644 index 00000000..a073e658 Binary files /dev/null and b/.gitbook/assets/inverse_winding-1 (1).jpeg differ diff --git a/.gitbook/assets/inverse_winding-2 (1) (1) (1) (1).jpeg b/.gitbook/assets/inverse_winding-2 (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..3b7ea917 Binary files /dev/null and b/.gitbook/assets/inverse_winding-2 (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/inverse_winding-2 (1) (1) (1).jpeg b/.gitbook/assets/inverse_winding-2 (1) (1) (1).jpeg new file mode 100644 index 00000000..3b7ea917 Binary files /dev/null and b/.gitbook/assets/inverse_winding-2 (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/inverse_winding-2 (1) (1) (2) (1).jpeg b/.gitbook/assets/inverse_winding-2 (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..3b7ea917 Binary files /dev/null and b/.gitbook/assets/inverse_winding-2 (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/inverse_winding-2 (1) (1) (2).jpeg b/.gitbook/assets/inverse_winding-2 (1) (1) (2).jpeg new file mode 100644 index 00000000..3b7ea917 Binary files /dev/null and b/.gitbook/assets/inverse_winding-2 (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/inverse_winding-2 (1) (1).jpeg b/.gitbook/assets/inverse_winding-2 (1) (1).jpeg new file mode 100644 index 00000000..3b7ea917 Binary files /dev/null and b/.gitbook/assets/inverse_winding-2 (1) (1).jpeg differ diff --git a/.gitbook/assets/layout-listitem.png b/.gitbook/assets/layout-listitem.png new file mode 100644 index 00000000..9cb241df Binary files /dev/null and b/.gitbook/assets/layout-listitem.png differ diff --git a/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..ac09306d Binary files /dev/null and b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..ac09306d Binary files /dev/null and b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..ac09306d Binary files /dev/null and b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..ac09306d Binary files /dev/null and b/.gitbook/assets/non-zero-winding-1 (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (1) (1).png b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (1) (1).png new file mode 100644 index 00000000..fe8c49ec Binary files /dev/null and b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (1) (1).png differ diff --git a/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (1).png b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (1).png new file mode 100644 index 00000000..fe8c49ec Binary files /dev/null and b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (2) (1).png b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..fe8c49ec Binary files /dev/null and b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (2).png b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (2).png new file mode 100644 index 00000000..fe8c49ec Binary files /dev/null and b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2).png b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2).png new file mode 100644 index 00000000..fe8c49ec Binary files /dev/null and b/.gitbook/assets/non-zero-winding-2 (2) (2) (2) (1) (2) (2) (2).png differ diff --git a/.gitbook/assets/non-zero-winding-3 (1) (1).jpeg b/.gitbook/assets/non-zero-winding-3 (1) (1).jpeg new file mode 100644 index 00000000..a57fddef Binary files /dev/null and b/.gitbook/assets/non-zero-winding-3 (1) (1).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-3 (1).jpeg b/.gitbook/assets/non-zero-winding-3 (1).jpeg new file mode 100644 index 00000000..a57fddef Binary files /dev/null and b/.gitbook/assets/non-zero-winding-3 (1).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-3 (2) (1).jpeg b/.gitbook/assets/non-zero-winding-3 (2) (1).jpeg new file mode 100644 index 00000000..a57fddef Binary files /dev/null and b/.gitbook/assets/non-zero-winding-3 (2) (1).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-3 (2).jpeg b/.gitbook/assets/non-zero-winding-3 (2).jpeg new file mode 100644 index 00000000..a57fddef Binary files /dev/null and b/.gitbook/assets/non-zero-winding-3 (2).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-3.jpeg b/.gitbook/assets/non-zero-winding-3.jpeg new file mode 100644 index 00000000..a57fddef Binary files /dev/null and b/.gitbook/assets/non-zero-winding-3.jpeg differ diff --git a/.gitbook/assets/non-zero-winding-4 (1).jpeg b/.gitbook/assets/non-zero-winding-4 (1).jpeg new file mode 100644 index 00000000..696c6148 Binary files /dev/null and b/.gitbook/assets/non-zero-winding-4 (1).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-4 (2) (1).jpeg b/.gitbook/assets/non-zero-winding-4 (2) (1).jpeg new file mode 100644 index 00000000..696c6148 Binary files /dev/null and b/.gitbook/assets/non-zero-winding-4 (2) (1).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-4 (2).jpeg b/.gitbook/assets/non-zero-winding-4 (2).jpeg new file mode 100644 index 00000000..696c6148 Binary files /dev/null and b/.gitbook/assets/non-zero-winding-4 (2).jpeg differ diff --git a/.gitbook/assets/non-zero-winding-4.jpeg b/.gitbook/assets/non-zero-winding-4.jpeg new file mode 100644 index 00000000..696c6148 Binary files /dev/null and b/.gitbook/assets/non-zero-winding-4.jpeg differ diff --git a/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (1).png b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (1).png new file mode 100644 index 00000000..58363001 Binary files /dev/null and b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (2) (1).png b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..58363001 Binary files /dev/null and b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (2).png b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (2).png new file mode 100644 index 00000000..58363001 Binary files /dev/null and b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2).png b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2).png new file mode 100644 index 00000000..58363001 Binary files /dev/null and b/.gitbook/assets/notification_area (2) (2) (2) (1) (2) (2) (2).png differ diff --git a/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..30da8fb4 Binary files /dev/null and b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (2) (1).png b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (2) (1).png new file mode 100644 index 00000000..30da8fb4 Binary files /dev/null and b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (2) (1).png differ diff --git a/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..30da8fb4 Binary files /dev/null and b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2).png b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2).png new file mode 100644 index 00000000..30da8fb4 Binary files /dev/null and b/.gitbook/assets/notification_drawer (2) (2) (2) (2) (2) (2).png differ diff --git a/.gitbook/assets/paint-stroke-width (1).jpeg b/.gitbook/assets/paint-stroke-width (1).jpeg new file mode 100644 index 00000000..b66c25d1 Binary files /dev/null and b/.gitbook/assets/paint-stroke-width (1).jpeg differ diff --git a/.gitbook/assets/paint-stroke-width (2) (1).jpeg b/.gitbook/assets/paint-stroke-width (2) (1).jpeg new file mode 100644 index 00000000..b66c25d1 Binary files /dev/null and b/.gitbook/assets/paint-stroke-width (2) (1).jpeg differ diff --git a/.gitbook/assets/paint-stroke-width (2).jpeg b/.gitbook/assets/paint-stroke-width (2).jpeg new file mode 100644 index 00000000..b66c25d1 Binary files /dev/null and b/.gitbook/assets/paint-stroke-width (2).jpeg differ diff --git a/.gitbook/assets/paint-stroke-width.jpeg b/.gitbook/assets/paint-stroke-width.jpeg new file mode 100644 index 00000000..b66c25d1 Binary files /dev/null and b/.gitbook/assets/paint-stroke-width.jpeg differ diff --git a/activity/images/parcelable-vs-seralizable.png b/.gitbook/assets/parcelable-vs-seralizable.png old mode 100755 new mode 100644 similarity index 100% rename from activity/images/parcelable-vs-seralizable.png rename to .gitbook/assets/parcelable-vs-seralizable.png diff --git a/.gitbook/assets/path-1 (1) (1).jpeg b/.gitbook/assets/path-1 (1) (1).jpeg new file mode 100644 index 00000000..273e6899 Binary files /dev/null and b/.gitbook/assets/path-1 (1) (1).jpeg differ diff --git a/.gitbook/assets/path-1 (1).jpeg b/.gitbook/assets/path-1 (1).jpeg new file mode 100644 index 00000000..273e6899 Binary files /dev/null and b/.gitbook/assets/path-1 (1).jpeg differ diff --git a/.gitbook/assets/path-1 (2) (1).jpeg b/.gitbook/assets/path-1 (2) (1).jpeg new file mode 100644 index 00000000..273e6899 Binary files /dev/null and b/.gitbook/assets/path-1 (2) (1).jpeg differ diff --git a/.gitbook/assets/path-1 (2).jpeg b/.gitbook/assets/path-1 (2).jpeg new file mode 100644 index 00000000..273e6899 Binary files /dev/null and b/.gitbook/assets/path-1 (2).jpeg differ diff --git a/.gitbook/assets/path-1.jpeg b/.gitbook/assets/path-1.jpeg new file mode 100644 index 00000000..273e6899 Binary files /dev/null and b/.gitbook/assets/path-1.jpeg differ diff --git a/.gitbook/assets/path-2 (1) (1).jpeg b/.gitbook/assets/path-2 (1) (1).jpeg new file mode 100644 index 00000000..17a169cc Binary files /dev/null and b/.gitbook/assets/path-2 (1) (1).jpeg differ diff --git a/.gitbook/assets/path-2 (1) (2) (1).jpeg b/.gitbook/assets/path-2 (1) (2) (1).jpeg new file mode 100644 index 00000000..17a169cc Binary files /dev/null and b/.gitbook/assets/path-2 (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/path-2 (1) (2).jpeg b/.gitbook/assets/path-2 (1) (2).jpeg new file mode 100644 index 00000000..17a169cc Binary files /dev/null and b/.gitbook/assets/path-2 (1) (2).jpeg differ diff --git a/.gitbook/assets/path-2 (1).jpeg b/.gitbook/assets/path-2 (1).jpeg new file mode 100644 index 00000000..17a169cc Binary files /dev/null and b/.gitbook/assets/path-2 (1).jpeg differ diff --git a/.gitbook/assets/path-3 (1) (1).jpeg b/.gitbook/assets/path-3 (1) (1).jpeg new file mode 100644 index 00000000..bae56213 Binary files /dev/null and b/.gitbook/assets/path-3 (1) (1).jpeg differ diff --git a/.gitbook/assets/path-3 (1) (2) (1).jpeg b/.gitbook/assets/path-3 (1) (2) (1).jpeg new file mode 100644 index 00000000..bae56213 Binary files /dev/null and b/.gitbook/assets/path-3 (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/path-3 (1) (2).jpeg b/.gitbook/assets/path-3 (1) (2).jpeg new file mode 100644 index 00000000..bae56213 Binary files /dev/null and b/.gitbook/assets/path-3 (1) (2).jpeg differ diff --git a/.gitbook/assets/path-3 (1).jpeg b/.gitbook/assets/path-3 (1).jpeg new file mode 100644 index 00000000..bae56213 Binary files /dev/null and b/.gitbook/assets/path-3 (1).jpeg differ diff --git a/.gitbook/assets/path-4 (1) (1) (1) (1).jpeg b/.gitbook/assets/path-4 (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..d92835de Binary files /dev/null and b/.gitbook/assets/path-4 (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/path-4 (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/path-4 (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..d92835de Binary files /dev/null and b/.gitbook/assets/path-4 (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/path-4 (1) (1) (1) (2).jpeg b/.gitbook/assets/path-4 (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..d92835de Binary files /dev/null and b/.gitbook/assets/path-4 (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/path-4 (1) (1) (1).jpeg b/.gitbook/assets/path-4 (1) (1) (1).jpeg new file mode 100644 index 00000000..d92835de Binary files /dev/null and b/.gitbook/assets/path-4 (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (1).jpeg b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (1).jpeg new file mode 100644 index 00000000..ea67af2e Binary files /dev/null and b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (1).jpeg differ diff --git a/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (2) (1).jpeg b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (2) (1).jpeg new file mode 100644 index 00000000..ea67af2e Binary files /dev/null and b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (2) (1).jpeg differ diff --git a/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (2).jpeg b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (2).jpeg new file mode 100644 index 00000000..ea67af2e Binary files /dev/null and b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD (2).jpeg differ diff --git a/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD.jpeg b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD.jpeg new file mode 100644 index 00000000..ea67af2e Binary files /dev/null and b/.gitbook/assets/path-filltype-INVERSE_EVEN_ODD.jpeg differ diff --git a/.gitbook/assets/path-op (1) (1) (1) (1).jpeg b/.gitbook/assets/path-op (1) (1) (1) (1).jpeg new file mode 100644 index 00000000..de00036f Binary files /dev/null and b/.gitbook/assets/path-op (1) (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/path-op (1) (1) (1) (2) (1).jpeg b/.gitbook/assets/path-op (1) (1) (1) (2) (1).jpeg new file mode 100644 index 00000000..de00036f Binary files /dev/null and b/.gitbook/assets/path-op (1) (1) (1) (2) (1).jpeg differ diff --git a/.gitbook/assets/path-op (1) (1) (1) (2).jpeg b/.gitbook/assets/path-op (1) (1) (1) (2).jpeg new file mode 100644 index 00000000..de00036f Binary files /dev/null and b/.gitbook/assets/path-op (1) (1) (1) (2).jpeg differ diff --git a/.gitbook/assets/path-op (1) (1) (1).jpeg b/.gitbook/assets/path-op (1) (1) (1).jpeg new file mode 100644 index 00000000..de00036f Binary files /dev/null and b/.gitbook/assets/path-op (1) (1) (1).jpeg differ diff --git a/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (1) (1).jpg b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (1) (1).jpg new file mode 100644 index 00000000..69700040 Binary files /dev/null and b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (1) (1).jpg differ diff --git a/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..69700040 Binary files /dev/null and b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (2) (1).jpg b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (2) (1).jpg new file mode 100644 index 00000000..69700040 Binary files /dev/null and b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (2) (1).jpg differ diff --git a/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (2).jpg b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..69700040 Binary files /dev/null and b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2).jpg b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2).jpg new file mode 100644 index 00000000..69700040 Binary files /dev/null and b/.gitbook/assets/porterduffxfermode (2) (2) (2) (2) (2).jpg differ diff --git a/.gitbook/assets/resourcedecoder-1.png b/.gitbook/assets/resourcedecoder-1.png new file mode 100644 index 00000000..e9d1e0dd Binary files /dev/null and b/.gitbook/assets/resourcedecoder-1.png differ diff --git a/.gitbook/assets/restore_instance.png b/.gitbook/assets/restore_instance.png new file mode 100644 index 00000000..698b83a8 Binary files /dev/null and b/.gitbook/assets/restore_instance.png differ diff --git a/.gitbook/assets/room_architecture.png b/.gitbook/assets/room_architecture.png new file mode 100644 index 00000000..45566db7 Binary files /dev/null and b/.gitbook/assets/room_architecture.png differ diff --git a/.gitbook/assets/service_binding_tree_lifecycle.png b/.gitbook/assets/service_binding_tree_lifecycle.png new file mode 100644 index 00000000..8b826f67 Binary files /dev/null and b/.gitbook/assets/service_binding_tree_lifecycle.png differ diff --git a/.gitbook/assets/service_lifecycle.png b/.gitbook/assets/service_lifecycle.png new file mode 100644 index 00000000..413bf562 Binary files /dev/null and b/.gitbook/assets/service_lifecycle.png differ diff --git a/.gitbook/assets/studio-constraint-first (1).mp4 b/.gitbook/assets/studio-constraint-first (1).mp4 new file mode 100644 index 00000000..94d5eaba Binary files /dev/null and b/.gitbook/assets/studio-constraint-first (1).mp4 differ diff --git a/.gitbook/assets/studio-constraint-first (2) (1).mp4 b/.gitbook/assets/studio-constraint-first (2) (1).mp4 new file mode 100644 index 00000000..94d5eaba Binary files /dev/null and b/.gitbook/assets/studio-constraint-first (2) (1).mp4 differ diff --git a/.gitbook/assets/studio-constraint-first (2).mp4 b/.gitbook/assets/studio-constraint-first (2).mp4 new file mode 100644 index 00000000..94d5eaba Binary files /dev/null and b/.gitbook/assets/studio-constraint-first (2).mp4 differ diff --git a/.gitbook/assets/studio-constraint-first.mp4 b/.gitbook/assets/studio-constraint-first.mp4 new file mode 100644 index 00000000..94d5eaba Binary files /dev/null and b/.gitbook/assets/studio-constraint-first.mp4 differ diff --git a/.gitbook/assets/studio-inspections-config.png b/.gitbook/assets/studio-inspections-config.png new file mode 100644 index 00000000..15a5a5ba Binary files /dev/null and b/.gitbook/assets/studio-inspections-config.png differ diff --git a/.gitbook/assets/tablayout-1 (1).gif b/.gitbook/assets/tablayout-1 (1).gif new file mode 100644 index 00000000..5de5c67a Binary files /dev/null and b/.gitbook/assets/tablayout-1 (1).gif differ diff --git a/.gitbook/assets/tablayout-1.gif b/.gitbook/assets/tablayout-1.gif new file mode 100644 index 00000000..5de5c67a Binary files /dev/null and b/.gitbook/assets/tablayout-1.gif differ diff --git a/.gitbook/assets/tablayout-2 (1) (1) (1) (1) (1).gif b/.gitbook/assets/tablayout-2 (1) (1) (1) (1) (1).gif new file mode 100644 index 00000000..3cee6c74 Binary files /dev/null and b/.gitbook/assets/tablayout-2 (1) (1) (1) (1) (1).gif differ diff --git a/.gitbook/assets/tablayout-2 (1) (1) (1) (1).gif b/.gitbook/assets/tablayout-2 (1) (1) (1) (1).gif new file mode 100644 index 00000000..3cee6c74 Binary files /dev/null and b/.gitbook/assets/tablayout-2 (1) (1) (1) (1).gif differ diff --git a/.gitbook/assets/tablayout-3 (1) (1) (1).gif b/.gitbook/assets/tablayout-3 (1) (1) (1).gif new file mode 100644 index 00000000..e2ae292d Binary files /dev/null and b/.gitbook/assets/tablayout-3 (1) (1) (1).gif differ diff --git a/.gitbook/assets/tablayout-3 (1) (1).gif b/.gitbook/assets/tablayout-3 (1) (1).gif new file mode 100644 index 00000000..e2ae292d Binary files /dev/null and b/.gitbook/assets/tablayout-3 (1) (1).gif differ diff --git a/.gitbook/assets/test.java b/.gitbook/assets/test.java new file mode 100644 index 00000000..54102633 --- /dev/null +++ b/.gitbook/assets/test.java @@ -0,0 +1,13 @@ +public Intent transformIntentToExplicitAsNeeded(Intent intent) { + ComponentName component = intent.getComponent(); + if (component == null + || component.getPackageName().equals(mContext.getPackageName())) { + ResolveInfo info = mPluginManager.resolveActivity(intent); + if (info != null && info.activityInfo != null) { + component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); + intent.setComponent(component); + } + } + + return intent; +} \ No newline at end of file diff --git a/.gitbook/assets/uml/Context.pu b/.gitbook/assets/uml/Context.pu new file mode 100644 index 00000000..7479b1f5 --- /dev/null +++ b/.gitbook/assets/uml/Context.pu @@ -0,0 +1,15 @@ +@startuml +abstract class Context{ + +} +class ContextImpl{ + +} +Context <|-- ContextImpl +Context <|-- ContextWrapper +ContextWrapper *-- ContextImpl +ContextWrapper <|-- ContextThemeWrapper +ContextWrapper <|-- Service +ContextWrapper <|-- Application +ContextThemeWrapper <|-- Activity +@enduml \ No newline at end of file diff --git a/.gitbook/assets/uml/LifeCycle.pu b/.gitbook/assets/uml/LifeCycle.pu new file mode 100644 index 00000000..122400c8 --- /dev/null +++ b/.gitbook/assets/uml/LifeCycle.pu @@ -0,0 +1,15 @@ +@startuml +abstract class Lifecycle{ + void addObserver(LifecycleObserver observer); + void removeObserver(LifecycleObserver observer); + State getCurrentState(); +} + + +LifecycleOwner *-- Lifecycle + + +Lifecycle <|-- LifecycleRegistry + + +@enduml \ No newline at end of file diff --git a/.gitbook/assets/uml/LifecycleObserver.pu b/.gitbook/assets/uml/LifecycleObserver.pu new file mode 100644 index 00000000..35cc513b --- /dev/null +++ b/.gitbook/assets/uml/LifecycleObserver.pu @@ -0,0 +1,20 @@ +@startuml +interface LifecycleObserver{ + +} +interface LifecycleEventObserver{ + +} +interface FullLifecycleObserver{ + +} +LifecycleObserver <|-- LifecycleEventObserver +LifecycleObserver <|-- FullLifecycleObserver +LifecycleEventObserver <|-- CompositeGeneratedAdaptersObserver +LifecycleEventObserver <|-- ReflectiveGenericLifecycleObserver +LifecycleEventObserver <|-- SingleGeneratedAdapterObserver +LifecycleEventObserver <|-- FullLifecycleObserverAdapter + + + +@enduml \ No newline at end of file diff --git a/.gitbook/assets/uml/WindowManager.pu b/.gitbook/assets/uml/WindowManager.pu new file mode 100644 index 00000000..65816023 --- /dev/null +++ b/.gitbook/assets/uml/WindowManager.pu @@ -0,0 +1,26 @@ +@startuml + +interface ViewManager{ + addView(); + updateViewLayout(); + removeView(); +} + +interface WindowManager{ + +} +class WindowManagerImpl{ +} +abstract class Window{ +} +class PhoneWindow{ +} +class WindowManagerGlobal{ + +} +ViewManager <|-- WindowManager +WindowManager <|.. WindowManagerImpl +WindowManagerImpl *-- Window +Window <|-- PhoneWindow +WindowManagerImpl *-- WindowManagerGlobal +@enduml \ No newline at end of file diff --git a/.gitbook/assets/valueanimator.png b/.gitbook/assets/valueanimator.png new file mode 100644 index 00000000..6cc2a13b Binary files /dev/null and b/.gitbook/assets/valueanimator.png differ diff --git a/.gitbook/assets/volley-request.png b/.gitbook/assets/volley-request.png new file mode 100644 index 00000000..e495ae45 Binary files /dev/null and b/.gitbook/assets/volley-request.png differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/.obsidian/app.json b/.obsidian/app.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.obsidian/app.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.obsidian/appearance.json b/.obsidian/appearance.json new file mode 100644 index 00000000..990f3376 --- /dev/null +++ b/.obsidian/appearance.json @@ -0,0 +1,3 @@ +{ + "baseFontSize": 16 +} \ No newline at end of file diff --git a/.obsidian/core-plugins.json b/.obsidian/core-plugins.json new file mode 100644 index 00000000..26bc70e7 --- /dev/null +++ b/.obsidian/core-plugins.json @@ -0,0 +1,14 @@ +[ + "file-explorer", + "global-search", + "switcher", + "graph", + "backlink", + "page-preview", + "note-composer", + "command-palette", + "markdown-importer", + "word-count", + "open-with-default-app", + "file-recovery" +] \ No newline at end of file diff --git a/.obsidian/hotkeys.json b/.obsidian/hotkeys.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/.obsidian/hotkeys.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.obsidian/workspace b/.obsidian/workspace new file mode 100644 index 00000000..ca017908 --- /dev/null +++ b/.obsidian/workspace @@ -0,0 +1,100 @@ +{ + "main": { + "id": "b1dc3945ca2c6974", + "type": "split", + "children": [ + { + "id": "28a8f897855fa170", + "type": "leaf", + "state": { + "type": "markdown", + "state": { + "file": "network/okhttp/lan-jie-qi.md", + "mode": "source" + } + } + } + ], + "direction": "vertical" + }, + "left": { + "id": "5fb5e62275baeb82", + "type": "split", + "children": [ + { + "id": "964d78054c40b410", + "type": "tabs", + "children": [ + { + "id": "457821bf7a9d4f88", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": {} + } + }, + { + "id": "78fae20552b3e0de", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "9d15a64e5af6c480", + "type": "split", + "children": [ + { + "id": "938b06bddc83a16f", + "type": "tabs", + "children": [ + { + "id": "4503fe3237496153", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "file": "network/okhttp/lan-jie-qi.md", + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "active": "28a8f897855fa170", + "lastOpenFiles": [ + "network/okhttp/lan-jie-qi.md", + "gong-ju-shi-yong/README.md", + "gong-ju-shi-yong/ming-ling-hang-gong-ju/README.md", + "gong-ju-shi-yong/ming-ling-hang-gong-ju/logcat.md", + "gong-ju-shi-yong/ming-ling-hang-gong-ju/dumpsys.md", + "gong-ju-shi-yong/ming-ling-hang-gong-ju/adb.md", + "gong-ju-shi-yong/androidstudio/ying-yong-fa-bu.md", + "gong-ju-shi-yong/androidstudio/README.md" + ] +} \ No newline at end of file diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 0b7c0e31..84875883 --- a/README.md +++ b/README.md @@ -1,68 +1,32 @@ -## Android常用学习资源 +# Android学习资料 +## 博客 +* [https://medium.com/androiddevelopers](https://medium.com/androiddevelopers) +* [https://jakewharton.com/](https://jakewharton.com/) +* [https://androidweekly.net/](https://androidweekly.net/) +* [https://blog.stylingandroid.com/](https://blog.stylingandroid.com/) +* [http://gityuan.com/](http://gityuan.com/) +* [https://weishu.me/](https://weishu.me/) +* [https://androidperformance.com/](https://androidperformance.com/) +* [https://glumes.com/](https://glumes.com/) +* [https://blog.csdn.net/innost](https://blog.csdn.net/innost) +* [https://liuwangshu.cn/](https://liuwangshu.cn/) -

Material Design

+## 工具 -* [Android-ObservableScrollView](https://github.com/ksoichiro/Android-ObservableScrollView) -* [FloatingActionButton](https://github.com/Clans/FloatingActionButton) -* [Floating Action Buttons In Android](http://www.truiton.com/2015/04/floating-action-buttons-android/) -* [Material Dialogs](https://github.com/afollestad/material-dialogs) -* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) -* [material-design-icons-adt-template](https://github.com/intrications/material-design-icons-adt-template) -* [android-material-design-icon-generator-plugin](https://github.com/konifar/android-material-design-icon-generator-plugin) -* [Android RecyclerView vs ListView | Comparison](http://www.truiton.com/2015/03/android-recyclerview-vs-listview-comparison/) -* [Android Material Design With Backward Compatibility](http://www.truiton.com/2015/02/android-material-design-backward-compatibility/) -* [Missing Android Material Components](http://www.dmytrodanylyk.com/pages/blog/missing-material-components.html) +* [MT管理器](https://mt2.cn/) +* [chuck](https://github.com/jgilfelt/chuck) +## 逆向 -

RecyclerView

+* [BlackDex](https://github.com/CodingGay/BlackDex) +* [BlackObfuscator](https://github.com/CodingGay/BlackObfuscator) +* [StringFog](https://github.com/MegatronKing/StringFog) +* [LSParanoid](https://github.com/LSPosed/LSParanoid) +* [paranoid](https://github.com/MichaelRocks/paranoid) +* [JustTrustMe](https://github.com/Fuzion24/JustTrustMe) -* [LollipopContactsRecyclerViewFastScroller](https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller) + - -

ListView

-* [StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders) - -

ViewPager

-* [MaterialViewPager](https://github.com/florent37/MaterialViewPager) - -

kotlin

- -* [Announcing Anko for Android](http://blog.jetbrains.com/kotlin/2015/04/announcing-anko-for-android/) -* [Kotlin for Android (III): Extension functions and default values](http://antonioleiva.com/kotlin-android-extension-functions/) - - -

Gradle

- -* [Gradle Plugin 使用指南(中文版)](http://avatarqing.github.io/Gradle-Plugin-User-Guide-Chinese-Verision/) -* [Gradle插件用户指南(译)](http://rinvay.github.io/android/2015/03/26/Gradle-Plugin-User-Guide(Translation)) - -

MVP

-* [Introduction to Model-View-Presenter on Android](http://konmik.github.io/introduction-to-model-view-presenter-on-android.html) - -

Dagger

-* [Dagger 2 + Espresso 2 + Mockito ](http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html) -* [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/) -* [MVP + Dagger2 + Retrofit (and Swagger)](https://medium.com/@franpulido/retrofit-dagger-mvp-305ac2cf646c) - -

RxJava

-* [NotRxJava guide for lazy folks](http://yarikx.github.io/NotRxJava/) - -

Test

-* [AGAINST ANDROID UNIT TESTS](http://philosophicalhacker.com/2015/04/10/against-android-unit-tests/) -* [Triumph! Android Studio 1.2 Sneaks In Full Testing Support](https://www.bignerdranch.com/blog/triumph-android-studio-1-2-sneaks-in-full-testing-support/) -* [WHY ANDROID UNIT TESTING IS SO HARD (PT 1)](http://philosophicalhacker.com/2015/04/17/why-android-unit-testing-is-so-hard-pt-1/) -* [Elegant Unit Testing - Droidcon Spain 2015 ](https://speakerdeck.com/guardiola31337/elegant-unit-testing-droidcon-spain-2015) -* [HOW TO MAKE OUR ANDROID APPS UNIT TESTABLE (PT. 1)](http://philosophicalhacker.com/2015/05/01/how-to-make-our-android-apps-unit-testable-pt-1/) - -

工具

-* [androidtool-mac](https://github.com/mortenjust/androidtool-mac) - - - -

未分类

-* [Android官方培训课程中文版](http://hukai.me/android-training-course-in-chinese/) -* [The Making of Falcon Pro 3](http://realm.io/news/joaquim-verges-making-falcon-pro-3/) -* [Mastering the Android -Touch System](http://wugengxin.cn/download/pdf/android/PRE_andevcon_mastering-the-android-touch-system.pdf) diff --git a/SUMMARY.md b/SUMMARY.md old mode 100755 new mode 100644 index b2bcc6aa..808a3148 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,59 +1,137 @@ +# Table of contents -# Summary - -* [Introduction](README.md) +* [Android学习资料](README.md) +* [Android知识点梳理](interview-questions.md) +* [Android Studio](tools/android-studio/android-studio.md) +* [aop](aop.md) * UI - * Layouts - * Components - * [TextView](ui/textview.md) - * [WebView](ui/webview.md) - * [RecyclerView](ui/recyclerview.md) - * [FragmentTabHost](ui/fragmenttabhost.md) - * [ViewPager](ui/viewpager.md) - * AdapterView - + [ListView](ui/listview.md) - * Adapter - * [ActionBar](ui/actionbar.md) - * [Toast](ui/toast.md) - * [Notification](ui/notification.md) - * [LayoutInflater](ui/layoutinflater.md) - * [自定义View](ui/customview.md) - * Resource - * [DrawableResource](ui/drawable-resource.md) -* Intent -* Activity - * [Activity启动模式](activity/Activity启动模式.md) - * [数据传递](activity/页面切换之间的数据传递.md) -* Fragment - * [Fragment管理](fragment/manager.md) -* Service -* [获取AndroidManifest.xml信息](get_androidmanifest_info.md) -* [多线程] - * [Handler](multithread/handler.md) -* [存储] - * [SharedPreferences](data/sharedpreferences.md) -* [数据库] - * [SQLite](database/SQLite.md) - * [数据库操作](database/数据库操作.md) - * [ActiveAndroid](database/activeandroid.md) -* [RxJava](RxJava.md) -* [数据绑定](数据绑定.md) -* [JSON解析](parse/gson.md) -* 网络请求 - * [HttpURLConnection使用](net/httpurlconnection.md) - * [OkHttp](net/okhttp.md) - * [Retrofit](net/retrofit.md) - * [Volley](net/volley.md) -* 网络图片加载 - * [Android-Universal-Image-Loader](net/android-universal-image-loader.md) - * [Picasso](net/picasso.md) -* [DownloadProvider](net/downloadprovider.md) -* [依赖注入](依赖注入.md) -* 注解 - * [ButterKnife](annotation/butterknife.md) - * [AndroidAnnotations](annotation/androidannotations.md) -* 工具 - * AndroidStudio - * [提示和技巧](tools/android-studio/tips_and_tricks.md) - * [常见问题](tools/android-studio/question.md) - * [android-resource-remover](tools/android-resource-remover.md) + * [ViewPager2](ui/viewpager2.md) + * [View](ui/view/README.md) + * [View.post()分析](ui/view/view.post-fen-xi.md) + * [自定义View](ui/view/custom-views/README.md) + * [Canvas使用](ui/view/custom-views/canvas.md) + * [Paint使用](ui/view/custom-views/paint.md) + * [Path使用](ui/view/custom-views/path.md) + * [PathMeasure使用](ui/view/custom-views/pathmeasure.md) + * [Matrix使用](ui/view/custom-views/pathmeasure-1.md) + * [View事件分发](ui/view/view-shi-jian-fen-fa.md) + * [TextView](ui/textview.md) + * [EditText](ui/edittext.md) + * [引导页](ui/yin-dao-ye.md) + * [菜单](ui/menu.md) + * [Toolbar](ui/toolbar.md) + * [RecyclerView](ui/recyclerview.md) + * [ViewPager](ui/viewpager.md) + * [BottomSheet](ui/bottomsheet.md) + * [通知](ui/notifications.md) + * [CalendarView](ui/calendarview.md) + * [TimePicker](ui/timepicker.md) + * [LayoutInfalter](ui/layoutinfalter.md) + * [shadowlayout](ui/shadowlayout.md) + * [状态栏适配](ui/zhuang-tai-lan-kuo-pei.md) + * [RecyclerView](ui/recyclerview-1.md) + * [屏幕适配](ui/screen-adaptation.md) +* [组件](components/README.md) + * [Activity](components/activities.md) + * [Intent](components/intent.md) + * [服务](components/services/README.md) + * [服务](components/services/services.md) + * [绑定服务](components/services/bound-services.md) + * [AIDL](components/services/aidl.md) + * [Broadcasts](components/broadcasts.md) + * [Fragment](components/fragment.md) + * [ContentProvider](components/contentprovider.md) +* [权限](permissions.md) +* [数据存储](data-storage/README.md) + * [数据和文件存储](data-storage/shu-ju-he-wen-jian-cun-chu.md) + * [将文件保存到外部存储中](data-storage/jiang-wen-jian-bao-cun-dao-wai-bu-cun-chu-zhong.md) + * [将文件保存在内部存储中](data-storage/jiang-wen-jian-bao-cun-zai-nei-bu-cun-chu-zhong.md) + * [Android 存储用例和最佳做法](data-storage/use-cases.md) + * [保存键值对数据](data-storage/shared-preferences.md) +* [编译时注解](annotation/README.md) + * [Untitled](annotation/untitled.md) + * [Java注解处理器\](annotation/annotation-processing.md)](annotation/yi-java-zhu-jie-chu-li-qi-annotationannotationprocessing.md.md) + * [JavaPoet 文档翻译](annotation/javapoet-wen-dang-fan-yi.md) +* [图形和图像](graphics/README.md) + * [Drawable使用](graphics/drawable.md) +* [网络](network/README.md) + * [WebView](network/webview.md) + * [OkHttp文档翻译](network/okhttp/README.md) + * [OkHttp使用](network/okhttp/okhttp-shi-yong.md) + * [OkHttp拦截器](network/okhttp/okhttp-lan-jie-qi.md) + * [Retrofit使用](network/retrofit.md) + * [Retrofit源码分析](network/retrofit-source.md) + * [HttpURLConnection使用](network/httpurlconnection.md) + * [Volley使用](network/volley.md) + * [Volley源码分析](network/volley-source.md) + * [Glide使用](network/glide.md) + * [Glide源码分析](network/glide-source.md) + * [RxJava](network/rxjava.md) +* [插件化](cha-jian-hua/README.md) + * [插件化框架](cha-jian-hua/cha-jian-hua-kuang-jia.md) + * [VirtualAPK分析](cha-jian-hua/virtualapk-fen-xi.md) +* [依赖注入](dependency-injection.md) +* [开源库](kai-yuan-ku/README.md) + * [EventBus源码分析](thirdparty/eventbus.md) + * [Okio概览](thirdparty/okio-overview.md) + * [Okio菜谱](thirdparty/okio-recipes.md) + * [Okio](thirdparty/okio.md) + * [OkHttp拦截器](thirdparty/okhttp-interceptor.md) + * [OKHttp缓存](thirdparty/okhttp-cache.md) + * [Okhttp源码分析](thirdparty/okhttp.md) +* [集合](ji-he/README.md) + * [SparseArray](ji-he/sparsearray.md) + * [ArrayMap](ji-he/arraymap.md) +* [架构](jia-gou.md) +* [Jetpack](jetpacket/README.md) + * [databinding adapter](jetpacket/databinding-adapter.md) + * [databinding坑](jetpacket/databinding-keng.md) + * [databinding源码分析](jetpacket/databinding-yuan-ma-fen-xi.md) + * [Android Data Binding: Let’s Flip This Thing](jetpacket/android-data-binding-lets-flip-this-thing.md) + * [Untitled](jetpacket/untitled.md) + * [Lifecycle源码分析](jetpacket/lifecycle-source-analysis.md) + * [ViewModel源码分析](jetpacket/viewmodel-source-analysis.md) + * [LiveData源码分析](jetpacket/livedata-source-analysis.md) +* [音频和视频](yin-pin-he-shi-pin/README.md) + * [CameraX使用](yin-pin-he-shi-pin/camerax.md) + * [ExoPlayer](yin-pin-he-shi-pin/exoplayer.md) + * [GsyVideoPlayer](yin-pin-he-shi-pin/gsyvideoplayer.md) +* [性能优化](performance/README.md) + * [崩溃优化](performance/crash.md) + * [内存优化](performance/memory/README.md) + * [LeakCanary如何工作](performance/memory/fundamentals-how-leakcanary-works.md) + * [渲染优化](performance/render.md) + * [电池优化](performance/power.md) + * [启动优化](performance/launch-time.md) + * [网络优化](performance/network.md) + * [安装包大小优化](performance/reduce-apk-size.md) +* [NDK开发](ndk-kai-fa.md) +* [系统源码分析](aosp/README.md) + * [Android源码下载](aosp/android-yuan-ma-xia-zai.md) + * [Android系统启动](aosp/android-xi-tong-qi-dong/README.md) + * [init进程启动过程](aosp/android-xi-tong-qi-dong/zygote.md) + * [Zygote进程启动过程](aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-guo-cheng.md) + * [Zygote进程启动流程](aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-liu-cheng.md) + * [SystemServer处理过程](aosp/android-xi-tong-qi-dong/systemserver-chu-li-guo-cheng.md) + * [应用程序进程启动过程](aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/README.md) + * [应用程序进程启动过程介绍](aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng-jie-shao.md) + * [Window](aosp/window.md) + * [WMS](aosp/wms.md) + * [WindowManagerService](aosp/windowmanagerservice.md) + * [四大组件的工作过程](aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/README.md) + * [Activity启动流程分析](aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng-fen-xi.md) + * [Activity启动流程](aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng.md) + * [ActivityManagerService分析](aosp/activitymanagerservice-fen-xi.md) + * [Context详解](aosp/context.md) + * [Binder原理](aosp/binder-yuan-li/README.md) + * [Binder驱动](aosp/binder-yuan-li/binder-qu-dong.md) + * [获取ServiceManager](aosp/binder-yuan-li/huo-qu-servicemanager.md) + * [启动ServiceManager](aosp/binder-yuan-li/qi-dong-servicemanager.md) + * [Parcel源码分析](aosp/binder-yuan-li/parcel-yuan-ma-fen-xi.md) + * [Android图形系统概述](aosp/android-tu-xing-xi-tong-gai-shu.md) + * [Choreographer原理](aosp/choreographer.md) + * [Handler使用](aosp/handler-shi-yong.md) + * [Handler源码分析](aosp/handler-yuan-ma-fen-xi.md) +* 逆向 + * [Android抓包](reverse-engineering/capture.md) + * [网易云音乐逆向](reverse-engineering/netease-cloud-music-reverse.md) diff --git "a/activity/Activity\345\220\257\345\212\250\346\250\241\345\274\217.md" "b/activity/Activity\345\220\257\345\212\250\346\250\241\345\274\217.md" deleted file mode 100755 index e155797e..00000000 --- "a/activity/Activity\345\220\257\345\212\250\346\250\241\345\274\217.md" +++ /dev/null @@ -1,14 +0,0 @@ -## Activity启动模式 - -* standard:每次都创建一个新的实例。 -* singleTop:如果已经存在实例,则将其移动到栈顶。 -* singleTask:如果已经存在实例,将上面所有的activity推出栈。 -* singleInstance: - - -### 参考 -* [Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance](http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en) -* [Activity的四种launchMode](http://blog.csdn.net/liuhe688/article/details/6754323) -* [Activity的task相关](http://blog.csdn.net/liuhe688/article/details/6761337) -* [Reveal Activity Transitions](https://halfthought.wordpress.com/2014/12/02/reveal-activity-transitions/) - diff --git a/android-architecture.md b/android-architecture.md new file mode 100644 index 00000000..f356ce1e --- /dev/null +++ b/android-architecture.md @@ -0,0 +1,983 @@ + + + + +MVP架构中各个组件扮演的角色: + +* Model:数据层。负责从网络获取数据,并且将这些数据缓存到数据库,以及网络错误时,从数据库读取数据的逻辑。 +* View:UI层。负责调用Presenter执行操作,并根据Presenter状态刷新UI。 +* Presenter:Model和View之间的纽带,负责从Model中获取数据,并调用View展示。 + +下面通过不同的例子分别分析不同组件的实现。 + + +### android-architecture + +[android-architecture](https://github.com/googlesamples/android-architecture/)是google官方的一个架构的例子。 + + +#### Model实现 + +TasksDataSource接口定义了所有获取数据的方法。 + +```java +/** + * Main entry point for accessing tasks data. + *

+ * For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other + * methods to inform the user of network/database errors or successful operations. + * For example, when a new task is created, it's synchronously stored in cache but usually every + * operation on database or network should be executed in a different thread. + */ +public interface TasksDataSource { + + interface LoadTasksCallback { + + void onTasksLoaded(List tasks); + + void onDataNotAvailable(); + } + + interface GetTaskCallback { + + void onTaskLoaded(Task task); + + void onDataNotAvailable(); + } + + void getTasks(@NonNull LoadTasksCallback callback); + + void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback); + + void saveTask(@NonNull Task task); + + void completeTask(@NonNull Task task); + + void completeTask(@NonNull String taskId); + + void activateTask(@NonNull Task task); + + void activateTask(@NonNull String taskId); + + void clearCompletedTasks(); + + void refreshTasks(); + + void deleteAllTasks(); + + void deleteTask(@NonNull String taskId); +} +``` + +TasksRepository实现了TasksDataSource接口,根据需求分别从网络或者缓存中获取数据。比如我们的需求优先从网络中获取数据,当网络异常时,则从数据库中读取缓存数据,这里使用一个布尔值mCacheIsDirty来判断是否从远程获取数据。 + +```java +package com.example.android.architecture.blueprints.todoapp.data.source; + +import static com.google.common.base.Preconditions.checkNotNull; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.example.android.architecture.blueprints.todoapp.data.Task; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Concrete implementation to load tasks from the data sources into a cache. + *

+ * For simplicity, this implements a dumb synchronisation between locally persisted data and data + * obtained from the server, by using the remote data source only if the local database doesn't + * exist or is empty. + */ +public class TasksRepository implements TasksDataSource { + + private static TasksRepository INSTANCE = null; + + private final TasksDataSource mTasksRemoteDataSource; + + private final TasksDataSource mTasksLocalDataSource; + + /** + * This variable has package local visibility so it can be accessed from tests. + */ + Map mCachedTasks; + + /** + * Marks the cache as invalid, to force an update the next time data is requested. This variable + * has package local visibility so it can be accessed from tests. + */ + boolean mCacheIsDirty = false; + + // Prevent direct instantiation. + private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource, + @NonNull TasksDataSource tasksLocalDataSource) { + mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource); + mTasksLocalDataSource = checkNotNull(tasksLocalDataSource); + } + + /** + * Returns the single instance of this class, creating it if necessary. + * + * @param tasksRemoteDataSource the backend data source + * @param tasksLocalDataSource the device storage data source + * @return the {@link TasksRepository} instance + */ + public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource, + TasksDataSource tasksLocalDataSource) { + if (INSTANCE == null) { + INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource); + } + return INSTANCE; + } + + /** + * Used to force {@link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance + * next time it's called. + */ + public static void destroyInstance() { + INSTANCE = null; + } + + /** + * Gets tasks from cache, local data source (SQLite) or remote data source, whichever is + * available first. + *

+ * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if all data sources fail to + * get the data. + */ + @Override + public void getTasks(@NonNull final LoadTasksCallback callback) { + checkNotNull(callback); + + // Respond immediately with cache if available and not dirty + if (mCachedTasks != null && !mCacheIsDirty) { + callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); + return; + } + + if (mCacheIsDirty) { + // If the cache is dirty we need to fetch new data from the network. + getTasksFromRemoteDataSource(callback); //从远程获取数据 + } else { + // Query the local storage if available. If not, query the network. + mTasksLocalDataSource.getTasks(new LoadTasksCallback() { + @Override + public void onTasksLoaded(List tasks) { + refreshCache(tasks); + callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); + } + + @Override + public void onDataNotAvailable() { + //当本地数据不可用时从远程获取数据 + getTasksFromRemoteDataSource(callback); + } + }); + } + } + + @Override + public void saveTask(@NonNull Task task) { + checkNotNull(task); + mTasksRemoteDataSource.saveTask(task); + mTasksLocalDataSource.saveTask(task); + + // Do in memory cache update to keep the app UI up to date + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.put(task.getId(), task); + } + + @Override + public void completeTask(@NonNull Task task) { + checkNotNull(task); + mTasksRemoteDataSource.completeTask(task); + mTasksLocalDataSource.completeTask(task); + + Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true); + + // Do in memory cache update to keep the app UI up to date + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.put(task.getId(), completedTask); + } + + @Override + public void completeTask(@NonNull String taskId) { + checkNotNull(taskId); + completeTask(getTaskWithId(taskId)); + } + + @Override + public void activateTask(@NonNull Task task) { + checkNotNull(task); + mTasksRemoteDataSource.activateTask(task); + mTasksLocalDataSource.activateTask(task); + + Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId()); + + // Do in memory cache update to keep the app UI up to date + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.put(task.getId(), activeTask); + } + + @Override + public void activateTask(@NonNull String taskId) { + checkNotNull(taskId); + activateTask(getTaskWithId(taskId)); + } + + @Override + public void clearCompletedTasks() { + mTasksRemoteDataSource.clearCompletedTasks(); + mTasksLocalDataSource.clearCompletedTasks(); + + // Do in memory cache update to keep the app UI up to date + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + Iterator> it = mCachedTasks.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + if (entry.getValue().isCompleted()) { + it.remove(); + } + } + } + + /** + * Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it + * uses the network data source. This is done to simplify the sample. + *

+ * Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to + * get the data. + */ + @Override + public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) { + checkNotNull(taskId); + checkNotNull(callback); + + Task cachedTask = getTaskWithId(taskId); + + // Respond immediately with cache if available + if (cachedTask != null) { + callback.onTaskLoaded(cachedTask); + return; + } + + // Load from server/persisted if needed. + + // Is the task in the local data source? If not, query the network. + mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() { + @Override + public void onTaskLoaded(Task task) { + // Do in memory cache update to keep the app UI up to date + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.put(task.getId(), task); + callback.onTaskLoaded(task); + } + + @Override + public void onDataNotAvailable() { + mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() { + @Override + public void onTaskLoaded(Task task) { + // Do in memory cache update to keep the app UI up to date + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.put(task.getId(), task); + callback.onTaskLoaded(task); + } + + @Override + public void onDataNotAvailable() { + callback.onDataNotAvailable(); + } + }); + } + }); + } + + @Override + public void refreshTasks() { + mCacheIsDirty = true; + } + + @Override + public void deleteAllTasks() { + mTasksRemoteDataSource.deleteAllTasks(); + mTasksLocalDataSource.deleteAllTasks(); + + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.clear(); + } + + @Override + public void deleteTask(@NonNull String taskId) { + mTasksRemoteDataSource.deleteTask(checkNotNull(taskId)); + mTasksLocalDataSource.deleteTask(checkNotNull(taskId)); + + mCachedTasks.remove(taskId); + } + + private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) { + mTasksRemoteDataSource.getTasks(new LoadTasksCallback() { + @Override + public void onTasksLoaded(List tasks) { + refreshCache(tasks); + refreshLocalDataSource(tasks); + callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values())); + } + + @Override + public void onDataNotAvailable() { + callback.onDataNotAvailable(); + } + }); + } + + private void refreshCache(List tasks) { + if (mCachedTasks == null) { + mCachedTasks = new LinkedHashMap<>(); + } + mCachedTasks.clear(); + for (Task task : tasks) { + mCachedTasks.put(task.getId(), task); + } + mCacheIsDirty = false; + } + + private void refreshLocalDataSource(List tasks) { + mTasksLocalDataSource.deleteAllTasks(); + for (Task task : tasks) { + mTasksLocalDataSource.saveTask(task); + } + } + + @Nullable + private Task getTaskWithId(@NonNull String id) { + checkNotNull(id); + if (mCachedTasks == null || mCachedTasks.isEmpty()) { + return null; + } else { + return mCachedTasks.get(id); + } + } +} + +``` +TasksRemoteDataSource从网络获取数据,这里是模拟了一个网络请求。 + +```java +/** + * Implementation of the data source that adds a latency simulating network. + */ +public class TasksRemoteDataSource implements TasksDataSource { + + private static TasksRemoteDataSource INSTANCE; + + private static final int SERVICE_LATENCY_IN_MILLIS = 5000; + + private final static Map TASKS_SERVICE_DATA; + + static { + TASKS_SERVICE_DATA = new LinkedHashMap<>(2); + addTask("Build tower in Pisa", "Ground looks good, no foundation work required."); + addTask("Finish bridge in Tacoma", "Found awesome girders at half the cost!"); + } + + public static TasksRemoteDataSource getInstance() { + if (INSTANCE == null) { + INSTANCE = new TasksRemoteDataSource(); + } + return INSTANCE; + } + + // Prevent direct instantiation. + private TasksRemoteDataSource() {} + + private static void addTask(String title, String description) { + Task newTask = new Task(title, description); + TASKS_SERVICE_DATA.put(newTask.getId(), newTask); + } + + /** + * Note: {@link LoadTasksCallback#onDataNotAvailable()} is never fired. In a real remote data + * source implementation, this would be fired if the server can't be contacted or the server + * returns an error. + */ + @Override + public void getTasks(final @NonNull LoadTasksCallback callback) { + // Simulate network by delaying the execution. + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values())); + } + }, SERVICE_LATENCY_IN_MILLIS); + } + + /** + * Note: {@link GetTaskCallback#onDataNotAvailable()} is never fired. In a real remote data + * source implementation, this would be fired if the server can't be contacted or the server + * returns an error. + */ + @Override + public void getTask(@NonNull String taskId, final @NonNull GetTaskCallback callback) { + final Task task = TASKS_SERVICE_DATA.get(taskId); + + // Simulate network by delaying the execution. + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + callback.onTaskLoaded(task); + } + }, SERVICE_LATENCY_IN_MILLIS); + } + + @Override + public void saveTask(@NonNull Task task) { + TASKS_SERVICE_DATA.put(task.getId(), task); + } + + @Override + public void completeTask(@NonNull Task task) { + Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true); + TASKS_SERVICE_DATA.put(task.getId(), completedTask); + } + + @Override + public void completeTask(@NonNull String taskId) { + // Not required for the remote data source because the {@link TasksRepository} handles + // converting from a {@code taskId} to a {@link task} using its cached data. + } + + @Override + public void activateTask(@NonNull Task task) { + Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId()); + TASKS_SERVICE_DATA.put(task.getId(), activeTask); + } + + @Override + public void activateTask(@NonNull String taskId) { + // Not required for the remote data source because the {@link TasksRepository} handles + // converting from a {@code taskId} to a {@link task} using its cached data. + } + + @Override + public void clearCompletedTasks() { + Iterator> it = TASKS_SERVICE_DATA.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + if (entry.getValue().isCompleted()) { + it.remove(); + } + } + } + + @Override + public void refreshTasks() { + // Not required because the {@link TasksRepository} handles the logic of refreshing the + // tasks from all the available data sources. + } + + @Override + public void deleteAllTasks() { + TASKS_SERVICE_DATA.clear(); + } + + @Override + public void deleteTask(@NonNull String taskId) { + TASKS_SERVICE_DATA.remove(taskId); + } +} +``` + +TasksLocalDataSource从数据库中获取数据。 + +```java +/** + * Concrete implementation of a data source as a db. + */ +public class TasksLocalDataSource implements TasksDataSource { + + private static TasksLocalDataSource INSTANCE; + + private TasksDbHelper mDbHelper; + + // Prevent direct instantiation. + private TasksLocalDataSource(@NonNull Context context) { + checkNotNull(context); + mDbHelper = new TasksDbHelper(context); + } + + public static TasksLocalDataSource getInstance(@NonNull Context context) { + if (INSTANCE == null) { + INSTANCE = new TasksLocalDataSource(context); + } + return INSTANCE; + } + + /** + * Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if the database doesn't exist + * or the table is empty. + */ + @Override + public void getTasks(@NonNull LoadTasksCallback callback) { + List tasks = new ArrayList(); + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + + String[] projection = { + TaskEntry.COLUMN_NAME_ENTRY_ID, + TaskEntry.COLUMN_NAME_TITLE, + TaskEntry.COLUMN_NAME_DESCRIPTION, + TaskEntry.COLUMN_NAME_COMPLETED + }; + + Cursor c = db.query( + TaskEntry.TABLE_NAME, projection, null, null, null, null, null); + + if (c != null && c.getCount() > 0) { + while (c.moveToNext()) { + String itemId = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_ENTRY_ID)); + String title = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_TITLE)); + String description = + c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_DESCRIPTION)); + boolean completed = + c.getInt(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_COMPLETED)) == 1; + Task task = new Task(title, description, itemId, completed); + tasks.add(task); + } + } + if (c != null) { + c.close(); + } + + db.close(); + + if (tasks.isEmpty()) { + // This will be called if the table is new or just empty. + callback.onDataNotAvailable(); + } else { + callback.onTasksLoaded(tasks); + } + + } + + /** + * Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if the {@link Task} isn't + * found. + */ + @Override + public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) { + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + + String[] projection = { + TaskEntry.COLUMN_NAME_ENTRY_ID, + TaskEntry.COLUMN_NAME_TITLE, + TaskEntry.COLUMN_NAME_DESCRIPTION, + TaskEntry.COLUMN_NAME_COMPLETED + }; + + String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; + String[] selectionArgs = { taskId }; + + Cursor c = db.query( + TaskEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, null); + + Task task = null; + + if (c != null && c.getCount() > 0) { + c.moveToFirst(); + String itemId = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_ENTRY_ID)); + String title = c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_TITLE)); + String description = + c.getString(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_DESCRIPTION)); + boolean completed = + c.getInt(c.getColumnIndexOrThrow(TaskEntry.COLUMN_NAME_COMPLETED)) == 1; + task = new Task(title, description, itemId, completed); + } + if (c != null) { + c.close(); + } + + db.close(); + + if (task != null) { + callback.onTaskLoaded(task); + } else { + callback.onDataNotAvailable(); + } + } + + @Override + public void saveTask(@NonNull Task task) { + checkNotNull(task); + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + ContentValues values = new ContentValues(); + values.put(TaskEntry.COLUMN_NAME_ENTRY_ID, task.getId()); + values.put(TaskEntry.COLUMN_NAME_TITLE, task.getTitle()); + values.put(TaskEntry.COLUMN_NAME_DESCRIPTION, task.getDescription()); + values.put(TaskEntry.COLUMN_NAME_COMPLETED, task.isCompleted()); + + db.insert(TaskEntry.TABLE_NAME, null, values); + + db.close(); + } + + @Override + public void completeTask(@NonNull Task task) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + ContentValues values = new ContentValues(); + values.put(TaskEntry.COLUMN_NAME_COMPLETED, true); + + String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; + String[] selectionArgs = { task.getId() }; + + db.update(TaskEntry.TABLE_NAME, values, selection, selectionArgs); + + db.close(); + } + + @Override + public void completeTask(@NonNull String taskId) { + // Not required for the local data source because the {@link TasksRepository} handles + // converting from a {@code taskId} to a {@link task} using its cached data. + } + + @Override + public void activateTask(@NonNull Task task) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + ContentValues values = new ContentValues(); + values.put(TaskEntry.COLUMN_NAME_COMPLETED, false); + + String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; + String[] selectionArgs = { task.getId() }; + + db.update(TaskEntry.TABLE_NAME, values, selection, selectionArgs); + + db.close(); + } + + @Override + public void activateTask(@NonNull String taskId) { + // Not required for the local data source because the {@link TasksRepository} handles + // converting from a {@code taskId} to a {@link task} using its cached data. + } + + @Override + public void clearCompletedTasks() { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + String selection = TaskEntry.COLUMN_NAME_COMPLETED + " LIKE ?"; + String[] selectionArgs = { "1" }; + + db.delete(TaskEntry.TABLE_NAME, selection, selectionArgs); + + db.close(); + } + + @Override + public void refreshTasks() { + // Not required because the {@link TasksRepository} handles the logic of refreshing the + // tasks from all the available data sources. + } + + @Override + public void deleteAllTasks() { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + db.delete(TaskEntry.TABLE_NAME, null, null); + + db.close(); + } + + @Override + public void deleteTask(@NonNull String taskId) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + + String selection = TaskEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; + String[] selectionArgs = { taskId }; + + db.delete(TaskEntry.TABLE_NAME, selection, selectionArgs); + + db.close(); + } +} +``` + +#### Presenter + +在构造函数中传入Model和View,并为View设置Presenter。所有的Presenter都继承自BasePresenter + +```java +public interface BasePresenter { + + void start(); + +} +``` + +```java +/** + * Listens to user actions from the UI ({@link TasksFragment}), retrieves the data and updates the + * UI as required. + */ +public class TasksPresenter implements TasksContract.Presenter { + + private final TasksRepository mTasksRepository; + + private final TasksContract.View mTasksView; + + private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS; + + private boolean mFirstLoad = true; + + public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) { + mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null"); + mTasksView = checkNotNull(tasksView, "tasksView cannot be null!"); + + mTasksView.setPresenter(this); + } + + @Override + public void start() { + loadTasks(false); + } + + @Override + public void result(int requestCode, int resultCode) { + // If a task was successfully added, show snackbar + if (AddEditTaskActivity.REQUEST_ADD_TASK == requestCode && Activity.RESULT_OK == resultCode) { + mTasksView.showSuccessfullySavedMessage(); + } + } + + @Override + public void loadTasks(boolean forceUpdate) { + // Simplification for sample: a network reload will be forced on first load. + loadTasks(forceUpdate || mFirstLoad, true); + mFirstLoad = false; + } + + /** + * @param forceUpdate Pass in true to refresh the data in the {@link TasksDataSource} + * @param showLoadingUI Pass in true to display a loading icon in the UI + */ + private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) { + if (showLoadingUI) { + mTasksView.setLoadingIndicator(true); + } + if (forceUpdate) { + mTasksRepository.refreshTasks(); + } + + // The network request might be handled in a different thread so make sure Espresso knows + // that the app is busy until the response is handled. + EspressoIdlingResource.increment(); // App is busy until further notice + + mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() { + @Override + public void onTasksLoaded(List tasks) { + List tasksToShow = new ArrayList(); + + // This callback may be called twice, once for the cache and once for loading + // the data from the server API, so we check before decrementing, otherwise + // it throws "Counter has been corrupted!" exception. + if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) { + EspressoIdlingResource.decrement(); // Set app as idle. + } + + // We filter the tasks based on the requestType + for (Task task : tasks) { + switch (mCurrentFiltering) { + case ALL_TASKS: + tasksToShow.add(task); + break; + case ACTIVE_TASKS: + if (task.isActive()) { + tasksToShow.add(task); + } + break; + case COMPLETED_TASKS: + if (task.isCompleted()) { + tasksToShow.add(task); + } + break; + default: + tasksToShow.add(task); + break; + } + } + // The view may not be able to handle UI updates anymore + if (!mTasksView.isActive()) { + return; + } + if (showLoadingUI) { + mTasksView.setLoadingIndicator(false); + } + + processTasks(tasksToShow); + } + + @Override + public void onDataNotAvailable() { + // The view may not be able to handle UI updates anymore + if (!mTasksView.isActive()) { + return; + } + mTasksView.showLoadingTasksError(); + } + }); + } + + private void processTasks(List tasks) { + if (tasks.isEmpty()) { + // Show a message indicating there are no tasks for that filter type. + processEmptyTasks(); + } else { + // Show the list of tasks + mTasksView.showTasks(tasks); + // Set the filter label's text. + showFilterLabel(); + } + } + + private void showFilterLabel() { + switch (mCurrentFiltering) { + case ACTIVE_TASKS: + mTasksView.showActiveFilterLabel(); + break; + case COMPLETED_TASKS: + mTasksView.showCompletedFilterLabel(); + break; + default: + mTasksView.showAllFilterLabel(); + break; + } + } + + private void processEmptyTasks() { + switch (mCurrentFiltering) { + case ACTIVE_TASKS: + mTasksView.showNoActiveTasks(); + break; + case COMPLETED_TASKS: + mTasksView.showNoCompletedTasks(); + break; + default: + mTasksView.showNoTasks(); + break; + } + } + + @Override + public void addNewTask() { + mTasksView.showAddTask(); + } + + @Override + public void openTaskDetails(@NonNull Task requestedTask) { + checkNotNull(requestedTask, "requestedTask cannot be null!"); + mTasksView.showTaskDetailsUi(requestedTask.getId()); + } + + @Override + public void completeTask(@NonNull Task completedTask) { + checkNotNull(completedTask, "completedTask cannot be null!"); + mTasksRepository.completeTask(completedTask); + mTasksView.showTaskMarkedComplete(); + loadTasks(false, false); + } + + @Override + public void activateTask(@NonNull Task activeTask) { + checkNotNull(activeTask, "activeTask cannot be null!"); + mTasksRepository.activateTask(activeTask); + mTasksView.showTaskMarkedActive(); + loadTasks(false, false); + } + + @Override + public void clearCompletedTasks() { + mTasksRepository.clearCompletedTasks(); + mTasksView.showCompletedTasksCleared(); + loadTasks(false, false); + } + + /** + * Sets the current task filtering type. + * + * @param requestType Can be {@link TasksFilterType#ALL_TASKS}, + * {@link TasksFilterType#COMPLETED_TASKS}, or + * {@link TasksFilterType#ACTIVE_TASKS} + */ + @Override + public void setFiltering(TasksFilterType requestType) { + mCurrentFiltering = requestType; + } + + @Override + public TasksFilterType getFiltering() { + return mCurrentFiltering; + } + +} +``` + +#### View + +Activity和Fragment以及自定义View都可以充当View。 + +android-architecture中使用的是fragment。 + +所有的View都实现BaseView接口,BaseView中定义了一个setPresenter,为View设置Presenter。 + +```java +public interface BaseView { + void setPresenter(T presenter); +} +``` + +为View设置Presenter + +```java +//创建fragment +TasksFragment tasksFragment = + (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame); + if (tasksFragment == null) { + // Create the fragment + tasksFragment = TasksFragment.newInstance(); + ActivityUtils.addFragmentToActivity( + getSupportFragmentManager(), tasksFragment, R.id.contentFrame); + } + +//创建Presenter并设置fragment 在Presenter构造函数为位fragment设置Presenter + // Create the presenter +mTasksPresenter = new TasksPresenter( + Injection.provideTasksRepository(getApplicationContext()), tasksFragment); +``` + +### 参考 + +* [Android Architecture Patterns Part 2:Model-View-Presenter](https://medium.com/upday-devs/android-architecture-patterns-part-2-model-view-presenter-8a6faaae14a5) +* [Android Architecture Patterns Part 3:Model-View-ViewModel](https://medium.com/upday-devs/android-architecture-patterns-part-3-model-view-viewmodel-e7eeee76b73b) diff --git a/android-studio.md b/android-studio.md new file mode 100644 index 00000000..66b2a70c --- /dev/null +++ b/android-studio.md @@ -0,0 +1,61 @@ + +### 知识结构 + + + + + + +## 提示和技巧 + + +## 快捷键 +| 操作 | Mac| +| -- | -- | +| 命令查表 | cmd+shift+A | +|快速修复 | alt+enter | +| 格式化代码 |alt+cmd+L | +| 显示选中API文档| F1 | +| 显示选中方法参数 |cmd+P | +| 跳到源码| cmd+鼠标左键 或者F4 | +| 生成方法 | cmd+N 或者 ctrl+enter | +| 删除一行| cmd+delete | +| 复制一行| cmd+D| +|搜索symbol | alt+cmd+O | +|构建 | cmd+F9| +|构建和运行 |ctrl+R| +|打开一个类 |cmd+O| +|打开一个文件 |cmd+shift+O| +|显示类的变量和方法使用位置 |alt+F7| +|显示类的变量和方法使用位置(以对话框的方式展示) |cmd+alt+F7| +|定位到类、方法或变量的声明 |cmd+B 或者cmd+鼠标左键| +|列出所有实现选中类或接口的类或接口|cmd+alt+B | +|跳转到父类的方法|cmd+U | +|移动代码|alt+shift+up/down | +|显示所有方法和变量|cmd+F12 | +|Sourround With|cmd+alt+T | +|大小写转换|cmd+shift+U | +|使用模板|cmd+J | +||alt+cmd+F | +|[查找和替换](https://www.jetbrains.com/idea/help/find-and-replace-in-path.html)|查找cmd+shift+F 替换cmd+shift+R| +|Surround with| cmd+alt+T| +|布局中Design和Text切换| ++ ← / → | +|布局和代码切换| ++↑ | + +局部变量抽取为成员变量ctrl + alt + F +多行编辑ctrl+g +显示粘贴板 shift + commd+v + + +### 文章 + +* [Keyboard Shortcuts](https://developer.android.com/studio/intro/keyboard-shortcuts.html) + +https://medium.com/@mmbialas/50-android-studio-tips-tricks-resources-you-should-be-familiar-with-as-an-android-developer-af86e7cf56d2#.ydnovmrwm +* 64k限制 + * [Configure Apps with Over 64K Methods](https://developer.android.com/studio/build/multidex.html) + * [dex分包变形记](https://dev.qq.com/topic/5913db5c29d8be2a14b64da8) + * [美团Android DEX自动拆包及动态加载简介](http://tech.meituan.com/mt-android-auto-split-dex.html) +* Gradle + * [How to speed up your slow Gradle builds](How to speed up your slow Gradle builds) + * [Gradle详解](http://www.infoq.com/cn/articles/android-in-depth-gradle) \ No newline at end of file diff --git a/androidstudio/publish-app.md b/androidstudio/publish-app.md new file mode 100644 index 00000000..0e559cea --- /dev/null +++ b/androidstudio/publish-app.md @@ -0,0 +1,70 @@ + + + + +### jarsigner使用 + +语法 + +``` +jarsigner [选项] jar-file 别名 + jarsigner -verify [选项] jar-file [别名...] + +[-keystore ] 密钥库位置 + +[-storepass <口令>] 用于密钥库完整性的口令 + +[-storetype <类型>] 密钥库类型 + +[-keypass <口令>] 私有密钥的口令 (如果不同) + +[-certchain <文件>] 替代证书链文件的名称 + +[-sigfile <文件>] .SF/.DSA 文件的名称 + +[-signedjar <文件>] 已签名的 JAR 文件的名称 + +[-digestalg <算法>] 摘要算法的名称 + +[-sigalg <算法>] 签名算法的名称 + +[-verify] 验证已签名的 JAR 文件 + +[-verbose[:suboptions]] 签名/验证时输出详细信息。 + 子选项可以是 all, grouped 或 summary + +[-certs] 输出详细信息和验证时显示证书 + +[-tsa ] 时间戳颁发机构的位置 + +[-tsacert <别名>] 时间戳颁发机构的公共密钥证书 + +[-tsapolicyid ] 时间戳颁发机构的 TSAPolicyID + +[-altsigner <类>] 替代的签名机制的类名 + +[-altsignerpath <路径列表>] 替代的签名机制的位置 + +[-internalsf] 在签名块内包含 .SF 文件 + +[-sectionsonly] 不计算整个清单的散列 + +[-protected] 密钥库具有受保护验证路径 + +[-providerName <名称>] 提供方名称 + +[-providerClass <类> 加密服务提供方的名称 + [-providerArg <参数>]]... 主类文件和构造器参数 + +[-strict] 将警告视为错误 +``` +例子: + +``` +jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore +my_application.apk alias_name +``` +### 参考 + +* [发布您的应用](https://developer.android.com/studio/publish/index.html) +* [apksigner](https://developer.android.com/studio/command-line/apksigner.html) \ No newline at end of file diff --git a/annotation/README.md b/annotation/README.md new file mode 100644 index 00000000..14f58a73 --- /dev/null +++ b/annotation/README.md @@ -0,0 +1,2 @@ +# 编译时注解 + diff --git a/annotation/androidannotations.md b/annotation/androidannotations.md deleted file mode 100755 index d48ded04..00000000 --- a/annotation/androidannotations.md +++ /dev/null @@ -1,163 +0,0 @@ -#AndroidAnnotations - - -[AndroidAnnotations][1]是一个能够让你快速进行Android开发的开源框架,它能让你专注于真正重要的地方。 -使代码更加精简,使项目更加容易维护,它的目标就是`Fast Android Development.Easy maintainance`。 -通过一段时间的使用发现,相比原生的Android开发,确实能够让你少些很多代码,它的[首页][2]也给出了一个简单 -的例子,通过例子也可以看到代码比之前几乎少写了一半。 - -使用Annotations可以实现如下功能。 - - -* 提供了View,resources等注入 -* 提供了`click`,`touch`等事件处理 -* 增强Activity,Fragment等组建。 -* 简单的线程操作 -* 集成了大量的第三方框架 - - -## 1.开始使用 - -在这里我使用的是开发工具是Android Studio,构建工具是Gradle,如果你使用其他IDE和构建工具,可以参考[这里][3]。 - - -``` -apply plugin: 'com.android.application' -apply plugin: 'android-apt' - - -android { - compileSdkVersion 20 - buildToolsVersion "20.0.0" - - defaultConfig { - applicationId "com.malinkang.androidannotation" - minSdkVersion 15 - targetSdkVersion 20 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - runProguard false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) -} -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:0.12.+' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4+' - } -} - -repositories { - mavenCentral() - mavenLocal() -} - -def AAVersion = '3.1' - -dependencies { - apt "org.androidannotations:androidannotations:$AAVersion" - compile "org.androidannotations:androidannotations-api:$AAVersion" -} - -apt { - arguments { - androidManifestFile variant.processResources.manifestFile - resourcePackageName 'com.malinkang.androidannotation' - } -} - -``` - -在真正开始使用之前,我们需要知道AndroidAnnotations是如何工作的。AndroidAnnotations使用`Java Annotation Processing Tool`在编译的时候会自动生成一些类的子类。例如使用`@EActivity`注解一个Activity,会生成一个Activity的子类,生成的子类名称在原来类的名字后面加了一个下划线。 -```java -@EActivity -public class MyActivity extends Activity { - // ... -} -``` -将会自动生成下面的子类 - -```java - -public final class MyActivity_ extends MyActivity { - // ... -} -``` - -子类复写父类的方法,真正工作的activity其实是MyActivity_,所以在`AndroidManifest.xml`中配置MyActivity_而不是MyActivity。 - -``` - - -``` - -## 2. 注入 - -### 2.1 View 注解 - -`@ViewById`注解功能等价于`findViewById()`方法。使用时注意字段不能使用`private`修饰。 - -```java -@EActivity -public class MyActivity extends Activity { - - // Injects R.id.myEditText - @ViewById - EditText myEditText; - - @ViewById(R.id.myTextView) - TextView textView; -} -``` - -`@ViewsById`注解可以一次获取多个View。 - -```java -@EActivity -public class MyActivity extends Activity { - - @ViewsById({R.id.myTextView1, R.id.myOtherTextView}) - List textViews; - - @AfterViews - void updateTextWithDate() { - for (TextView textView : textViews) { - textView.setText("Date: " + new Date()); - } - } -} -``` -使用`@AfterViews`注解注解方法,可以让此方法在View完全加载完成后再调用 - -```java -@EActivity(R.layout.main) -public class MyActivity extends Activity { - - @ViewById - TextView myTextView; - - @AfterViews - void updateTextWithDate() { - myTextView.setText("Date: " + new Date()); - } -} -``` - - - - - -[1]: https://github.com/excilys/androidannotations/ -[2]: http://androidannotations.org/ -[3]: https://github.com/excilys/androidannotations/wiki/Configuration diff --git a/annotation/annotation-processing.md b/annotation/annotation-processing.md new file mode 100644 index 00000000..0dc820c8 --- /dev/null +++ b/annotation/annotation-processing.md @@ -0,0 +1,909 @@ +# \[译\]Java注解处理器 + +翻译参考:[Java注解处理器](https://www.race604.com/annotation-processing/) + +[原文](http://hannesdorfmann.com/annotation-processing/annotationprocessing101) + +在这篇文章中,我将阐述怎样写一个注解处理器\(Annotation Processor\)。在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么;然后,我将一步一步实现一个简单的注解器。 + +## 一些基本概念 + +在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。 + +注解处理器(Annotation Processor)是**javac**的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以对自定义注解,并注册相应的注解处理器。到这里,我假设你已经知道什么是注解,并且知道怎么申明的一个注解。如果你不熟悉注解,你可以在这[官方文档](http://docs.oracle.com/javase/tutorial/java/annotations/index.html)中得到更多信息。注解处理器在Java 5开始就有了,但是从Java 6(2006年12月发布)开始才有可用的API。过了一些时间,Java世界才意识到注解处理器的强大作用,所以它到最近几年才流行起来。 + +一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是`.java`文件)作为输出。这具体的含义什么呢?你可以生成Java代码!这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被**javac**编译。 + +## 虚处理器`AbstractProcessor` + +我们首先看一下处理器的API。每一个处理器都是继承于`AbstractProcessor`,如下所示: + +```java +package com.example; + +public class MyProcessor extends AbstractProcessor { + + @Override + public synchronized void init(ProcessingEnvironment env){ } + + @Override + public boolean process(Set annoations, RoundEnvironment env) { } + + @Override + public Set getSupportedAnnotationTypes() { } + + @Override + public SourceVersion getSupportedSourceVersion() { } + +} +``` + + + +* `init(ProcessingEnvironment env)`: 每一个注解处理器类都**必须有一个空的构造函数**。然而,这里有一个特殊的`init()`方法,它会被注解处理工具调用,并输入`ProcessingEnviroment`参数。`ProcessingEnviroment`提供很多有用的工具类`Elements`, `Types`和`Filer`。后面我们将看到详细的内容。 +* `process(Set annotations, RoundEnvironment env)`: 这相当于每个处理器的主函数`main()`。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数`RoundEnviroment`,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。 +* `getSupportedAnnotationTypes()`: 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。 +* `getSupportedSourceVersion()`: 用来指定你使用的Java版本。通常这里返回`SourceVersion.latestSupported()`。然而,如果你有足够的理由只支持Java 6的话,你也可以返回`SourceVersion.RELEASE_6`。我推荐你使用前者。 + +## 注册你的处理器 + +你可能会问,_我怎样将处理器`MyProcessor`注册到javac中_。你必须提供一个`.jar`文件。就像其他**.jar**文件一样,你打包你的注解处理器到此文件中。并且,在你的jar中,你需要打包一个特定的文件`javax.annotation.processing.Processor`到`META-INF/services`路径下。所以,你的.jar文件看起来就像下面这样: + +* MyProcessor.jar +* * com +* * * example +* * * * MyProcessor.class +* * META-INF +* * * services +* * * * javax.annotation.processing.Processor + +打包进MyProcessor.jar中的`javax.annotation.processing.Processor`的内容是,注解处理器的合法的全名列表,每一个元素换行分割: + +```text +com.example.MyProcessor +com.foo.OtherProcessor +net.blabla.SpecialProcessor +``` + +把`MyProcessor.jar`放到你的builpath中,javac会自动检查和读取`javax.annotation.processing.Processor`中的内容,并且注册`MyProcessor`作为注解处理器。 + +## 例子:工厂模式 + +是时候来说一个实际的例子了。我们将使用**maven**工具来作为我们的编译系统和依赖管理工具。如果你不熟悉**maven**,不用担心,因为maven不是必须的。本例子的完成代码在[Github](https://github.com/sockeqwe/annotationprocessing101)上。 + +开始之前,我必须说,要为这个教程找到一个需要用注解处理器解决的简单问题,实在并不容易。这里我们将实现一个非常简单的工厂模式(不是抽象工厂模式)。这将对注解处理器的API做一个非常简明的介绍。所以,这个问题的程序并不是那么有用,也不是一个真实世界的例子。所以在此申明,你将学习关于注解处理过程的相关内容,而不是设计模式。 + +我们将要解决的问题是:我们将实现一个披萨店,这个披萨店给消费者提供两种披萨(“Margherita”和“Calzone”)以及提拉米苏甜点\(Tiramisu\)。 + +看一下如下的代码,不需要做任何更多的解释: + +```java +public interface Meal { + public float getPrice(); +} + +public class MargheritaPizza implements Meal { + + @Override public float getPrice() { + return 6.0f; + } +} + +public class CalzonePizza implements Meal { + + @Override public float getPrice() { + return 8.5f; + } +} + +public class Tiramisu implements Meal { + + @Override public float getPrice() { + return 4.5f; + } +} +``` + +为了在我们的披萨店`PizzsStore`下订单,消费者需要输入餐\(Meal\)的名字。 + + +```java +public class PizzaStore { + + public Meal order(String mealName) { + + if (mealName == null) { + throw new IllegalArgumentException("Name of the meal is null!"); + } + + if ("Margherita".equals(mealName)) { + return new MargheritaPizza(); + } + + if ("Calzone".equals(mealName)) { + return new CalzonePizza(); + } + + if ("Tiramisu".equals(mealName)) { + return new Tiramisu(); + } + + throw new IllegalArgumentException("Unknown meal '" + mealName + "'"); + } + + public static void main(String[] args) throws IOException { + PizzaStore pizzaStore = new PizzaStore(); + Meal meal = pizzaStore.order(readConsole()); + System.out.println("Bill: $" + meal.getPrice()); + } +} +``` + +正如你所见,在`order()`方法中,我们有很多的`if`语句,并且如果我们每添加一种新的披萨,我们都要添加一条新的`if`语句。但是等一下,使用注解处理和工厂模式,我们可以让注解处理器来帮我们自动生成这些`if`语句。如此以来,我们期望的是如下的代码: + +```java +public class PizzaStore { + + private MealFactory factory = new MealFactory(); + + public Meal order(String mealName) { + return factory.create(mealName); + } + + public static void main(String[] args) throws IOException { + PizzaStore pizzaStore = new PizzaStore(); + Meal meal = pizzaStore.order(readConsole()); + System.out.println("Bill: $" + meal.getPrice()); + } +} +``` + +`MealFactory`应该是如下的样子: + +```java +public class MealFactory { + + public Meal create(String id) { + if (id == null) { + throw new IllegalArgumentException("id is null!"); + } + if ("Calzone".equals(id)) { + return new CalzonePizza(); + } + + if ("Tiramisu".equals(id)) { + return new Tiramisu(); + } + + if ("Margherita".equals(id)) { + return new MargheritaPizza(); + } + + throw new IllegalArgumentException("Unknown id = " + id); + } +} +``` + +### `@Factory`注解 + +你能猜到么:我们想用注解处理器自动生成`MealFactory`。更一般的说,我们将想要提供一个注解和一个处理器来生成工厂类。 + +我们先来看一下`@Factory`注解: + +```java +@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) +public @interface Factory { + + /** + * 工厂的名字 + */ + Class type(); + + /** + * 用来表示生成哪个对象的唯一id + */ + String id(); +} +``` + +想法是这样的:我们将使用同样的`type()`注解那些属于同一个工厂的类,并且用注解的`id()`做一个映射,例如从`"Calzone"`映射到`"ClzonePizza"`类。我们应用`@Factory`注解到我们的类中,如下: + +```java +@Factory( + id = "Margherita", + type = Meal.class +) +public class MargheritaPizza implements Meal { + + @Override public float getPrice() { + return 6f; + } +} +``` + +```java +@Factory( + id = "Calzone", + type = Meal.class +) +public class CalzonePizza implements Meal { + + @Override public float getPrice() { + return 8.5f; + } +} +``` + +```java +@Factory( + id = "Tiramisu", + type = Meal.class +) +public class Tiramisu implements Meal { + + @Override public float getPrice() { + return 4.5f; + } +} +``` + + + +你可能会问你自己,我们是否可以只把`@Factory`注解应用到我们的`Meal`接口上?答案是,注解是不能继承的。一个类`class X`被注解,并不意味着它的子类`class Y extends X`会自动被注解。在我们开始写处理器的代码之前,我们先规定如下一些规则: + +1. 只有类可以被`@Factory`注解,因为接口或者抽象类并不能用`new`操作实例化; +2. 被`@Factory`注解的类,必须至少提供一个公开的默认构造器(即没有参数的构造函数)。否者我们没法实例化一个对象。 +3. 被`@Factory`注解的类必须直接或者间接的继承于`type()`指定的类型; +4. 具有相同的`type`的注解类,将被聚合在一起生成一个工厂类。这个生成的类使用_Factory_后缀,例如`type = Meal.class`,将生成`MealFactory`工厂类; +5. `id`只能是String类型,并且在同一个`type`组中必须唯一。 + +### 处理器 + +我将通过添加代码和一段解释的方法,一步一步的引导你来构建我们的处理器。省略号\(`...`\)表示省略那些已经讨论过的或者将在后面的步骤中讨论的代码,目的是为了让我们的代码有更好的可读性。正如我们前面说的,我们完整的代码可以在[Github](https://github.com/sockeqwe/annotationprocessing101)上找到。好了,让我们来看一下我们的处理器类`FactoryProcessor`的骨架: + +```java +@AutoService(Processor.class) +public class FactoryProcessor extends AbstractProcessor { + + private Types typeUtils; + private Elements elementUtils; + private Filer filer; + private Messager messager; + private Map factoryClasses = new LinkedHashMap(); + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + typeUtils = processingEnv.getTypeUtils(); + elementUtils = processingEnv.getElementUtils(); + filer = processingEnv.getFiler(); + messager = processingEnv.getMessager(); + } + + @Override + public Set getSupportedAnnotationTypes() { + Set annotataions = new LinkedHashSet(); + annotataions.add(Factory.class.getCanonicalName()); + return annotataions; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + ... + } +} +``` + +你看到在代码的第一行是`@AutoService(Processor.class)`,这是什么?这是一个其他注解处理器中引入的注解。`AutoService`注解处理器是Google开发的,用来生成`META-INF/services/javax.annotation.processing.Processor`文件的。是的,你没有看错,我们可以在注解处理器中使用注解。非常方便,难道不是么?在`getSupportedAnnotationTypes()`中,我们指定本处理器将处理`@Factory`注解。 + +### Elements和TypeMirrors + +在`init()`中我们获得如下引用: + +* **Elements**:一个用来处理`Element`的工具类(后面将做详细说明); +* **Types**:一个用来处理`TypeMirror`的工具类(后面将做详细说明); +* **Filer**:正如这个名字所示,使用Filer你可以创建文件。 + +在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的`Element`。换句话说:`Element`代表程序的元素,例如包、类或者方法。每个`Element`代表一个静态的、语言级别的构件。在下面的例子中,我们通过注释来说明这个: + +```java +package com.example; // PackageElement + +public class Foo { // TypeElement + + private int a; // VariableElement + private Foo other; // VariableElement + + public Foo () {} // ExecuteableElement + + public void setA ( // ExecuteableElement + int newA // VariableElement + ) {} +} +``` + +你必须换个角度来看源代码,它只是结构化的文本,他不是可运行的。你可以想象它就像你将要去解析的XML文件一样(或者是编译器中抽象的语法树)。就像XML解释器一样,有一些类似DOM的元素。你可以从一个元素导航到它的父或者子元素上。 + +举例来说,假如你有一个代表`public class Foo`类的`TypeElement`元素,你可以遍历它的孩子,如下: + +```java +TypeElement fooClass = ... ; +for (Element e : fooClass.getEnclosedElements()){ // iterate over children + Element parent = e.getEnclosingElement(); // parent == fooClass +} +``` + +正如你所见,**Element**代表的是源代码。`TypeElement`代表的是源代码中的类型元素,例如类。然而,`TypeElement`并不包含类本身的信息。你可以从`TypeElement`中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过`TypeMirror`获取。你可以通过调用`elements.asType()`获取元素的`TypeMirror`。 + +### 搜索@Factory注解 + +我们来一步一步实现`process()`方法。首先,我们从搜索被注解了`@Factory`的类开始: + + +```java +@AutoService(Processor.class) +public class FactoryProcessor extends AbstractProcessor { + + private Types typeUtils; + private Elements elementUtils; + private Filer filer; + private Messager messager; + private Map factoryClasses = new LinkedHashMap(); + ... + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + + // 遍历所有被注解了@Factory的元素 + for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) { + ... + } + } + ... +} +``` + +这里并没有什么高深的技术。`roundEnv.getElementsAnnotatedWith(Factory.class))`返回所有被注解了`@Factory`的元素的列表。你可能已经注意到,我们并没有说“_所有被注解了`@Factory`的类的列表_”,因为它真的是返回`Element`的列表。请记住:`Element`可以是类、方法、变量等。所以,接下来,我们必须检查这些`Element`是否是一个类: + + +```java +@Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + + for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) { + + // 检查被注解为@Factory的元素是否是一个类 + if (annotatedElement.getKind() != ElementKind.CLASS) { + ... + } + } + ... +} +``` + +为什么要这么做?我们要确保只有class元素被我们的处理器处理。前面我们已经学习到类是用`TypeElement`表示。我们为什么不这样判断呢`if (! (annotatedElement instanceof TypeElement) )`?这是错误的,因为接口(interface)类型也是TypeElement。所以在注解处理器中,我们要避免使用`instanceof`,而是配合`TypeMirror`使用`EmentKind`或者`TypeKind`。 + +### 错误处理 + +在`init()`中,我们也获得了一个`Messager`对象的引用。`Messager`提供给注解处理器一个报告错误、警告以及提示信息的途径。它不是注解处理器开发者的日志工具,而是用来写一些信息给使用此注解器的第三方开发者的。在[官方文档](http://docs.oracle.com/javase/7/docs/api/javax/tools/Diagnostic.Kind.html)中描述了消息的不同级别。非常重要的是[`Kind.ERROR`](http://docs.oracle.com/javase/7/docs/api/javax/tools/Diagnostic.Kind.html#ERROR),因为这种类型的信息用来表示我们的注解处理器处理失败了。很有可能是第三方开发者错误的使用了`@Factory`注解(例如,给接口使用了@Factory注解)。这个概念和传统的Java应用有点不一样,在传统Java应用中我们可能就抛出一个异常`Exception`。如果你在`process()`中抛出一个异常,那么运行注解处理器的JVM将会崩溃(就像其他Java应用一样),使用我们注解处理器FactoryProcessor第三方开发者将会从javac中得到非常难懂的出错信息,因为它包含FactoryProcessor的堆栈跟踪(Stacktace)信息。因此,注解处理器就有一个`Messager`类,它能够打印非常优美的错误信息。除此之外,你还可以连接到出错的元素。在像IntelliJ这种现代的IDE(集成开发环境)中,第三方开发者可以直接点击错误信息,IDE将会直接跳转到第三方开发者项目的出错的源文件的相应的行。 + +我们重新回到`process()`方法的实现。如果遇到一个非类类型被注解`@Factory`,我们发出一个出错信息: + +```java +public boolean process(Set annotations, RoundEnvironment roundEnv) { + + for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) { + + // 检查被注解为@Factory的元素是否是一个类 + if (annotatedElement.getKind() != ElementKind.CLASS) { + error(annotatedElement, "Only classes can be annotated with @%s", + Factory.class.getSimpleName()); + return true; // 退出处理 + } + ... + } + +private void error(Element e, String msg, Object... args) { + messager.printMessage( + Diagnostic.Kind.ERROR, + String.format(msg, args), + e); + } + +} +``` + +让Messager显示相关出错信息,更重要的是**注解处理器程序必须完成运行而不崩溃**,这就是为什么在调用`error()`后直接`return`。如果我们不直接返回,`process()`将继续运行,因为`messager.printMessage( Diagnostic.Kind.ERROR)`不会停止此进程。因此,如果我们在打印错误信息以后不返回的话,我们很可能就会运行到一个_NullPointerException_等。就像我们前面说的,如果我们继续运行`process()`,问题是如果在`process()`中抛出一个未处理的异常,javac将会打印出内部的_NullPointerException_,而不是`Messager`中的错误信息。 + +### 数据模型 + +在继续检查被注解@Fractory的类是否满足我们上面说的5条规则之前,我们将介绍一个让我们更方便继续处理的数据结构。有时候,一个问题或者解释器看起来如此简单,以至于程序员倾向于用一个面向过程方式来写整个处理器。但是你知道吗?一个注解处理器任然是一个Java程序,所以我们需要使用面向对象编程、接口、设计模式,以及任何你将在其他普通Java程序中使用的技巧。 + +我们的`FactoryProcessor`非常简单,但是我们仍然想要把一些信息存为对象。在`FactoryAnnotatedClass`中,我们保存被注解类的数据,比如合法的类的名字,以及@Factory注解本身的一些信息。所以,我们保存`TypeElement`和处理过的@Factory注解: + +```java +public class FactoryAnnotatedClass { + + private TypeElement annotatedClassElement; + private String qualifiedSuperClassName; + private String simpleTypeName; + private String id; + + public FactoryAnnotatedClass(TypeElement classElement) throws IllegalArgumentException { + this.annotatedClassElement = classElement; + Factory annotation = classElement.getAnnotation(Factory.class); + id = annotation.id(); + + if (StringUtils.isEmpty(id)) { + throw new IllegalArgumentException( + String.format("id() in @%s for class %s is null or empty! that's not allowed", + Factory.class.getSimpleName(), classElement.getQualifiedName().toString())); + } + + // Get the full QualifiedTypeName + try { + Class clazz = annotation.type(); + qualifiedSuperClassName = clazz.getCanonicalName(); + simpleTypeName = clazz.getSimpleName(); + } catch (MirroredTypeException mte) { + DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror(); + TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement(); + qualifiedSuperClassName = classTypeElement.getQualifiedName().toString(); + simpleTypeName = classTypeElement.getSimpleName().toString(); + } + } + + /** + * 获取在{@link Factory#id()}中指定的id + * return the id + */ + public String getId() { + return id; + } + + /** + * 获取在{@link Factory#type()}指定的类型合法全名 + * + * @return qualified name + */ + public String getQualifiedFactoryGroupName() { + return qualifiedSuperClassName; + } + + + /** + * 获取在 {@link Factory#type()} 中指定的类型的简单名字 + * + * @return qualified name + */ + public String getSimpleFactoryGroupName() { + return simpleTypeName; + } + + /** + * 获取被@Factory注解的原始元素 + */ + public TypeElement getTypeElement() { + return annotatedClassElement; + } +} +``` + +代码很多,但是最重要的部分是在构造函数中。其中你能找到如下的代码: + +```java +Factory annotation = classElement.getAnnotation(Factory.class); +id = annotation.id(); // Read the id value (like "Calzone" or "Tiramisu") + +if (StringUtils.isEmpty(id)) { + throw new IllegalArgumentException( + String.format("id() in @%s for class %s is null or empty! that's not allowed", + Factory.class.getSimpleName(), classElement.getQualifiedName().toString())); +} +``` + +这里我们获取@Factory注解,并且检查id是否为空?如果为空,我们将抛出**IllegalArgumentException**异常。你可能感到疑惑的是,前面我们说了不要抛出异常,而是使用`Messager`。这里仍然不矛盾。我们抛出内部的异常,你在将在后面看到会在`process()`中捕获这个异常。我这样做出于一下两个原因: + +1. 我想示意我们应该像普通的Java程序一样编码。抛出和捕获异常是非常好的Java编程实践; +2. 如果我们想要在`FactoryAnnotatedClass`中打印信息,我需要也传入`Messager`对象,并且我们在**错误处理**一节中已经提到,为了打印`Messager`信息,我们必须成功停止处理器运行。如果我们使用`Messager`打印了错误信息,我们怎样告知`process()`出现了错误呢?最容易,并且我认为最直观的方式就是抛出一个异常,然后让`process()`捕获之。 + +接下来,我们将获取`@Fractory`注解中的`type`成员。我们比较关心的是合法的全名: + +```java +try { + Class clazz = annotation.type(); + qualifiedGroupClassName = clazz.getCanonicalName(); + simpleFactoryGroupName = clazz.getSimpleName(); +} catch (MirroredTypeException mte) { + DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror(); + TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement(); + qualifiedGroupClassName = classTypeElement.getQualifiedName().toString(); + simpleFactoryGroupName = classTypeElement.getSimpleName().toString(); +} +``` + +这里有一点小麻烦,因为这里的类型是一个`java.lang.Class`。这就意味着,他是一个真正的Class对象。因为注解处理是在编译Java源代码之前。我们需要考虑如下两种情况: + +1. **这个类已经被编译**:这种情况是:如果第三方`.jar`包含已编译的被@Factory注解`.class`文件。在这种情况下,我们可以想`try`中那块代码中所示直接获取`Class`。 +2. **这个还没有被编译**:这种情况是我们尝试编译被@Fractory注解的源代码。这种情况下,直接获取Class会抛出`MirroredTypeException`异常。幸运的是,MirroredTypeException包含一个`TypeMirror`,它表示我们未编译类。因为我们已经知道它必定是一个类类型(我们已经在前面检查过),我们可以直接强制转换为`DeclaredType`,然后读取`TypeElement`来获取合法的名字。 + +好了,我们现在还需要一个数据结构`FactoryGroupedClasses`,它将简单的组合所有的`FactoryAnnotatedClasses`到一起。 + +```java +public class FactoryGroupedClasses { + + private String qualifiedClassName; + + private Map itemsMap = + new LinkedHashMap(); + + public FactoryGroupedClasses(String qualifiedClassName) { + this.qualifiedClassName = qualifiedClassName; + } + + public void add(FactoryAnnotatedClass toInsert) throws IdAlreadyUsedException { + + FactoryAnnotatedClass existing = itemsMap.get(toInsert.getId()); + if (existing != null) { + throw new IdAlreadyUsedException(existing); + } + + itemsMap.put(toInsert.getId(), toInsert); + } + + public void generateCode(Elements elementUtils, Filer filer) throws IOException { + ... + } +} +``` + +正如你所见,这是一个基本的`Map`,这个映射表用来映射@Factory.id\(\)到FactoryAnnotatedClass。我们选择`Map`这个数据类型,是因为我们要确保每个id是唯一的,我们可以很容易通过map查找实现。`generateCode()`方法将被用来生成工厂类代码(将在后面讨论)。 + +### 匹配标准 + +我们继续实现`process()`方法。接下来我们想要检查被注解的类必须有只要一个公开的构造函数,不是抽象类,继承于特定的类型,以及是一个公开类: + +```java +public class FactoryProcessor extends AbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + + for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) { + + ... + + // 因为我们已经知道它是ElementKind.CLASS类型,所以可以直接强制转换 + TypeElement typeElement = (TypeElement) annotatedElement; + + try { + FactoryAnnotatedClass annotatedClass = + new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException + + if (!isValidClass(annotatedClass)) { + return true; // 已经打印了错误信息,退出处理过程 + } + } catch (IllegalArgumentException e) { + // @Factory.id()为空 + error(typeElement, e.getMessage()); + return true; + } + ... + } + + private boolean isValidClass(FactoryAnnotatedClass item) { + + // 转换为TypeElement, 含有更多特定的方法 + TypeElement classElement = item.getTypeElement(); + + if (!classElement.getModifiers().contains(Modifier.PUBLIC)) { + error(classElement, "The class %s is not public.", + classElement.getQualifiedName().toString()); + return false; + } + + // 检查是否是一个抽象类 + if (classElement.getModifiers().contains(Modifier.ABSTRACT)) { + error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%", + classElement.getQualifiedName().toString(), Factory.class.getSimpleName()); + return false; + } + + // 检查继承关系: 必须是@Factory.type()指定的类型子类 + TypeElement superClassElement = + elementUtils.getTypeElement(item.getQualifiedFactoryGroupName()); + if (superClassElement.getKind() == ElementKind.INTERFACE) { + // 检查接口是否实现了 if(!classElement.getInterfaces().contains(superClassElement.asType())) { + error(classElement, "The class %s annotated with @%s must implement the interface %s", + classElement.getQualifiedName().toString(), Factory.class.getSimpleName(), + item.getQualifiedFactoryGroupName()); + return false; + } + } else { + // 检查子类 + TypeElement currentClass = classElement; + while (true) { + TypeMirror superClassType = currentClass.getSuperclass(); + + if (superClassType.getKind() == TypeKind.NONE) { + // 到达了基本类型(java.lang.Object), 所以退出 + error(classElement, "The class %s annotated with @%s must inherit from %s", + classElement.getQualifiedName().toString(), Factory.class.getSimpleName(), + item.getQualifiedFactoryGroupName()); + return false; + } + + if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) { + // 找到了要求的父类 + break; + } + + // 在继承树上继续向上搜寻 + currentClass = (TypeElement) typeUtils.asElement(superClassType); + } + } + + // 检查是否提供了默认公开构造函数 + for (Element enclosed : classElement.getEnclosedElements()) { + if (enclosed.getKind() == ElementKind.CONSTRUCTOR) { + ExecutableElement constructorElement = (ExecutableElement) enclosed; + if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers() + .contains(Modifier.PUBLIC)) { + // 找到了默认构造函数 + return true; + } + } + } + + // 没有找到默认构造函数 + error(classElement, "The class %s must provide an public empty default constructor", + classElement.getQualifiedName().toString()); + return false; + } +} +``` + +我们这里添加了`isValidClass()`方法,来检查是否我们所有的规则都被满足了: + +* 必须是公开类:`classElement.getModifiers().contains(Modifier.PUBLIC)` +* 必须是非抽象类:`classElement.getModifiers().contains(Modifier.ABSTRACT)` +* 必须是`@Factoy.type()`指定的类型的子类或者接口的实现:首先我们使用`elementUtils.getTypeElement(item.getQualifiedFactoryGroupName())`创建一个传入的`Class`\(`@Factoy.type()`\)的元素。是的,你可以仅仅通过已知的合法类名来直接创建`TypeElement`(使用TypeMirror)。接下来我们检查它是一个接口还是一个类:`superClassElement.getKind() == ElementKind.INTERFACE`。所以我们这里有两种情况:如果是接口,就判断`classElement.getInterfaces().contains(superClassElement.asType())`;如果是类,我们就必须使用`currentClass.getSuperclass()`扫描继承层级。注意,整个检查也可以使用`typeUtils.isSubtype()`来实现。 +* 类必须有一个公开的默认构造函数:我们遍历所有的闭元素`classElement.getEnclosedElements()`,然后检查`ElementKind.CONSTRUCTOR`、`Modifier.PUBLIC`以及`constructorElement.getParameters().size() == 0`。 + +如果所有这些条件都满足,`isValidClass()`返回`true`,否者就打印错误信息,并且返回`false`。 + +### 组合被注解的类 + +一旦我们检查`isValidClass()`成功,我们将添加`FactoryAnnotatedClass`到对应的`FactoryGroupedClasses`中,如下: + +```java +public class FactoryProcessor extends AbstractProcessor { + + private Map factoryClasses = + new LinkedHashMap(); + + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + ... + try { + FactoryAnnotatedClass annotatedClass = + new FactoryAnnotatedClass(typeElement); // throws IllegalArgumentException + + if (!isValidClass(annotatedClass)) { + return true; // 错误信息被打印,退出处理流程 + } + + // 所有检查都没有问题,所以可以添加了 + FactoryGroupedClasses factoryClass = + factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName()); + if (factoryClass == null) { + String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName(); + factoryClass = new FactoryGroupedClasses(qualifiedGroupName); + factoryClasses.put(qualifiedGroupName, factoryClass); + } + + // 如果和其他的@Factory标注的类的id相同冲突, + // 抛出IdAlreadyUsedException异常 + factoryClass.add(annotatedClass); + } catch (IllegalArgumentException e) { + // @Factory.id()为空 --> 打印错误信息 + error(typeElement, e.getMessage()); + return true; + } catch (IdAlreadyUsedException e) { + FactoryAnnotatedClass existing = e.getExisting(); + // 已经存在 + error(annotatedElement, + "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id", + typeElement.getQualifiedName().toString(), Factory.class.getSimpleName(), + existing.getTypeElement().getQualifiedName().toString()); + return true; + } + } + ... +} +``` + +### 代码生成 + +我们已经收集了所有的被`@Factory`注解的类保存到为`FactoryAnnotatedClass`,并且组合到了`FactoryGroupedClasses`。现在我们将为每个工厂生成Java文件了: + +```java +@Override +public boolean process(Set annotations, RoundEnvironment roundEnv) { + ... + try { + for (FactoryGroupedClasses factoryClass : factoryClasses.values()) { + factoryClass.generateCode(elementUtils, filer); + } + } catch (IOException e) { + error(null, e.getMessage()); + } + + return true; +} +``` + +写Java文件,和写其他普通文件没有什么两样。使用`Filer`提供的`Writer`对象,我们可以连接字符串来写我们生成的Java代码。幸运的是,Square公司(因为提供了许多非常优秀的开源项目二非常有名)给我们提供了[`JavaWriter`](https://github.com/square/javawriter),这是一个高级的生成Java代码的库: + +```java +public class FactoryGroupedClasses { + + /** + * 将被添加到生成的工厂类的名字中 + */ + private static final String SUFFIX = "Factory"; + + private String qualifiedClassName; + + private Map itemsMap = + new LinkedHashMap(); + ... + + public void generateCode(Elements elementUtils, Filer filer) throws IOException { + + TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName); + String factoryClassName = superClassName.getSimpleName() + SUFFIX; + + JavaFileObject jfo = filer.createSourceFile(qualifiedClassName + SUFFIX); + Writer writer = jfo.openWriter(); + JavaWriter jw = new JavaWriter(writer); + + // 写包名 + PackageElement pkg = elementUtils.getPackageOf(superClassName); + if (!pkg.isUnnamed()) { + jw.emitPackage(pkg.getQualifiedName().toString()); + jw.emitEmptyLine(); + } else { + jw.emitPackage(""); + } + + jw.beginType(factoryClassName, "class", EnumSet.of(Modifier.PUBLIC)); + jw.emitEmptyLine(); + jw.beginMethod(qualifiedClassName, "create", EnumSet.of(Modifier.PUBLIC), "String", "id"); + + jw.beginControlFlow("if (id == null)"); + jw.emitStatement("throw new IllegalArgumentException(\"id is null!\")"); + jw.endControlFlow(); + + for (FactoryAnnotatedClass item : itemsMap.values()) { + jw.beginControlFlow("if (\"%s\".equals(id))", item.getId()); + jw.emitStatement("return new %s()", item.getTypeElement().getQualifiedName().toString()); + jw.endControlFlow(); + jw.emitEmptyLine(); + } + + jw.emitStatement("throw new IllegalArgumentException(\"Unknown id = \" + id)"); + jw.endMethod(); + jw.endType(); + jw.close(); + } +} +``` + +> 注意:因为JavaWriter非常非常的流行,所以很多处理器、库、工具都依赖于JavaWriter。如果你使用依赖管理工具,例如maven或者gradle,假如一个库依赖的JavaWriter的版本比其他的库新,这将会导致一些问题。所以我建议你直接拷贝重新打包JavaWiter到你的注解处理器代码中(实际它只是一个Java文件)。 + +**更新**:JavaWrite现在已经被[`JavaPoet`](https://github.com/square/javapoet)取代了。 + +### 处理循环 + +注解处理过程可能会多于一次。官方javadoc定义处理过程如下: + +> 注解处理过程是一个有序的循环过程。在每次循环中,一个处理器可能被要求去处理那些在上一次循环中产生的源文件和类文件中的注解。第一次循环的输入是运行此工具的初始输入。这些初始输入,可以看成是虚拟的第0此的循环的输出。 + +一个简单的定义:一个处理循环是调用一个注解处理器的`process()`方法。对应到我们的工厂模式的例子中:`FactoryProcessor`被初始化一次(不是每次循环都会新建处理器对象),然而,如果生成了新的源文件`process()`能够被调用多次。听起来有点奇怪不是么?原因是这样的,这些生成的文件中也可能包含@Factory注解,它们还将会被`FactoryProcessor`处理。 + +例如我们的`PizzaStore`的例子中将会经过3次循环处理: + + + +| Round | Input | Output | +| :--- | :--- | :--- | +| 1 | CalzonePizza.java Tiramisu.java MargheritaPizza.java Meal.java PizzaStore.java | MealFactory.java | +| 2 | MealFactory.java | --- none --- | +| 3 | --- none --- | --- none --- | + +我解释处理循环还有另外一个原因。如果你看一下我们的`FactoryProcessor`代码你就能注意到,我们收集数据和保存它们在一个私有的域中`Map factoryClasses`。在第一轮中,我们检测到了MagheritaPizza, CalzonePizza和Tiramisu,然后生成了MealFactory.java。在第二轮中把MealFactory作为输入。因为在MealFactory中没有检测到@Factory注解,我们预期并没有错误,然而我们得到如下的信息: + +```text +Attempt to recreate a file for type com.hannesdorfmann.annotationprocessing101.factory.MealFactory +``` + +这个问题是因为我们没有清除`factoryClasses`,这意味着,在第二轮的`process()`中,任然保存着第一轮的数据,并且会尝试生成在第一轮中已经生成的文件,从而导致这个错误的出现。在我们的这个场景中,我们知道只有在第一轮中检查`@Factory`注解的类,所以我们可以简单的修复这个问题,如下: + +```java +@Override +public boolean process(Set annotations, RoundEnvironment roundEnv) { + try { + for (FactoryGroupedClasses factoryClass : factoryClasses.values()) { + factoryClass.generateCode(elementUtils, filer); + } + + // 清除factoryClasses + factoryClasses.clear(); + + } catch (IOException e) { + error(null, e.getMessage()); + } + ... + return true; +} +``` + +我知道这有其他的方法来处理这个问题,例如我们也可以设置一个布尔值标签等。关键的点是:我们要记住注解处理过程是需要经过多轮处理的,并且你不能重载或者重新创建已经生成的源代码。 + +### 分离处理器和注解 + +如果你已经看了我们的[代码库](https://github.com/sockeqwe/annotationprocessing101/tree/master/factory),你将发现我们组织我们的代码到两个maven模块中了。我们这么做是因为,我们想让我们的工厂模式的例子的使用者,在他们的工程中只编译注解,而包含处理器模块只是为了编译。有点晕?我们举个例子,如果我们只有一个包。如果另一个开发者想要把我们的工厂模式处理器用于他的项目中,他就必须包含`@Factory`注解和整个`FactoryProcessor`的代码(包括FactoryAnnotatedClass和FactoryGroupedClasses)到他们项目中。我非常确定的是,他并不需要在他已经编译好的项目中包含处理器相关的代码。如果你是一个Android的开发者,你肯定听说过65k个方法的限制(即在一个.dex文件中,只能寻址65000个方法)。如果你在FactoryProcessor中使用guava,并且把注解和处理器打包在一个包中,这样的话,Android APK安装包中不只是包含FactoryProcessor的代码,而也包含了整个guava的代码。Guava有大约20000个方法。所以分开注解和处理器是非常有意义的。 + +### 生成的类的实例化 + +你已经看到了,在这个`PizzaStore`的例子中,生成了`MealFactory`类,它和其他手写的Java类没有任何区别。进而,你需要就想其他Java对象,手动实例化 它: + +```java +public class PizzaStore { + + private MealFactory factory = new MealFactory(); + + public Meal order(String mealName) { + return factory.create(mealName); + } + ... +} +``` + +如果你是一个Android的开发者,你应该也非常熟悉一个叫做[`ButterKnife`](http://jakewharton.github.io/butterknife/)的注解处理器。在ButterKnife中,你使用`@InjectView`注解Android的View。ButterKnifeProcessor生成一个`MyActivity$$ViewInjector`,但是在ButterKnife你不需要手动调用`new MyActivity$$ViewInjector()`实例化一个ButterKnife注入的对象,而是使用`Butterknife.inject(activity)`。ButterKnife内部使用反射机制来实例化`MyActivity$$ViewInjector()`对象: + +```java +try { + Class injector = Class.forName(clsName + "$$ViewInjector"); +} catch (ClassNotFoundException e) { ... } +``` + +但是反射机制不是很慢么,我们使用注解处理来生成本地代码,会不会导致很多的反射性能的问题?的确,反射机制的性能确实是一个问题。然而,它不需要手动去创建对象,确实提高了开发者的开发速度。ButterKnife中有一个哈希表HashMap来**缓存**实例化过的对象。所以`MyActivity$$ViewInjector`只是使用反射机制实例化一次,第二次需要`MyActivity$$ViewInjector`的时候,就直接冲哈希表中获得。 + +[`FragmentArgs`](https://github.com/sockeqwe/fragmentargs)非常类似于ButterKnife。它使用反射机制来创建对象,而不需要开发者手动来做这些。FragmentArgs在处理注解的时候生成一个特别的查找表类,它其实就是一种哈希表,所以整个FragmentArgs库只是在第一次使用的时候,执行一次反射调用,一旦整个`Class.forName()`的Fragemnt的参数对象被创建,后面的都是本地代码运行了。 + +作为一个注解注解处理器的开发者,这些都由你来决定,为其他的注解器使用者,在反射和可用性上找到一个好的平衡。 + +## 总结 + +到此,我希望你对注解处理过程有一个非常深刻的理解。我必须再次说明一下:注解处理器是一个非常强大的工具,减少了很多无聊的代码的编写。我也想提醒的是,注解处理器可以做到比我上面提到的工厂模式的例子复杂很多的事情。例如,泛型的类型擦除,因为注解处理器是发生在类型擦除(type erasure)之前的(译者注:类型擦除可以参考[这里](http://justjavac.iteye.com/blog/1741638))。就像你所看到的,你在写注解处理的时候,有两个普遍的问题你需要处理:第一问题, 如果你想在其他类中使用ElementUtils, TypeUtils和Messager,你就必须把他们作为参数传进去。在我为Android开发的注解器[`AnnotatedAdapter`](https://github.com/sockeqwe/AnnotatedAdapter)中,我尝试使用Dagger(一个依赖注入库)来解决这个问题。在这个简单的处理中使用它听起来有点过头了,但是它确实很好用;第二个问题,你必须做**查询**`Elements`的操作。就想我之前提到的,处理Element就解析XML或者HTML一样。对于HTML你可以是用jQuery,如果在注解处理器中,有类似于jQuery的库那那绝对是酷毙了。如果你知道有类似的库,请在下面的评论告诉我。 + +请注意的是,在FactoryProcessor代码中有一些缺陷和陷阱。这些“错误”是我故意放进去的,是为了演示一些在开发过程中的常见错误(例如“Attempt to recreate a file”)。如果你想基于FactoryProcessor写你自己注解处理器,请**不要**直接拷贝粘贴这些陷阱过去,你应该从最开始就避免它们。 + +我在后续的博客中将会写注解处理器的单元测试,敬请关注。 + + + + +### + + + + diff --git a/annotation/butterknife-source-analysis.md b/annotation/butterknife-source-analysis.md new file mode 100644 index 00000000..74618ec2 --- /dev/null +++ b/annotation/butterknife-source-analysis.md @@ -0,0 +1,966 @@ + + +本文分析的源码为`8.4.0`。 + +#### 项目结构 + +![](/images/butterknife-1.png) + +`butterknife`包含ButterKnife核心的Api,如ButterKnife。`butterknife-annotations`包含了所有定义的注解。`butterknife-compiler`包含了生成模板代码的代码。这里我们重点研究ButterKnife是如何生成模板代码以及在我们的类中是如何调用的。 + +#### 核心类 + +`ViewBinding`是一个接口,有两个直接的子类`FieldViewBinding`和`MethodViewBinding`。 + +`FieldViewBinding`封装了使用`@BindView`注解的Field的信息。 +```java +private final String name;//字段名 +private final TypeName type;//类型 +private final boolean required;//是否必须 +``` + +`MethodViewBinding`封装了使用`@OnClick`等注解注解的方法的信息。 + +```java +private final String name;//方法名 +private final List parameters;//方法参数 +private final boolean required;//是否必须 +``` + + + +注解@ListenerClass代表一个监听器的类,作用在如@OnClick等事件监听的注解上。 +```java +@Retention(RUNTIME) @Target(ANNOTATION_TYPE) +public @interface ListenerClass { + String targetType();//目标类型 + /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */ + String setter();//将该类设置给目标类型的方法 + /** + * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If + * empty {@link #setter()} will be used by default. + */ + String remover() default ""; + /** Fully-qualified class name of the listener type. */ + String type(); + + /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */ + Class> callbacks() default NONE.class; + + /** + * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()} + * and an error to specify more than one value. + */ + ListenerMethod[] method() default { }; + + /** Default value for {@link #callbacks()}. */ + enum NONE { } +} +``` +@ListenerMethod注解代表一个监听类的方法,因为类里面可能有多个方法,所以是一个数组。 +```java +@Retention(RUNTIME) @Target(FIELD) +public @interface ListenerMethod { + /** Name of the listener method for which this annotation applies. */ + String name(); + + /** List of method parameters. If the type is not a primitive it must be fully-qualified. */ + String[] parameters() default { }; + + /** Primitive or fully-qualified return type of the listener method. May also be {@code void}. */ + String returnType() default "void"; + + /** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */ + String defaultReturn() default "null"; +} +``` +@OnClick +```java +@Target(METHOD) +@Retention(CLASS) +@ListenerClass( + targetType = "android.view.View", + setter = "setOnClickListener", + type = "butterknife.internal.DebouncingOnClickListener", + method = @ListenerMethod( + name = "doClick", + parameters = "android.view.View" + ) +) +public @interface OnClick { + /** View IDs to which the method will be bound. */ + @IdRes int[] value() default { View.NO_ID }; +} +``` + +`ViewBindings`封装了一个View的所有`ViewBinding`。例如在SimpleActivity中有如下代码 +```java +@BindView(R.id.hello) Button hello;//对应封装成一个FieldViewBinding +@OnClick(R.id.hello) void sayHello() { + Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show(); + ButterKnife.apply(headerViews, ALPHA_FADE);//对应封装成一个MethodViewBinding +} +@OnLongClick(R.id.hello) boolean sayGetOffMe() { + Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show();//对应封装成一个MethodViewBinding + return true; +} +``` +这里`R.id.hello`这个view对应的ViewBindings包含一个FieldViewBinding和两个MethodViewBinding。 +```java +final class ViewBindings { + private final Id id; //View对应的id + private final Map>> methodBindings = + new LinkedHashMap<>();//Map用于存储多个MethodViewBinding + private FieldViewBinding fieldBinding; + ... +} +``` +这里使用 Map来存储MethodViewBinding。因为同一个ListenerMethod可能对应多个MethodViewBinding。比如下面代码就是一个ListenerMethod就对应了两个MethodViewBinding。 +```java + @OnClick({R.id.hello})void sayHello() { + Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show(); + ButterKnife.apply(headerViews, ALPHA_FADE); + } + @OnClick({R.id.hello})void sayHello2() { + Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show(); + ButterKnife.apply(headerViews, ALPHA_FADE); + } +``` +`BindingSet`可以看做是一个类里面所有所有Binding信息。同时还负责生成代码的逻辑。 +```java +final class BindingSet { + private final TypeName targetTypeName;// + private final ClassName bindingClassName;//生成辅助类的类名 + private final boolean isFinal;//是否是final类型 + private final List viewBindings;//用于存储ViewBinding + private final List collectionBindings; + private final List resourceBindings; + private final BindingSet parentBinding;//父parentBinding +} +``` + +ResourceBinding是一个接口,有两个实现类FieldResourceBinding和FieldDrawableBinding。 +```java +//例如 @BindColor(android.R.color.black) @ColorInt int blackColor; +//将会生成代码 target.blackColor = ContextCompat.getColor(context, android.R.color.black); +//ResourceMethod方法指的是生成代码的对象 +final class FieldResourceBinding implements ResourceBinding { + enum Type { + BITMAP(new ResourceMethod(BindingSet.BITMAP_FACTORY, "decodeResource", true, 1)), + BOOL("getBoolean"), + COLOR(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColor", false, 1), + new ResourceMethod(null, "getColor", false, 23)), + COLOR_STATE_LIST(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColorStateList", false, 1), + new ResourceMethod(null, "getColorStateList", false, 23)), + DIMEN_AS_INT("getDimensionPixelSize"), + DIMEN_AS_FLOAT("getDimension"), + FLOAT(new ResourceMethod(BindingSet.UTILS, "getFloat", false, 1)), + INT("getInteger"), + INT_ARRAY("getIntArray"), + STRING("getString"), + STRING_ARRAY("getStringArray"), + TEXT_ARRAY("getTextArray"), + TYPED_ARRAY("obtainTypedArray"); + + private final List methods; + + Type(ResourceMethod... methods) { + List methodList = new ArrayList<>(methods.length); + Collections.addAll(methodList, methods); + Collections.sort(methodList); + Collections.reverse(methodList); + this.methods = unmodifiableList(methodList); + } + + Type(String methodName) { + methods = singletonList(new ResourceMethod(null, methodName, true, 1)); + } + + ResourceMethod methodForSdk(int sdk) { + for (ResourceMethod method : methods) { + if (method.sdk <= sdk) { + return method; + } + } + throw new AssertionError(); + } + } + //资源方法 + static final class ResourceMethod implements Comparable { + final ClassName typeName;//类名 + final String name;//方法名 + final boolean requiresResources; + final int sdk;//sdk版本号 因为sdk版本号不一样,调用的方法可能不一样。 + + ResourceMethod(ClassName typeName, String name, boolean requiresResources, int sdk) { + this.typeName = typeName; + this.name = name; + this.requiresResources = requiresResources; + this.sdk = sdk; + } + + @Override + public int compareTo(ResourceMethod other) { + return Integer.compare(sdk, other.sdk);//按照sdk排序 + } + } + + //构造函数 + private final Id id; + private final String name; + private final Type type; + + FieldResourceBinding(Id id, String name, Type type) { + this.id = id; + this.name = name; + this.type = type; + } + + @Override + public Id id() { + return id; + } + + @Override + public boolean requiresResources(int sdk) { + return type.methodForSdk(sdk).requiresResources; + } + + @Override + public CodeBlock render(int sdk) { + ResourceMethod method = type.methodForSdk(sdk); + if (method.typeName == null) { + if (method.requiresResources) { + return CodeBlock.of("target.$L = res.$L($L)", name, method.name, id.code); + } + return CodeBlock.of("target.$L = context.$L($L)", name, method.name, id.code); + } + if (method.requiresResources) { + return CodeBlock.of("target.$L = $T.$L(res, $L)", name, method.typeName, method.name, + id.code); + } + return CodeBlock.of("target.$L = $T.$L(context, $L)", name, method.typeName, method.name, + id.code); + } +} +``` + +#### 解析注解 + + +知道各个类的作用之后,下一步工作就是了解如何解析这些标注了注解的字段和方法的信息,并封装成类。 +ButterKnife解析这些信息的类为`ButterKnifeProcessor`,解析的工作由`findAndParseTargets`方法完成。 +```java +private Map findAndParseTargets(RoundEnvironment env) { //使用Map存储BindingSet.Builder避免同一个类重复创建BindingSet.Builder + Map builderMap = new LinkedHashMap<>();// + Set erasedTargetNames = new LinkedHashSet<>();// + scanForRClasses(env); + // Process each @BindArray element. + //获取注解@BindArray的Element + for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { + if (!SuperficialValidation.validateElement(element)) continue; + try { + parseResourceArray(element, builderMap, erasedTargetNames); + } catch (Exception e) { + logParsingError(element, BindArray.class, e); + } + } + ... +} +``` +`findAndParseTargets`获取不同注解对应的Element,然后调用对应的parse方法来解析。 +##### 解析@BindView +`parseBindView`解析`@BindView`注解的Element。 +```java +private void parseBindView(Element element, Map builderMap, + Set erasedTargetNames) { + //验证注解是否使用正确 + boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) + || isBindingInWrongPackage(BindView.class, element); + + TypeMirror elementType = element.asType(); + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + //例如当@BindView T view; 这时候就是一个TypeKind.TYPEVAR类型 + if (elementType.getKind() == TypeKind.TYPEVAR) {//泛型类型 + TypeVariable typeVariable = (TypeVariable) elementType; + elementType = typeVariable.getUpperBound();//获取上边界 + } + if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {//判断field的类型 + if (elementType.getKind() == TypeKind.ERROR) { + note(element, "@%s field with unresolved type (%s) " + + "must elsewhere be generated as a View or interface. (%s.%s)", + BindView.class.getSimpleName(), elementType, enclosingElement.getQualifiedName(), + element.getSimpleName()); + } else { + error(element, "@%s fields must extend from View or be an interface. (%s.%s)", + BindView.class.getSimpleName(), enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + } + if (hasError) { + return; + } + int id = element.getAnnotation(BindView.class).value();//获取id + BindingSet.Builder builder = builderMap.get(enclosingElement);//创建 BindingSet.Builder + if (builder != null) { + ViewBindings viewBindings = builder.getViewBinding(getId(id)); + //当FieldBinding已经存在说明已经绑定过了。重复绑定将抛异常 + if (viewBindings != null && viewBindings.getFieldBinding() != null) { + FieldViewBinding existingBinding = viewBindings.getFieldBinding(); + + error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", + BindView.class.getSimpleName(), id, existingBinding.getName(), + enclosingElement.getQualifiedName(), element.getSimpleName()); + return; + } + } else { + builder = getOrCreateBindingBuilder(builderMap, enclosingElement); + } + String name = element.getSimpleName().toString();//获取FieldName + TypeName type = TypeName.get(elementType);//获取Field的Type + boolean required = isFieldRequired(element);//是否必须 + builder.addField(getId(id), new FieldViewBinding(name, type, required)); + // Add the type-erased version to the valid binding targets set. + erasedTargetNames.add(enclosingElement); +} + +``` +`isInaccessibleViaGeneratedCode`方法用于通过修饰符来判断字段是否能够被生成的代码访问。由于ButterKnife的原理是通过辅助类来完成findViewById的操作,所以必须引用原来的的字段,因此不能设置为private +```java +target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class); +``` +isInaccessibleViaGeneratedCode方法的源码 +```java +private boolean isInaccessibleViaGeneratedCode(Class annotationClass, + String targetThing, Element element) { + boolean hasError = false; + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + // Verify method modifiers. + Set modifiers = element.getModifiers(); + if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) { + error(element, "@%s %s must not be private or static. (%s.%s)", + annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + // Verify containing type. + if (enclosingElement.getKind() != CLASS) { + error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", + annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + // Verify containing class visibility is not private. + if (enclosingElement.getModifiers().contains(PRIVATE)) { + error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", + annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + return hasError; +} +``` +##### 解析方法注解 +```java +for (Class listener : LISTENERS) { + findAndParseListener(env, listener, builderMap, erasedTargetNames); +} +``` +```java + private void findAndParseListener(RoundEnvironment env, + Class annotationClass, + Map builderMap, Set erasedTargetNames) { + for (Element element : env.getElementsAnnotatedWith(annotationClass)) { + if (!SuperficialValidation.validateElement(element)) continue; + try { + parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames); + } catch (Exception e) { + StringWriter stackTrace = new StringWriter(); + e.printStackTrace(new PrintWriter(stackTrace)); + + error(element, "Unable to generate view binder for @%s.\n\n%s", + annotationClass.getSimpleName(), stackTrace.toString()); + } + } + } +``` +```java + private void parseListenerAnnotation(Class annotationClass, Element element, + Map builderMap, Set erasedTargetNames) + throws Exception { + // This should be guarded by the annotation's @Target but it's worth a check for safe casting. + if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) { + throw new IllegalStateException( + String.format("@%s annotation must be on a method.", annotationClass.getSimpleName())); + } + + ExecutableElement executableElement = (ExecutableElement) element; + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + // Assemble information on the method. + Annotation annotation = element.getAnnotation(annotationClass); + Method annotationValue = annotationClass.getDeclaredMethod("value"); + if (annotationValue.getReturnType() != int[].class) { + throw new IllegalStateException( + String.format("@%s annotation value() type not int[].", annotationClass)); + } + int[] ids = (int[]) annotationValue.invoke(annotation);//通过反射获取值 + String name = executableElement.getSimpleName().toString();//方法名 + boolean required = isListenerRequired(executableElement); + // Verify that the method and its containing class are accessible via generated code. + boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element); + hasError |= isBindingInWrongPackage(annotationClass, element); + //发现重复的id + Integer duplicateId = findDuplicate(ids); + if (duplicateId != null) { + error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)", + annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + + ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class); + if (listener == null) { + throw new IllegalStateException( + String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(), + annotationClass.getSimpleName())); + } + for (int id : ids) { + if (id == NO_ID.value) { + if (ids.length == 1) { + if (!required) { + error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)", + enclosingElement.getQualifiedName(), element.getSimpleName()); + hasError = true; + } + } else { + error(element, "@%s annotation contains invalid ID %d. (%s.%s)", + annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + } + } + + ListenerMethod method; + ListenerMethod[] methods = listener.method(); + if (methods.length > 1) { + throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.", + annotationClass.getSimpleName())); + } else if (methods.length == 1) { + if (listener.callbacks() != ListenerClass.NONE.class) { + throw new IllegalStateException( + String.format("Both method() and callback() defined on @%s.", + annotationClass.getSimpleName())); + } + method = methods[0]; + } else { + Method annotationCallback = annotationClass.getDeclaredMethod("callback"); + Enum callback = (Enum) annotationCallback.invoke(annotation); + Field callbackField = callback.getDeclaringClass().getField(callback.name()); + method = callbackField.getAnnotation(ListenerMethod.class); + if (method == null) { + throw new IllegalStateException( + String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(), + annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(), + callback.name())); + } + } + + // Verify that the method has equal to or less than the number of parameters as the listener. + List methodParameters = executableElement.getParameters(); + if (methodParameters.size() > method.parameters().length) { + error(element, "@%s methods can have at most %s parameter(s). (%s.%s)", + annotationClass.getSimpleName(), method.parameters().length, + enclosingElement.getQualifiedName(), element.getSimpleName()); + hasError = true; + } + // Verify method return type matches the listener. + TypeMirror returnType = executableElement.getReturnType(); + if (returnType instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) returnType; + returnType = typeVariable.getUpperBound(); + } + if (!returnType.toString().equals(method.returnType())) { + error(element, "@%s methods must have a '%s' return type. (%s.%s)", + annotationClass.getSimpleName(), method.returnType(), + enclosingElement.getQualifiedName(), element.getSimpleName()); + hasError = true; + } + if (hasError) { + return; + } + Parameter[] parameters = Parameter.NONE; + if (!methodParameters.isEmpty()) { + parameters = new Parameter[methodParameters.size()]; + BitSet methodParameterUsed = new BitSet(methodParameters.size()); + String[] parameterTypes = method.parameters(); + for (int i = 0; i < methodParameters.size(); i++) { + VariableElement methodParameter = methodParameters.get(i); + TypeMirror methodParameterType = methodParameter.asType(); + if (methodParameterType instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) methodParameterType; + methodParameterType = typeVariable.getUpperBound(); + } + + for (int j = 0; j < parameterTypes.length; j++) { + if (methodParameterUsed.get(j)) { + continue; + } + if (isSubtypeOfType(methodParameterType, parameterTypes[j]) + || isInterface(methodParameterType)) { + parameters[i] = new Parameter(j, TypeName.get(methodParameterType)); + methodParameterUsed.set(j); + break; + } + } + if (parameters[i] == null) { + StringBuilder builder = new StringBuilder(); + builder.append("Unable to match @") + .append(annotationClass.getSimpleName()) + .append(" method arguments. (") + .append(enclosingElement.getQualifiedName()) + .append('.') + .append(element.getSimpleName()) + .append(')'); + for (int j = 0; j < parameters.length; j++) { + Parameter parameter = parameters[j]; + builder.append("\n\n Parameter #") + .append(j + 1) + .append(": ") + .append(methodParameters.get(j).asType().toString()) + .append("\n "); + if (parameter == null) { + builder.append("did not match any listener parameters"); + } else { + builder.append("matched listener parameter #") + .append(parameter.getListenerPosition() + 1) + .append(": ") + .append(parameter.getType()); + } + } + builder.append("\n\nMethods may have up to ") + .append(method.parameters().length) + .append(" parameter(s):\n"); + for (String parameterType : method.parameters()) { + builder.append("\n ").append(parameterType); + } + builder.append( + "\n\nThese may be listed in any order but will be searched for from top to bottom."); + error(executableElement, builder.toString()); + return; + } + } + } + + MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required); + BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); + for (int id : ids) { + if (!builder.addMethod(getId(id), listener, method, binding)) { + error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)", + id, enclosingElement.getQualifiedName(), element.getSimpleName()); + return; + } + } + // Add the type-erased version to the valid binding targets set. + erasedTargetNames.add(enclosingElement); + } +``` + +##### 解析资源绑定 + +解析资源的代码基本都差不多。这里只给出parseResourceString的源码和注释。 + +```java + private void parseResourceString(Element element, + Map builderMap, Set erasedTargetNames) { + boolean hasError = false; + TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); + + // Verify that the target type is String. + if (!STRING_TYPE.equals(element.asType().toString())) { + error(element, "@%s field type must be 'String'. (%s.%s)", + BindString.class.getSimpleName(), enclosingElement.getQualifiedName(), + element.getSimpleName()); + hasError = true; + } + + // Verify common generated code restrictions. + hasError |= isInaccessibleViaGeneratedCode(BindString.class, "fields", element); + hasError |= isBindingInWrongPackage(BindString.class, element); + + if (hasError) { + return; + } + + // Assemble information on the field. + String name = element.getSimpleName().toString(); + int id = element.getAnnotation(BindString.class).value(); + + BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); + builder.addResource( + new FieldResourceBinding(getId(id), name, FieldResourceBinding.Type.STRING)); + + erasedTargetNames.add(enclosingElement); + } + +``` +##### 设置parentBinding +在findAndParseTargets最后几行代码的作用是为解析出来的BindingSet.Builder设置parentBinding +```java + Deque> entries = + new ArrayDeque<>(builderMap.entrySet()); + Map bindingMap = new LinkedHashMap<>(); + while (!entries.isEmpty()) { + Map.Entry entry = entries.removeFirst(); + TypeElement type = entry.getKey(); + BindingSet.Builder builder = entry.getValue(); + TypeElement parentType = findParentType(type, erasedTargetNames); + if (parentType == null) {//没有parentBinding,不是某各类的子类 + bindingMap.put(type, builder.build()); + } else { + BindingSet parentBinding = bindingMap.get(parentType); + if (parentBinding != null) { + builder.setParent(parentBinding); + bindingMap.put(type, builder.build()); + } else { + // Has a superclass binding but we haven't built it yet. Re-enqueue for later. + entries.addLast(entry); + } + } + } +``` + +#### 生成代码 + +生成代码主要由BindingSet的brewJava方法完成。 +```java + JavaFile brewJava(int sdk) { + return JavaFile.builder(bindingClassName.packageName(), createType(sdk)) + .addFileComment("Generated code from Butter Knife. Do not modify!")//添加注释 + .build(); + } +``` +```java +private TypeSpec createType(int sdk) { + TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())//类名 + .addModifiers(PUBLIC);//修饰符 + if (isFinal) { + result.addModifiers(FINAL); + } + + if (parentBinding != null) { + result.superclass(parentBinding.bindingClassName);//添加父类 + } else { + result.addSuperinterface(UNBINDER);//添加接口UnBinder + } + + if (hasTargetField()) { + result.addField(targetTypeName, "target", PRIVATE);//添加字段target + } + // + if (!constructorNeedsView()) { + // Add a delegating constructor with a target type + view signature for reflective use. + result.addMethod(createBindingViewDelegateConstructor(targetTypeName)); + } + result.addMethod(createBindingConstructor(targetTypeName, sdk)); + + if (hasViewBindings() || parentBinding == null) { + result.addMethod(createBindingUnbindMethod(result, targetTypeName)); + } + + return result.build(); + } +``` +createBindingConstructor创建构造函数 +```java +private MethodSpec createBindingConstructor(TypeName targetType, int sdk) { + MethodSpec.Builder constructor = MethodSpec.constructorBuilder() + .addAnnotation(UI_THREAD)//添加注解 + .addModifiers(PUBLIC);//添加修饰符 + if (hasMethodBindings()) {//如果没有MethodBinding就不用声明成final类型 + constructor.addParameter(targetType, "target", FINAL); + } else { + constructor.addParameter(targetType, "target"); + } + if (constructorNeedsView()) {//当构造函数需要View + constructor.addParameter(VIEW, "source"); + } else { + constructor.addParameter(CONTEXT, "context"); + } + + if (hasUnqualifiedResourceBindings()) {//添加SuppressWarnings注解 + // Aapt can change IDs out from underneath us, just suppress since all will work at runtime. + constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) + .addMember("value", "$S", "ResourceType") + .build()); + } + if (parentBinding != null) {//添加父类构造方法的调用 + if (parentBinding.constructorNeedsView()) { + constructor.addStatement("super(target, source)"); + } else if (constructorNeedsView()) { + constructor.addStatement("super(target, source.getContext())"); + } else { + constructor.addStatement("super(target, context)"); + } + constructor.addCode("\n"); + } + if (hasTargetField()) {// + constructor.addStatement("this.target = target"); + constructor.addCode("\n"); + } + + if (hasViewBindings()) { + if (hasViewLocal()) { + // Local variable in which all views will be temporarily stored. + constructor.addStatement("$T view", VIEW); + } + for (ViewBindings bindings : viewBindings) { + addViewBindings(constructor, bindings); + } + for (FieldCollectionViewBinding binding : collectionBindings) { + constructor.addStatement("$L", binding.render()); + } + if (!resourceBindings.isEmpty()) { + constructor.addCode("\n"); + } + } + // + if (!resourceBindings.isEmpty()) { + if (constructorNeedsView()) { + constructor.addStatement("$T context = source.getContext()", CONTEXT); + } + if (hasResourceBindingsNeedingResource(sdk)) { + constructor.addStatement("$T res = context.getResources()", RESOURCES); + } + for (ResourceBinding binding : resourceBindings) { + constructor.addStatement("$L", binding.render(sdk)); + } + } + return constructor.build(); +} +``` +addViewBindings用于添加ViewBindings对象所对应的表达式。当只有FieldViewBinding时直接生成代码 `target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class);`。 + + +```java +private void addViewBindings(MethodSpec.Builder result, ViewBindings bindings) { + if (bindings.isSingleFieldBinding()) {//如果只有FieldBinding + // Optimize the common case where there's a single binding directly to a field. + FieldViewBinding fieldBinding = bindings.getFieldBinding(); + CodeBlock.Builder builder = CodeBlock.builder() + .add("target.$L = ", fieldBinding.getName()); + //当type是View类型不需要强制转换 + boolean requiresCast = requiresCast(fieldBinding.getType()); + if (!requiresCast && !fieldBinding.isRequired()) { + builder.add("source.findViewById($L)", bindings.getId().code); + } else { + builder.add("$T.find", UTILS);//Utils.findRequiredViewAsType(source,12345, + builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); + if (requiresCast) { + builder.add("AsType"); + } + builder.add("(source, $L", bindings.getId().code); + if (fieldBinding.isRequired() || requiresCast) { + builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); + } + if (requiresCast) { + builder.add(", $T.class", fieldBinding.getRawType()); + } + builder.add(")"); + } + result.addStatement("$L", builder.build()); + return; + } + //如同时存在FieldViewBinding和MethodViewBinding + //先生成 view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', method 'sayHello2', and method 'sayGetOffMe'"); +//调用addFieldBindings方法生成 target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class); + + List requiredViewBindings = bindings.getRequiredBindings(); + if (requiredViewBindings.isEmpty()) { + result.addStatement("view = source.findViewById($L)", bindings.getId().code); + } else if (!bindings.isBoundToRoot()) { + result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, + bindings.getId().code, asHumanDescription(requiredViewBindings)); + } + addFieldBindings(result, bindings); + addMethodBindings(result, bindings); +} +``` +```java + private void addFieldBindings(MethodSpec.Builder result, ViewBindings bindings) { + FieldViewBinding fieldBinding = bindings.getFieldBinding(); + if (fieldBinding != null) { + if (requiresCast(fieldBinding.getType())) { + result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)", + fieldBinding.getName(), UTILS, bindings.getId().code, + asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType()); + } else { + result.addStatement("target.$L = view", fieldBinding.getName()); + } + } +} +``` +```java + private void addMethodBindings(MethodSpec.Builder result, ViewBindings bindings) { + Map>> classMethodBindings = + bindings.getMethodBindings(); + if (classMethodBindings.isEmpty()) { + return; + } + + // We only need to emit the null check if there are zero required bindings. + boolean needsNullChecked = bindings.getRequiredBindings().isEmpty(); + if (needsNullChecked) { + result.beginControlFlow("if (view != null)"); + } + + // Add the view reference to the binding. + String fieldName = "viewSource"; + String bindName = "source"; + if (!bindings.isBoundToRoot()) { + fieldName = "view" + bindings.getId().value; + bindName = "view"; + } + result.addStatement("$L = $N", fieldName, bindName); + + for (Map.Entry>> e + : classMethodBindings.entrySet()) { + ListenerClass listener = e.getKey(); + Map> methodBindings = e.getValue(); + + TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("") + .superclass(ClassName.bestGuess(listener.type())); + + for (ListenerMethod method : getListenerMethods(listener)) { + MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name()) + .addAnnotation(Override.class) + .addModifiers(PUBLIC) + .returns(bestGuess(method.returnType())); + String[] parameterTypes = method.parameters(); + for (int i = 0, count = parameterTypes.length; i < count; i++) { + callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i); + } + + boolean hasReturnType = !"void".equals(method.returnType()); + CodeBlock.Builder builder = CodeBlock.builder(); + if (hasReturnType) { + builder.add("return "); + } + + if (methodBindings.containsKey(method)) { + for (MethodViewBinding binding : methodBindings.get(method)) { + builder.add("target.$L(", binding.getName()); + List parameters = binding.getParameters(); + String[] listenerParameters = method.parameters(); + for (int i = 0, count = parameters.size(); i < count; i++) { + if (i > 0) { + builder.add(", "); + } + + Parameter parameter = parameters.get(i); + int listenerPosition = parameter.getListenerPosition(); + + if (parameter.requiresCast(listenerParameters[listenerPosition])) { + builder.add("$T.<$T>castParam(p$L, $S, $L, $S, $L)", UTILS, parameter.getType(), + listenerPosition, method.name(), listenerPosition, binding.getName(), i); + } else { + builder.add("p$L", listenerPosition); + } + } + builder.add(");\n"); + } + } else if (hasReturnType) { + builder.add("$L;\n", method.defaultReturn()); + } + callbackMethod.addCode(builder.build()); + callback.addMethod(callbackMethod.build()); + } + + boolean requiresRemoval = listener.remover().length() != 0; + String listenerField = null; + if (requiresRemoval) { + TypeName listenerClassName = bestGuess(listener.type()); + listenerField = fieldName + ((ClassName) listenerClassName).simpleName(); + result.addStatement("$L = $L", listenerField, callback.build()); + } + + if (!VIEW_TYPE.equals(listener.targetType())) { + result.addStatement("(($T) $N).$L($L)", bestGuess(listener.targetType()), bindName, + listener.setter(), requiresRemoval ? listenerField : callback.build()); + } else { + result.addStatement("$N.$L($L)", bindName, listener.setter(), + requiresRemoval ? listenerField : callback.build()); + } + } + + if (needsNullChecked) { + result.endControlFlow(); + } +} +``` + +#### 调用辅助方法 + +我们在类中通过调用ButterKnife的bind方法来调用辅助类的。 + +```java +@NonNull @UiThread +public static Unbinder bind(@NonNull Object target, @NonNull View source) { + return createBinding(target, source); +} +private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { + Class targetClass = target.getClass(); + if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); + Constructor constructor = findBindingConstructorForClass(targetClass); + if (constructor == null) { + return Unbinder.EMPTY; + } +//noinspection TryWithIdenticalCatches Resolves to API 19+ only type. + try { + return constructor.newInstance(target, source); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to invoke " + constructor, e); + } catch (InstantiationException e) { + throw new RuntimeException("Unable to invoke " + constructor, e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw new RuntimeException("Unable to create binding instance.", cause); + } +} + @Nullable @CheckResult @UiThread + private static Constructor findBindingConstructorForClass(Class cls) { + Constructor bindingCtor = BINDINGS.get(cls); + if (bindingCtor != null) { + if (debug) Log.d(TAG, "HIT: Cached in binding map."); + return bindingCtor; + } + String clsName = cls.getName(); + if (clsName.startsWith("android.") || clsName.startsWith("java.")) { + if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); + return null; + } + try { + Class bindingClass = Class.forName(clsName + "_ViewBinding"); + //noinspection unchecked + bindingCtor = (Constructor) bindingClass.getConstructor(cls, View.class); + if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor."); + } catch (ClassNotFoundException e) { + if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); + bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Unable to find binding constructor for " + clsName, e); + } + BINDINGS.put(cls, bindingCtor); + return bindingCtor; + } +``` + + + diff --git a/annotation/butterknife.md b/annotation/butterknife.md index f8f06d41..4084be23 100755 --- a/annotation/butterknife.md +++ b/annotation/butterknife.md @@ -1,10 +1,7 @@ -#ButterKnife - - ButterKnife是一个Android View注入的库。 -## 1 配置Eclipse +### 1 配置Eclipse 在使用ButterKnife需要先配置一下Eclipse。 @@ -162,8 +159,7 @@ ImageView photo = ButterKnife.findById(view, R.id.photo); ``` - -##扩展阅读 +## 扩展阅读 [Butterknife](http://jakewharton.github.io/butterknife/) diff --git a/annotation/javapoet-wen-dang-fan-yi.md b/annotation/javapoet-wen-dang-fan-yi.md new file mode 100644 index 00000000..dbeb0ab0 --- /dev/null +++ b/annotation/javapoet-wen-dang-fan-yi.md @@ -0,0 +1,777 @@ +# JavaPoet 文档翻译 + +[原文](https://github.com/square/javapoet) +`JavaPoet` 是一套生成`.java`源文件的Java接口。 + +当做一些比如注解处理或者和元数据文件(比如数据库的schemas,协议格式)交互的事情时,源文件生成非常有用。通过生成代码,你不用写模板代码同时也保证了元数据的唯一来源。 + +## Example + +下面是样板式的 `HelloWorld` class: + +```java +package com.example.helloworld; + +public final class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, JavaPoet!"); + } +} +``` + +而下面是通过JavaPoet生成的(令人兴奋的)代码: + +```java +MethodSpec main = MethodSpec.methodBuilder("main") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(void.class) + .addParameter(String[].class, "args") + .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") + .build(); + +TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addMethod(main) + .build(); + +JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) + .build(); + +javaFile.writeTo(System.out); + +``` + +为了声明main方法,我们用修饰符,返回类型,参数和代码语句创建一个名为"main"的`MethodSpec`。我们添加main方法到一个`HelloWorld`类中,然后添加这个类到`HelloWorld.java`文件中。 + +在这个例子中我们把文件写到`System.out`中,但我们也能得到文件的字符串\(`JavaFile.toString()`\) 或把它写到文件系统里\(`JavaPoet.writeTo()`\)。 + +[Javadoc](https://square.github.io/javapoet/1.x/javapoet/)是全部的JavaPoet API,我们接下来看一下。 + +## 代码和控制流 + +大多数的JavaPoet的API用简单的不可变的Java对象。也有builders,方法链和可变参数来使API友好。JavaPoet 提供类和接口的模型\(`TypeSpec`\),属性的模型\(`FieldSpec`\),方法和构造函数的模型\(`MethodSpec`\),参数的模型\(`ParameterSpec`\) 和注解的模型\(`AnnotationSpec`\)。 + +但是方法和构造函数的函数体没有被模型化。没有表达式类,没有语句类也没有语法树。取而代之的是,JavaPoet使用字符串来表示代码块: + + +```java +MethodSpec main = MethodSpec.methodBuilder("main") + .addCode("" + + "int total = 0;\n" + + "for (int i = 0; i < 10; i++) {\n" + + " total += i;\n" + + "}\n") + .build(); +``` + +将生成如下代码: + +```text +void main() { + int total = 0; + for (int i = 0; i < 10; i++) { + total += i; + } +} +``` + +手动写分号,换行和缩进是枯燥的所以JavaPoet提供了API使之写得更容易。`addStatement()`接管了分行和新行,`beginControlFlow()` + `endControlFlow()` 一同用于括号,新行和缩进: + +```java +MethodSpec main = MethodSpec.methodBuilder("main") + .addStatement("int total = 0") + .beginControlFlow("for (int i = 0; i < 10; i++)") + .addStatement("total += i") + .endControlFlow() + .build(); +``` + +这个例子是简陋的,因为生成的代码是不变的!设想取代写死0到10,我们想要操作和范围是可配置的。这是一个方法来生成一个方法: + +```java +private MethodSpec computeRange(String name, int from, int to, String op) { + return MethodSpec.methodBuilder(name) + .returns(int.class) + .addStatement("int result = 0") + .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)") + .addStatement("result = result " + op + " i") + .endControlFlow() + .addStatement("return result") + .build(); +} +``` + +当我们调用`computeRange("multiply10to20", 10, 20, "*")`时下面是我们得到的代码: + +```java +int multiply10to20() { + int result = 0; + for (int i = 10; i < 20; i++) { + result = result * i; + } + return result; +} +``` + +方法生成方法!并且由于JavaPoet生成源代码而不是字节码,你可以通过阅读它来确保正确性。 + +## $L 用于表示字面量 + +在调用`beginControlFlow()` 和 `addStatement`时字符串连接是令人分心的。太多的操作符。为了解决这个问题,JavaPoet提供了一个受[`String.format()`](http://developer.android.com/reference/java/util/Formatter.html)启发而又不兼容的语法。该语法接收\*\*`$L`\*\*来发出一个字面值到输出中。行为类似`Formatter`'s `%s`: + +```java +private MethodSpec computeRange(String name, int from, int to, String op) { + return MethodSpec.methodBuilder(name) + .returns(int.class) + .addStatement("int result = 0") + .beginControlFlow("for (int i = $L; i < $L; i++)", from, to) + .addStatement("result = result $L i", op) + .endControlFlow() + .addStatement("return result") + .build(); +} +``` + +字面量被直接不经过转义地写到输出代码中。字面量的参数可能是字符串原始类型,和一些后文描述的JavaPoet类型。 + +## $S 用于表示字符串 + +当发送包含字符串的代码时,我们可以用\*\*`$S`\*\* 来发送一个**string**,包含双引号的包裹和转义。下面是一个发送三个方法的程序,每个都返回它自己的名字: + +```java +public static void main(String[] args) throws Exception { + TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addMethod(whatsMyName("slimShady")) + .addMethod(whatsMyName("eminem")) + .addMethod(whatsMyName("marshallMathers")) + .build(); + + JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) + .build(); + + javaFile.writeTo(System.out); +} + +private static MethodSpec whatsMyName(String name) { + return MethodSpec.methodBuilder(name) + .returns(String.class) + .addStatement("return $S", name) + .build(); +} +``` + +在这个例子中,使用`$S` 给我们添加双引号标记: + +```java +public final class HelloWorld { + String slimShady() { + return "slimShady"; + } + + String eminem() { + return "eminem"; + } + + String marshallMathers() { + return "marshallMathers"; + } +} +``` + +## $T 用来表示类型 + +我们Java程序员喜欢我们的类型:类型使我们的代码易于理解。并且JavaPoet也是同理。它有丰富的内建支持类型,包含自动生成`import`语句。使用\*\*`$T`\*\* 来引用 **types**: + +```java +MethodSpec today = MethodSpec.methodBuilder("today") + .returns(Date.class) + .addStatement("return new $T()", Date.class) + .build(); + +TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addMethod(today) + .build(); + +JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) + .build(); + +javaFile.writeTo(System.out); +``` + +这生成下面的`.java` 文件,包含了必要的`import`语句 + +```java +package com.example.helloworld; + +import java.util.Date; + +public final class HelloWorld { + Date today() { + return new Date(); + } +} +``` + +我们传递`Date.class`来引用一个当我们生成代码时恰好可用的类。这不需要这么做\(使用一个存在的类\)。有一个相似的例子,但这个引用的类(还)并不存在(比如要引用的这个类也是要生成的类): + +```java +ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); + +MethodSpec today = MethodSpec.methodBuilder("tomorrow") + .returns(hoverboard) + .addStatement("return new $T()", hoverboard) + .build(); +``` + +并且这个还并不存在的类也被引入进来了: + +```java +package com.example.helloworld; + +import com.mattel.Hoverboard; + +public final class HelloWorld { + Hoverboard tomorrow() { + return new Hoverboard(); + } +} +``` + +`ClassName`类型非常重要,并且在你使用JavaPoet时你将频繁地需要使用它。它辨认任何\_声明\_的类。声明的类型仅仅是Java丰富的类型系统的开始:你也可以拥有数组,参数化的类型,通配符类型和类型变量。JavaPoet有用于构建这些的类: + +```java +ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); +ClassName list = ClassName.get("java.util", "List"); +ClassName arrayList = ClassName.get("java.util", "ArrayList"); +TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard); + +MethodSpec beyond = MethodSpec.methodBuilder("beyond") + .returns(listOfHoverboards) + .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList) + .addStatement("result.add(new $T())", hoverboard) + .addStatement("result.add(new $T())", hoverboard) + .addStatement("result.add(new $T())", hoverboard) + .addStatement("return result") + .build(); +``` + +JavaPoet将分解每个类型并在可能的地方引入它的组件。 + +```java +package com.example.helloworld; + +import com.mattel.Hoverboard; +import java.util.ArrayList; +import java.util.List; + +public final class HelloWorld { + List beyond() { + List result = new ArrayList<>(); + result.add(new Hoverboard()); + result.add(new Hoverboard()); + result.add(new Hoverboard()); + return result; + } +} +``` + +### **Import static** + +JavaPoet支持`import static`。它通过明确地收集类型成员名字来完成的。让我们使用一些静态语法糖来完善前一个例子: + +```java +... +ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards"); + +MethodSpec beyond = MethodSpec.methodBuilder("beyond") + .returns(listOfHoverboards) + .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList) + .addStatement("result.add($T.createNimbus(2000))", hoverboard) + .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard) + .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards) + .addStatement("$T.sort(result)", Collections.class) + .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class) + .build(); + +TypeSpec hello = TypeSpec.classBuilder("HelloWorld") + .addMethod(beyond) + .build(); + +JavaFile.builder("com.example.helloworld", hello) + .addStaticImport(hoverboard, "createNimbus") //静态导入 + .addStaticImport(namedBoards, "*") + .addStaticImport(Collections.class, "*") + .build(); +``` + +JavaPoet will first add your `import static` block to the file as configured, match and mangle all calls accordingly and also import all other types as needed. + +JavaPoet将先添加你的`import static`块到文件中作为配置,匹配和压碎所有调用因此也引入所有需要的其他类型。(翻译不通) + + +```java +package com.example.helloworld; + +import static com.mattel.Hoverboard.Boards.*; +import static com.mattel.Hoverboard.createNimbus; +import static java.util.Collections.*; + +import com.mattel.Hoverboard; +import java.util.ArrayList; +import java.util.List; + +class HelloWorld { + List beyond() { + List result = new ArrayList<>(); + result.add(createNimbus(2000)); + result.add(createNimbus("2001")); + result.add(createNimbus(THUNDERBOLT)); + sort(result); + return result.isEmpty() ? emptyList() : result; + } +} +``` + +## $N 用于表示名字 + +生成的代码经常是自我参考的。使用 **`$N`** 来通过它的名字来指代另一个生成的声明。这是一个方法调用另一个方法: + +```java +public String byteToHex(int b) { + char[] result = new char[2]; + result[0] = hexDigit((b >>> 4) & 0xf); + result[1] = hexDigit(b & 0xf); + return new String(result); +} + +public char hexDigit(int i) { + return (char) (i < 10 ? i + '0' : i - 10 + 'a'); +} +``` + +当生成上面的代码,我使用`$N`传递`hexDigit()`方法作为`byteToHex()`方法的一个参数: + +```java +MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit") + .addParameter(int.class, "i") + .returns(char.class) + .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')") + .build(); + +MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex") + .addParameter(int.class, "b") + .returns(String.class) + .addStatement("char[] result = new char[2]") + .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit) + .addStatement("result[1] = $N(b & 0xf)", hexDigit) + .addStatement("return new String(result)") + .build(); +``` + +## 代码块格式字符串 + +代码块也许有多种方法指定他们的占位符的值。在代码块中只有一种形式也许被用于每个操作。(没读懂什么意思) + +### **相对参数\(按顺序的参数\)** + +给每个在格式化字符串中的占位符传递一个参数值到`CodeBlock.add()`。在每个例子中,我们生成代码来说"I ate 3 tacos" + +```java +CodeBlock.builder().add("I ate $L $L", 3, "tacos") +``` + +### **位置参数\(指定位置的参数\)** + +传入一个整数索引\(从1开始\)到格式化字符串占位符之前来指定要使用的参数。 + +```java +CodeBlock.builder().add("I ate $2L $1L", "tacos", 3) +``` + +### **命名参数\(指定名字的参数\)** + +使用语法`$argumentName:X`其中`X`是格式化字符并调用`CodeBlock.addNamed()`使用一个包含所有格式化字符串中的参数键的字典。参数名使用`a-z`, `A-Z`, `0-9`, 和 `_`,并且必须以小写字符开头。 + +```java +Map map = new LinkedHashMap<>(); +map.put("food", "tacos"); +map.put("count", 3); +CodeBlock.builder().addNamed("I ate $count:L $food:L", map) +``` + +## 方法 + +以上所有方法都有方法体。使用`Modifiers.ABSTRACT`可以获取一个没有方法体的方法。如果是抽象类或者接口的话则是合法的。 + +```java +MethodSpec flux = MethodSpec.methodBuilder("flux") + .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED) + .build(); + +TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addMethod(flux) + .build(); +``` + +生成这样的代码: + +```java +public abstract class HelloWorld { + protected abstract void flux(); +} +``` + +其他修饰符在允许的地方工作。记住当指定修饰符,JavaPoet使用[`javax.lang.model.element.Modifier`](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Modifier.html),这个类在Android不可用。这个限制只在代码生成的代码中有效;输出的代码可以运行在各种地方:JVM,Android和GWT。 + +方法也有参数,异常,可变参数,Java文档,注解,类型变量和一个返回类型。所有这些都可以在`MethodSpec.Builder`中配置。 + +## 构造器 + +`MethodSpec` (当做构造器)稍有不妥;它也可以用作构造函数: + +```java +MethodSpec flux = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(String.class, "greeting") + .addStatement("this.$N = $N", "greeting", "greeting") + .build(); + +TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC) + .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL) + .addMethod(flux) + .build(); +``` + +生成如下代码: + +```java +public class HelloWorld { + private final String greeting; + + public HelloWorld(String greeting) { + this.greeting = greeting; + } +} +``` + +对于多数部分(情况),构造器类似方法一样。当发送代码时,JavaPoet将把构造器放到普通方法之前输出到输出文件。 + +## 参数 + +声明方法和构造器中的参数使用`ParameterSpec.builder()`或者`MethodSpec`的简便方法 `addParameter()`: + +```java +ParameterSpec android = ParameterSpec.builder(String.class, "android") + .addModifiers(Modifier.FINAL) //final修饰 + .build(); + +MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords") + .addParameter(android) + .addParameter(String.class, "robot", Modifier.FINAL) + .build(); +``` + +虽然上面代码生成`android` 和 `robot`的参数不同,但是输出是相同的: + +```java +void welcomeOverlords(final String android, final String robot) { +} +``` + +当参数有注解\(例如 `@Nullable`\)时扩展的`Builder`类型是必要的。 + +## 成员变量 + +与参数类似,成员变量也是既能使用builder创建也能使用方便的帮助方法创建: + +```java +FieldSpec android = FieldSpec.builder(String.class, "android") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build(); + +TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC) + .addField(android) + .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL) + .build(); +``` + +生成: + +```java +public class HelloWorld { + private final String android; + + private final String robot; +} +``` + +当一个成员变量有Javadoc,注解,或者成员变量初始化则扩展的`Builder`形式是必要的。成员变量初始化使用与代码块相同的[`String.format()`](http://developer.android.com/reference/java/util/Formatter.html)类似语法 + +```java +FieldSpec android = FieldSpec.builder(String.class, "android") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .initializer("$S + $L", "Lollipop v.", 5.0d) + .build(); +``` + +生成: + +```java +private final String android = "Lollipop v." + 5.0; +``` + +## 接口 + +JavaPoet处理接口没有问题。记住接口放必须总是使用`PUBLIC ABSTRACT`并且接口的成员变量必须总是`PUBLIC STATIC FINAL`。当定义接口时这些修饰符总是必要的: + +```java +TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld") + .addModifiers(Modifier.PUBLIC) + .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("$S", "change") + .build()) + .addMethod(MethodSpec.methodBuilder("beep") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .build()) + .build(); +``` + +但是当代码被生成时这些修饰符总是被忽略。这是默认的所以我们不需要在把它们包含在`javac`中! + +```java +public interface HelloWorld { + String ONLY_THING_THAT_IS_CONSTANT = "change"; + + void beep(); +} +``` + +## 枚举 + +使用`enumBuilder`创建枚举类型,并且 `addEnumConstant()` 用于添加每个值: + +```java +TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo") + .addModifiers(Modifier.PUBLIC) + .addEnumConstant("ROCK") + .addEnumConstant("SCISSORS") + .addEnumConstant("PAPER") + .build(); +``` + +生成: + +```java +public enum Roshambo { + ROCK, + + SCISSORS, + + PAPER +} +``` + +想要枚举被支持,枚举值覆盖方法或者调用超类的构造函数。下面是一个全面的例子: + +```java +TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo") + .addModifiers(Modifier.PUBLIC) + .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist") + .addMethod(MethodSpec.methodBuilder("toString") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addStatement("return $S", "avalanche!") + .build()) + .build()) + .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace") + .build()) + .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat") + .build()) + .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL) + .addMethod(MethodSpec.constructorBuilder() + .addParameter(String.class, "handsign") + .addStatement("this.$N = $N", "handsign", "handsign") + .build()) + .build(); +``` + +生成: + +```java +public enum Roshambo { + ROCK("fist") { + @Override + public void toString() { + return "avalanche!"; + } + }, + + SCISSORS("peace"), + + PAPER("flat"); + + private final String handsign; + + Roshambo(String handsign) { + this.handsign = handsign; + } +} +``` + +## 匿名内部类 + +在枚举代码中,我们使用`Types.anonymousInnerClass()`。匿名内部类也能被用于代码块中。它们可以被`$L`引用的值: + +```java +TypeSpec comparator = TypeSpec.anonymousClassBuilder("") + .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class)) + .addMethod(MethodSpec.methodBuilder("compare") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(String.class, "a") + .addParameter(String.class, "b") + .returns(int.class) + .addStatement("return $N.length() - $N.length()", "a", "b") + .build()) + .build(); + +TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") + .addMethod(MethodSpec.methodBuilder("sortByLength") + .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings") + .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator) + .build()) + .build(); +``` + +这生成一个包含一个有方法的类的方法: + +```java +void sortByLength(List strings) { + Collections.sort(strings, new Comparator() { + @Override + public int compare(String a, String b) { + return a.length() - b.length(); + } + }); +} +``` + +定义匿名内部类的一个棘手的部分是超类构造器的参数。在上述代码中我们传递空字符串带包没有参数:`TypeSpec.anonymousClassBuilder("")`。用JavaPoet代码块语法用逗号来分隔参数类传递不同参数。 + +## 注解 + +简单注解很容易: + +```java +MethodSpec toString = MethodSpec.methodBuilder("toString") + .addAnnotation(Override.class) + .returns(String.class) + .addModifiers(Modifier.PUBLIC) + .addStatement("return $S", "Hoverboard") + .build(); +``` + +生成带`@Override`注解的方法: + +```java + @Override + public String toString() { + return "Hoverboard"; + } +``` + +使用`AnnotationSpec.builder()`设置注解的属性 + +```java +MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addAnnotation(AnnotationSpec.builder(Headers.class) + .addMember("accept", "$S", "application/json; charset=utf-8") + .addMember("userAgent", "$S", "Square Cash") + .build()) + .addParameter(LogRecord.class, "logRecord") + .returns(LogReceipt.class) + .build(); +``` + +生成带有`accept` 和 `userAgent`属性的注解: + +```java +@Headers( + accept = "application/json; charset=utf-8", + userAgent = "Square Cash" +) +LogReceipt recordEvent(LogRecord logRecord); +``` + +注解值可以是注解本身。使用`$L`嵌套注解: + +```java +MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addAnnotation(AnnotationSpec.builder(HeaderList.class) + .addMember("value", "$L", AnnotationSpec.builder(Header.class) + .addMember("name", "$S", "Accept") + .addMember("value", "$S", "application/json; charset=utf-8") + .build()) + .addMember("value", "$L", AnnotationSpec.builder(Header.class) + .addMember("name", "$S", "User-Agent") + .addMember("value", "$S", "Square Cash") + .build()) + .build()) + .addParameter(LogRecord.class, "logRecord") + .returns(LogReceipt.class) + .build(); +``` + +生成: + +```java +@HeaderList({ + @Header(name = "Accept", value = "application/json; charset=utf-8"), + @Header(name = "User-Agent", value = "Square Cash") +}) +LogReceipt recordEvent(LogRecord logRecord); +``` + +记住你可以用相同的属性名多次调用`addMember()` 来填充列表该属性的值。 + +## Javadoc + +成员变量,方法和类型可以使用Javadoc来生成文档: + +```java +MethodSpec dismiss = MethodSpec.methodBuilder("dismiss") + .addJavadoc("Hides {@code message} from the caller's history. Other\n" + + "participants in the conversation will continue to see the\n" + + "message in their own history unless they also delete it.\n") + .addJavadoc("\n") + .addJavadoc("

Use {@link #delete($T)} to delete the entire\n" + + "conversation for all participants.\n", Conversation.class) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(Message.class, "message") + .build(); +``` + +生成: + +```java + /** + * Hides {@code message} from the caller's history. Other + * participants in the conversation will continue to see the + * message in their own history unless they also delete it. + * + *

Use {@link #delete(Conversation)} to delete the entire + * conversation for all participants. + */ + void dismiss(Message message); +``` + +当在Javadoc中引用类型时使用`$T`来获得自动导入。 + diff --git a/annotation/untitled.md b/annotation/untitled.md new file mode 100644 index 00000000..dbc4d9ec --- /dev/null +++ b/annotation/untitled.md @@ -0,0 +1,87 @@ +# Untitled + +## ProcessingEnvironment + +```java +public interface ProcessingEnvironment { + Map getOptions(); + + Messager getMessager(); + + Filer getFiler(); //获取Filer + + Elements getElementUtils(); //Elements + + Types getTypeUtils(); //获取Types + + SourceVersion getSourceVersion(); + + Locale getLocale(); +} +``` + +## Elements + +Elements是一个处理Element的工具类。常用的方法如下: + +```java +public interface Elements { + PackageElement getPackageElement(CharSequence var1); //获取指定Element的PackageElement + TypeElement getTypeElement(CharSequence var1);//获取TypeElement +} +``` + +## Element + +在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的`Element`。换句话说:`Element`代表程序的元素,例如包、类或者方法。每个`Element`代表一个静态的、语言级别的构件。在下面的例子中,我们通过注释来说明这个。 + + + +```java +package com.example; // PackageElement + +public class Foo { // TypeElement + + private int a; // VariableElement + private Foo other; // VariableElement + + public Foo () {} // ExecuteableElement + + public void setA ( // ExecuteableElement + int newA // VariableElement + ) {} +} +``` + +```java +public interface Element extends AnnotatedConstruct { + TypeMirror asType();//获取TypeMirror + + ElementKind getKind(); //获取ElementKind + + Set getModifiers(); + + Name getSimpleName(); + + Element getEnclosingElement(); + + List getEnclosedElements(); + + boolean equals(Object var1); + + int hashCode(); + + List getAnnotationMirrors(); + + A getAnnotation(Class var1); + + R accept(ElementVisitor var1, P var2); +} +``` + +**Element**代表的是源代码。`TypeElement`代表的是源代码中的类型元素,例如类。然而,`TypeElement`并不包含类本身的信息。你可以从`TypeElement`中获取类的名字,但是你获取不到类的信息,例如它的父类。这种信息需要通过`TypeMirror`获取。你可以通过调用`elements.asType()`获取元素的`TypeMirror`。 + + + + + diff --git a/annotation/yi-java-zhu-jie-chu-li-qi-annotationannotationprocessing.md.md b/annotation/yi-java-zhu-jie-chu-li-qi-annotationannotationprocessing.md.md new file mode 100644 index 00000000..4cd23c0f --- /dev/null +++ b/annotation/yi-java-zhu-jie-chu-li-qi-annotationannotationprocessing.md.md @@ -0,0 +1,2 @@ +# \[译]Java注解处理器]\(annotation/annotation-processing.md) + diff --git a/aop.md b/aop.md new file mode 100644 index 00000000..828225a0 --- /dev/null +++ b/aop.md @@ -0,0 +1,8 @@ +# aop + +{% embed url="https://github.com/eleme/lancet" %} + +{% embed url="https://github.com/HujiangTechnology/gradle\_plugin\_android\_aspectjx" %} + + + diff --git a/aosp/README.md b/aosp/README.md new file mode 100644 index 00000000..faaef6a5 --- /dev/null +++ b/aosp/README.md @@ -0,0 +1,2 @@ +# 系统源码分析 + diff --git a/aosp/activitymanagerservice-fen-xi.md b/aosp/activitymanagerservice-fen-xi.md new file mode 100644 index 00000000..04603f29 --- /dev/null +++ b/aosp/activitymanagerservice-fen-xi.md @@ -0,0 +1,2 @@ +# ActivityManagerService分析 + diff --git a/aosp/android-tu-xing-xi-tong-gai-shu.md b/aosp/android-tu-xing-xi-tong-gai-shu.md new file mode 100644 index 00000000..ba220b20 --- /dev/null +++ b/aosp/android-tu-xing-xi-tong-gai-shu.md @@ -0,0 +1,2 @@ +# Android图形系统概述 + diff --git a/aosp/android-xi-tong-qi-dong/README.md b/aosp/android-xi-tong-qi-dong/README.md new file mode 100644 index 00000000..0bb7785e --- /dev/null +++ b/aosp/android-xi-tong-qi-dong/README.md @@ -0,0 +1,2 @@ +# Android系统启动 + diff --git a/aosp/android-xi-tong-qi-dong/init-jin-cheng-qi-dong-guo-cheng.md b/aosp/android-xi-tong-qi-dong/init-jin-cheng-qi-dong-guo-cheng.md new file mode 100644 index 00000000..062ffa0a --- /dev/null +++ b/aosp/android-xi-tong-qi-dong/init-jin-cheng-qi-dong-guo-cheng.md @@ -0,0 +1,2 @@ +# init进程启动过程 + diff --git a/aosp/android-xi-tong-qi-dong/systemserver-chu-li-guo-cheng.md b/aosp/android-xi-tong-qi-dong/systemserver-chu-li-guo-cheng.md new file mode 100644 index 00000000..a309a119 --- /dev/null +++ b/aosp/android-xi-tong-qi-dong/systemserver-chu-li-guo-cheng.md @@ -0,0 +1,2 @@ +# SystemServer处理过程 + diff --git a/aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-guo-cheng.md b/aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-guo-cheng.md new file mode 100644 index 00000000..9fdf8656 --- /dev/null +++ b/aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-guo-cheng.md @@ -0,0 +1,2 @@ +# Zygote进程启动过程 + diff --git a/aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-liu-cheng.md b/aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-liu-cheng.md new file mode 100644 index 00000000..c9fb436c --- /dev/null +++ b/aosp/android-xi-tong-qi-dong/zygote-jin-cheng-qi-dong-liu-cheng.md @@ -0,0 +1,2 @@ +# Zygote进程启动流程 + diff --git a/aosp/android-xi-tong-qi-dong/zygote.md b/aosp/android-xi-tong-qi-dong/zygote.md new file mode 100644 index 00000000..e5e3a88b --- /dev/null +++ b/aosp/android-xi-tong-qi-dong/zygote.md @@ -0,0 +1,1003 @@ +--- +title: Zygote进程启动流程 +tags: + - 源码分析 +comments: true +--- + +# Zygote进程启动流程 + +在`Android`系统中,应用程序进程以及运行系统的关键服务的`SystemServer`进程都是由`Zygote`进程来创建的,我们也将它称为孵化器。它通过`fork`(复制进程)的形式来创建应用程序进程和`SystemServer`进程。 + +Zygote进程是在init进程启动时创建的,起初Zygote进程的名称并不是叫“zygote”,而是叫“app\_process”,这个名称是在Android.mk中定义的,Zygote进程启动后,Linux系统下的pctrl系统会调用app\_process,将其名称换成了“zygote”。 + +## Zygote启动脚本分析 + +在`init.rc`文件中采用了`import`引入`Zygote`启动脚本。 + +```java +//system/core/rootdir/init.rc +import /init.${ro.hardware}.rc +import /init.usb.configfs.rc +import /init.${ro.zygote}.rc//导入zygote.rc +``` + +可以看出`init.rc`不会直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件。 + +从`Android 5.0`开始,`Android`开始支持64位程序,`Zygote`也就有了32位和64位的区别,所以在这里用`ro.zygote`属性来控制使用不同的`Zygote`启动脚本,从而也就启动了不同版本的`Zygote`进程,`ro.zygote`属性的取值有以下4种: + +* init.zygote32.rc +* init.zygote32\_64.rc +* init.zygote64.rc +* init.zygote64\_32.rc + +```java +//system/core/rootdir/init.zygote64.rc +//启动zygote service 路径/system/bin/app_process64 +service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server + class main + socket zygote stream 660 root system + onrestart write /sys/android_power/request_state wake + onrestart write /sys/power/state on + onrestart restart audioserver + onrestart restart cameraserver + onrestart restart media + onrestart restart netd + writepid /dev/cpuset/foreground/tasks +``` + +```java +//frameworks/base/cmds/app_process/Android.mk +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + app_main.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + liblog \ + libbinder \ + libandroid_runtime \ + $(app_process_common_shared_libs) \ + +LOCAL_WHOLE_STATIC_LIBRARIES := libsigchain + +LOCAL_LDFLAGS := -ldl -Wl,--version-script,art/sigchainlib/version-script.txt -Wl,--export-dynamic +LOCAL_CPPFLAGS := -std=c++11 + +LOCAL_MODULE := app_process__asan +LOCAL_MULTILIB := both //编译32位和64位 +LOCAL_MODULE_STEM_32 := app_process32 //32位 +LOCAL_MODULE_STEM_64 := app_process64//64位 +``` + +## app\_main + +### main + +`app_main.cpp`的`main`方法会接收传进的来参数,并根据传进来的参数调用`AndroidRuntime`的`start`方法。 + +```cpp +//frameworks/base/cmds/app_process/app_main.cpp +int main(int argc, char* const argv[]) +{ + //... + while (i < argc) { + const char* arg = argv[i++]; + if (strcmp(arg, "--zygote") == 0) { + zygote = true; //传进来的参数--zygote 将布尔值zygote设置为true + niceName = ZYGOTE_NICE_NAME; + } else if (strcmp(arg, "--start-system-server") == 0) { + startSystemServer = true; //启动SystemServer进程 + } else if (strcmp(arg, "--application") == 0) { + application = true; + } else if (strncmp(arg, "--nice-name=", 12) == 0) { + niceName.setTo(arg + 12); + } else if (strncmp(arg, "--", 2) != 0) { + className.setTo(arg); + break; + } else { + --i; + break; + } + } + Vector args; + if (!className.isEmpty()) { + //... + } else { + //... + //start-system-server写入参数中 + if (startSystemServer) { + args.add(String8("start-system-server")); + } + //... + } + if (!niceName.isEmpty()) { //线程名字不是空 设置线程名字 + runtime.setArgv0(niceName.string()); + set_process_name(niceName.string()); + } + + if (zygote) { + //调用AppRuntime的start方法启动ZygoteInit + runtime.start("com.android.internal.os.ZygoteInit", args, zygote); + } else if (className) { + runtime.start("com.android.internal.os.RuntimeInit", args, zygote); + } else { + fprintf(stderr, "Error: no class name or --zygote supplied.\n"); + app_usage(); + LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); + return 10; + } +} +``` + +## AndroidRuntime + +### start + +`AndroidRuntime`的`start`方法负责启动虚拟机,并根据传进来的类名,获取到对应的类,并执行该类的`main`方法。在`app_main.cpp`的`main`方法中,我们传进来的是`ZygoteInit`类, 所以执行的就是`ZygoteInit`的`main`方法。 + +```cpp +//frameworks/base/core/jni/AndroidRuntime.cpp +void AndroidRuntime::start(const char* className, const Vector& options, bool zygote) +{ //... + JniInvocation jni_invocation; + jni_invocation.Init(NULL); + JNIEnv* env; + //启动虚拟机 + if (startVm(&mJavaVM, &env, zygote) != 0) { + return; + } + onVmCreated(env); //启动虚拟机后的回调 + //... + + //将className中的. 替换为/ + char* slashClassName = toSlashClassName(className); + //寻找类 + jclass startClass = env->FindClass(slashClassName); + if (startClass == NULL) { + ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); + /* keep going */ + } else { + //获取main方法 + jmethodID startMeth = env->GetStaticMethodID(startClass, "main", + "([Ljava/lang/String;)V"); + if (startMeth == NULL) { + ALOGE("JavaVM unable to find main() in '%s'\n", className); + /* keep going */ + } else { + //调用main方法 + env->CallStaticVoidMethod(startClass, startMeth, strArray); + +#if 0 + if (env->ExceptionCheck()) + threadExitUncaughtException(env); +#endif + } + } + //... +} +``` + +## ZygoteInit + +### main + +在`ZygoteInit`的`main`方法中,主要做了四件事情: + +* 注册一个Socket +* 预加载各种资源 +* 启动`SystemServer` +* 进入循环,等待AMS请求创建新的进程 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +public static void main(String argv[]) { + //... + try { + //... + //注册一个Socket + registerZygoteSocket(socketName); + //... + //预加载各种资源 + preload(); + //... + //启动SystemServer + if (startSystemServer) { + startSystemServer(abiList, socketName); + } + Log.i(TAG, "Accepting command socket connections"); + //死循环 + runSelectLoop(abiList); + closeServerSocket(); + } catch (MethodAndArgsCaller caller) { + caller.run(); + } catch (Throwable ex) { + Log.e(TAG, "Zygote died with exception", ex); + closeServerSocket(); + throw ex; + } +} +``` + +### registerZygoteSocket + +`registerZygoteSocket`方法主要负责注册一个`Socket`。 + +```java +private static void registerZygoteSocket(String socketName) { + if (sServerSocket == null) { + int fileDesc; + //拼接socket名字 + final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; + try { + String env = System.getenv(fullSocketName); + fileDesc = Integer.parseInt(env); + } catch (RuntimeException ex) { + throw new RuntimeException(fullSocketName + " unset or invalid", ex); + } + + try { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(fileDesc); + //创建Socket + sServerSocket = new LocalServerSocket(fd); + } catch (IOException ex) { + throw new RuntimeException( + "Error binding to local socket '" + fileDesc + "'", ex); + } + } +} +``` + +### startSystemServer + +```java +private static boolean startSystemServer(String abiList, String socketName) + throws MethodAndArgsCaller, RuntimeException { + + //创建数组,保存启动SystemServer的启动参数 + /* Hardcoded command line to start the system server */ + String args[] = { + "--setuid=1000", + "--setgid=1000", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007,3009,3010", + "--capabilities=" + capabilities + "," + capabilities, + "--nice-name=system_server", + "--runtime-args", + "com.android.server.SystemServer", + }; + ZygoteConnection.Arguments parsedArgs = null; + + int pid; + + try { + parsedArgs = new ZygoteConnection.Arguments(args); + ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); + ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); + + /* Request to fork the system server process */ + //fork一个新进程 + pid = Zygote.forkSystemServer( + parsedArgs.uid, parsedArgs.gid, + parsedArgs.gids, + parsedArgs.debugFlags, + null, + parsedArgs.permittedCapabilities, + parsedArgs.effectiveCapabilities); + } catch (IllegalArgumentException ex) { + throw new RuntimeException(ex); + } + + /* For child process */ + if (pid == 0) { + if (hasSecondZygote(abiList)) { + waitForSecondaryZygote(socketName); + } + //处理SystemServer + handleSystemServerProcess(parsedArgs); + } + + return true; +} +``` + +### runSelectLoop + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +private static void runSelectLoop(String abiList) throws MethodAndArgsCaller { + ArrayList fds = new ArrayList(); + ArrayList peers = new ArrayList(); + + fds.add(sServerSocket.getFileDescriptor()); + peers.add(null); + //无限循环等待AMS的请求 + while (true) { + StructPollfd[] pollFds = new StructPollfd[fds.size()]; + for (int i = 0; i < pollFds.length; ++i) { + pollFds[i] = new StructPollfd(); + pollFds[i].fd = fds.get(i); + pollFds[i].events = (short) POLLIN; + } + try { + Os.poll(pollFds, -1); + } catch (ErrnoException ex) { + throw new RuntimeException("poll failed", ex); + } + for (int i = pollFds.length - 1; i >= 0; --i) { + if ((pollFds[i].revents & POLLIN) == 0) { + continue; + } + if (i == 0) { + //有新的连接请求 + ZygoteConnection newPeer = acceptCommandPeer(abiList); + peers.add(newPeer); + fds.add(newPeer.getFileDesciptor()); + } else { + //已建立的连接中有客户端发过来的数据需要处理 + boolean done = peers.get(i).runOnce(); + if (done) { + peers.remove(i); + fds.remove(i); + } + } + } + } +} +``` + +### acceptCommandPeer + +```java +private static ZygoteConnection acceptCommandPeer(String abiList) { + try { + return new ZygoteConnection(sServerSocket.accept(), abiList); + } catch (IOException ex) { + throw new RuntimeException( + "IOException during accept()", ex); + } +} +``` + +## ZygoteConnection + +### 构造函数 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java +ZygoteConnection(LocalSocket socket, String abiList) throws IOException { + mSocket = socket; + this.abiList = abiList; + + mSocketOutStream + = new DataOutputStream(socket.getOutputStream()); + + mSocketReader = new BufferedReader( + new InputStreamReader(socket.getInputStream()), 256); + + mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS); + try { + peer = mSocket.getPeerCredentials(); + } catch (IOException ex) { + Log.e(TAG, "Cannot read peer credentials", ex); + throw ex; + } +} +``` + +### runOnce + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java +boolean runOnce() throws ZygoteInit.MethodAndArgsCaller { + //... + int pid = -1; + FileDescriptor childPipeFd = null; + FileDescriptor serverPipeFd = null; + + try { + parsedArgs = new Arguments(args); + + if (parsedArgs.abiListQuery) { + return handleAbiListQuery(); + } + + if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) + + ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities)); + } + //... + int [] fdsToClose = { -1, -1 }; + + FileDescriptor fd = mSocket.getFileDescriptor(); + + if (fd != null) { + fdsToClose[0] = fd.getInt$(); + } + + fd = ZygoteInit.getServerSocketFileDescriptor(); + + if (fd != null) { + fdsToClose[1] = fd.getInt$(); + } + + fd = null; + //创建应用程序进程 返回pid + pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, + parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, + parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet, + parsedArgs.appDataDir); + } catch (ErrnoException ex) { + logAndPrintError(newStderr, "Exception creating pipe", ex); + } catch (IllegalArgumentException ex) { + logAndPrintError(newStderr, "Invalid zygote arguments", ex); + } catch (ZygoteSecurityException ex) { + logAndPrintError(newStderr, + "Zygote security policy prevents request: ", ex); + } + + try { + if (pid == 0) { + // in child + IoUtils.closeQuietly(serverPipeFd); + serverPipeFd = null; + //② + handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); + + // should never get here, the child is expected to either + // throw ZygoteInit.MethodAndArgsCaller or exec(). + return true; + } else { + // in parent...pid of < 0 means failure + IoUtils.closeQuietly(childPipeFd); + childPipeFd = null; + return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); + } + } finally { + IoUtils.closeQuietly(childPipeFd); + IoUtils.closeQuietly(serverPipeFd); + } +} +``` + +`zygote`需要为每个新启动的应用程序声称该自己独立的进程。不过runOnce并没有直接使用fork来完成这一工作,而是调用了`forkAndSpecialize`。另外,新创建的进程中一定需要运行工应用程序本身的代码,这一部分工作是在`handleChildProc`中展开的。 + +执行完上述的任务后,父进程还需要做一些清尾工作才算“大功告成”。包括:将子进程加入进程组;正确关闭文件;调用方返回结果值等。 + +### readArgumentList + +```java +private String[] readArgumentList() + throws IOException { + /** + * See android.os.Process.zygoteSendArgsAndGetPid() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure. + */ + + int argc; + + try { + String s = mSocketReader.readLine(); + + if (s == null) { + // EOF reached. + return null; + } + argc = Integer.parseInt(s); + } catch (NumberFormatException ex) { + Log.e(TAG, "invalid Zygote wire format: non-int at argc"); + throw new IOException("invalid wire format"); + } + + // See bug 1092107: large argc can be used for a DOS attack + if (argc > MAX_ZYGOTE_ARGC) { + throw new IOException("max arg count exceeded"); + } + + String[] result = new String[argc]; + for (int i = 0; i < argc; i++) { + result[i] = mSocketReader.readLine(); + if (result[i] == null) { + // We got an unexpected EOF. + throw new IOException("truncated request"); + } + } + + return result; +} +``` + +## Zygote + +### forkAndSpecialize + +forkAndSpecialize的处理分为3个阶段,即preFork、nativeForkAndSpecialize以及postForkCommon。 + +```java +public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + String instructionSet, String appDataDir) { + VM_HOOKS.preFork(); + int pid = nativeForkAndSpecialize( + uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + instructionSet, appDataDir); + // Enable tracing as soon as possible for the child process. + if (pid == 0) { + Trace.setTracingEnabled(true); + + // Note that this event ends at the end of handleChildProc, + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + } + VM_HOOKS.postForkCommon(); + return pid; +} +``` + +```cpp +//art/runtime/native/dalvik_system_ZygoteHooks.cc +static jlong ZygoteHooks_nativePreFork(JNIEnv* env, jclass) { + Runtime* runtime = Runtime::Current(); + CHECK(runtime->IsZygote()) << "runtime instance not started with -Xzygote"; + + runtime->PreZygoteFork(); + + if (Trace::GetMethodTracingMode() != TracingMode::kTracingInactive) { + // Tracing active, pause it. + Trace::Pause(); + } + + // Grab thread before fork potentially makes Thread::pthread_key_self_ unusable. + return reinterpret_cast(ThreadForEnv(env)); +} +``` + +```cpp +//frameworks/base/core/jni/com_android_internal_os_Zygote.cpp +static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, + jint debug_flags, jobjectArray rlimits, + jint mount_external, jstring se_info, jstring se_name, + jintArray fdsToClose, jstring instructionSet, jstring appDataDir) { + jlong capabilities = 0; + + // Grant CAP_WAKE_ALARM to the Bluetooth process. + // Additionally, allow bluetooth to open packet sockets so it can start the DHCP client. + // TODO: consider making such functionality an RPC to netd. + if (multiuser_get_app_id(uid) == AID_BLUETOOTH) { + capabilities |= (1LL << CAP_WAKE_ALARM); + capabilities |= (1LL << CAP_NET_RAW); + capabilities |= (1LL << CAP_NET_BIND_SERVICE); + } + + // Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock" + bool gid_wakelock_found = false; + if (gid == AID_WAKELOCK) { + gid_wakelock_found = true; + } else if (gids != NULL) { + jsize gids_num = env->GetArrayLength(gids); + ScopedIntArrayRO ar(env, gids); + if (ar.get() == NULL) { + RuntimeAbort(env, __LINE__, "Bad gids array"); + } + for (int i = 0; i < gids_num; i++) { + if (ar[i] == AID_WAKELOCK) { + gid_wakelock_found = true; + break; + } + } + } + if (gid_wakelock_found) { + capabilities |= (1LL << CAP_BLOCK_SUSPEND); + } + + return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags, + rlimits, capabilities, capabilities, mount_external, se_info, + se_name, false, fdsToClose, instructionSet, appDataDir); +} +``` + +```cpp +//frameworks/base/core/jni/com_android_internal_os_Zygote.cpp +// Utility routine to fork zygote and specialize the child process. +static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, + jint debug_flags, jobjectArray javaRlimits, + jlong permittedCapabilities, jlong effectiveCapabilities, + jint mount_external, + jstring java_se_info, jstring java_se_name, + bool is_system_server, jintArray fdsToClose, + jstring instructionSet, jstring dataDir) { + SetSigChldHandler(); + +#ifdef ENABLE_SCHED_BOOST + SetForkLoad(true); +#endif + + sigset_t sigchld; + sigemptyset(&sigchld); + sigaddset(&sigchld, SIGCHLD); + + // Temporarily block SIGCHLD during forks. The SIGCHLD handler might + // log, which would result in the logging FDs we close being reopened. + // This would cause failures because the FDs are not whitelisted. + // + // Note that the zygote process is single threaded at this point. + if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) { + ALOGE("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_BLOCK, { SIGCHLD }) failed."); + } + + // Close any logging related FDs before we start evaluating the list of + // file descriptors. + __android_log_close(); + + // If this is the first fork for this zygote, create the open FD table. + // If it isn't, we just need to check whether the list of open files has + // changed (and it shouldn't in the normal case). + if (gOpenFdTable == NULL) { + gOpenFdTable = FileDescriptorTable::Create(); + if (gOpenFdTable == NULL) { + RuntimeAbort(env, __LINE__, "Unable to construct file descriptor table."); + } + } else if (!gOpenFdTable->Restat()) { + RuntimeAbort(env, __LINE__, "Unable to restat file descriptor table."); + } + + pid_t pid = fork();//孵化出一个新进程 + + if (pid == 0) { + // The child process. + gMallocLeakZygoteChild = 1; + + // Clean up any descriptors which must be closed immediately + DetachDescriptors(env, fdsToClose); + + // Re-open all remaining open file descriptors so that they aren't shared + // with the zygote across a fork. + if (!gOpenFdTable->ReopenOrDetach()) { + RuntimeAbort(env, __LINE__, "Unable to reopen whitelisted descriptors."); + } + + if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { + ALOGE("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_UNBLOCK, { SIGCHLD }) failed."); + } + + // Keep capabilities across UID change, unless we're staying root. + if (uid != 0) { + EnableKeepCapabilities(env); + } + + DropCapabilitiesBoundingSet(env); + + bool use_native_bridge = !is_system_server && (instructionSet != NULL) + && android::NativeBridgeAvailable(); + if (use_native_bridge) { + ScopedUtfChars isa_string(env, instructionSet); + use_native_bridge = android::NeedsNativeBridge(isa_string.c_str()); + } + if (use_native_bridge && dataDir == NULL) { + // dataDir should never be null if we need to use a native bridge. + // In general, dataDir will never be null for normal applications. It can only happen in + // special cases (for isolated processes which are not associated with any app). These are + // launched by the framework and should not be emulated anyway. + use_native_bridge = false; + ALOGW("Native bridge will not be used because dataDir == NULL."); + } + + if (!MountEmulatedStorage(uid, mount_external, use_native_bridge)) { + ALOGW("Failed to mount emulated storage: %s", strerror(errno)); + if (errno == ENOTCONN || errno == EROFS) { + // When device is actively encrypting, we get ENOTCONN here + // since FUSE was mounted before the framework restarted. + // When encrypted device is booting, we get EROFS since + // FUSE hasn't been created yet by init. + // In either case, continue without external storage. + } else { + RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage"); + } + } + + if (!is_system_server) { + int rc = createProcessGroup(uid, getpid()); + if (rc != 0) { + if (rc == -EROFS) { + ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); + } else { + ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc)); + } + } + } + + SetGids(env, javaGids); + + SetRLimits(env, javaRlimits); + + if (use_native_bridge) { + ScopedUtfChars isa_string(env, instructionSet); + ScopedUtfChars data_dir(env, dataDir); + android::PreInitializeNativeBridge(data_dir.c_str(), isa_string.c_str()); + } + + int rc = setresgid(gid, gid, gid); + if (rc == -1) { + ALOGE("setresgid(%d) failed: %s", gid, strerror(errno)); + RuntimeAbort(env, __LINE__, "setresgid failed"); + } + + rc = setresuid(uid, uid, uid); + if (rc == -1) { + ALOGE("setresuid(%d) failed: %s", uid, strerror(errno)); + RuntimeAbort(env, __LINE__, "setresuid failed"); + } + + if (NeedsNoRandomizeWorkaround()) { + // Work around ARM kernel ASLR lossage (http://b/5817320). + int old_personality = personality(0xffffffff); + int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE); + if (new_personality == -1) { + ALOGW("personality(%d) failed: %s", new_personality, strerror(errno)); + } + } + + SetCapabilities(env, permittedCapabilities, effectiveCapabilities); + + SetSchedulerPolicy(env); + + const char* se_info_c_str = NULL; + ScopedUtfChars* se_info = NULL; + if (java_se_info != NULL) { + se_info = new ScopedUtfChars(env, java_se_info); + se_info_c_str = se_info->c_str(); + if (se_info_c_str == NULL) { + RuntimeAbort(env, __LINE__, "se_info_c_str == NULL"); + } + } + const char* se_name_c_str = NULL; + ScopedUtfChars* se_name = NULL; + if (java_se_name != NULL) { + se_name = new ScopedUtfChars(env, java_se_name); + se_name_c_str = se_name->c_str(); + if (se_name_c_str == NULL) { + RuntimeAbort(env, __LINE__, "se_name_c_str == NULL"); + } + } + rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str); + if (rc == -1) { + ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid, + is_system_server, se_info_c_str, se_name_c_str); + RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed"); + } + + // Make it easier to debug audit logs by setting the main thread's name to the + // nice name rather than "app_process". + if (se_info_c_str == NULL && is_system_server) { + se_name_c_str = "system_server"; + } + if (se_info_c_str != NULL) { + SetThreadName(se_name_c_str); + } + + delete se_info; + delete se_name; + + UnsetSigChldHandler(); + + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags, + is_system_server, instructionSet); + if (env->ExceptionCheck()) { + RuntimeAbort(env, __LINE__, "Error calling post fork hooks."); + } + } else if (pid > 0) { + // the parent process + +#ifdef ENABLE_SCHED_BOOST + // unset scheduler knob + SetForkLoad(false); +#endif + + // We blocked SIGCHLD prior to a fork, we unblock it here. + if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { + ALOGE("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_UNBLOCK, { SIGCHLD }) failed."); + } + } + return pid; +} +``` + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java +private void handleChildProc(Arguments parsedArgs, + FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr) + throws ZygoteInit.MethodAndArgsCaller { + /** + * By the time we get here, the native code has closed the two actual Zygote + * socket connections, and substituted /dev/null in their place. The LocalSocket + * objects still need to be closed properly. + */ + + closeSocket(); + ZygoteInit.closeServerSocket(); + + if (descriptors != null) { + try { + Os.dup2(descriptors[0], STDIN_FILENO); + Os.dup2(descriptors[1], STDOUT_FILENO); + Os.dup2(descriptors[2], STDERR_FILENO); + + for (FileDescriptor fd: descriptors) { + IoUtils.closeQuietly(fd); + } + newStderr = System.err; + } catch (ErrnoException ex) { + Log.e(TAG, "Error reopening stdio", ex); + } + } + + if (parsedArgs.niceName != null) { //子进程别名不为null + Process.setArgV0(parsedArgs.niceName); + } + + // End of the postFork event. + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + if (parsedArgs.invokeWith != null) { + WrapperInit.execApplication(parsedArgs.invokeWith, + parsedArgs.niceName, parsedArgs.targetSdkVersion, + VMRuntime.getCurrentInstructionSet(), + pipeFd, parsedArgs.remainingArgs); + } else { + RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, + parsedArgs.remainingArgs, null /* classLoader */); + } +} +``` + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +private static void handleSystemServerProcess( + ZygoteConnection.Arguments parsedArgs) + throws ZygoteInit.MethodAndArgsCaller { + + closeServerSocket(); + + // set umask to 0077 so new files and directories will default to owner-only permissions. + Os.umask(S_IRWXG | S_IRWXO); + + if (parsedArgs.niceName != null) { + Process.setArgV0(parsedArgs.niceName); + } + + final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); + if (systemServerClasspath != null) { + performSystemServerDexOpt(systemServerClasspath); + } + + if (parsedArgs.invokeWith != null) { + String[] args = parsedArgs.remainingArgs; + // If we have a non-null system server class path, we'll have to duplicate the + // existing arguments and append the classpath to it. ART will handle the classpath + // correctly when we exec a new process. + if (systemServerClasspath != null) { + String[] amendedArgs = new String[args.length + 2]; + amendedArgs[0] = "-cp"; + amendedArgs[1] = systemServerClasspath; + System.arraycopy(parsedArgs.remainingArgs, 0, amendedArgs, 2, parsedArgs.remainingArgs.length); + } + + WrapperInit.execApplication(parsedArgs.invokeWith, + parsedArgs.niceName, parsedArgs.targetSdkVersion, + VMRuntime.getCurrentInstructionSet(), null, args); + } else { + ClassLoader cl = null; + if (systemServerClasspath != null) { + cl = createSystemServerClassLoader(systemServerClasspath, + parsedArgs.targetSdkVersion); + + Thread.currentThread().setContextClassLoader(cl); + } + + /* + * Pass the remaining arguments to SystemServer. + */ + RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); + } + + /* should never reach here */ +} +``` + +```java +//frameworks/base/core/java/com/android/internal/os/RuntimeInit.java +public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) + throws ZygoteInit.MethodAndArgsCaller { + if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote"); + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit"); + redirectLogStreams(); + + commonInit(); + nativeZygoteInit(); + applicationInit(targetSdkVersion, argv, classLoader);//调用SystemServer的main方法 +} +``` + +```java +private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) + throws ZygoteInit.MethodAndArgsCaller { + // If the application calls System.exit(), terminate the process + // immediately without running any shutdown hooks. It is not possible to + // shutdown an Android application gracefully. Among other things, the + // Android runtime shutdown hooks close the Binder driver, which can cause + // leftover running threads to crash before the process actually exits. + nativeSetExitWithoutCleanup(true); + + // We want to be fairly aggressive about heap utilization, to avoid + // holding on to a lot of memory that isn't needed. + VMRuntime.getRuntime().setTargetHeapUtilization(0.75f); + VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); + + final Arguments args; + try { + args = new Arguments(argv); + } catch (IllegalArgumentException ex) { + Slog.e(TAG, ex.getMessage()); + // let the process exit + return; + } + + // The end of of the RuntimeInit event (see #zygoteInit). + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + // Remaining arguments are passed to the start class's static main + invokeStaticMain(args.startClass, args.startArgs, classLoader); +} +``` + +```java +private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) + throws ZygoteInit.MethodAndArgsCaller { + Class cl; + + try { + cl = Class.forName(className, true, classLoader); + } catch (ClassNotFoundException ex) { + throw new RuntimeException( + "Missing class when invoking static main " + className, + ex); + } + + Method m; + try { + m = cl.getMethod("main", new Class[] { String[].class }); + } catch (NoSuchMethodException ex) { + throw new RuntimeException( + "Missing static main on " + className, ex); + } catch (SecurityException ex) { + throw new RuntimeException( + "Problem getting static main on " + className, ex); + } + + int modifiers = m.getModifiers(); + if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) { + throw new RuntimeException( + "Main method is not public and static on " + className); + } + + /* + * This throw gets caught in ZygoteInit.main(), which responds + * by invoking the exception's run() method. This arrangement + * clears up all the stack frames that were required in setting + * up the process. + */ + throw new ZygoteInit.MethodAndArgsCaller(m, argv); +} +``` + diff --git a/aosp/android-yuan-ma-xia-zai.md b/aosp/android-yuan-ma-xia-zai.md new file mode 100644 index 00000000..a02652b9 --- /dev/null +++ b/aosp/android-yuan-ma-xia-zai.md @@ -0,0 +1,5 @@ +# Android源码下载 + +* [搭建构建环境](https://source.android.google.cn/setup/build/initializing?hl=zh-cn) +* [下载源代码](https://source.android.google.cn/setup/downloading?hl=zh-cn) +* [编译 Android](https://source.android.google.cn/setup/build/building?hl=zh-cn) diff --git a/aosp/arraymap.md b/aosp/arraymap.md new file mode 100644 index 00000000..7cef64a5 --- /dev/null +++ b/aosp/arraymap.md @@ -0,0 +1,122 @@ +# ArrayMap + +## ArrayMap + +## put + +```java +@Override +public V put(K key, V value) { + final int osize = mSize; + final int hash; + int index; + //如果key为空 + if (key == null) { + hash = 0; + index = indexOfNull(); + } else { + //计算hash值 + hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode(); + //查找 + index = indexOf(key, hash); + } + //如果已经存在 + if (index >= 0) { + //计算value的索引 + index = (index<<1) + 1; + final V old = (V)mArray[index]; + mArray[index] = value; + return old; + } + + index = ~index; + if (osize >= mHashes.length) { + final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1)) + : (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); + + if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) { + throw new ConcurrentModificationException(); + } + + if (mHashes.length > 0) { + if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); + System.arraycopy(oarray, 0, mArray, 0, oarray.length); + } + + freeArrays(ohashes, oarray, osize); + } + + if (index < osize) { + if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index) + + " to " + (index+1)); + System.arraycopy(mHashes, index, mHashes, index + 1, osize - index); + System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1); + } + + if (CONCURRENT_MODIFICATION_EXCEPTIONS) { + if (osize != mSize || index >= mHashes.length) { + throw new ConcurrentModificationException(); + } + } + mHashes[index] = hash; + mArray[index<<1] = key; + mArray[(index<<1)+1] = value; + mSize++; + return null; +} +``` + +## indexOf + +```java + int indexOf(Object key, int hash) { + final int N = mSize; + + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + //二分查找 + int index = binarySearchHashes(mHashes, N, hash); + + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + + // If the key at the returned index matches, that's what we want. + if (key.equals(mArray[index<<1])) { + return index; + } + + // Search for a matching key after the index. + //遍历搜索 + int end; + for (end = index + 1; end < N && mHashes[end] == hash; end++) { + if (key.equals(mArray[end << 1])) return end; + } + + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { + if (key.equals(mArray[i << 1])) return i; + } + + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; +} +``` + +## 参考 + +[http://gityuan.com/2019/01/13/arraymap/](http://gityuan.com/2019/01/13/arraymap/) + diff --git a/aosp/binder-yuan-li/README.md b/aosp/binder-yuan-li/README.md new file mode 100644 index 00000000..424acb62 --- /dev/null +++ b/aosp/binder-yuan-li/README.md @@ -0,0 +1,2 @@ +# Binder原理 + diff --git a/aosp/binder-yuan-li/binder-qu-dong.md b/aosp/binder-yuan-li/binder-qu-dong.md new file mode 100644 index 00000000..bcf47de1 --- /dev/null +++ b/aosp/binder-yuan-li/binder-qu-dong.md @@ -0,0 +1,2 @@ +# Binder驱动 + diff --git a/aosp/binder-yuan-li/huo-qu-servicemanager.md b/aosp/binder-yuan-li/huo-qu-servicemanager.md new file mode 100644 index 00000000..fcd36419 --- /dev/null +++ b/aosp/binder-yuan-li/huo-qu-servicemanager.md @@ -0,0 +1,2 @@ +# 获取ServiceManager + diff --git a/aosp/binder-yuan-li/parcel-yuan-ma-fen-xi.md b/aosp/binder-yuan-li/parcel-yuan-ma-fen-xi.md new file mode 100644 index 00000000..c7145ba4 --- /dev/null +++ b/aosp/binder-yuan-li/parcel-yuan-ma-fen-xi.md @@ -0,0 +1,2 @@ +# Parcel源码分析 + diff --git a/aosp/binder-yuan-li/qi-dong-servicemanager.md b/aosp/binder-yuan-li/qi-dong-servicemanager.md new file mode 100644 index 00000000..2eafd9b9 --- /dev/null +++ b/aosp/binder-yuan-li/qi-dong-servicemanager.md @@ -0,0 +1,2 @@ +# 启动ServiceManager + diff --git a/aosp/binder/README.md b/aosp/binder/README.md new file mode 100644 index 00000000..6b54fdb2 --- /dev/null +++ b/aosp/binder/README.md @@ -0,0 +1,6 @@ +# Binder原理 + +* \*\*\*\*[写给 Android 应用工程师的 Binder 原理剖析](https://zhuanlan.zhihu.com/p/35519585) + + + diff --git a/aosp/binder/framework-binder-driver.md b/aosp/binder/framework-binder-driver.md new file mode 100644 index 00000000..5c8a0221 --- /dev/null +++ b/aosp/binder/framework-binder-driver.md @@ -0,0 +1,12 @@ +--- +title: Binder驱动分析 +date: '2020-01-25T09:44:44.000Z' +tags: + - 源码分析 +draft: true +--- + +# Binder驱动 + +* [理解Android Binder机制\(1/3\):驱动篇](https://paul.pub/android-binder-driver/) + diff --git a/aosp/binder/framework-get-sm.md b/aosp/binder/framework-get-sm.md new file mode 100644 index 00000000..dd49bb78 --- /dev/null +++ b/aosp/binder/framework-get-sm.md @@ -0,0 +1,341 @@ +--- +title: 获取ServiceManager +date: '2020-02-08T09:44:44.000Z' +tags: + - 源码分析 +--- + +# 获取ServiceManager + +`ServiceManager`的`addService`和`getService`方法都会首先调用`getIServiceManager`来获取`ServiceManager`。 + +## 类图 + +## ServiceManager + +### addService + +```java +//frameworks/base/core/java/android/os/ServiceManager.java +public static void addService(String name, IBinder service) { + try { + getIServiceManager().addService(name, service, false);//添加服务 + } catch (RemoteException e) { + Log.e(TAG, "error in addService", e); + } +} +``` + +### getService + +```java +//frameworks/base/core/java/android/os/ServiceManager.java +private static HashMap sCache = new HashMap();//缓存 +public static IBinder getService(String name) { + try { + IBinder service = sCache.get(name);//查询缓存 + if (service != null) { + return service;//从缓存中查到结果,直接返回 + } else { + return getIServiceManager().getService(name);//向SM发起查询 + } + } catch (RemoteException e) { + Log.e(TAG, "error in getService", e); + } + return null; +} +``` + +### getIServiceManager + +```java +private static IServiceManager getIServiceManager() { + if (sServiceManager != null) { + return sServiceManager; + } + + // Find the service manager + //调用BinderInternal获取IBinder + sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject()); + return sServiceManager; +} +``` + +```java +//frameworks/base/core/java/android/os/ServiceManagerNative.java +public static IServiceManager asInterface(IBinder obj) { + if (obj == null) { + return null; + } + + // ServiceManager is never local + return new ServiceManagerProxy(obj); +} +``` + +### getContextObject + +```java +//frameworks/base/core/java/com/android/internal/os/BinderInternal.java +//获取IBinder +public static final native IBinder getContextObject(); +``` + +## android\_util\_Binder + +### getContextObject + +```java +//frameworks/base/core/jni/android_util_Binder.cpp +static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz) +{ + sp b = ProcessState::self()->getContextObject(NULL);//调用ProcessState的getContextObject方法 + return javaObjectForIBinder(env, b);//native的IBinder转换为Java的IBinder +} +``` + +### javaObjectForIBinder + +```cpp +jobject javaObjectForIBinder(JNIEnv* env, const sp& val) +{ + if (val == NULL) return NULL; + + if (val->checkSubclass(&gBinderOffsets)) { + // One of our own! + jobject object = static_cast(val.get())->object(); + LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object); + return object; + } + + // For the rest of the function we will hold this lock, to serialize + // looking/creation/destruction of Java proxies for native Binder proxies. + AutoMutex _l(mProxyLock); + + // Someone else's... do we know about it? + jobject object = (jobject)val->findObject(&gBinderProxyOffsets); + if (object != NULL) { + jobject res = jniGetReferent(env, object); + if (res != NULL) { + ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res); + return res; + } + LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get()); + android_atomic_dec(&gNumProxyRefs); + val->detachObject(&gBinderProxyOffsets); + env->DeleteGlobalRef(object); + } + //创建BinderProxy对象 + object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor); + if (object != NULL) { + LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object); + // The proxy holds a reference to the native object. + env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get()); + val->incStrong((void*)javaObjectForIBinder); + + // The native object needs to hold a weak reference back to the + // proxy, so we can retrieve the same proxy if it is still active. + jobject refObject = env->NewGlobalRef( + env->GetObjectField(object, gBinderProxyOffsets.mSelf)); + val->attachObject(&gBinderProxyOffsets, refObject, + jnienv_to_javavm(env), proxy_cleanup); + + // Also remember the death recipients registered on this proxy + sp drl = new DeathRecipientList; + drl->incStrong((void*)javaObjectForIBinder); + env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast(drl.get())); + + // Note that a new object reference has been created. + android_atomic_inc(&gNumProxyRefs); + incRefsCreated(env); + } + return object; +} +``` + +## ProcessState + +### self + +`ProcessState`是一个Singleton(单例)类型的类,在一个进程中,只会存在一个实例。通过`ProcessState::self()`接口获取这个实例。一旦获取这个实例,便会执行其构造函数,由此完成了对于Binder设备的初始化工作。 + +```cpp +//frameworks/native/libs/binder/ProcessState.cpp +sp ProcessState::self() +{ + Mutex::Autolock _l(gProcessMutex); + if (gProcess != NULL) { + return gProcess; + } + //创建实例 + gProcess = new ProcessState; + return gProcess; +} +``` + +### 构造函数 + +```cpp +//构造函数 +ProcessState::ProcessState() + : mDriverFD(open_driver())//打开驱动 + , mVMStart(MAP_FAILED) + , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER) + , mThreadCountDecrement(PTHREAD_COND_INITIALIZER) + , mExecutingThreadsCount(0) + , mMaxThreads(DEFAULT_MAX_BINDER_THREADS) + , mStarvationStartTimeMs(0) + , mManagesContexts(false) + , mBinderContextCheckFunc(NULL) + , mBinderContextUserData(NULL) + , mThreadPoolStarted(false) + , mThreadPoolSeq(1) +{ + if (mDriverFD >= 0) { //成功打开/dev/binder + // mmap the binder, providing a chunk of virtual address space to receive transactions. + mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); + if (mVMStart == MAP_FAILED) { + // *sigh* + ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n"); + close(mDriverFD); + mDriverFD = -1; + } + } + + LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened. Terminating."); +} +``` + +### open\_driver + +`open_driver`的函数实现如下所示。在这个函数中完成了三个工作: + +* 首先通过`open`系统调用打开了`dev/binder`设备 +* 然后通过ioctl获取Binder实现的版本号,并检查是否匹配 +* 最后通过ioctl设置进程支持的最大线程数量 + +```cpp +static int open_driver() +{ + int fd = open("/dev/binder", O_RDWR | O_CLOEXEC); + if (fd >= 0) { + int vers = 0; + status_t result = ioctl(fd, BINDER_VERSION, &vers); //获取实现的版本号 + if (result == -1) { + ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno)); + close(fd); + fd = -1; + } + if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) { + ALOGE("Binder driver protocol does not match user space protocol!"); + close(fd); + fd = -1; + } + size_t maxThreads = DEFAULT_MAX_BINDER_THREADS; + result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); + if (result == -1) { + ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); + } + } else { + ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno)); + } + return fd; +} +``` + +### getContextObject + +```cpp +sp ProcessState::getContextObject(const sp& /*caller*/) +{ + return getStrongProxyForHandle(0); //传入0代表ServiceManager +} +``` + +### getStrongProxyForHandle + +```cpp +sp ProcessState::getStrongProxyForHandle(int32_t handle) +{ + sp result; + + AutoMutex _l(mLock); + //查找handle对应的资源 + handle_entry* e = lookupHandleLocked(handle); + + if (e != NULL) { + // We need to create a new BpBinder if there isn't currently one, OR we + // are unable to acquire a weak reference on this current one. See comment + // in getWeakProxyForHandle() for more info about this. + IBinder* b = e->binder; + if (b == NULL || !e->refs->attemptIncWeak(this)) { + if (handle == 0) { + // Special case for context manager... + // The context manager is the only object for which we create + // a BpBinder proxy without already holding a reference. + // Perform a dummy transaction to ensure the context manager + // is registered before we create the first local reference + // to it (which will occur when creating the BpBinder). + // If a local reference is created for the BpBinder when the + // context manager is not present, the driver will fail to + // provide a reference to the context manager, but the + // driver API does not return status. + // + // Note that this is not race-free if the context manager + // dies while this code runs. + // + // TODO: add a driver API to wait for context manager, or + // stop special casing handle 0 for context manager and add + // a driver API to get a handle to the context manager with + // proper reference counting. + + Parcel data; + //测试Binder是否就绪 + status_t status = IPCThreadState::self()->transact( + 0, IBinder::PING_TRANSACTION, data, NULL, 0); + if (status == DEAD_OBJECT) + return NULL; + } + + b = new BpBinder(handle); //创建BpBinder + e->binder = b; + if (b) e->refs = b->getWeakRefs(); + result = b; + } else { + // This little bit of nastyness is to allow us to add a primary + // reference to the remote proxy when this team doesn't have one + // but another team is sending the handle to us. + result.force_set(b); + e->refs->decWeak(this); + } + } + + return result; +} +``` + +当handle值所对应的IBinder不存在或弱引用无效时会创建BpBinder,否则直接获取。 针对handle==0的特殊情况,通过PING\_TRANSACTION来判断是否准备就绪。如果在context manager还未生效前,一个BpBinder的本地引用就已经被创建,那么驱动将无法提供context manager的引用。 + +### lookupHandleLocked + +```cpp +ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle) +{ + const size_t N=mHandleToObject.size(); + if (N <= (size_t)handle) { + handle_entry e; + e.binder = NULL; + e.refs = NULL; + status_t err = mHandleToObject.insertAt(e, N, handle+1-N); + if (err < NO_ERROR) return NULL; + } + return &mHandleToObject.editItemAt(handle); +} +``` + +根据handle值来查找对应的`handle_entry`,`handle_entry`是一个结构体,里面记录IBinder和weakref\_type两个指针。当handle大于mHandleToObject的Vector长度时,则向该Vector中添加\(handle+1-N\)个handle\_entry结构体。 + +## 参考 + +* [Binder系列4—获取ServiceManager](http://gityuan.com/2015/11/08/binder-get-sm/) + diff --git a/aosp/binder/framework-parcel.md b/aosp/binder/framework-parcel.md new file mode 100644 index 00000000..83cdc347 --- /dev/null +++ b/aosp/binder/framework-parcel.md @@ -0,0 +1,30 @@ +--- +title: Parcel源码分析 +date: '2020-02-20T15:11:27.000Z' +tags: + - 源码分析 +draft: true +--- + +# Parcel源码分析 + +```java +//frameworks/base/core/java/android/os/Parcel.java +``` + +```cpp +//frameworks/base/core/jni/android_os_Parcel.cpp +static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr) +{ + Parcel* parcel = reinterpret_cast(nativePtr); + if (parcel != NULL) { + return javaObjectForIBinder(env, parcel->readStrongBinder()); + } + return NULL; +} +``` + +```cpp +//frameworks/base/core/jni/android_os_Parcel.cpp +``` + diff --git a/aosp/binder/framework-start-sm.md b/aosp/binder/framework-start-sm.md new file mode 100644 index 00000000..d03abdf7 --- /dev/null +++ b/aosp/binder/framework-start-sm.md @@ -0,0 +1,495 @@ +--- +title: 启动ServiceManager +date: '2020-02-01T22:07:20.000Z' +tags: + - 源码分析 +--- + +# 启动ServiceManager + +`ServiceManager`在`init.rc`中启动的。 + +```java +//system/core/rootdir/init.rc +service servicemanager /system/bin/servicemanager + class core + user system + group system + critical + onrestart restart healthd + onrestart restart zygote + onrestart restart media + onrestart restart surfaceflinger + onrestart restart drm +``` + +servicemanager是用`C/C++`编写,源码路径在工程的`frameworks/native/cmds/servicemanager`目录中,先看看它的make文件。 + +```java +//frameworks/native/cmds/servicemanager/Android.mk +include $(CLEAR_VARS) +LOCAL_SHARED_LIBRARIES := liblog libcutils libselinux +LOCAL_SRC_FILES := service_manager.c binder.c //servicemanager对应的c文件 +LOCAL_CFLAGS += $(svc_c_flags) +LOCAL_MODULE := servicemanager //生成可执行文件的文件名 +include $(BUILD_EXECUTABLE) +``` + +## service\_manager.c + +### main + +```c +int main(int argc, char **argv) +{ + struct binder_state *bs; + + bs = binder_open(128*1024); //打开Binder设备 + if (!bs) { + ALOGE("failed to open binder driver\n"); + return -1; + } + //将自己设置为Binder大管家,整个Android系统只允许一个ServiceManager存在, + //因而后面还有人调用这个函数就会失败 + if (binder_become_context_manager(bs)) { + ALOGE("cannot become context manager (%s)\n", strerror(errno)); + return -1; + } + + selinux_enabled = is_selinux_enabled(); + sehandle = selinux_android_service_context_handle(); + selinux_status_open(true); + + if (selinux_enabled > 0) { + if (sehandle == NULL) { + ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n"); + abort(); + } + + if (getcon(&service_manager_context) != 0) { + ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n"); + abort(); + } + } + + union selinux_callback cb; + cb.func_audit = audit_callback; + selinux_set_callback(SELINUX_CB_AUDIT, cb); + cb.func_log = selinux_log_callback; + selinux_set_callback(SELINUX_CB_LOG, cb); + + binder_loop(bs, svcmgr_handler);//进入循环,等待客户的请求 + + return 0; +} +``` + +### svcmgr\_handler + +```c +int svcmgr_handler(struct binder_state *bs, + struct binder_transaction_data *txn, + struct binder_io *msg, + struct binder_io *reply) +{ + struct svcinfo *si; + uint16_t *s; + size_t len; + uint32_t handle; + uint32_t strict_policy; + int allow_isolated; + + //ALOGI("target=%p code=%d pid=%d uid=%d\n", + // (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid); + + if (txn->target.ptr != BINDER_SERVICE_MANAGER) + return -1; + + if (txn->code == PING_TRANSACTION) + return 0; + + // Equivalent to Parcel::enforceInterface(), reading the RPC + // header with the strict mode policy mask and the interface name. + // Note that we ignore the strict_policy and don't propagate it + // further (since we do no outbound RPCs anyway). + strict_policy = bio_get_uint32(msg); + s = bio_get_string16(msg, &len); + if (s == NULL) { + return -1; + } + + if ((len != (sizeof(svcmgr_id) / 2)) || + memcmp(svcmgr_id, s, sizeof(svcmgr_id))) { + fprintf(stderr,"invalid id %s\n", str8(s, len)); + return -1; + } + + if (sehandle && selinux_status_updated() > 0) { + struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle(); + if (tmp_sehandle) { + selabel_close(sehandle); + sehandle = tmp_sehandle; + } + } + + switch(txn->code) { + case SVC_MGR_GET_SERVICE: + case SVC_MGR_CHECK_SERVICE: //根据Server名称查找它的handle值 + s = bio_get_string16(msg, &len); + if (s == NULL) { + return -1; + } + handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid); + if (!handle) + break; + bio_put_ref(reply, handle);//保存查询结果,以返回给客户端 + return 0; + + case SVC_MGR_ADD_SERVICE://用于注册一个Binder Server + s = bio_get_string16(msg, &len); + if (s == NULL) { + return -1; + } + handle = bio_get_ref(msg); + allow_isolated = bio_get_uint32(msg) ? 1 : 0; + if (do_add_service(bs, s, len, handle, txn->sender_euid, + allow_isolated, txn->sender_pid)) + return -1; + break; + + case SVC_MGR_LIST_SERVICES: { //获取列表中对应的Server + uint32_t n = bio_get_uint32(msg); + + if (!svc_can_list(txn->sender_pid)) { + ALOGE("list_service() uid=%d - PERMISSION DENIED\n", + txn->sender_euid); + return -1; + } + si = svclist; + while ((n-- > 0) && si) + si = si->next; + if (si) { + bio_put_string16(reply, si->name); //保存结果 + return 0; + } + return -1; + } + default: + ALOGE("unknown code %d\n", txn->code); + return -1; + } + + bio_put_uint32(reply, 0); + return 0; +} +``` + +### do\_add\_service + +```c +int do_add_service(struct binder_state *bs, + const uint16_t *s, size_t len, + uint32_t handle, uid_t uid, int allow_isolated, + pid_t spid) +{ + struct svcinfo *si; + + //ALOGI("add_service('%s',%x,%s) uid=%d\n", str8(s, len), handle, + // allow_isolated ? "allow_isolated" : "!allow_isolated", uid); + + if (!handle || (len == 0) || (len > 127)) + return -1; + + if (!svc_can_register(s, len, spid, uid)) { + ALOGE("add_service('%s',%x) uid=%d - PERMISSION DENIED\n", + str8(s, len), handle, uid); + return -1; + } + + si = find_svc(s, len); + if (si) { + if (si->handle) { + ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n", + str8(s, len), handle, uid); + svcinfo_death(bs, si); + } + si->handle = handle; + } else { + si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t)); + if (!si) { + ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n", + str8(s, len), handle, uid); + return -1; + } + si->handle = handle; + si->len = len; + memcpy(si->name, s, (len + 1) * sizeof(uint16_t)); + si->name[len] = '\0'; + si->death.func = (void*) svcinfo_death; + si->death.ptr = si; + si->allow_isolated = allow_isolated; + si->next = svclist; + svclist = si; + } + + binder_acquire(bs, handle); + binder_link_to_death(bs, handle, &si->death); + return 0; +} +``` + +## binder.c + +### binder\_open + +```c +//frameworks/native/cmds/servicemanager/binder.c +struct binder_state *binder_open(size_t mapsize) +{ + struct binder_state *bs; //这个结构构体记录了SM中关于Binder的所有信息,如fd、map的大小等 + struct binder_version vers; + + bs = malloc(sizeof(*bs)); + if (!bs) { + errno = ENOMEM; + return NULL; + } + + bs->fd = open("/dev/binder", O_RDWR);//打开Binder驱动节点 + if (bs->fd < 0) { + fprintf(stderr,"binder: cannot open device (%s)\n", + strerror(errno)); + goto fail_open; + } + + if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) || + (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) { + fprintf(stderr, + "binder: kernel driver version (%d) differs from user space version (%d)\n", + vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION); + goto fail_open; + } + + bs->mapsize = mapsize; //mapsize是SM自己设置的,为128*1024,即128k + bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); + if (bs->mapped == MAP_FAILED) { + fprintf(stderr,"binder: cannot map device (%s)\n", + strerror(errno)); + goto fail_map; + } + + return bs; + +fail_map: + close(bs->fd);//关闭file +fail_open: + free(bs); + return NULL; +} +``` + +### binder\_become\_context\_manager + +```c +int binder_become_context_manager(struct binder_state *bs) +{ + return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0); +} +``` + +### binder\_loop + +```c +void binder_loop(struct binder_state *bs, binder_handler func) +{ //func函数在SM中对应svcmgr_handler() + int res; + struct binder_write_read bwr; //这是执行BINDER_WRITE_READ 命令所需的数据格式 + uint32_t readbuf[32]; //一次读取容量 + + bwr.write_size = 0; + bwr.write_consumed = 0; + bwr.write_buffer = 0; + + readbuf[0] = BC_ENTER_LOOPER; + binder_write(bs, readbuf, sizeof(uint32_t)); + + for (;;) { + bwr.read_size = sizeof(readbuf); + bwr.read_consumed = 0; + bwr.read_buffer = (uintptr_t) readbuf;//读取的消息存储到readbuf中 + + res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//读取驱动发送的消息 + + if (res < 0) { + ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno)); + break; + } + + res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);//处理这条消息 + if (res == 0) { + ALOGE("binder_loop: unexpected reply?!\n"); + break; + } + if (res < 0) { + ALOGE("binder_loop: io error %d %s\n", res, strerror(errno)); + break; + } + } +} +``` + +### binder\_parse + +```c +int binder_parse(struct binder_state *bs, struct binder_io *bio, + uintptr_t ptr, size_t size, binder_handler func) +{ + int r = 1; + uintptr_t end = ptr + (uintptr_t) size; + + while (ptr < end) { + uint32_t cmd = *(uint32_t *) ptr; + ptr += sizeof(uint32_t); +#if TRACE + fprintf(stderr,"%s:\n", cmd_name(cmd)); +#endif + switch(cmd) { + case BR_NOOP: + break; + case BR_TRANSACTION_COMPLETE: + break; + case BR_INCREFS: + case BR_ACQUIRE: + case BR_RELEASE: + case BR_DECREFS: +#if TRACE + fprintf(stderr," %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void *))); +#endif + ptr += sizeof(struct binder_ptr_cookie); + break; + case BR_TRANSACTION: { + struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr; + if ((end - ptr) < sizeof(*txn)) { + ALOGE("parse: txn too small!\n"); + return -1; + } + binder_dump_txn(txn); + if (func) { + unsigned rdata[256/4]; + struct binder_io msg; + struct binder_io reply; + int res; + + bio_init(&reply, rdata, sizeof(rdata), 4); + bio_init_from_txn(&msg, txn); + res = func(bs, txn, &msg, &reply); + //将执行结果回复给底层Binder驱动 + binder_send_reply(bs, &reply, txn->data.ptr.buffer, res); + } + ptr += sizeof(*txn); + break; + } + case BR_REPLY: { + struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr; + if ((end - ptr) < sizeof(*txn)) { + ALOGE("parse: reply too small!\n"); + return -1; + } + binder_dump_txn(txn); + if (bio) { + bio_init_from_txn(bio, txn); + bio = 0; + } else { + /* todo FREE BUFFER */ + } + ptr += sizeof(*txn); + r = 0; + break; + } + case BR_DEAD_BINDER: { + struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr; + ptr += sizeof(binder_uintptr_t); + death->func(bs, death->ptr); + break; + } + case BR_FAILED_REPLY: + r = -1; + break; + case BR_DEAD_REPLY: + r = -1; + break; + default: + ALOGE("parse: OOPS %d\n", cmd); + return -1; + } + } + + return r; +} +``` + +### binder\_send\_reply + +```c +void binder_send_reply(struct binder_state *bs, + struct binder_io *reply, + binder_uintptr_t buffer_to_free, + int status) +{ + struct { + uint32_t cmd_free; + binder_uintptr_t buffer; + uint32_t cmd_reply; + struct binder_transaction_data txn; + } __attribute__((packed)) data; + + data.cmd_free = BC_FREE_BUFFER; + data.buffer = buffer_to_free; + data.cmd_reply = BC_REPLY; + data.txn.target.ptr = 0; + data.txn.cookie = 0; + data.txn.code = 0; + if (status) { + data.txn.flags = TF_STATUS_CODE; + data.txn.data_size = sizeof(int); + data.txn.offsets_size = 0; + data.txn.data.ptr.buffer = (uintptr_t)&status; + data.txn.data.ptr.offsets = 0; + } else { + data.txn.flags = 0; + data.txn.data_size = reply->data - reply->data0; + data.txn.offsets_size = ((char*) reply->offs) - ((char*) reply->offs0); + data.txn.data.ptr.buffer = (uintptr_t)reply->data0; + data.txn.data.ptr.offsets = (uintptr_t)reply->offs0; + } + binder_write(bs, &data, sizeof(data)); +} +``` + +### binder\_write + +```c +int binder_write(struct binder_state *bs, void *data, size_t len) +{ + struct binder_write_read bwr; + int res; + + bwr.write_size = len; + bwr.write_consumed = 0; + bwr.write_buffer = (uintptr_t) data; + bwr.read_size = 0; + bwr.read_consumed = 0; + bwr.read_buffer = 0; + res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); + if (res < 0) { + fprintf(stderr,"binder_write: ioctl failed (%s)\n", + strerror(errno)); + } + return res; +} +``` + +## 参考 + +* [Binder系列3—启动ServiceManager](http://gityuan.com/2015/11/07/binder-start-sm/) + diff --git a/aosp/choreographer.md b/aosp/choreographer.md new file mode 100644 index 00000000..8efa0759 --- /dev/null +++ b/aosp/choreographer.md @@ -0,0 +1,673 @@ +--- +title: Choreographer原理 +tags: + - Android +comments: true +--- +## Choreographer启动流程 + +在Activity启动过程,执行完onResume后,会调用Activity.makeVisible\(\),然后再调用到addView\(\), 层层调用会进入如下方法: + +```java +public ViewRootImpl(Context context, Display display) { + ... + //获取Choreographer实例 + mChoreographer = Choreographer.getInstance(); + ... +} +``` + +### getInstance\(\) + +```java +//frameworks/base/core/java/android/view/Choreographer.java +public static Choreographer getInstance() { + return sThreadInstance.get(); +} + // Thread local storage for the choreographer. +private static final ThreadLocal sThreadInstance = + new ThreadLocal() { + @Override + protected Choreographer initialValue() { + //获取当前线程Looper + Looper looper = Looper.myLooper(); + if (looper == null) { + throw new IllegalStateException("The current thread must have a looper!"); + } + Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); + if (looper == Looper.getMainLooper()) { + mMainInstance = choreographer; + } + return choreographer; + } +}; +``` + +### 创建Choreographer + +```java +//frameworks/base/core/java/android/view/Choreographer.java +private Choreographer(Looper looper, int vsyncSource) { + mLooper = looper; + //创建Handler + mHandler = new FrameHandler(looper); + //创建用于接收Vsync的对象 + mDisplayEventReceiver = USE_VSYNC + ? new FrameDisplayEventReceiver(looper, vsyncSource) + : null; + //上一次帧绘制时间点 + mLastFrameTimeNanos = Long.MIN_VALUE; + //帧间时长 + mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); + //创建回调对象 + mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; + for (int i = 0; i <= CALLBACK_LAST; i++) { + mCallbackQueues[i] = new CallbackQueue(); + } + // b/68769804: For low FPS experiments. + setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1)); +} +``` + +### 创建FrameHandler + +```java +private final class FrameHandler extends Handler { + public FrameHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DO_FRAME: + doFrame(System.nanoTime(), 0); + break; + case MSG_DO_SCHEDULE_VSYNC: + doScheduleVsync(); + break; + case MSG_DO_SCHEDULE_CALLBACK: + doScheduleCallback(msg.arg1); + break; + } + } +} +``` + +### postCallback + +```java +public void postCallback(int callbackType, Runnable action, Object token) { + postCallbackDelayed(callbackType, action, token, 0); +} +public void postCallbackDelayed(int callbackType, + Runnable action, Object token, long delayMillis) { + if (action == null) { + throw new IllegalArgumentException("action must not be null"); + } + if (callbackType < 0 || callbackType > CALLBACK_LAST) { + throw new IllegalArgumentException("callbackType is invalid"); + } + + postCallbackDelayedInternal(callbackType, action, token, delayMillis); +} +``` + +### postCallbackDelayedInternal + +```java +private void postCallbackDelayedInternal(int callbackType, + Object action, Object token, long delayMillis) { + if (DEBUG_FRAMES) { + Log.d(TAG, "PostCallback: type=" + callbackType + + ", action=" + action + ", token=" + token + + ", delayMillis=" + delayMillis); + } + + synchronized (mLock) { + final long now = SystemClock.uptimeMillis(); + final long dueTime = now + delayMillis; + // 1. 将 mTraversalRunnable 塞入队列 + mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); + + if (dueTime <= now) {// 立即执行 + // 2. 由于 delayMillis 是 0,所以会执行到这里 + scheduleFrameLocked(now); + } else {// 延迟执行 + Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); + msg.arg1 = callbackType; + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, dueTime); + } + } +} +``` + +```java +private void scheduleFrameLocked(long now) { + if (!mFrameScheduled) { + mFrameScheduled = true; + if (USE_VSYNC) { + // Android 4.1 之后 USE_VSYNCUSE_VSYNC 默认为 true + if (DEBUG_FRAMES) { + Log.d(TAG, "Scheduling next frame on vsync."); + } + + // If running on the Looper thread, then schedule the vsync immediately, + // otherwise post a message to schedule the vsync from the UI thread + // as soon as possible. + //如果是当前线程,直接申请 vsync,否则通过 handler 通信 + if (isRunningOnLooperThreadLocked()) { + scheduleVsyncLocked(); + } else { + Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); + msg.setAsynchronous(true); + mHandler.sendMessageAtFrontOfQueue(msg); + } + } else { + final long nextFrameTime = Math.max( + mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); + if (DEBUG_FRAMES) { + Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); + } + Message msg = mHandler.obtainMessage(MSG_DO_FRAME); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, nextFrameTime); + } + } +} +``` + +```java +private void scheduleVsyncLocked() { + mDisplayEventReceiver.scheduleVsync(); +} +``` + +```java +@UnsupportedAppUsage +public void scheduleVsync() { + if (mReceiverPtr == 0) { + Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + + "receiver has already been disposed."); + } else { + // 注册监听 vsync 信号,会回调 dispatchVsync() 方法 + nativeScheduleVsync(mReceiverPtr); + } +} +``` + +### 创建FrameDisplayEventReceiver + +```java +private final class FrameDisplayEventReceiver extends DisplayEventReceiver + implements Runnable { + private boolean mHavePendingVsync; + private long mTimestampNanos; + private int mFrame; + + public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { + super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS); + } +} +``` + +#### DisplayEventReceiver + +```java +@UnsupportedAppUsage +public DisplayEventReceiver(Looper looper) { + this(looper, VSYNC_SOURCE_APP, CONFIG_CHANGED_EVENT_SUPPRESS); +} +public DisplayEventReceiver(Looper looper, int vsyncSource, int configChanged) { + if (looper == null) { + throw new IllegalArgumentException("looper must not be null"); + } + //获取主线程消息队列 + mMessageQueue = looper.getQueue(); + //调用Native方法 + mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue, + vsyncSource, configChanged); + + mCloseGuard.open("dispose"); +} +``` + +#### **nativeInit** + +```cpp +//android_view_DisplayEventReceiver.cpp +static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, + jobject messageQueueObj, jint vsyncSource, jint configChanged) { + //获取MessageQueue + sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); + if (messageQueue == NULL) { + jniThrowRuntimeException(env, "MessageQueue is not initialized."); + return 0; + } + //创建NativeDisplayEventReceiver对象 + sp receiver = new NativeDisplayEventReceiver(env, + receiverWeak, messageQueue, vsyncSource, configChanged); + status_t status = receiver->initialize(); + if (status) { + String8 message; + message.appendFormat("Failed to initialize display event receiver. status=%d", status); + jniThrowRuntimeException(env, message.string()); + return 0; + } + //获取DisplayEventReceiver对象的引用 + receiver->incStrong(gDisplayEventReceiverClassInfo.clazz); // retain a reference for the object + return reinterpret_cast(receiver.get()); +} +``` + +**创建NativeDisplayEventReceiver** + +NativeDisplayEventReceiver继承于LooperCallback对象,此处mReceiverWeakGlobal记录的是Java层 DisplayEventReceiver对象的全局引用。 + +```cpp +//可以看做是构造函数调用父类的构造函数 +NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, + jobject receiverWeak, const sp& messageQueue, jint vsyncSource, + jint configChanged) : + DisplayEventDispatcher(messageQueue->getLooper(),//获取Looper + static_cast(vsyncSource), + static_cast(configChanged)), + mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), + mMessageQueue(messageQueue) { + ALOGV("receiver %p ~ Initializing display event receiver.", this); +} +``` + +#### **initialize** + +`DisplayEventDispatcher`继承`LooperCallback` + +```cpp +//androidfw/DisplayEventDispatcher.h +class DisplayEventDispatcher : public LooperCallback +``` + +```java +//androidfw/DisplayEventDispatcher.cpp +status_t DisplayEventDispatcher::initialize() { + //mReceiver为DisplayEventReceiver类型 + status_t result = mReceiver.initCheck(); + if (result) { + ALOGW("Failed to initialize display event receiver, status=%d", result); + return result; + } + //监听mReceiver的所获取的文件句柄 + int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, + this, NULL); + if (rc < 0) { + return UNKNOWN_ERROR; + } + return OK; +} +``` + +监听mReceiver的所获取的文件句柄,一旦有数据到来,则回调this\(此处NativeDisplayEventReceiver\)中所复写LooperCallback对象的 handleEvent。 + +```cpp +//Looper.cpp +int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) { + return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : nullptr, data); +} +``` + +## Vysnc回调流程 + +### handleEvent + +```cpp +//androidfw/DisplayEventDispatcher.cpp +int DisplayEventDispatcher::handleEvent(int, int events, void*) { + if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { + ALOGE("Display event receiver pipe was closed or an error occurred. " + "events=0x%x", events); + return 0; // remove the callback + } + + if (!(events & Looper::EVENT_INPUT)) { + ALOGW("Received spurious callback for unhandled poll event. " + "events=0x%x", events); + return 1; // keep the callback + } + + // Drain all pending events, keep the last vsync. + nsecs_t vsyncTimestamp; + PhysicalDisplayId vsyncDisplayId; + uint32_t vsyncCount; + //清除所有的pending事件,只保留最后一次vsync + if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) { + ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", displayId=%" + ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", count=%d", + this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount); + mWaitingForVsync = false; + //分发Vsync + dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount); + } + + return 1; // keep the callback +} +``` + +#### **processPendingEvents** + +```java +bool NativeDisplayEventReceiver::processPendingEvents( + nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) { + bool gotVsync = false; + DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE]; + ssize_t n; + while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) { + for (ssize_t i = 0; i < n; i++) { + const DisplayEventReceiver::Event& ev = buf[i]; + switch (ev.header.type) { + case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: + gotVsync = true; //获取VSync信号 + *outTimestamp = ev.header.timestamp; + *outId = ev.header.id; + *outCount = ev.vsync.count; + break; + case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: + dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected); + break; + default: + break; + } + } + } + return gotVsync; +} +``` + +遍历所有的事件,当有多个VSync事件到来,则只关注最近一次的事件。 + +### **dispatchVsync** + +```java +void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, + uint32_t count) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + ScopedLocalRef receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal)); + if (receiverObj.get()) { + ALOGV("receiver %p ~ Invoking vsync handler.", this); + env->CallVoidMethod(receiverObj.get(), + gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, displayId, count); + ALOGV("receiver %p ~ Returned from vsync handler.", this); + } + + mMessageQueue->raiseAndClearException(env, "dispatchVsync"); +} +``` + +此处调用到Java层的DisplayEventReceiver对象的dispatchVsync\(\)方法,接下来进入Java层。 + +```java + //DisplayEventReceiver.java +private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) { + onVsync(timestampNanos, physicalDisplayId, frame); +} +``` + +Choreographer对象实例化的过程,创建的对象是DisplayEventReceiver子类 FrameDisplayEventReceiver对象,接下来进入该对象。 + +### onVsync + +```java +//FrameDisplayEventReceiver.java +@Override +public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { + // Post the vsync event to the Handler. + // The idea is to prevent incoming vsync events from completely starving + // the message queue. If there are no messages in the queue with timestamps + // earlier than the frame time, then the vsync event will be processed immediately. + // Otherwise, messages that predate the vsync event will be handled first. + long now = System.nanoTime(); + if (timestampNanos > now) { + Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f) + + " ms in the future! Check that graphics HAL is generating vsync " + + "timestamps using the correct timebase."); + timestampNanos = now; + } + + if (mHavePendingVsync) { + Log.w(TAG, "Already have a pending vsync event. There should only be " + + "one at a time."); + } else { + mHavePendingVsync = true; + } + + mTimestampNanos = timestampNanos; + mFrame = frame; + Message msg = Message.obtain(mHandler, this); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); +} +``` + +可见onVsync\(\)过程是通过FrameHandler向主线程Looper发送了一个自带callback的消息,此处callback为FrameDisplayEventReceiver。 当主线程Looper执行到该消息时,则调用FrameDisplayEventReceiver.run\(\)方法. + +#### run\(\) + +```java +@Override +public void run() { + mHavePendingVsync = false; + doFrame(mTimestampNanos, mFrame); +} +``` + +### doFrame\(\) + +```java +@UnsupportedAppUsage +void doFrame(long frameTimeNanos, int frame) { + final long startNanos; + synchronized (mLock) { + //false直接返回 + if (!mFrameScheduled) { + return; // no work to do + } + + if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { + mDebugPrintNextFrameTimeDelta = false; + Log.d(TAG, "Frame time delta: " + + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); + } + + long intendedFrameTimeNanos = frameTimeNanos; + startNanos = System.nanoTime(); + final long jitterNanos = startNanos - frameTimeNanos; + if (jitterNanos >= mFrameIntervalNanos) { + final long skippedFrames = jitterNanos / mFrameIntervalNanos; + //当掉帧个数超过30,则输出响应log + if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { + Log.i(TAG, "Skipped " + skippedFrames + " frames! " + + "The application may be doing too much work on its main thread."); + } + final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; + if (DEBUG_JANK) { + Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + + "which is more than the frame interval of " + + (mFrameIntervalNanos * 0.000001f) + " ms! " + + "Skipping " + skippedFrames + " frames and setting frame " + + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); + } + frameTimeNanos = startNanos - lastFrameOffset; + } + + if (frameTimeNanos < mLastFrameTimeNanos) { + if (DEBUG_JANK) { + Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + + "previously skipped frame. Waiting for next vsync."); + } + scheduleVsyncLocked(); + return; + } + + if (mFPSDivisor > 1) { + long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; + if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { + scheduleVsyncLocked(); + return; + } + } + + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); + mFrameScheduled = false; + mLastFrameTimeNanos = frameTimeNanos; + } + + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame"); + AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); + + mFrameInfo.markInputHandlingStart(); + doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); + //标记动画开始时间 + mFrameInfo.markAnimationsStart(); + //执行回调方法 + doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); + doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); + mFrameInfo.markPerformTraversalsStart(); + doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); + + doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); + } finally { + AnimationUtils.unlockAnimationClock(); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + + if (DEBUG_FRAMES) { + final long endNanos = System.nanoTime(); + Log.d(TAG, "Frame " + frame + ": Finished, took " + + (endNanos - startNanos) * 0.000001f + " ms, latency " + + (startNanos - frameTimeNanos) * 0.000001f + " ms."); + } +} +``` + +此处frameTimeNanos是底层VSYNC信号到达的时间戳。 + +1. 每调用一次scheduleFrameLocked\(\),则mFrameScheduled=true,能执行一次doFrame\(\)操作,执行完doFrame\(\)并设置mFrameScheduled=false; +2. 最终有4个回调类别,如下所示: + * INPUT:输入事件 + * ANIMATION:动画 + * TRAVERSAL:窗口刷新,执行measure/layout/draw操作 + * COMMIT:遍历完成的提交操作,用来修正动画启动时间 + +### doCallbacks + +```java +void doCallbacks(int callbackType, long frameTimeNanos) { + CallbackRecord callbacks; + synchronized (mLock) { + // We use "now" to determine when callbacks become due because it's possible + // for earlier processing phases in a frame to post callbacks that should run + // in a following phase, such as an input event that causes an animation to start. + final long now = System.nanoTime(); + //从队列查找相应类型的CallbackRecord对象 + callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( + now / TimeUtils.NANOS_PER_MS); + if (callbacks == null) { + return; + } + mCallbacksRunning = true; + + // Update the frame time if necessary when committing the frame. + // We only update the frame time if we are more than 2 frames late reaching + // the commit phase. This ensures that the frame time which is observed by the + // callbacks will always increase from one frame to the next and never repeat. + // We never want the next frame's starting frame time to end up being less than + // or equal to the previous frame's commit frame time. Keep in mind that the + // next frame has most likely already been scheduled by now so we play it + // safe by ensuring the commit time is always at least one frame behind. + if (callbackType == Choreographer.CALLBACK_COMMIT) { + final long jitterNanos = now - frameTimeNanos; + Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); + //当commit类型回调执行的时间点超过2帧,则更新mLastFrameTimeNanos。 + if (jitterNanos >= 2 * mFrameIntervalNanos) { + final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + + mFrameIntervalNanos; + if (DEBUG_JANK) { + Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f) + + " ms which is more than twice the frame interval of " + + (mFrameIntervalNanos * 0.000001f) + " ms! " + + "Setting frame time to " + (lastFrameOffset * 0.000001f) + + " ms in the past."); + mDebugPrintNextFrameTimeDelta = true; + } + frameTimeNanos = now - lastFrameOffset; + mLastFrameTimeNanos = frameTimeNanos; + } + } + } + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); + for (CallbackRecord c = callbacks; c != null; c = c.next) { + if (DEBUG_FRAMES) { + Log.d(TAG, "RunCallback: type=" + callbackType + + ", action=" + c.action + ", token=" + c.token + + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); + } + c.run(frameTimeNanos); + } + } finally { + synchronized (mLock) { + + mCallbacksRunning = false; + //回收callbacks,加入对象池mCallbackPool + do { + final CallbackRecord next = callbacks.next; + recycleCallbackLocked(callbacks); + callbacks = next; + } while (callbacks != null); + } + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } +} +``` + +该方法主要功能: + +* 从队列头mHead查找CallbackRecord对象,当队列头部的callbacks对象为空或者执行时间还没到达,则直接返回; +* 开始执行相应回调的run\(\)方法; +* 回收callbacks,加入对象池mCallbackPool,就是说callback一旦执行完成,则会被回收。 + +### CallbackRecord + +```java +private static final class CallbackRecord { + public CallbackRecord next; + public long dueTime; + public Object action; // Runnable or FrameCallback + public Object token; + + @UnsupportedAppUsage + public void run(long frameTimeNanos) { + if (token == FRAME_CALLBACK_TOKEN) { + ((FrameCallback)action).doFrame(frameTimeNanos); + } else { + ((Runnable)action).run(); + } + } +} +``` + +这里的回调方法run\(\)有两种执行情况: + +* 当token的数据类型为FRAME\_CALLBACK\_TOKEN,则执行该对象的doFrame\(\)方法; +* 当token为其他类型,则执行该对象的run\(\)方法。 + +那么需要的场景便是由WMS调用scheduleAnimationLocked\(\)方法来设置mFrameScheduled=true来触发动画, 接下来说说动画控制的过程。 + +## 动画显示过程 + +## 参考 + +* [面试官:如何监测应用的 FPS ?](https://juejin.cn/post/6890407553457963022) + diff --git a/aosp/context.md b/aosp/context.md new file mode 100644 index 00000000..5c6071ba --- /dev/null +++ b/aosp/context.md @@ -0,0 +1,333 @@ +# Context分析 + +## Context关联类 + +Context意为上下文,是一个应用程序环境信息的接口。 + +在开发中我们经常使用Context,它的使用场景总的来说分为两大类,它们分别是: + +* 使用Context调用方法,比如启动Activity、访问资源、调用系统级服务等。 +* 调用方法时传入Context,比如弹出Toast、创建Dialog等。 + +`Activity`、`Service`和`Application`都间接地继承自Context,因此我们可以计算出一个应用程序进程中有多少个Context,这个数量等于Activity和Service的总个数加1,1指的是Application的数量。 + +Context 是一个抽象类,它的内部定义了很多方法以及静态常量,它的具体实现类为ContextImpl。和Context相关联的类,除了`ContextImpl`,还有ContextWrapper、ContextThemeWrapper和Activity等,如图所示。 + +![](https://malinkang.cn/images/jvm/202111241437141.png) + +`ContextImpl`和`ContextWrapper`继承自`Context`,`ContextWrapper`内部包含`Context`类型的mBase对象,mBase 具体指向ContextImpl。ContextImpl 提供了很多功能,但是外界需要使用并拓展ContextImpl的功能,因此设计上使用了装饰模式,ContextWrapper是装饰类,它对ContextImpl进行包装,ContextWrapper主要是起了方法传递的作用,ContextWrapper中几乎所有的方法都是调用ContextImpl的相应方法来实现的。ContextThemeWrapper、Service和Application都继承自ContextWrapper,这样它们都可以通过mBase来使用Context的方法,同时它们也是装饰类,在ContextWrapper的基础上又添加了不同的功能。`ContextThemeWrapper`中包含和主题相关的方法(比如getTheme方法),因此,需要主题的Activity继承`ContextThemeWrapper`,而不需要主题的Service继承ContextWrapper。 + +Context的关联类采用了装饰模式,主要有以下的优点: + +* 使用者(比如Service)能够更方便地使用Context。 +* 如果`ContextImpl`发生了变化,它的装饰类ContextWrapper不需要做任何修改。 +* `ContextImpl`的实现不会暴露给使用者,使用者也不必关心`ContextImpl`的实现。 +* 通过组合而非继承的方式,拓展ContextImpl的功能,在运行时选择不同的装饰类,实现不同的功能。 + +为了更好地理解Context的关联类的设计理念,就需要理解Application、Activity、Service的Context的创建过程,下面分别对它们进行介绍。 + +## ApplicationContext创建 + +我们通过调用`getApplicationContext()`来获取应用程序全局的ApplicationContext,那么Application Context是如何创建的呢?在一个应用程序启动完成后,应用程序就会有一个全局的Application Context,那么我们就从应用程序启动过程开始着手。 + +![ApplicationContext创建过程](https://malinkang.cn/images/jvm/202111241522593.png) + +### performLaunchActivity() + +```java +//frameworks/base/core/java/android/app/ActivityThread.java +private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { + try { + //调用LoadedApk的makeApplication方法 + Application app = r.packageInfo.makeApplication(false, mInstrumentation); + } + return activity; +} +``` + +### makeApplication() + +```java +//frameworks/base/core/java/android/app/LoadedApk.java +public Application makeApplication(boolean forceDefaultAppClass, + Instrumentation instrumentation) { + //判断是否为null + if (mApplication != null) { + return mApplication; + } + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication"); + + Application app = null; + //获取Application类名 + String appClass = mApplicationInfo.className; + if (forceDefaultAppClass || (appClass == null)) { + appClass = "android.app.Application"; + } + + try { + //获取ClassLoader + java.lang.ClassLoader cl = getClassLoader(); + if (!mPackageName.equals("android")) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + "initializeJavaContextClassLoader"); + initializeJavaContextClassLoader(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + //调用ContextImpl静态方法createAppContext创建ContextImpl + ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); + //创建Application + app = mActivityThread.mInstrumentation.newApplication( + cl, appClass, appContext); + appContext.setOuterContext(app); + } catch (Exception e) { + if (!mActivityThread.mInstrumentation.onException(app, e)) { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + throw new RuntimeException( + "Unable to instantiate application " + appClass + + ": " + e.toString(), e); + } + } + mActivityThread.mAllApplications.add(app); + mApplication = app; //赋值 + + if (instrumentation != null) { + try { + instrumentation.callApplicationOnCreate(app); + } catch (Exception e) { + if (!instrumentation.onException(app, e)) { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + throw new RuntimeException( + "Unable to create application " + app.getClass().getName() + + ": " + e.toString(), e); + } + } + } + //... + + return app; +} +``` + +### createAppContext() + +```java +//frameworks/base/core/java/android/app/ContextImpl.java +static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { + return createAppContext(mainThread, packageInfo, null); +} +static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo, + String opPackageName) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, + ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName); + context.setResources(packageInfo.getResources()); + context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI + : CONTEXT_TYPE_NON_UI; + return context; +} + +``` + + + +### newApplication() + +```java +//frameworks/base/core/java/android/app/Instrumentation.java +public Application newApplication(ClassLoader cl, String className, Context context) + throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + //先获取AppComponentFactory + Application app = getFactory(context.getPackageName()) + .instantiateApplication(cl, className); + //调用Application的attach方法 + app.attach(context); + return app; +} +``` + +### getFactory() + +```java +//frameworks/base/core/java/android/app/Instrumentation.java +private AppComponentFactory getFactory(String pkg) { + if (pkg == null) { + Log.e(TAG, "No pkg specified, disabling AppComponentFactory"); + return AppComponentFactory.DEFAULT; + } + if (mThread == null) { + Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation," + + " disabling AppComponentFactory", new Throwable()); + return AppComponentFactory.DEFAULT; + } + LoadedApk apk = mThread.peekPackageInfo(pkg, true); + // This is in the case of starting up "android". + if (apk == null) apk = mThread.getSystemContext().mPackageInfo; + return apk.getAppFactory(); +} +``` + +### instantiateApplication() + +```java +//frameworks/base/core/java/android/app/AppComponentFactory.java +//创建Application +public @NonNull Application instantiateApplication(@NonNull ClassLoader cl, + @NonNull String className) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Application) cl.loadClass(className).newInstance(); +} +``` + +### attach() + +```java +//Application.java +final void attach(Context context) { + attachBaseContext(context); + mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; +} +``` + +### attachBaseContext() + +```java +//ContextWrapper.java +protected void attachBaseContext(Context base) { + if (mBase != null) { + throw new IllegalStateException("Base context already set"); + } + mBase = base; +} +``` + +这个base一路传递过来指的是ContextImpl,它是Context的实现类,将ContextImpl赋值给ContextWrapper的Context类型的成员变量mBase,这样在ContextWrapper中就可以使用Context的方法,而Application继承自ContextWrapper,同样可以使用Context的方法。Application的attach方法的作用就是使Application可以使用Context的方法,这样Application才可以用来代表Application Context。 + +## 获取ApplicationContext + +```java +//frameworks/base/core/java/android/content/ContextWrapper.java +@Override +public Context getApplicationContext() { + //调用ContextImpl的getApplicationContext + return mBase.getApplicationContext(); +} +``` + +```java +//frameworks/base/core/java/android/app/ContextImpl.java +@Override +public Context getApplicationContext() { + //调用LoadedApk的getApplication()方法 + return (mPackageInfo != null) ? + mPackageInfo.getApplication() : mMainThread.getApplication(); +} +``` + +```java +//LoadedApk.java +Application getApplication() { + return mApplication; +} +``` + +## 创建ActivityContext + +```java +//frameworks/base/core/java/android/app/ActivityThread.java +private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { + //... + //创建ContextImpl + ContextImpl appContext = createBaseContextForActivity(r); + Activity activity = null; + try { + java.lang.ClassLoader cl = appContext.getClassLoader(); + activity = mInstrumentation.newActivity( + cl, component.getClassName(), r.intent); + StrictMode.incrementExpectedActivityCount(activity.getClass()); + r.intent.setExtrasClassLoader(cl); + r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo), + appContext.getAttributionSource()); + if (r.state != null) { + r.state.setClassLoader(cl); + } + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to instantiate activity " + component + + ": " + e.toString(), e); + } + } + //... + try { + //调用activity的attach方法 + 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, window, r.configCallback, + r.assistToken, r.shareableActivityToken); + } + //... +} +``` + +### createBaseContextForActivity() + +```java +//frameworks/base/core/java/android/app/ActivityThread.java +private ContextImpl createBaseContextForActivity(ActivityClientRecord r) { + final int displayId; + try { + displayId = ActivityTaskManager.getService().getActivityDisplayId(r.token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + //调用createActivityContext + ContextImpl appContext = ContextImpl.createActivityContext( + this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); + final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); + // For debugging purposes, if the activity's package name contains the value of + // the "debug.use-second-display" system property as a substring, then show + // its content on a secondary display if there is one. + String pkgName = SystemProperties.get("debug.second-display.pkg"); + if (pkgName != null && !pkgName.isEmpty() + && r.packageInfo.mPackageName.contains(pkgName)) { + for (int id : dm.getDisplayIds()) { + if (id != Display.DEFAULT_DISPLAY) { + Display display = + dm.getCompatibleDisplay(id, appContext.getResources()); + appContext = (ContextImpl) appContext.createDisplayContext(display); + break; + } + } + } + return appContext; +} +``` + +```java +//frameworks/base/core/java/android/app/ContextImpl.java +static ContextImpl createActivityContext(ActivityThread mainThread, + LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, + Configuration overrideConfiguration) { + + //... + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, ContextParams.EMPTY, + attributionTag, null, activityInfo.splitName, activityToken, null, 0, classLoader, + null); + context.mContextType = CONTEXT_TYPE_ACTIVITY; + //... + return context; +} +``` + + + +### 参考 + +* [Android 开发者,你真的懂 Context 吗?](https://juejin.im/post/6844904179060703246) +* [对于 Context,你了解多少?](https://github.com/Moosphan/Android-Daily-Interview/issues/14) +* [Android 复习笔记 —— 扒一扒 Context](https://juejin.cn/post/6864346705081401352) +* Android进阶解密 + diff --git a/aosp/context/README.md b/aosp/context/README.md new file mode 100644 index 00000000..8bc900bf --- /dev/null +++ b/aosp/context/README.md @@ -0,0 +1,2 @@ +# 理解上下文Context + diff --git a/aosp/context/applicationcontext-fen-xi.md b/aosp/context/applicationcontext-fen-xi.md new file mode 100644 index 00000000..f947eb8c --- /dev/null +++ b/aosp/context/applicationcontext-fen-xi.md @@ -0,0 +1,18 @@ +# + +## 创建 + +```mermaid +sequenceDiagram + ActivityThread->>ActivityThread:performLaunchActivity() + ActivityThread->>LoadedApk:makeApplication() + LoadedApk->>ContextImpl:createAppContext() + LoadedApk->>Instrumentation:newApplication() + Instrumentation->>Instrumentation:getFactory() + Instrumentation->>AppComponentFactory:instantiateApplication() + Instrumentation->>Application:attach() + +``` + + + diff --git a/aosp/framework-add-and-get-service.md b/aosp/framework-add-and-get-service.md new file mode 100644 index 00000000..f5bd1eba --- /dev/null +++ b/aosp/framework-add-and-get-service.md @@ -0,0 +1,577 @@ +--- +title: 添加和获取服务 +tags: + - 源码分析 +--- + + +## ServiceManager + +### addService + +```java +public static void addService(String name, IBinder service) { + try { + getIServiceManager().addService(name, service, false);//添加服务 + } catch (RemoteException e) { + Log.e(TAG, "error in addService", e); + } +} +``` + +## ServiceManagerProxy + +### addService + +```java +//frameworks/base/core/java/android/os/ServiceManagerNative.java +public void addService(String name, IBinder service, boolean allowIsolated) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IServiceManager.descriptor); + data.writeString(name); + data.writeStrongBinder(service);//写入一个StrongBinder + data.writeInt(allowIsolated ? 1 : 0); + mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0); + reply.recycle(); + data.recycle(); +} +``` + +```java +public IBinder getService(String name) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IServiceManager.descriptor); + data.writeString(name); + mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0); + IBinder binder = reply.readStrongBinder(); + reply.recycle(); + data.recycle(); + return binder; +} +``` + + + +## BinderProxy + +### transact + +```java +//frameworks/base/core/java/android/os/Binder.java +public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + Binder.checkParcel(this, code, data, "Unreasonably large binder buffer"); + if (Binder.isTracingEnabled()) { Binder.getTransactionTracker().addTrace(); } + return transactNative(code, data, reply, flags); //调用native方法 +} +``` + +## android_util_Binder + + + +### transact + +```java +//frameworks/base/core/jni/android_util_Binder.cpp +static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, + jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException +{ + if (dataObj == NULL) { + jniThrowNullPointerException(env, NULL); + return JNI_FALSE; + } + + Parcel* data = parcelForJavaObject(env, dataObj);//转换成native中的Parcel实现 + if (data == NULL) { + return JNI_FALSE; + } + Parcel* reply = parcelForJavaObject(env, replyObj); + if (reply == NULL && replyObj != NULL) { + return JNI_FALSE; + } + + IBinder* target = (IBinder*) + env->GetLongField(obj, gBinderProxyOffsets.mObject); + if (target == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!"); + return JNI_FALSE; + } + + ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n", + target, obj, code); + + + bool time_binder_calls; + int64_t start_millis; + if (kEnableBinderSample) { + // Only log the binder call duration for things on the Java-level main thread. + // But if we don't + time_binder_calls = should_time_binder_calls(); + + if (time_binder_calls) { + start_millis = uptimeMillis(); + } + } + + //printf("Transact from Java code to %p sending: ", target); data->print(); + status_t err = target->transact(code, *data, reply, flags); //调用BpBinder的transact方法 + //if (reply) printf("Transact from Java code to %p received: ", target); reply->print(); + + if (kEnableBinderSample) { + if (time_binder_calls) { + conditionally_log_binder_call(start_millis, target, code); + } + } + + if (err == NO_ERROR) { + return JNI_TRUE; + } else if (err == UNKNOWN_TRANSACTION) { + return JNI_FALSE; + } + + signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize()); + return JNI_FALSE; +} +``` + + + +## BpBinder + +```cpp +//frameworks/native/libs/binder/BpBinder.cpp +status_t BpBinder::transact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + // Once a binder has died, it will never come back to life. + if (mAlive) { + //调用IPCThreadState的transact方法 + status_t status = IPCThreadState::self()->transact( + mHandle, code, data, reply, flags); + if (status == DEAD_OBJECT) mAlive = 0; + return status; + } + + return DEAD_OBJECT; +} +``` + +## IPCThreadState + +每个线程都有一个`IPCThreadState`,每个`IPCThreadState`中都有一个mIn、一个mOut。成员变量mProcess保存了ProcessState变量(每个进程只有一个)。 + +- mIn 用来接收来自Binder设备的数据,默认大小为256字节; +- mOut用来存储发往Binder设备的数据,默认大小为256字节。 + +```cpp +IPCThreadState::IPCThreadState() + : mProcess(ProcessState::self()), + mMyThreadId(gettid()), + mStrictModePolicy(0), + mLastTransactionBinderFlags(0) +{ + pthread_setspecific(gTLS, this); + clearCaller(); + mIn.setDataCapacity(256);//设置容量 + mOut.setDataCapacity(256); +} +``` + + + +### transact + +```cpp +//frameworks/native/libs/binder/IPCThreadState.cpp +status_t IPCThreadState::transact(int32_t handle, + uint32_t code, const Parcel& data, + Parcel* reply, uint32_t flags) +{ + status_t err = data.errorCheck(); + + flags |= TF_ACCEPT_FDS; + + IF_LOG_TRANSACTIONS() { + TextOutput::Bundle _b(alog); + alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand " + << handle << " / code " << TypeCode(code) << ": " + << indent << data << dedent << endl; + } + + if (err == NO_ERROR) { + LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(), + (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY"); + //传输数据 + err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); + } + + if (err != NO_ERROR) { + if (reply) reply->setError(err); + return (mLastError = err); + } + + if ((flags & TF_ONE_WAY) == 0) { + #if 0 + if (code == 4) { // relayout + ALOGI(">>>>>> CALLING transaction 4"); + } else { + ALOGI(">>>>>> CALLING transaction %d", code); + } + #endif + if (reply) { + err = waitForResponse(reply); + } else { + Parcel fakeReply; + err = waitForResponse(&fakeReply); + } + #if 0 + if (code == 4) { // relayout + ALOGI("<<<<<< RETURNING transaction 4"); + } else { + ALOGI("<<<<<< RETURNING transaction %d", code); + } + #endif + + IF_LOG_TRANSACTIONS() { + TextOutput::Bundle _b(alog); + alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand " + << handle << ": "; + if (reply) alog << indent << *reply << dedent << endl; + else alog << "(none requested)" << endl; + } + } else { + err = waitForResponse(NULL, NULL); + } + + return err; +} +``` + +### writeTransactionData + +```cpp +status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, + int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer) +{ + binder_transaction_data tr; //存储一次事务的数据 + + tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */ + tr.target.handle = handle; + tr.code = code; + tr.flags = binderFlags; + tr.cookie = 0; + tr.sender_pid = 0; + tr.sender_euid = 0; + + const status_t err = data.errorCheck(); + if (err == NO_ERROR) { + tr.data_size = data.ipcDataSize(); + tr.data.ptr.buffer = data.ipcData(); + tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t); + tr.data.ptr.offsets = data.ipcObjects(); + } else if (statusBuffer) { + tr.flags |= TF_STATUS_CODE; + *statusBuffer = err; + tr.data_size = sizeof(status_t); + tr.data.ptr.buffer = reinterpret_cast(statusBuffer); + tr.offsets_size = 0; + tr.data.ptr.offsets = 0; + } else { + return (mLastError = err); + } + + mOut.writeInt32(cmd); + mOut.write(&tr, sizeof(tr));//写入到mOut + + return NO_ERROR; +} +``` + +### waitForResponse + +```cpp +status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) +{ + uint32_t cmd; + int32_t err; + + while (1) { + if ((err=talkWithDriver()) < NO_ERROR) break; + err = mIn.errorCheck(); + if (err < NO_ERROR) break; + if (mIn.dataAvail() == 0) continue; + + cmd = (uint32_t)mIn.readInt32(); + + IF_LOG_COMMANDS() { + alog << "Processing waitForResponse Command: " + << getReturnString(cmd) << endl; + } + + switch (cmd) { + case BR_TRANSACTION_COMPLETE: + if (!reply && !acquireResult) goto finish; + break; + + case BR_DEAD_REPLY: + err = DEAD_OBJECT; + goto finish; + + case BR_FAILED_REPLY: + err = FAILED_TRANSACTION; + goto finish; + + case BR_ACQUIRE_RESULT: + { + ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT"); + const int32_t result = mIn.readInt32(); + if (!acquireResult) continue; + *acquireResult = result ? NO_ERROR : INVALID_OPERATION; + } + goto finish; + + case BR_REPLY: + { + binder_transaction_data tr; + err = mIn.read(&tr, sizeof(tr)); + ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY"); + if (err != NO_ERROR) goto finish; + + if (reply) { + if ((tr.flags & TF_STATUS_CODE) == 0) { + reply->ipcSetDataReference( + reinterpret_cast(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), + freeBuffer, this); + } else { + err = *reinterpret_cast(tr.data.ptr.buffer); + freeBuffer(NULL, + reinterpret_cast(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), this); + } + } else { + freeBuffer(NULL, + reinterpret_cast(tr.data.ptr.buffer), + tr.data_size, + reinterpret_cast(tr.data.ptr.offsets), + tr.offsets_size/sizeof(binder_size_t), this); + continue; + } + } + goto finish; + + default: + err = executeCommand(cmd); + if (err != NO_ERROR) goto finish; + break; + } + } + +finish: + if (err != NO_ERROR) { + if (acquireResult) *acquireResult = err; + if (reply) reply->setError(err); + mLastError = err; + } + + return err; +} +``` +### talkWithDriver + +talkWithDriver通过ioctl BINDER_WRITE_READ来与驱动通信。 + +```cpp +status_t IPCThreadState::talkWithDriver(bool doReceive) +{ + if (mProcess->mDriverFD <= 0) { + return -EBADF; + } + + binder_write_read bwr;//存储一次读写操作的数据 + + // Is the read buffer empty? + const bool needRead = mIn.dataPosition() >= mIn.dataSize(); + + // We don't want to write anything if we are still reading + // from data left in the input buffer and the caller + // has requested to read the next data. + const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0; + + bwr.write_size = outAvail; + bwr.write_buffer = (uintptr_t)mOut.data(); + + // This is what we'll read. + if (doReceive && needRead) { + bwr.read_size = mIn.dataCapacity(); + bwr.read_buffer = (uintptr_t)mIn.data(); + } else { + bwr.read_size = 0; + bwr.read_buffer = 0; + } + + IF_LOG_COMMANDS() { + TextOutput::Bundle _b(alog); + if (outAvail != 0) { + alog << "Sending commands to driver: " << indent; + const void* cmds = (const void*)bwr.write_buffer; + const void* end = ((const uint8_t*)cmds)+bwr.write_size; + alog << HexDump(cmds, bwr.write_size) << endl; + while (cmds < end) cmds = printCommand(alog, cmds); + alog << dedent; + } + alog << "Size of receive buffer: " << bwr.read_size + << ", needRead: " << needRead << ", doReceive: " << doReceive << endl; + } + + // Return immediately if there is nothing to do. + if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR; + + bwr.write_consumed = 0; + bwr.read_consumed = 0; + status_t err; + do { + IF_LOG_COMMANDS() { + alog << "About to read/write, write size = " << mOut.dataSize() << endl; + } +#if defined(__ANDROID__) + //通过ioctl不停的读写操作,跟Binder Driver进行通信 + if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) + err = NO_ERROR; + else + err = -errno; +#else + err = INVALID_OPERATION; +#endif + if (mProcess->mDriverFD <= 0) { + err = -EBADF; + } + IF_LOG_COMMANDS() { + alog << "Finished read/write, write size = " << mOut.dataSize() << endl; + } + } while (err == -EINTR); + + IF_LOG_COMMANDS() { + alog << "Our err: " << (void*)(intptr_t)err << ", write consumed: " + << bwr.write_consumed << " (of " << mOut.dataSize() + << "), read consumed: " << bwr.read_consumed << endl; + } + + if (err >= NO_ERROR) { + if (bwr.write_consumed > 0) { + if (bwr.write_consumed < mOut.dataSize()) + mOut.remove(0, bwr.write_consumed); + else + mOut.setDataSize(0); + } + if (bwr.read_consumed > 0) { + mIn.setDataSize(bwr.read_consumed); + mIn.setDataPosition(0); + } + IF_LOG_COMMANDS() { + TextOutput::Bundle _b(alog); + alog << "Remaining data size: " << mOut.dataSize() << endl; + alog << "Received commands from driver: " << indent; + const void* cmds = mIn.data(); + const void* end = mIn.data() + mIn.dataSize(); + alog << HexDump(cmds, mIn.dataSize()) << endl; + while (cmds < end) cmds = printReturnCommand(alog, cmds); + alog << dedent; + } + return NO_ERROR; + } + + return err; +} +``` + +## 驱动层 + +```c +//https://github.com/torvalds/linux/blob/master/drivers/android/binder.c +static int binder_ioctl_write_read(struct file *filp, + unsigned int cmd, unsigned long arg, + struct binder_thread *thread) +{ + int ret = 0; + struct binder_proc *proc = filp->private_data; + unsigned int size = _IOC_SIZE(cmd); + void __user *ubuf = (void __user *)arg; + struct binder_write_read bwr; + + if (size != sizeof(struct binder_write_read)) { + ret = -EINVAL; + goto out; + } + //将用户空间的数据copy到内核空间 + if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { + ret = -EFAULT; + goto out; + } + binder_debug(BINDER_DEBUG_READ_WRITE, + "%d:%d write %lld at %016llx, read %lld at %016llx\n", + proc->pid, thread->pid, + (u64)bwr.write_size, (u64)bwr.write_buffer, + (u64)bwr.read_size, (u64)bwr.read_buffer); + + if (bwr.write_size > 0) { + //将数据放入目标进程 + ret = binder_thread_write(proc, thread, + bwr.write_buffer, + bwr.write_size, + &bwr.write_consumed); + trace_binder_write_done(ret); + if (ret < 0) { + bwr.read_consumed = 0; + if (copy_to_user(ubuf, &bwr, sizeof(bwr))) + ret = -EFAULT; + goto out; + } + } + if (bwr.read_size > 0) { + ret = binder_thread_read(proc, thread, bwr.read_buffer, + bwr.read_size, + &bwr.read_consumed, + filp->f_flags & O_NONBLOCK); + trace_binder_read_done(ret); + binder_inner_proc_lock(proc); + if (!binder_worklist_empty_ilocked(&proc->todo)) + binder_wakeup_proc_ilocked(proc); + binder_inner_proc_unlock(proc); + if (ret < 0) { + if (copy_to_user(ubuf, &bwr, sizeof(bwr))) + ret = -EFAULT; + goto out; + } + } + binder_debug(BINDER_DEBUG_READ_WRITE, + "%d:%d wrote %lld of %lld, read return %lld of %lld\n", + proc->pid, thread->pid, + (u64)bwr.write_consumed, (u64)bwr.write_size, + (u64)bwr.read_consumed, (u64)bwr.read_size); + if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { + ret = -EFAULT; + goto out; + } +out: + return ret; +} +``` + +## 流程图 + + + +## 参考 + +* [写给 Android 应用工程师的 Binder 原理剖析](https://zhuanlan.zhihu.com/p/35519585) + + + diff --git a/aosp/framework-ams.md b/aosp/framework-ams.md new file mode 100644 index 00000000..46a4aae5 --- /dev/null +++ b/aosp/framework-ams.md @@ -0,0 +1,130 @@ +--- +title: AMS分析 +tags: + - 源码分析 +--- + + + +```java +public void setSystemProcess() { + try { + ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true, + DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); + ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats); + ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false, + DUMP_FLAG_PRIORITY_HIGH); + ServiceManager.addService("gfxinfo", new GraphicsBinder(this)); + ServiceManager.addService("dbinfo", new DbBinder(this)); + if (MONITOR_CPU_USAGE) { + ServiceManager.addService("cpuinfo", new CpuBinder(this), + /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); + } + ServiceManager.addService("permission", new PermissionController(this)); + ServiceManager.addService("processinfo", new ProcessInfoService(this)); + ServiceManager.addService("cacheinfo", new CacheBinder(this)); + + ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( + "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY); + mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader()); + + synchronized (this) { + ProcessRecord app = mProcessList.newProcessRecordLocked(info, info.processName, + false, + 0, + new HostingRecord("system")); + app.setPersistent(true); + app.pid = MY_PID; + app.getWindowProcessController().setPid(MY_PID); + app.maxAdj = ProcessList.SYSTEM_ADJ; + app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); + addPidLocked(app); + mProcessList.updateLruProcessLocked(app, false, null); + updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException( + "Unable to find android system package", e); + } + + // Start watching app ops after we and the package manager are up and running. + mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null, + new IAppOpsCallback.Stub() { + @Override public void opChanged(int op, int uid, String packageName) { + if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) { + if (getAppOpsManager().checkOpNoThrow(op, uid, packageName) + != AppOpsManager.MODE_ALLOWED) { + runInBackgroundDisabled(uid); + } + } + } + }); + + final int[] cameraOp = {AppOpsManager.OP_CAMERA}; + mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() { + @Override + public void opActiveChanged(int op, int uid, String packageName, boolean active) { + cameraActiveChanged(uid, active); + } + }); +} +``` + + + +## ActivityManagerNative + + + + + +```java +//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java +``` + + + +```java +//frameworks/base/core/java/android/app/ActivityManagerNative.java +static public IActivityManager getDefault() { + return gDefault.get(); +} +``` + +```java +private static final Singleton gDefault = new Singleton() { + protected IActivityManager create() { + IBinder b = ServiceManager.getService("activity"); + if (false) { + Log.v("ActivityManager", "default service binder = " + b); + } + IActivityManager am = asInterface(b); + if (false) { + Log.v("ActivityManager", "default service = " + am); + } + return am; + } + }; +} +``` + +```java +static public IActivityManager asInterface(IBinder obj) { + if (obj == null) { + return null; + } + IActivityManager in = + (IActivityManager)obj.queryLocalInterface(descriptor); + if (in != null) { + return in; + } + //创建ActivityManagerProxy + return new ActivityManagerProxy(obj); +} + +``` + + + + + diff --git a/aosp/framework-bind-service.md b/aosp/framework-bind-service.md new file mode 100644 index 00000000..69918a0a --- /dev/null +++ b/aosp/framework-bind-service.md @@ -0,0 +1,1347 @@ +--- +title: bindService流程分析 +tags: + - 源码分析 +--- + + +## ContextWrapper + +### bindService +```java +//frameworks/base/core/java/android/content/ContextWrapper.java +@Override +public boolean bindService(Intent service, ServiceConnection conn,int flags) { + return mBase.bindService(service, conn, flags); +} +``` + +## ContextImpl + +### bindService + +```java +//frameworks/base/core/java/android/app/ContextImpl.java +@Override +public boolean bindService(Intent service, ServiceConnection conn,int flags) { + warnIfCallingFromSystemProcess(); + return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), + Process.myUserHandle()); +} + +``` + +### bindServiceCommon + +```java +final LoadedApk mPackageInfo; +private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler + handler, UserHandle user) { + IServiceConnection sd; + if (conn == null) { + throw new IllegalArgumentException("connection is null"); + } + if (mPackageInfo != null) { + //获取的是内部静态类InnerConnection + sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); + } else { + throw new RuntimeException("Not supported in system context"); + } + validateServiceIntent(service); + try { + IBinder token = getActivityToken(); + if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null + && mPackageInfo.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + flags |= BIND_WAIVE_PRIORITY; + } + service.prepareToLeaveProcess(this); + //调用AMP bindService + int res = ActivityManagerNative.getDefault().bindService( + mMainThread.getApplicationThread(), getActivityToken(), service, + service.resolveTypeIfNeeded(getContentResolver()), + sd, flags, getOpPackageName(), user.getIdentifier()); + if (res < 0) { + throw new SecurityException( + "Not allowed to bind to service " + service); + } + return res != 0; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } +} +``` + +## LoadedApk + +### getServiceDispatcher + +```java +//frameworks/base/core/java/android/app/LoadedApk.java +private final ArrayMap> mServices + = new ArrayMap>(); +public final IServiceConnection getServiceDispatcher(ServiceConnection c, + Context context, Handler handler, int flags) { + synchronized (mServices) { + LoadedApk.ServiceDispatcher sd = null; + ArrayMap map = mServices.get(context); + if (map != null) { + sd = map.get(c); + } + if (sd == null) { + sd = new ServiceDispatcher(c, context, handler, flags);//创建ServiceDispatcher + if (map == null) { + map = new ArrayMap(); + mServices.put(context, map); + } + map.put(c, sd); + } else { + sd.validate(context, handler); + } + //调用ServiceDispatcher的getIServiceConnection获取IServiceConnection + return sd.getIServiceConnection(); + } +} +``` + +## ServiceDispatcher + +```java +//frameworks/base/core/java/android/app/LoadedApk.java +ServiceDispatcher(ServiceConnection conn, + Context context, Handler activityThread, int flags) { + mIServiceConnection = new InnerConnection(this); //创建 + mConnection = conn; //传递进来的 + mContext = context; + mActivityThread = activityThread; + mLocation = new ServiceConnectionLeaked(null); + mLocation.fillInStackTrace(); + mFlags = flags; +} +``` + +### getIServiceConnection + +```java +IServiceConnection getIServiceConnection() { + return mIServiceConnection; +} +``` + +### InnerConnection + +```java +private static class InnerConnection extends IServiceConnection.Stub { + final WeakReference mDispatcher; + + InnerConnection(LoadedApk.ServiceDispatcher sd) { + mDispatcher = new WeakReference(sd); + } + + public void connected(ComponentName name, IBinder service) throws RemoteException { + LoadedApk.ServiceDispatcher sd = mDispatcher.get(); + if (sd != null) { + sd.connected(name, service); //调用传入的ServiceDispatcher的connected方法 + } + } +} +``` + +### connected + +```java +public void connected(ComponentName name, IBinder service) { + if (mActivityThread != null) { + mActivityThread.post(new RunConnection(name, service, 0)); + } else { + doConnected(name, service); + } +} +``` + +### RunConnection + +```java +private final class RunConnection implements Runnable { + RunConnection(ComponentName name, IBinder service, int command) { + mName = name; + mService = service; + mCommand = command; + } + + public void run() { + if (mCommand == 0) { + doConnected(mName, mService); + } else if (mCommand == 1) { + doDeath(mName, mService); + } + } + + final ComponentName mName; + final IBinder mService; + final int mCommand; +} +``` + +### doConnected + +```java +public void doConnected(ComponentName name, IBinder service) { + ServiceDispatcher.ConnectionInfo old; + ServiceDispatcher.ConnectionInfo info; + + synchronized (this) { + if (mForgotten) { + // We unbound before receiving the connection; ignore + // any connection received. + return; + } + old = mActiveConnections.get(name); + if (old != null && old.binder == service) { + // Huh, already have this one. Oh well! + return; + } + + if (service != null) { + // A new service is being connected... set it all up. + info = new ConnectionInfo(); + info.binder = service; + info.deathMonitor = new DeathMonitor(name, service); + try { + service.linkToDeath(info.deathMonitor, 0); + mActiveConnections.put(name, info); + } catch (RemoteException e) { + // This service was dead before we got it... just + // don't do anything with it. + mActiveConnections.remove(name); + return; + } + + } else { + // The named service is being disconnected... clean up. + mActiveConnections.remove(name); + } + + if (old != null) { + old.binder.unlinkToDeath(old.deathMonitor, 0); + } + } + + // If there was an old service, it is now disconnected. + if (old != null) { + mConnection.onServiceDisconnected(name); + } + // If there is a new service, it is now connected. + if (service != null) { + mConnection.onServiceConnected(name, service); + } +} +``` + +## ActivityManagerProxy + +```java +//frameworks/base/core/java/android/app/ActivityManagerNative.java +//AMP +public int bindService(IApplicationThread caller, IBinder token, + Intent service, String resolvedType, IServiceConnection connection, + int flags, String callingPackage, int userId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeStrongBinder(token); + service.writeToParcel(data, 0); + data.writeString(resolvedType); + //将InnerConnection对象传递system_server + data.writeStrongBinder(connection.asBinder()); + data.writeInt(flags); + data.writeString(callingPackage); + data.writeInt(userId); + //经过Binder IPC进入system_server进程 + mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; +} +``` + +## ActivityManagerNative + +```java + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case BIND_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + IBinder token = data.readStrongBinder(); + Intent service = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + b = data.readStrongBinder(); + int fl = data.readInt(); + String callingPackage = data.readString(); + int userId = data.readInt(); + IServiceConnection conn = IServiceConnection.Stub.asInterface(b); + int res = bindService(app, token, service, resolvedType, conn, fl, + callingPackage, userId); //调用ams的bindService + reply.writeNoException(); + reply.writeInt(res); + return true; + } + } +} +``` + +## ActivityManagerService + +### bindService + +```java +//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java +final ActiveServices mServices; +public int bindService(IApplicationThread caller, IBinder token, Intent service, + String resolvedType, IServiceConnection connection, int flags, String callingPackage, + int userId) throws TransactionTooLargeException { + enforceNotIsolatedCaller("bindService"); + + // Refuse possible leaked file descriptors + if (service != null && service.hasFileDescriptors() == true) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + if (callingPackage == null) { + throw new IllegalArgumentException("callingPackage cannot be null"); + } + + synchronized(this) { + return mServices.bindServiceLocked(caller, token, service, + resolvedType, connection, flags, callingPackage, userId); + } +} +``` + +### publishService + +```java +public void publishService(IBinder token, Intent intent, IBinder service) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors() == true) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + synchronized(this) { + if (!(token instanceof ServiceRecord)) { + throw new IllegalArgumentException("Invalid service token"); + } + //调用publishServiceLocked + mServices.publishServiceLocked((ServiceRecord)token, intent, service); + } +} +``` + + + +## ActiveServices + +### bindServiceLocked + +```java +//frameworks/base/services/core/java/com/android/server/am/ActiveServices.java +int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, + String resolvedType, final IServiceConnection connection, int flags, + String callingPackage, final int userId) throws TransactionTooLargeException { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + + " type=" + resolvedType + " conn=" + connection.asBinder() + + " flags=0x" + Integer.toHexString(flags)); + final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); + if (callerApp == null) { + throw new SecurityException( + "Unable to find app for caller " + caller + + " (pid=" + Binder.getCallingPid() + + ") when binding service " + service); + } + + ActivityRecord activity = null; + if (token != null) { + activity = ActivityRecord.isInStackLocked(token); + if (activity == null) { + Slog.w(TAG, "Binding with unknown activity: " + token); + return 0; + } + } + + int clientLabel = 0; + PendingIntent clientIntent = null; + final boolean isCallerSystem = callerApp.info.uid == Process.SYSTEM_UID; + + if (isCallerSystem) { + // Hacky kind of thing -- allow system stuff to tell us + // what they are, so we can report this elsewhere for + // others to know why certain services are running. + service.setDefusable(true); + clientIntent = service.getParcelableExtra(Intent.EXTRA_CLIENT_INTENT); + if (clientIntent != null) { + clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0); + if (clientLabel != 0) { + // There are no useful extras in the intent, trash them. + // System code calling with this stuff just needs to know + // this will happen. + service = service.cloneFilter(); + } + } + } + + if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { + mAm.enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "BIND_TREAT_LIKE_ACTIVITY"); + } + + if ((flags & Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0 && !isCallerSystem) { + throw new SecurityException( + "Non-system caller " + caller + " (pid=" + Binder.getCallingPid() + + ") set BIND_ALLOW_WHITELIST_MANAGEMENT when binding service " + service); + } + + final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND; + final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0; + //检索服务 + ServiceLookupResult res = + retrieveServiceLocked(service, resolvedType, callingPackage, Binder.getCallingPid(), + Binder.getCallingUid(), userId, true, callerFg, isBindExternal); + if (res == null) { + return 0; + } + if (res.record == null) { + return -1; + } + ServiceRecord s = res.record; + + boolean permissionsReviewRequired = false; + + // If permissions need a review before any of the app components can run, + // we schedule binding to the service but do not start its process, then + // we launch a review activity to which is passed a callback to invoke + // when done to start the bound service's process to completing the binding. + if (Build.PERMISSIONS_REVIEW_REQUIRED) { + if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired( + s.packageName, s.userId)) { + + permissionsReviewRequired = true; + + // Show a permission review UI only for binding from a foreground app + if (!callerFg) { + Slog.w(TAG, "u" + s.userId + " Binding to a service in package" + + s.packageName + " requires a permissions review"); + return 0; + } + + final ServiceRecord serviceRecord = s; + final Intent serviceIntent = service; + + RemoteCallback callback = new RemoteCallback( + new RemoteCallback.OnResultListener() { + @Override + public void onResult(Bundle result) { + synchronized(mAm) { + final long identity = Binder.clearCallingIdentity(); + try { + if (!mPendingServices.contains(serviceRecord)) { + return; + } + // If there is still a pending record, then the service + // binding request is still valid, so hook them up. We + // proceed only if the caller cleared the review requirement + // otherwise we unbind because the user didn't approve. + if (!mAm.getPackageManagerInternalLocked() + .isPermissionsReviewRequired( + serviceRecord.packageName, + serviceRecord.userId)) { + try { //拉起目标服务 + bringUpServiceLocked(serviceRecord, + serviceIntent.getFlags(), + callerFg, false, false); + } catch (RemoteException e) { + /* ignore - local call */ + } + } else { + unbindServiceLocked(connection); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + }); + + final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName); + intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback); + + if (DEBUG_PERMISSIONS_REVIEW) { + Slog.i(TAG, "u" + s.userId + " Launching permission review for package " + + s.packageName); + } + + mAm.mHandler.post(new Runnable() { + @Override + public void run() { + mAm.mContext.startActivityAsUser(intent, new UserHandle(userId)); + } + }); + } + } + + final long origId = Binder.clearCallingIdentity(); + + try { + if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "BIND SERVICE WHILE RESTART PENDING: " + + s); + } + + if ((flags&Context.BIND_AUTO_CREATE) != 0) { + s.lastActivity = SystemClock.uptimeMillis(); + if (!s.hasAutoCreateConnections()) { + // This is the first binding, let the tracker know. + ServiceState stracker = s.getTracker(); + if (stracker != null) { + stracker.setBound(true, mAm.mProcessStats.getMemFactorLocked(), + s.lastActivity); + } + } + } + + mAm.startAssociationLocked(callerApp.uid, callerApp.processName, callerApp.curProcState, + s.appInfo.uid, s.name, s.processName); + //获取AppBindRecord + AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); + ConnectionRecord c = new ConnectionRecord(b, activity, + connection, flags, clientLabel, clientIntent); + + IBinder binder = connection.asBinder(); + ArrayList clist = s.connections.get(binder); + if (clist == null) { + clist = new ArrayList(); + s.connections.put(binder, clist); + } + clist.add(c); + b.connections.add(c); + if (activity != null) { + if (activity.connections == null) { + activity.connections = new HashSet(); + } + activity.connections.add(c); + } + b.client.connections.add(c); + if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { + b.client.hasAboveClient = true; + } + if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { + s.whitelistManager = true; + } + if (s.app != null) { + updateServiceClientActivitiesLocked(s.app, c, true); + } + clist = mServiceConnections.get(binder); + if (clist == null) { + clist = new ArrayList(); + mServiceConnections.put(binder, clist); + } + clist.add(c); + + if ((flags&Context.BIND_AUTO_CREATE) != 0) { + s.lastActivity = SystemClock.uptimeMillis(); + if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, + permissionsReviewRequired) != null) { + return 0; + } + } + + if (s.app != null) { + if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { + s.app.treatLikeActivity = true; + } + if (s.whitelistManager) { + s.app.whitelistManager = true; + } + // This could have made the service more important. + mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities + || s.app.treatLikeActivity, b.client); + mAm.updateOomAdjLocked(s.app); + } + + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b + + ": received=" + b.intent.received + + " apps=" + b.intent.apps.size() + + " doRebind=" + b.intent.doRebind); + + if (s.app != null && b.intent.received) { + // Service is already running, so we can immediately + // publish the connection. + try { //? binder哪里创建的??? + c.conn.connected(s.name, b.intent.binder); + } catch (Exception e) { + Slog.w(TAG, "Failure sending service " + s.shortName + + " to connection " + c.conn.asBinder() + + " (in " + c.binding.client.processName + ")", e); + } + + // If this is the first app connected back to this binding, + // and the service had previously asked to be told when + // rebound, then do so. + if (b.intent.apps.size() == 1 && b.intent.doRebind) { + requestServiceBindingLocked(s, b.intent, callerFg, true); + } + } else if (!b.intent.requested) { + requestServiceBindingLocked(s, b.intent, callerFg, false); + } + + getServiceMap(s.userId).ensureNotStartingBackground(s); + + } finally { + Binder.restoreCallingIdentity(origId); + } + + return 1; +} +``` + +### retrieveServiceLocked + +```java +private ServiceLookupResult retrieveServiceLocked(Intent service, + String resolvedType, String callingPackage, int callingPid, int callingUid, int userId, + boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal) { + ServiceRecord r = null; + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service + + " type=" + resolvedType + " callingUid=" + callingUid); + + userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false, + ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null); + + ServiceMap smap = getServiceMap(userId); + final ComponentName comp = service.getComponent(); + if (comp != null) { + r = smap.mServicesByName.get(comp); + } + if (r == null && !isBindExternal) { + Intent.FilterComparison filter = new Intent.FilterComparison(service); + r = smap.mServicesByIntent.get(filter); + } + if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0 + && !callingPackage.equals(r.packageName)) { + // If an external service is running within its own package, other packages + // should not bind to that instance. + r = null; + } + if (r == null) { + try { + // TODO: come back and remove this assumption to triage all services + ResolveInfo rInfo = AppGlobals.getPackageManager().resolveService(service, + resolvedType, ActivityManagerService.STOCK_PM_FLAGS + | PackageManager.MATCH_DEBUG_TRIAGED_MISSING, + userId); + ServiceInfo sInfo = + rInfo != null ? rInfo.serviceInfo : null; + if (sInfo == null) { + Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId + + ": not found"); + return null; + } + ComponentName name = new ComponentName( + sInfo.applicationInfo.packageName, sInfo.name); + if ((sInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0) { + if (isBindExternal) { + if (!sInfo.exported) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name + + " is not exported"); + } + if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name + + " is not an isolatedProcess"); + } + // Run the service under the calling package's application. + ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo( + callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId); + if (aInfo == null) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + + "could not resolve client package " + callingPackage); + } + sInfo = new ServiceInfo(sInfo); + sInfo.applicationInfo = new ApplicationInfo(sInfo.applicationInfo); + sInfo.applicationInfo.packageName = aInfo.packageName; + sInfo.applicationInfo.uid = aInfo.uid; + name = new ComponentName(aInfo.packageName, name.getClassName()); + service.setComponent(name); + } else { + throw new SecurityException("BIND_EXTERNAL_SERVICE required for " + + name); + } + } else if (isBindExternal) { + throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name + + " is not an externalService"); + } + if (userId > 0) { + if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, + sInfo.name, sInfo.flags) + && mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) { + userId = 0; + smap = getServiceMap(0); + } + sInfo = new ServiceInfo(sInfo); + sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId); + } + r = smap.mServicesByName.get(name); + if (r == null && createIfNeeded) { + Intent.FilterComparison filter + = new Intent.FilterComparison(service.cloneFilter()); + ServiceRestarter res = new ServiceRestarter(); + BatteryStatsImpl.Uid.Pkg.Serv ss = null; + BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + ss = stats.getServiceStatsLocked( + sInfo.applicationInfo.uid, sInfo.packageName, + sInfo.name); + } + r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res); + res.setService(r); + smap.mServicesByName.put(name, r); + smap.mServicesByIntent.put(filter, r); + + // Make sure this component isn't in the pending list. + for (int i=mPendingServices.size()-1; i>=0; i--) { + ServiceRecord pr = mPendingServices.get(i); + if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid + && pr.name.equals(name)) { + mPendingServices.remove(i); + } + } + } + } catch (RemoteException ex) { + // pm is in same process, this will never happen. + } + } + if (r != null) { + if (mAm.checkComponentPermission(r.permission, + callingPid, callingUid, r.appInfo.uid, r.exported) + != PackageManager.PERMISSION_GRANTED) { + if (!r.exported) { + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " that is not exported from uid " + r.appInfo.uid); + return new ServiceLookupResult(null, "not exported from uid " + + r.appInfo.uid); + } + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " requires " + r.permission); + return new ServiceLookupResult(null, r.permission); + } else if (r.permission != null && callingPackage != null) { + final int opCode = AppOpsManager.permissionToOpCode(r.permission); + if (opCode != AppOpsManager.OP_NONE && mAm.mAppOpsService.noteOperation( + opCode, callingUid, callingPackage) != AppOpsManager.MODE_ALLOWED) { + Slog.w(TAG, "Appop Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " requires appop " + AppOpsManager.opToName(opCode)); + return null; + } + } + + if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid, + resolvedType, r.appInfo)) { + return null; + } + return new ServiceLookupResult(r, null); + } + return null; +} +``` + +### bringUpServiceLocked + +```java +private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, + boolean whileRestarting, boolean permissionsReviewRequired) + throws TransactionTooLargeException { + //Slog.i(TAG, "Bring up service:"); + //r.dump(" "); + + if (r.app != null && r.app.thread != null) { + sendServiceArgsLocked(r, execInFg, false); + return null; + } + + if (!whileRestarting && r.restartDelay > 0) { + // If waiting for a restart, then do nothing. + return null; + } + + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent); + + // We are now bringing the service up, so no longer in the + // restarting state. + if (mRestartingServices.remove(r)) { + r.resetRestartCounter(); + clearRestartingIfNeededLocked(r); + } + + // Make sure this service is no longer considered delayed, we are starting it now. + if (r.delayed) { + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r); + getServiceMap(r.userId).mDelayedStartList.remove(r); + r.delayed = false; + } + + // Make sure that the user who owns this service is started. If not, + // we don't want to allow it to run. + if (!mAm.mUserController.hasStartedUserState(r.userId)) { + String msg = "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": user " + r.userId + " is stopped"; + Slog.w(TAG, msg); + bringDownServiceLocked(r); + return msg; + } + + // Service is now being launched, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.packageName, false, r.userId); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.packageName + ": " + e); + } + + final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; + final String procName = r.processName; + ProcessRecord app; + + if (!isolated) { + app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); + if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + + " app=" + app); + if (app != null && app.thread != null) { + try { + app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats); + realStartServiceLocked(r, app, execInFg); + return null; + } catch (TransactionTooLargeException e) { + throw e; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting service " + r.shortName, e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + } else { + // If this service runs in an isolated process, then each time + // we call startProcessLocked() we will get a new isolated + // process, starting another process if we are currently waiting + // for a previous process to come up. To deal with this, we store + // in the service any current isolated process it is running in or + // waiting to have come up. + app = r.isolatedProc; + } + + // Not running -- get it started, and enqueue this service record + // to be executed when the app comes up. + if (app == null && !permissionsReviewRequired) { + if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, + "service", r.name, false, isolated, false)) == null) { + String msg = "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": process is bad"; + Slog.w(TAG, msg); + bringDownServiceLocked(r); + return msg; + } + if (isolated) { + r.isolatedProc = app; + } + } + + if (!mPendingServices.contains(r)) { + mPendingServices.add(r); + } + + if (r.delayedStop) { + // Oh and hey we've already been asked to stop! + r.delayedStop = false; + if (r.startRequested) { + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "Applying delayed stop (in bring up): " + r); + stopServiceLocked(r); + } + } + + return null; +} +``` + +### realStartServiceLocked + +```java +private final void realStartServiceLocked(ServiceRecord r, + ProcessRecord app, boolean execInFg) throws RemoteException { + if (app.thread == null) { + throw new RemoteException(); + } + if (DEBUG_MU) + Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid + + ", ProcessRecord.uid = " + app.uid); + r.app = app; + r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); + + final boolean newService = app.services.add(r); + bumpServiceExecutingLocked(r, execInFg, "create"); + mAm.updateLruProcessLocked(app, false, null); + mAm.updateOomAdjLocked(); + + boolean created = false; + try { + if (LOG_SERVICE_START_STOP) { + String nameTerm; + int lastPeriod = r.shortName.lastIndexOf('.'); + nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName; + EventLogTags.writeAmCreateService( + r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid); + } + synchronized (r.stats.getBatteryStats()) { + r.stats.startLaunchedLocked(); + } + mAm.notifyPackageUse(r.serviceInfo.packageName, + PackageManager.NOTIFY_PACKAGE_USE_SERVICE); + app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); + //调用ApplicationThreadProxy的scheduleCreateService + app.thread.scheduleCreateService(r, r.serviceInfo, + mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), + app.repProcState); + r.postNotification(); + created = true; + } catch (DeadObjectException e) { + Slog.w(TAG, "Application dead when creating service " + r); + mAm.appDiedLocked(app); + throw e; + } finally { + if (!created) { + // Keep the executeNesting count accurate. + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); + + // Cleanup. + if (newService) { + app.services.remove(r); + r.app = null; + } + + // Retry. + if (!inDestroying) { + scheduleServiceRestartLocked(r, false); + } + } + } + + if (r.whitelistManager) { + app.whitelistManager = true; + } + // + requestServiceBindingsLocked(r, execInFg); + + updateServiceClientActivitiesLocked(app, null, true); + + // If the service is in the started state, and there are no + // pending arguments, then fake up one so its onStartCommand() will + // be called. + if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), + null, null)); + } + + sendServiceArgsLocked(r, execInFg, true); + + if (r.delayed) { + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r); + getServiceMap(r.userId).mDelayedStartList.remove(r); + r.delayed = false; + } + + if (r.delayedStop) { + // Oh and hey we've already been asked to stop! + r.delayedStop = false; + if (r.startRequested) { + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "Applying delayed stop (from start): " + r); + stopServiceLocked(r); + } + } +} +``` + +### requestServiceBindingsLocked + +```java +private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) + throws TransactionTooLargeException { + for (int i=r.bindings.size()-1; i>=0; i--) { + IntentBindRecord ibr = r.bindings.valueAt(i); + if (!requestServiceBindingLocked(r, ibr, execInFg, false)) { + break; + } + } +} +``` + +### requestServiceBindingLocked + +```java +private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, + boolean execInFg, boolean rebind) throws TransactionTooLargeException { + if (r.app == null || r.app.thread == null) { + // If service is not currently running, can't yet bind. + return false; + } + if ((!i.requested || rebind) && i.apps.size() > 0) { + try { + bumpServiceExecutingLocked(r, execInFg, "bind"); + r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); + //调用ApplicationThreadProxy的scheduleBindService + r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, + r.app.repProcState); + if (!rebind) { + i.requested = true; + } + i.hasBound = true; + i.doRebind = false; + } catch (TransactionTooLargeException e) { + // Keep the executeNesting count accurate. + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e); + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); + throw e; + } catch (RemoteException e) { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r); + // Keep the executeNesting count accurate. + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); + return false; + } + } + return true; +} +``` + +### publishServiceLocked + +```java +void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) { + final long origId = Binder.clearCallingIdentity(); + try { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r + + " " + intent + ": " + service); + if (r != null) { + Intent.FilterComparison filter + = new Intent.FilterComparison(intent); + IntentBindRecord b = r.bindings.get(filter); + if (b != null && !b.received) { + b.binder = service;//binder赋值 + b.requested = true; + b.received = true; + for (int conni=r.connections.size()-1; conni>=0; conni--) { + ArrayList clist = r.connections.valueAt(conni); + for (int i=0; i bindings + = new ArrayMap(); +public AppBindRecord retrieveAppBindingLocked(Intent intent,ProcessRecord app) { + Intent.FilterComparison filter = new Intent.FilterComparison(intent); + IntentBindRecord i = bindings.get(filter); + if (i == null) { + i = new IntentBindRecord(this, filter); //创建IntentBindRecord + bindings.put(filter, i); + } + AppBindRecord a = i.apps.get(app); + if (a != null) { + return a; + } + a = new AppBindRecord(this, i, app); //创建 + i.apps.put(app, a); + return a; +} +``` + + + +## ProcessRecord + +```java +final class ProcessRecord { + IApplicationThread thread; +} +``` + + + +## IntentBindRecord + +```java +//frameworks/base/services/core/java/com/android/server/am/IntentBindRecord.java +final ArrayMap apps + = new ArrayMap(); +IBinder binder; +IntentBindRecord(ServiceRecord _service, Intent.FilterComparison _intent) { + service = _service; + intent = _intent; +} +``` + + + +## IApplicationThread + +{% plantuml %} + +class Binder{ + +} + +interface IApplicationThread{ + +} + +abstract class ApplicationThreadNative{ + +} + +class ApplicationThread{ + +} + +IApplicationThread <|.. ApplicationThreadNative + +Binder<|-- ApplicationThreadNative + +ApplicationThreadNative <|-- ApplicationThread + +IApplicationThread <|.. ApplicationThreadProxy + +{% endplantuml %} + +## ApplicationThreadProxy + +### scheduleCreateService + +```java +public final void scheduleCreateService(IBinder token, ServiceInfo info, + CompatibilityInfo compatInfo, int processState) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + info.writeToParcel(data, 0); + compatInfo.writeToParcel(data, 0); + data.writeInt(processState); + try { + mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + } catch (TransactionTooLargeException e) { + Log.e("CREATE_SERVICE", "Binder failure starting service; service=" + info); + throw e; + } + data.recycle(); +} +``` + +### scheduleBindService + +```java +public final void scheduleBindService(IBinder token, Intent intent, boolean rebind, + int processState) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + intent.writeToParcel(data, 0); + data.writeInt(rebind ? 1 : 0); + data.writeInt(processState); + mRemote.transact(SCHEDULE_BIND_SERVICE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); +} +``` + +## ApplicationThread + +```java +//frameworks/base/core/java/android/app/ActivityThread.java +public final void scheduleCreateService(IBinder token, + ServiceInfo info, CompatibilityInfo compatInfo, int processState) { + updateProcessState(processState, false); + CreateServiceData s = new CreateServiceData(); + s.token = token; + s.info = info; + s.compatInfo = compatInfo; + sendMessage(H.CREATE_SERVICE, s); //最终会调用handleCreateService +} +``` + +```java +public final void scheduleBindService(IBinder token, Intent intent, + boolean rebind, int processState) { + updateProcessState(processState, false); + BindServiceData s = new BindServiceData(); + s.token = token; + s.intent = intent; + s.rebind = rebind; + + if (DEBUG_SERVICE) + Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid=" + + Binder.getCallingUid() + " pid=" + Binder.getCallingPid()); + sendMessage(H.BIND_SERVICE, s); +} +``` + + + + + +## ActivityThread + +```java +private void handleCreateService(CreateServiceData data) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + LoadedApk packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo, data.compatInfo); + Service service = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = (Service) cl.loadClass(data.info.name).newInstance(); + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to instantiate service " + data.info.name + + ": " + e.toString(), e); + } + } + + try { + if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); + + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); + context.setOuterContext(service); + //创建Application + Application app = packageInfo.makeApplication(false, mInstrumentation); + service.attach(context, this, data.info.name, data.token, app, + ActivityManagerNative.getDefault()); + service.onCreate();//调用onCreate方法 + mServices.put(data.token, service); + try { + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to create service " + data.info.name + + ": " + e.toString(), e); + } + } +} +``` + +### handleBindService + +```java +private void handleBindService(BindServiceData data) { + Service s = mServices.get(data.token); + if (DEBUG_SERVICE) + Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind); + if (s != null) { + try { + data.intent.setExtrasClassLoader(s.getClassLoader()); + data.intent.prepareToEnterProcess(); + try { + if (!data.rebind) { + IBinder binder = s.onBind(data.intent); //调用Service的onBind方法获取IBinder + //amp 的publishService方法 + ActivityManagerNative.getDefault().publishService( + data.token, data.intent, binder); + } else { + s.onRebind(data.intent); //调用onRebind + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); + } + ensureJitEnabled(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to bind to service " + s + + " with " + data.intent + ": " + e.toString(), e); + } + } + } +} +``` + + + + + + + diff --git a/aosp/framework-systemserver.md b/aosp/framework-systemserver.md new file mode 100644 index 00000000..8535eaf4 --- /dev/null +++ b/aosp/framework-systemserver.md @@ -0,0 +1,323 @@ +--- +title: SystemServer启动流程 +tags: + - 源码分析 +--- + +`SystemServer`进程主要用于创建系统服务,我们熟知的AMS、WMS和PMS都是由它来创建的。 + +一旦在`init.rc`中为`Zygote`制定了启动参数`--start-system-server`,那么`ZygoteInit`就会调用`startSystemServer`来启动`SystemServer`集成。 + + + +## run + +```java +//frameworks/base/services/java/com/android/server/SystemServer.java +private void run() { + try { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "InitBeforeStartServices"); + // If a device's clock is before 1970 (before 0), a lot of + // APIs crash dealing with negative numbers, notably + // java.io.File#setLastModified, so instead we fake it and + // hope that time from cell towers or NTP fixes it shortly. + if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) { + Slog.w(TAG, "System clock is before 1970; setting to 1970."); + SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME); + } + + // If the system has "persist.sys.language" and friends set, replace them with + // "persist.sys.locale". Note that the default locale at this point is calculated + // using the "-Duser.locale" command line flag. That flag is usually populated by + // AndroidRuntime using the same set of system properties, but only the system_server + // and system apps are allowed to set them. + // + // NOTE: Most changes made here will need an equivalent change to + // core/jni/AndroidRuntime.cpp + if (!SystemProperties.get("persist.sys.language").isEmpty()) { + final String languageTag = Locale.getDefault().toLanguageTag(); + + SystemProperties.set("persist.sys.locale", languageTag); + SystemProperties.set("persist.sys.language", ""); + SystemProperties.set("persist.sys.country", ""); + SystemProperties.set("persist.sys.localevar", ""); + } + + // Here we go! + Slog.i(TAG, "Entered the Android system server!"); + EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, SystemClock.uptimeMillis()); + + // In case the runtime switched since last boot (such as when + // the old runtime was removed in an OTA), set the system + // property so that it is in sync. We can't do this in + // libnativehelper's JniInvocation::Init code where we already + // had to fallback to a different runtime because it is + // running as root and we need to be the system user to set + // the property. http://b/11463182 + SystemProperties.set("persist.sys.dalvik.vm.lib.2", VMRuntime.getRuntime().vmLibrary()); + + // Enable the sampling profiler. + if (SamplingProfilerIntegration.isEnabled()) { + SamplingProfilerIntegration.start(); + mProfilerSnapshotTimer = new Timer(); + mProfilerSnapshotTimer.schedule(new TimerTask() { + @Override + public void run() { + SamplingProfilerIntegration.writeSnapshot("system_server", null); + } + }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL); + } + + // Mmmmmm... more memory! + VMRuntime.getRuntime().clearGrowthLimit(); + + // The system server has to run all of the time, so it needs to be + // as efficient as possible with its memory usage. + VMRuntime.getRuntime().setTargetHeapUtilization(0.8f); + + // Some devices rely on runtime fingerprint generation, so make sure + // we've defined it before booting further. + Build.ensureFingerprintProperty(); + + // Within the system server, it is an error to access Environment paths without + // explicitly specifying a user. + Environment.setUserRequired(true); + + // Within the system server, any incoming Bundles should be defused + // to avoid throwing BadParcelableException. + BaseBundle.setShouldDefuse(true); + + // Ensure binder calls into the system always run at foreground priority. + BinderInternal.disableBackgroundScheduling(true); + + // Increase the number of binder threads in system_server + BinderInternal.setMaxThreads(sMaxBinderThreads); + + // Prepare the main looper thread (this thread). + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); + Looper.prepareMainLooper(); + + // Initialize native services. + System.loadLibrary("android_servers");//加载native服务 + + // Check whether we failed to shut down last time we tried. + // This call may not return. + performPendingShutdown(); + + // Initialize the system context. + createSystemContext(); + + // Create the system service manager. + mSystemServiceManager = new SystemServiceManager(mSystemContext);//创建SystemServiceManager + mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart); + LocalServices.addService(SystemServiceManager.class, mSystemServiceManager); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + // Start services. + try { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartServices"); + startBootstrapServices();//启动引导服务 + startCoreServices();//启动核心服务 + startOtherServices();//启动其他服务 + } catch (Throwable ex) { + Slog.e("System", "******************************************"); + Slog.e("System", "************ Failure starting system services", ex); + throw ex; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode for system server main thread."); + } + + // Loop forever. + Looper.loop(); + throw new RuntimeException("Main thread loop unexpectedly exited"); +} +``` + + + +```cpp +//frameworks/base/services/core/jni/com_android_server_SystemServer.cpp +//SensorService的启动和初始化 +static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) { + char propBuf[PROPERTY_VALUE_MAX]; + property_get("system_init.startsensorservice", propBuf, "1"); + if (strcmp(propBuf, "1") == 0) { + // Start the sensor service in a new thread + createThreadEtc(start_sensor_service, nullptr, + "StartSensorThread", PRIORITY_FOREGROUND); + } +} +``` + +## startBootstrapServices + +```java +private void startBootstrapServices() { + // Wait for installd to finish starting up so that it has a chance to + // create critical directories such as /data/user with the appropriate + // permissions. We need this to complete before we initialize other services. + Installer installer = mSystemServiceManager.startService(Installer.class); + + // Activity manager runs the show. + mActivityManagerService = mSystemServiceManager.startService( + ActivityManagerService.Lifecycle.class).getService(); + mActivityManagerService.setSystemServiceManager(mSystemServiceManager); + mActivityManagerService.setInstaller(installer); + + // Power manager needs to be started early because other services need it. + // Native daemons may be watching for it to be registered so it must be ready + // to handle incoming binder calls immediately (including being able to verify + // the permissions for those calls). + mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class); + + // Now that the power manager has been started, let the activity manager + // initialize power management features. + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "InitPowerManagement"); + mActivityManagerService.initPowerManagement(); + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + + // Manages LEDs and display backlight so we need it to bring up the display. + mSystemServiceManager.startService(LightsService.class); + + // Display manager is needed to provide display metrics before package manager + // starts up. + mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class); + + // We need the default display before we can initialize the package manager. + mSystemServiceManager.startBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); + + // Only run "core" apps if we're encrypting the device. + String cryptState = SystemProperties.get("vold.decrypt"); + if (ENCRYPTING_STATE.equals(cryptState)) { + Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); + mOnlyCore = true; + } else if (ENCRYPTED_STATE.equals(cryptState)) { + Slog.w(TAG, "Device encrypted - only parsing core apps"); + mOnlyCore = true; + } + + // Start the package manager. + traceBeginAndSlog("StartPackageManagerService"); + mPackageManagerService = PackageManagerService.main(mSystemContext, installer, + mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); + mFirstBoot = mPackageManagerService.isFirstBoot(); + mPackageManager = mSystemContext.getPackageManager(); + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + + // Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename + // A/B artifacts after boot, before anything else might touch/need them. + // Note: this isn't needed during decryption (we don't have /data anyways). + if (!mOnlyCore) { + boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt", + false); + if (!disableOtaDexopt) { + traceBeginAndSlog("StartOtaDexOptService"); + try { + OtaDexoptService.main(mSystemContext, mPackageManagerService); + } catch (Throwable e) { + reportWtf("starting OtaDexOptService", e); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + } + } + + traceBeginAndSlog("StartUserManagerService"); + mSystemServiceManager.startService(UserManagerService.LifeCycle.class); + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + + // Initialize attribute cache used to cache resources from packages. + AttributeCache.init(mSystemContext); + + // Set up the Application instance for the system process and get started. + mActivityManagerService.setSystemProcess(); + + // The sensor service needs access to package manager service, app ops + // service, and permissions service, therefore we start it after them. + startSensorService(); +} +``` + +## SystemServiceManager + +```java +//frameworks/base/services/core/java/com/android/server/SystemServiceManager.java +private final Context mContext; +public SystemServiceManager(Context context) { + mContext = context; +} +``` + +```java +//frameworks/base/services/core/java/com/android/server/SystemServiceManager.java +//frameworks/base/services/core/java/com/android/server/SystemService.java +private final ArrayList mServices = new ArrayList(); +@SuppressWarnings("unchecked") +public T startService(Class serviceClass) { + try { + final String name = serviceClass.getName(); + Slog.i(TAG, "Starting " + name); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name); + + // Create the service. + if (!SystemService.class.isAssignableFrom(serviceClass)) { + throw new RuntimeException("Failed to create " + name + + ": service must extend " + SystemService.class.getName()); + } + final T service; + try { + Constructor constructor = serviceClass.getConstructor(Context.class); + service = constructor.newInstance(mContext); + } catch (InstantiationException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service could not be instantiated", ex); + } catch (IllegalAccessException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service must have a public constructor with a Context argument", ex); + } catch (NoSuchMethodException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service must have a public constructor with a Context argument", ex); + } catch (InvocationTargetException ex) { + throw new RuntimeException("Failed to create service " + name + + ": service constructor threw an exception", ex); + } + + // Register it. + mServices.add(service); + + // Start it. + try { + service.onStart(); + } catch (RuntimeException ex) { + throw new RuntimeException("Failed to start service " + name + + ": onStart threw an exception", ex); + } + return service; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } +} +``` + +{% plantuml %} + +class SystemServiceManager{ + +​ startService() + +} + +abstract class SystemService{ + +} + +{% endplantuml %} diff --git a/aosp/handler-shi-yong.md b/aosp/handler-shi-yong.md new file mode 100644 index 00000000..4ecef012 --- /dev/null +++ b/aosp/handler-shi-yong.md @@ -0,0 +1,2 @@ +# Handler使用 + diff --git a/aosp/handler-yuan-ma-fen-xi.md b/aosp/handler-yuan-ma-fen-xi.md new file mode 100644 index 00000000..bc4ee7fa --- /dev/null +++ b/aosp/handler-yuan-ma-fen-xi.md @@ -0,0 +1,2 @@ +# Handler源码分析 + diff --git a/aosp/handler.md b/aosp/handler.md new file mode 100644 index 00000000..7a95f464 --- /dev/null +++ b/aosp/handler.md @@ -0,0 +1,896 @@ +# Handler源码分析 + +## Looper + +我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么作为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare\(\)...Looper.loop\(\)就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop\(\)方法里面有一段死循环的代码,所以主线程会进入while\(true\){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while\(true\){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。 + +在`Looper`的`loop()`方法中循环从`MessageQueue`中取出`Message`,然后通过`Handler`来处理消息。假若队列为空,那么它会进入休眠。 + +Handler方法提供接收一个Looper对象的构造函数。但是在UI线程中创建调用不带参数的构造函数也不会报错,那么UI线程的Looper是何时创建的呢?其实在程序启动时,系统就为UI线程创建了一个Looper对象。 + +```java +//ActivityThread的main方法 +public static void main(String[] args) { + //... + Looper.prepareMainLooper(); + + // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line. + // It will be in the format "seq=114" + long startSeq = 0; + if (args != null) { + for (int i = args.length - 1; i >= 0; --i) { + if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) { + startSeq = Long.parseLong( + args[i].substring(PROC_START_SEQ_IDENT.length())); + } + } + } + //创建一下ActivityThread对象,这边需要注意的时候ActivityThread并不是一个线程, + //它并没有继承Thread,而只是一个普通的类 + ActivityThread thread = new ActivityThread(); + //会创建一个Binder线程(具体是指ApplicationThread,该Binder线程会通过想 + //Handler将Message发送给主线程 + thread.attach(false, startSeq); + + if (sMainThreadHandler == null) { + sMainThreadHandler = thread.getHandler(); + } + + if (false) { + Looper.myLooper().setMessageLogging(new + LogPrinter(Log.DEBUG, "ActivityThread")); + } + + // End of event ActivityThreadMain. + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + Looper.loop(); + + throw new RuntimeException("Main thread loop unexpectedly exited"); +} +``` + +Looper的`prepareMainLooper`方法会创建一个`Looper`实例然后放入到`ThreadLocal`中,然后创建`Handler`时会获取该实例。 + +### prepare\(\) + +对于无参的情况,默认调用`prepare(true)`,表示的是这个Looper允许退出,而对于false的情况则表示当前Looper不允许退出。 + +```java +private static void prepare(boolean quitAllowed) { + //prepare多次调用会崩溃 + if (sThreadLocal.get() != null) { + throw new RuntimeException("Only one Looper may be created per thread"); + } + sThreadLocal.set(new Looper(quitAllowed)); +} +``` + +```java +public static void prepareMainLooper() { + prepare(false); + synchronized (Looper.class) { + if (sMainLooper != null) { + throw new IllegalStateException("The main Looper has already been prepared."); + } + sMainLooper = myLooper(); + } +} +``` + +```java +//获取主线程的Looper +public static Looper getMainLooper() { + synchronized (Looper.class) { + return sMainLooper; + } +} +``` + +quit\(\) 和 quitSafely\(\) 的本质就是让消息队列的 next\(\) 返回 null,以此来退出Looper.loop\(\)。 quit\(\) 调用后直接终止 Looper,不在处理任何 Message,所有尝试把 Message 放进消息队列的操作都会失败,比如 Handler.sendMessage\(\) 会返回 false,但是存在不安全性,因为有可能有 Message 还在消息队列中没来的及处理就终止 Looper 了。 quitSafely\(\) 调用后会在所有消息都处理后再终止 Looper,所有尝试把 Message 放进消息队列的操作也都会失败。 + +创建Looper的时候,会创建一个`MessageQueue`对象。 + +```java +//Looper的构造函数是私有的所以不能直接调用Looper的构造函数 +private Looper(boolean quitAllowed) { + //在Looper的构造函数中创建了MessageQueue,所以每个线程也只有1个MessageQueue + mQueue = new MessageQueue(quitAllowed); + mThread = Thread.currentThread(); +} +``` + +### loop\(\) + +```java +public static void loop() { + final Looper me = myLooper(); + if (me == null) { + throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); + } + final MessageQueue queue = me.mQueue; + //... + for (;;) { + Message msg = queue.next(); // might block + if (msg == null) { + // No message indicates that the message queue is quitting. + //没有消息表示消息队列正在退出。 + return; + } + //... + try { + + msg.target.dispatchMessage(msg); + dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; + } finally { + if (traceTag != 0) { + Trace.traceEnd(traceTag); + } + } + //... + //回收消息 + msg.recycleUnchecked(); + + } +} +``` + +在`loop()`中,调用`msg.target`的`dispatchMessage`方法来分发消息。`msg.target` 是一个`Handler`对象,哪个`Handler`把这个`Message`发到队列里,这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写的handler的handleMessage方法。 + +### quit\(\) + +```java +public void quit() { + mQueue.quit(false); //移除消息 +} + +public void quitSafely() { + mQueue.quit(true); //安全地消息移除 +} +``` + +Looper.quit\(\)方法的实现最终调用的是MessageQueue.quit\(\)方法 + +```java +//MessageQueue.java +void quit(boolean safe) { + //当mQuitAllowed为false,表示不运行退出,强行调用quit会爬出异常 + if (!mQuitAllowed) { + throw new IllegalStateException("Main thread not allowed to quit."); + } + synchronized (this) { + //防止多次执行退出 + if (mQuitting) { + return; + } + mQuitting = true; + if (safe) { + removeAllFutureMessagesLocked(); + } else { + removeAllMessagesLocked(); + } + // We can assume mPtr != 0 because mQuitting was previously false. + nativeWake(mPtr); + } +} +``` + +消息退出的方式: + +* 当safe =true时,只移除尚未触发的所有消息,对于正在触发的消息并不移除; +* 当safe =flase时,移除所有的消息 + +## Handler + +简单说`Handler`用于同一个进程的线程间通信。`Looper`让主线程无限循环地从自己的`MessageQueue`拿出消息处理,既然这样我们就知道处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到`MessageQueue`的实例,就可以往主线程的`MessageQueue`放入消息,主线程在轮询的时候就会在主线程处理这个消息。 + +### 构造函数 + +```java +public Handler(@Nullable Callback callback, boolean async) { + //匿名类、内部类活本地类都必须声明为static 否则会警告可能会出现内存泄露 + if (FIND_POTENTIAL_LEAKS) { + final Class klass = getClass(); + if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && + (klass.getModifiers() & Modifier.STATIC) == 0) { + Log.w(TAG, "The following Handler class should be static or leaks might occur: " + + klass.getCanonicalName()); + } + } + // //必须先执行Looper.prepare(),才能获取Looper对象,否则为null. + //从当前线程的TLS中获取Looper对象 + mLooper = Looper.myLooper(); + if (mLooper == null) { + throw new RuntimeException( + "Can't create handler inside thread " + Thread.currentThread() + + " that has not called Looper.prepare()"); + } + mQueue = mLooper.mQueue;//消息队列,来自Looper对象 + mCallback = callback; //回调方法 + mAsynchronous = async;//设置消息是否为异步处理方式 +} +``` + +`Looper`对象的`myLooper`方法 + +```text +static final ThreadLocal sThreadLocal = new ThreadLocal(); +public static @Nullable Looper myLooper() { + return sThreadLocal.get(); +} +``` + +`ThreadLocal`即线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。 + +### 消息分发 + +在`Looper.loop()`中,当发现有消息时,调用消息的目标`handler`,执行`dispatchMessage()`方法来分发消息。 + +```java +public void dispatchMessage(@NonNull Message msg) { + //当Message存在回调方法,回调msg.callback.run()方法; + if (msg.callback != null) { + handleCallback(msg); + } else { + if (mCallback != null) { + //当Handler存在Callback成员变量时,回调方法handleMessage(); + if (mCallback.handleMessage(msg)) { + return; + } + } + //Handler自身的回调方法handleMessage() + handleMessage(msg); + } +} +``` + +**分发消息流程:** + +1. 当`Message`的回调方法不为空时,则回调方法`msg.callback.run()`,其中callBack数据类型为Runnable,否则进入步骤2; +2. 当`Handler`的`mCallback`成员变量不为空时,则回调方法`mCallback.handleMessage(msg)`,否则进入步骤3; +3. 调用`Handler`自身的回调方法`handleMessage()`,该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。 + +对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage\(\),一般地往往通过覆写该方法从而实现自己的业务逻辑。 + +### 消息发送 + +发送消息调用链: + + + + +![](../.gitbook/assets/image%20%2869%29.png) + +post方法最终也是调用的send方法。只不过是把runnable对象赋值给Message的callback。 + +```java +public final boolean post(@NonNull Runnable r) { + return sendMessageDelayed(getPostMessage(r), 0); +} +``` + +```java +private static Message getPostMessage(Runnable r) { + Message m = Message.obtain(); + m.callback = r; + return m; +} +``` + +```java +public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { + if (delayMillis < 0) { + delayMillis = 0; + } + return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); +} +``` + +```java +private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, + long uptimeMillis) { + msg.target = this; + msg.workSourceUid = ThreadLocalWorkSource.getUid(); + if (mAsynchronous) { + msg.setAsynchronous(true); + } + return queue.enqueueMessage(msg, uptimeMillis); +} +``` + + + +## Message + +### 消息对象 + +每个消息用`Message`表示,`Message`主要包含以下内容: + +| 数据类型 | 成员变量 | 解释 | +| :--- | :--- | :--- | +| int | what | 消息类别 | +| long | when | 消息触发时间 | +| int | arg1 | 参数1 | +| int | arg2 | 参数2 | +| Object | obj | 消息内容 | +| Handler | target | 消息响应方 | +| Runnable | callback | 回调方法 | + +创建消息的过程,就是填充消息的上述内容的一项或多项。 + +```text +//Message采用单链表的形式,next指向下一个消息 +Message next; +``` + +### 消息池 + +在代码中,可能经常看到recycle\(\)方法,咋一看,可能是在做虚拟机的gc\(\)相关的工作,其实不然,这是用于把消息加入到消息池的作用。这样的好处是,当消息池不为空时,可以直接从消息池中获取Message对象,而不是直接创建,提高效率。 + +静态变量`sPool`的数据类型为Message,通过next成员变量,维护一个消息池;静态变量`MAX_POOL_SIZE`代表消息池的可用大小;消息池的默认大小为50。 + +消息池常用的操作方法是obtain\(\)和recycle\(\)。 + +```java +private static Message sPool; +``` + +```java +public static Message obtain() { + synchronized (sPoolSync) { + if (sPool != null) { + Message m = sPool; + sPool = m.next; + m.next = null; + m.flags = 0; // clear in-use flag + sPoolSize--; + return m; + } + } + return new Message(); +} +``` + +obtain\(\),从消息池取Message,都是把消息池表头的Message取走,再把表头指向next; + +recycle把不再使用的消息加入消息池 + +```java +public void recycle() { + if (isInUse()) { //判断消息是否正在使用 + if (gCheckRecycle) { //Android 5.0以后的版本默认为true,之前的版本默认为false. + throw new IllegalStateException("This message cannot be recycled because it is still in use."); + } + return; + } + recycleUnchecked(); +} + +//对于不再使用的消息,加入到消息池 +void recycleUnchecked() { + //将消息标示位置为IN_USE,并清空消息所有的参数。 + flags = FLAG_IN_USE; + what = 0; + arg1 = 0; + arg2 = 0; + obj = null; + replyTo = null; + sendingUid = -1; + when = 0; + target = null; + callback = null; + data = null; + synchronized (sPoolSync) { + if (sPoolSize < MAX_POOL_SIZE) { //当消息池没有满时,将Message对象加入消息池 + next = sPool; + sPool = this; + sPoolSize++; //消息池的可用大小进行加1操作 + } + } +} +``` + +## MessageQueue + +### IdleHandler + +IdleHandler是MessageQueue内部接口 + +```java +/** + * Callback interface for discovering when a thread is going to block + * waiting for more messages. + */ +public static interface IdleHandler { + /** + * Called when the message queue has run out of messages and will now + * wait for more. Return true to keep your idle handler active, false + * to have it removed. This may be called if there are still messages + * pending in the queue, but they are all scheduled to be dispatched + * after the current time. + */ + boolean queueIdle(); +} +``` + +```java +private final ArrayList mIdleHandlers = new ArrayList(); +``` + +```java +public void addIdleHandler(@NonNull IdleHandler handler) { + if (handler == null) { + throw new NullPointerException("Can't add a null IdleHandler"); + } + synchronized (this) { + mIdleHandlers.add(handler); + } +} +public void removeIdleHandler(@NonNull IdleHandler handler) { + synchronized (this) { + mIdleHandlers.remove(handler); + } +} +``` + +### next\(\) + +```java +Message next() { + // Return here if the message loop has already quit and been disposed. + // This can happen if the application tries to restart a looper after quit + // which is not supported. + final long ptr = mPtr; + if (ptr == 0) { //当消息循环已经退出,则直接返回 + return null; + } + + int pendingIdleHandlerCount = -1; // -1 only during first iteration + int nextPollTimeoutMillis = 0; + for (;;) { + if (nextPollTimeoutMillis != 0) { + Binder.flushPendingCommands(); + } + //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回 + nativePollOnce(ptr, nextPollTimeoutMillis); + + synchronized (this) { + // Try to retrieve the next message. Return if found. + final long now = SystemClock.uptimeMillis(); + Message prevMsg = null; + Message msg = mMessages; + //当消息的Handler为空时,则查询异步消息 + if (msg != null && msg.target == null) { + // Stalled by a barrier. Find the next asynchronous message in the queue. + //当查询到异步消息,则立刻退出循环 + do { + prevMsg = msg; + msg = msg.next; + } while (msg != null && !msg.isAsynchronous()); + } + if (msg != null) { + if (now < msg.when) { + // Next message is not ready. Set a timeout to wake up when it is ready. + //当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长 + nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); + } else { + // Got a message. + // 获取一条消息,并返回 + mBlocked = false; + if (prevMsg != null) { + prevMsg.next = msg.next; + } else { + mMessages = msg.next; + } + msg.next = null; + if (DEBUG) Log.v(TAG, "Returning message: " + msg); + //设置消息的使用状态,即flags |= FLAG_IN_USE + msg.markInUse(); + return msg; + } + } else { + // No more messages. + nextPollTimeoutMillis = -1; //没有消息 + } + + // Process the quit message now that all pending messages have been handled. + //消息正在退出,返回null + if (mQuitting) { + dispose(); + return null; + } + //当消息队列为空,或者是消息队列的第一个消息时 + // If first time idle, then get the number of idlers to run. + // Idle handles only run if the queue is empty or if the first message + // in the queue (possibly a barrier) is due to be handled in the future. + if (pendingIdleHandlerCount < 0 + && (mMessages == null || now < mMessages.when)) { + pendingIdleHandlerCount = mIdleHandlers.size(); + } + if (pendingIdleHandlerCount <= 0) { + // No idle handlers to run. Loop and wait some more. + //没有idle handlers 需要运行,则循环并等待。 + mBlocked = true; + continue; + } + + if (mPendingIdleHandlers == null) { + mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; + } + mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); + } + // Run the idle handlers. + // We only ever reach this code block during the first iteration. + //只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0. + for (int i = 0; i < pendingIdleHandlerCount; i++) { + final IdleHandler idler = mPendingIdleHandlers[i]; + mPendingIdleHandlers[i] = null; // release the reference to the handler + + boolean keep = false; + try { + keep = idler.queueIdle(); + } catch (Throwable t) { + Log.wtf(TAG, "IdleHandler threw exception", t); + } + + if (!keep) { + synchronized (this) { + mIdleHandlers.remove(idler); + } + } + } + + // Reset the idle handler count to 0 so we do not run them again. + //重置idle handler个数为0,以保证不会再次重复运行 + pendingIdleHandlerCount = 0; + + // While calling an idle handler, a new message could have been delivered + // so go back and look again for a pending message without waiting. + //当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message. + nextPollTimeoutMillis = 0; + + } +} +``` + +### enqueueMessage + +```java +boolean enqueueMessage(Message msg, long when) { + //每一个Message都必须有一个target,否则抛出异常 + if (msg.target == null) { + throw new IllegalArgumentException("Message must have a target."); + } + if (msg.isInUse()) { + throw new IllegalStateException(msg + " This message is already in use."); + } + + synchronized (this) { + //正在退出时,回收msg,加入到消息池 + if (mQuitting) { + IllegalStateException e = new IllegalStateException( + msg.target + " sending message to a Handler on a dead thread"); + Log.w(TAG, e.getMessage(), e); + msg.recycle(); + return false; + } + + msg.markInUse(); + msg.next = when; + Message p = mMessages; + boolean needWake; + if (p == null || when == 0 || when < p.when) { + // New head, wake up the event queue if blocked. + msg.next = p; + mMessages = msg; + needWake = mBlocked; + } else { + // Inserted within the middle of the queue. Usually we don't have to wake + // up the event queue unless there is a barrier at the head of the queue + // and the message is the earliest asynchronous message in the queue. + //将消息按时间顺序插入到MessageQueue + needWake = mBlocked && p.target == null && msg.isAsynchronous(); + Message prev; + //遍历Message 当p为空或者p.when大于when时跳出循环 + for (;;) { + prev = p; + p = p.next; + if (p == null || when < p.when) { + break; + } + if (needWake && p.isAsynchronous()) { + needWake = false; + } + } + //将msg添加到跳出循环的位置 + msg.next = p; // invariant: p == prev.next + prev.next = msg; + } + + // We can assume mPtr != 0 because mQuitting is false. + //唤醒操作 + if (needWake) { + nativeWake(mPtr); + } + } + return true; + } +``` + +`MessageQueue`是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。 + +### removeMessages + +```java +void removeMessages(Handler h, int what, Object object) { + if (h == null) { + return; + } + synchronized (this) { + Message p = mMessages; + //从消息队列的头部开始,移除所有符合条件的消息 + while (p != null && p.target == h && p.what == what + && (object == null || p.obj == object)) { + Message n = p.next; + mMessages = n; + p.recycleUnchecked(); + p = n; + } + //移除剩余的符合要求的消息 + while (p != null) { + Message n = p.next; + if (n != null) { + if (n.target == h && n.what == what + && (object == null || n.obj == object)) { + Message nn = n.next; + n.recycleUnchecked(); + p.next = nn; + continue; + } + } + p = n; + } + } +} +``` + +这个移除消息的方法,采用了两个while循环,第一个循环是从队头开始,移除符合条件的消息,第二个循环是从头部移除完连续的满足条件的消息之后,再从队列后面继续查询是否有满足条件的消息需要被移除。 + +#### postSyncBarrier + +```java +public int postSyncBarrier() { + return postSyncBarrier(SystemClock.uptimeMillis()); +} + +private int postSyncBarrier(long when) { + synchronized (this) { + final int token = mNextBarrierToken++; + final Message msg = Message.obtain(); + msg.markInUse(); + msg.when = when; + msg.arg1 = token; + + Message prev = null; + Message p = mMessages; + if (when != 0) { + while (p != null && p.when <= when) { + prev = p; + p = p.next; + } + } + if (prev != null) { + msg.next = p; + prev.next = msg; + } else { + msg.next = p; + mMessages = msg; + } + return token; + } +} +``` + +每一个普通Message必须有一个target,对于特殊的message是没有target,即同步barrier token。 这个消息的价值就是用于拦截同步消息,所以并不会唤醒Looper. + +```java +public void removeSyncBarrier(int token) { + synchronized (this) { + Message prev = null; + Message p = mMessages; + //从消息队列找到 target为空,并且token相等的Message + while (p != null && (p.target != null || p.arg1 != token)) { + prev = p; + p = p.next; + } + final boolean needWake; + if (prev != null) { + prev.next = p.next; + needWake = false; + } else { + mMessages = p.next; + needWake = mMessages == null || mMessages.target != null; + } + p.recycleUnchecked(); + + if (needWake && !mQuitting) { + nativeWake(mPtr); + } + } + } +``` + +postSyncBarrier只对同步消息产生影响,对于异步消息没有任何差别。 + +## Activity\#runOnUiThread + +```java +private Thread mUiThread; //在attach方法中将当前Thread赋值给mUiThread +final Handler mHandler = new Handler(); +public final void runOnUiThread(Runnable action) { + //判断当前是否是UI线程 不是则调用Handler的post方法 + if (Thread.currentThread() != mUiThread) { + mHandler.post(action); + } else { + action.run(); + } +} +``` + +## View\#post + +```java + public boolean post(Runnable action) { + final AttachInfo attachInfo = mAttachInfo; + //当attachInfo不为空调用attachInfo的mHandler + if (attachInfo != null) { + return attachInfo.mHandler.post(action); + } + // Postpone the runnable until we know on which thread it needs to run. + // Assume that the runnable will be successfully placed after attach. + //当在oncreate调用为空 + getRunQueue().post(action); + return true; + } +``` + +```java +//mAttachInfo赋值 +void dispatchAttachedToWindow(AttachInfo info, int visibility) { + mAttachInfo = info; + // Transfer all pending runnables. + if (mRunQueue != null) { + mRunQueue.executeActions(info.mHandler); + mRunQueue = null; + } +} +void dispatchDetachedFromWindow() { + AttachInfo info = null; +} +``` + +```java +private HandlerActionQueue getRunQueue() { + if (mRunQueue == null) { + mRunQueue = new HandlerActionQueue(); + } + return mRunQueue; +} +``` + +```java +public class HandlerActionQueue { + private HandlerAction[] mActions; + private int mCount; + + public void post(Runnable action) { + postDelayed(action, 0); + } + + public void postDelayed(Runnable action, long delayMillis) { + final HandlerAction handlerAction = new HandlerAction(action, delayMillis); + + synchronized (this) { + if (mActions == null) { + mActions = new HandlerAction[4]; + } + //将HandlerAction添加到数组中 + mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); + mCount++; + } + } + + public void removeCallbacks(Runnable action) { + synchronized (this) { + final int count = mCount; + int j = 0; + + final HandlerAction[] actions = mActions; + for (int i = 0; i < count; i++) { + if (actions[i].matches(action)) { + // Remove this action by overwriting it within + // this loop or nulling it out later. + continue; + } + + if (j != i) { + // At least one previous entry was removed, so + // this one needs to move to the "new" list. + actions[j] = actions[i]; + } + + j++; + } + + // The "new" list only has j entries. + mCount = j; + + // Null out any remaining entries. + for (; j < count; j++) { + actions[j] = null; + } + } + } + + public void executeActions(Handler handler) { + synchronized (this) { + final HandlerAction[] actions = mActions; + for (int i = 0, count = mCount; i < count; i++) { + final HandlerAction handlerAction = actions[i]; + handler.postDelayed(handlerAction.action, handlerAction.delay); + } + + mActions = null; + mCount = 0; + } + } + + public int size() { + return mCount; + } + + public Runnable getRunnable(int index) { + if (index >= mCount) { + throw new IndexOutOfBoundsException(); + } + return mActions[index].action; + } + + public long getDelay(int index) { + if (index >= mCount) { + throw new IndexOutOfBoundsException(); + } + return mActions[index].delay; + } + + private static class HandlerAction { + final Runnable action; + final long delay; + + public HandlerAction(Runnable action, long delay) { + this.action = action; + this.delay = delay; + } + + public boolean matches(Runnable otherAction) { + return otherAction == null && action == null + || action != null && action.equals(otherAction); + } + } +} +``` + +## 扩展阅读 + +* [Android 消息处理机制(Looper、Handler、MessageQueue,Message)](https://www.jianshu.com/p/02962454adf7) +* [Android--多线程之Handler](http://www.cnblogs.com/plokmju/p/android_Handler.html) +* [Handler 中 sendMessage\(\) 源代码剖析](http://blog.csdn.net/ahuier/article/details/17013647) +* [How to Leak a Context: Handlers & Inner Classes](http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html) +* [Android中为什么主线程不会因为Looper.loop\(\)里的死循环卡死?](https://www.zhihu.com/question/34652589) +* [Android 高级面试-1:Handler 相关](https://juejin.im/post/6844903778844409863) +* [Handler 10问,你顶的住吗?](https://mp.weixin.qq.com/s/V1xI2M8AibgB2whHSOTQGQ) +* [Handler的初级、中级、高级问法,你都掌握了吗?](https://juejin.cn/post/6893791473121280013) + + + + + diff --git a/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/README.md b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/README.md new file mode 100644 index 00000000..7cc16f5c --- /dev/null +++ b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/README.md @@ -0,0 +1,2 @@ +# 四大组件的工作过程 + diff --git a/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng-fen-xi.md b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng-fen-xi.md new file mode 100644 index 00000000..21fd23b1 --- /dev/null +++ b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng-fen-xi.md @@ -0,0 +1,2 @@ +# Activity启动流程分析 + diff --git a/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng.md b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng.md new file mode 100644 index 00000000..b94162e8 --- /dev/null +++ b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/activity-qi-dong-liu-cheng.md @@ -0,0 +1,2 @@ +# Activity启动流程 + diff --git a/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/start-activity.md b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/start-activity.md new file mode 100644 index 00000000..a327ae47 --- /dev/null +++ b/aosp/si-da-zu-jian-de-gong-zuo-guo-cheng/start-activity.md @@ -0,0 +1,1158 @@ +# Activity启动流程 + +`Activity`的启动过程分为两种,一种是根Activity的启动过程,另一种是普通Activity的启动过程。根Activity指的是应用程序启动的第一个Activity,因此根Activity的启动过程一般情况下也可以理解为应用程序的启动过程。普通Activity指的是除应用程序启动的第一个Activity之外的其他Activity。 + +Activity的启动过程比较复杂,因此这里分为3个部分来讲,分别是 + +1. Launcher请求AMS过程 +2. AMS到ApplicationThread的调用过程 +3. ActivityThread启动Activity。 + +## Launcher请求AMS过程 + +Launcher请求AMS的时序图如图所示 + +![](../../.gitbook/assets/image%20%2866%29.png) + +当我们点击应用程序的快捷图标时,就会调用Launcher的startActivitySafely方法,如下所示: + +```java +//packages/apps/Launcher3/src/com/android/launcher3/Launcher.java +public boolean startActivitySafely(View v, Intent intent, ItemInfo item, + @Nullable String sourceContainer) { + if (!hasBeenResumed()) { + // Workaround an issue where the WM launch animation is clobbered when finishing the + // recents animation into launcher. Defer launching the activity until Launcher is + // next resumed. + addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer)); + UiFactory.clearSwipeSharedState(true /* finishAnimation */); + return true; + } + //这里调用的是BaseDraggingActivity的startActivitySafely + boolean success = super.startActivitySafely(v, intent, item, sourceContainer); + if (success && v instanceof BubbleTextView) { + // This is set to the view that launched the activity that navigated the user away + // from launcher. Since there is no callback for when the activity has finished + // launching, enable the press state and keep this reference to reset the press + // state when we return to launcher. + BubbleTextView btv = (BubbleTextView) v; + btv.setStayPressed(true); + addOnResumeCallback(btv); + } + return success; +} +``` + +`BaseDraggingActivity`的`startActivitySafely`方法 + +```java +//packages/apps/Launcher3/src/com/android/launcher3/BaseDraggingActivity.java +public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, + @Nullable String sourceContainer) { + if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { + Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); + return false; + } + + Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null; + UserHandle user = item == null ? null : item.user; + + // Prepare intent + //① + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (v != null) { + intent.setSourceBounds(getViewBounds(v)); + } + try { + boolean isShortcut = (item instanceof WorkspaceItemInfo) + && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT + || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) + && !((WorkspaceItemInfo) item).isPromise(); + if (isShortcut) { + // Shortcuts need some special checks due to legacy reasons. + startShortcutIntentSafely(intent, optsBundle, item, sourceContainer); + } else if (user == null || user.equals(Process.myUserHandle())) { + // Could be launching some bookkeeping activity + //调用Activity的startActivity方法 + startActivity(intent, optsBundle); + AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), + Process.myUserHandle(), sourceContainer); + } else { + LauncherAppsCompat.getInstance(this).startActivityForProfile( + intent.getComponent(), user, intent.getSourceBounds(), optsBundle); + AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user, + sourceContainer); + } + getUserEventDispatcher().logAppLaunch(v, intent); + getStatsLogManager().logAppLaunch(v, intent); + return true; + } catch (NullPointerException|ActivityNotFoundException|SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); + } + return false; +} +``` + +在①处将Flag设置为Intent.FLAG\_ACTIVITY\_NEW\_TASK,这样根Activity会在新的任务栈中启动。在②处会调用startActivity方法。 + +### Activity\#startActivity + +```java +@Override +public void startActivity(Intent intent, @Nullable Bundle options) { + if (options != null) { + startActivityForResult(intent, -1, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + startActivityForResult(intent, -1); + } +} +``` + +在startActivity 方法中会调用startActivityForResult 方法,它的第二个参数为-1,表示Launcher不需要知道Activity启动的结果。 + +### Activity\#startActivityForResult + +```java +Activity mParent; + +public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, + @Nullable Bundle options) { + if (mParent == null) { + options = transferSpringboardActivityOptions(options); + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, this, + intent, requestCode, options); + //... + } + //... +} +``` + +mParent是Activity类型的,表示当前Activity的父类。因为目前根Activity还没有创建出来,因此,mParent==null成立。接着调用`Instrumentation`的`execStartActivity`方法,Instrumentation 主要用来监控应用程序和系统的交互。 + +### Instrumentation\#execStartActivity + +```java +//frameworks/base/core/java/android/app/Instrumentation.java +public ActivityResult execStartActivity( + Context who, IBinder contextThread, IBinder token, Activity target, + Intent intent, int requestCode, Bundle options) { + try { + intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(who); + //远程调用ATMS的startActivity方法 + int result = ActivityTaskManager.getService() + .startActivity(whoThread, who.getBasePackageName(), intent, + intent.resolveTypeIfNeeded(who.getContentResolver()), + token, target != null ? target.mEmbeddedID : null, + requestCode, 0, null, options); + checkStartActivityResult(result, intent);//校验返回结果 + } catch (RemoteException e) { + throw new RuntimeException("Failure from system", e); + } + return null; +} +``` + +首先调用`ActivityTaskManager`的`getService`方法来获取`IActivityTaskManager`的代理对象,接着调用它的`startActivity`方法。 + +### Instrumentation\#checkStartActivityResult + +```java +public static void checkStartActivityResult(int res, Object intent) { + if (!ActivityManager.isStartResultFatalError(res)) { + return; + } + + switch (res) { + case ActivityManager.START_INTENT_NOT_RESOLVED: + case ActivityManager.START_CLASS_NOT_FOUND: + if (intent instanceof Intent && ((Intent)intent).getComponent() != null) + throw new ActivityNotFoundException( + "Unable to find explicit activity class " + + ((Intent)intent).getComponent().toShortString() + + "; have you declared this activity in your AndroidManifest.xml?"); + throw new ActivityNotFoundException( + "No Activity found to handle " + intent); + case ActivityManager.START_PERMISSION_DENIED: + throw new SecurityException("Not allowed to start activity " + + intent); + case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: + throw new AndroidRuntimeException( + "FORWARD_RESULT_FLAG used while also requesting a result"); + case ActivityManager.START_NOT_ACTIVITY: + throw new IllegalArgumentException( + "PendingIntent is not an activity"); + case ActivityManager.START_NOT_VOICE_COMPATIBLE: + throw new SecurityException( + "Starting under voice control not allowed for: " + intent); + case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION: + throw new IllegalStateException( + "Session calling startVoiceActivity does not match active session"); + case ActivityManager.START_VOICE_HIDDEN_SESSION: + throw new IllegalStateException( + "Cannot start voice activity on a hidden session"); + case ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION: + throw new IllegalStateException( + "Session calling startAssistantActivity does not match active session"); + case ActivityManager.START_ASSISTANT_HIDDEN_SESSION: + throw new IllegalStateException( + "Cannot start assistant activity on a hidden session"); + case ActivityManager.START_CANCELED: + throw new AndroidRuntimeException("Activity could not be started for " + + intent); + default: + throw new AndroidRuntimeException("Unknown error code " + + res + " when starting " + intent); + } +} +``` + +### getService\(\) + +```java +public static IActivityTaskManager getService() { + return IActivityTaskManagerSingleton.get(); +} +``` + +```java +//通过AIDL通信 +private static final Singleton IActivityTaskManagerSingleton = + new Singleton() { + @Override + protected IActivityTaskManager create() { + final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE); + return IActivityTaskManager.Stub.asInterface(b); + } + }; +``` + +## ATMS到AT的调用过程 + +Launcher请求ActivityTaskManagerService后,代码逻辑已经进入ATMS中,接着是AMS到ApplicationThread的调用流程,时序图如图所示 + +![](../../.gitbook/assets/image%20%2864%29.png) + +### startActivity\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +//继承IActivityTaskManager.Stub 实现startActivity方法 +public class ActivityTaskManagerService extends IActivityTaskManager.Stub{ + @Override + public final int startActivity(IApplicationThread caller, String callingPackage, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { + return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, + resultWho, requestCode, startFlags, profilerInfo, bOptions, + UserHandle.getCallingUserId()); + } +} +``` + +### startActivityAsUser\(\) + +在`ActivityTaskManagerService`的`startActivity`方法中返回了`startActivityAsUser`方法,可以发现`startActivityAsUser`方法比`startActivity`方法多了一个参数`UserHandle.getCallingUserId()`,这个方法会获得调用者的`UserId`,`ActivityTaskManagerService`根据这个`UserId`来确定调用者的权限。 + +```java +@Override +public int startActivityAsUser(IApplicationThread caller, String callingPackage, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) { + //调用重载方法 + return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, + resultWho, requestCode, startFlags, profilerInfo, bOptions, userId, + true /*validateIncomingUser*/); +} +``` + +```java +int startActivityAsUser(IApplicationThread caller, String callingPackage, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, + boolean validateIncomingUser) { + //①判断调用者进程是否被隔离 + enforceNotIsolatedCaller("startActivityAsUser"); + //②检查使用者权限 + userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser, + Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser"); + // TODO: Switch to user app stacks here. + //先获取ActivityStartController对象。然后调用obtainStarter方法获取ActivityStarter对象 + //frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java + // + return getActivityStartController().obtainStarter(intent, "startActivityAsUser") + .setCaller(caller) + .setCallingPackage(callingPackage) + .setResolvedType(resolvedType) + .setResultTo(resultTo) + .setResultWho(resultWho) + .setRequestCode(requestCode) + .setStartFlags(startFlags) + .setProfilerInfo(profilerInfo) + .setActivityOptions(bOptions) + .setMayWait(userId)//设置MayWait为true + .execute(); +} +``` + +在注释1处判断调用者进程是否被隔离,如果被隔离则抛出SecurityException异常,在注释2处检查调用者是否有权限,如果没有权限也会抛出SecurityException异常。最后调用了ActivityStarter的execute 方法。 + +### execute\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java +int execute() { + try { + // TODO(b/64750076): Look into passing request directly to these methods to allow + // for transactional diffs and preprocessing. + if (mRequest.mayWait) { + return startActivityMayWait(mRequest.caller, mRequest.callingUid, + mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid, + mRequest.intent, mRequest.resolvedType, + mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo, + mRequest.resultWho, mRequest.requestCode, mRequest.startFlags, + mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig, + mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId, + mRequest.inTask, mRequest.reason, + mRequest.allowPendingRemoteAnimationRegistryLookup, + mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart); + } else { + //... + } + } finally { + onExecutionComplete(); + } +} +``` + +ActivityStarter是Android 7.0中新加入的类,它是加载Activity的控制类,会收集所有的逻辑来决定如何将Intent和Flags转换为Activity,并将Activity和Task以及Stack相关联。 + +### ActivityStarter\#startActivity + +```java +private int startActivityMayWait(IApplicationThread caller, int callingUid, + String callingPackage, int requestRealCallingPid, int requestRealCallingUid, + Intent intent, String resolvedType, IVoiceInteractionSession voiceSession, + IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, WaitResult outResult, + Configuration globalConfig, SafeActivityOptions options, boolean ignoreTargetSecurity, + int userId, TaskRecord inTask, String reason, + boolean allowPendingRemoteAnimationRegistryLookup, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { + //解析Intent + ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, + 0 /* matchFlags */, + computeResolveFilterUid( + callingUid, realCallingUid, mRequest.filterCallingUid)); + //... + // Collect information about the target of the Intent. + ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo); + + synchronized (mService.mGlobalLock) { + //调用startActivity + int res = startActivity(caller, intent, ephemeralIntent, resolvedType, aInfo, rInfo, + voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, + callingUid, callingPackage, realCallingPid, realCallingUid, startFlags, options, + ignoreTargetSecurity, componentSpecified, outRecord, inTask, reason, + allowPendingRemoteAnimationRegistryLookup, originatingPendingIntent, + allowBackgroundActivityStart); + //... + //... + return res; + } +} +``` + +```java +private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent, + String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, + String callingPackage, int realCallingPid, int realCallingUid, int startFlags, + SafeActivityOptions options, + boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity, + TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup, + PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { + mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent); + int err = ActivityManager.START_SUCCESS; + // Pull the optional Ephemeral Installer-only bundle out of the options early. + final Bundle verificationBundle + = options != null ? options.popAppVerificationBundle() : null; + + WindowProcessController callerApp = null; + if (caller != null) { //① 得到Launcher进程 + callerApp = mService.getProcessController(caller);//② + if (callerApp != null) { + //获取Launcher进程的pid和uid + callingPid = callerApp.getPid(); + callingUid = callerApp.mInfo.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = ActivityManager.START_PERMISSION_DENIED; + } + } + //... + //创建即将要启动的Activity的描述类ActivityRecord + ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid, + callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(), + resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null, + mSupervisor, checkedOptions, sourceRecord); + if (outActivity != null) { + outActivity[0] = r; //③ + } + //... + //④ 调用重载方法 + final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, + true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity); + mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outActivity[0]); + return res; +} +``` + +ActivityStarter的startActivity方法逻辑比较多,这里列出部分我们需要关心的代码。在注释1处判断IApplicationThread类型的caller是否为null,这个caller是方法调用一路传过来的,指向的是Launcher所在的应用程序进程的ApplicationThread对象,在注释2处调用ActivityTaskManagerService的getProcessController方法得到的是代表Launcher进程的callerApp对象,它是ProcessRecord类型的,ProcessRecord用于描述一个应用程序进程。同样地,ActivityRecord用于描述一个Activity,用来记录一个Activity 的所有信息。接下来创建ActivityRecord,用于描述将要启动的Activity,并在注释3处将创建的ActivityRecord赋值给ActivityRecord\[\]类型的outActivity,这个outActivity会作为注释4处的startActivity方法的参数传递下去。 + +```java +private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, + ActivityRecord[] outActivity, boolean restrictedBgActivity) { + int result = START_CANCELED; + final ActivityStack startedActivityStack; + try { + mService.mWindowManager.deferSurfaceLayout(); + //调用startActivityUnchecked + result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, + startFlags, doResume, options, inTask, outActivity, restrictedBgActivity); + } finally { + //... + } + postStartActivityProcessing(r, result, startedActivityStack); + return result; +} +``` + +startActivity方法紧接着调用了startActivityUnchecked方法: + +### startActivityUnchecked\(\) + +```java +private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, + ActivityRecord[] outActivity, boolean restrictedBgActivity) { + //... + //① + if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask + && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + newTask = true; + //②创建新的TaskRecord + result = setTaskFromReuseOrCreateNewTask(taskToAffiliate); + } else if (mSourceRecord != null) { + result = setTaskFromSourceRecord(); + } else if (mInTask != null) { + result = setTaskFromInTask(); + } else { + // This not being started from an existing activity, and not part of a new task... + // just put it in the top task, though these days this case should never happen. + result = setTaskToCurrentTopOrCreateNewTask(); + } + //... + if (mDoResume) { + final ActivityRecord topTaskActivity = + mStartActivity.getTaskRecord().topRunningActivityLocked(); + if (!mTargetStack.isFocusable() + || (topTaskActivity != null && topTaskActivity.mTaskOverlay + && mStartActivity != topTaskActivity)) { + // If the activity is not focusable, we can't resume it, but still would like to + // make sure it becomes visible as it starts (this will also trigger entry + // animation). An example of this are PIP activities. + // Also, we don't want to resume activities in a task that currently has an overlay + // as the starting activity just needs to be in the visible paused state until the + // over is removed. + mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS); + // Go ahead and tell window manager to execute app transition for this activity + // since the app transition will not be triggered through the resume channel. + mTargetStack.getDisplay().mDisplayContent.executeAppTransition(); + } else { + // If the target stack was not previously focusable (previous top running activity + // on that stack was not visible) then any prior calls to move the stack to the + // will not update the focused stack. If starting the new activity now allows the + // task stack to be focusable, then ensure that we now update the focused stack + // accordingly. + if (mTargetStack.isFocusable() + && !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) { + mTargetStack.moveToFront("startActivityUnchecked"); + } + //③ + mRootActivityContainer.resumeFocusedStacksTopActivities( + mTargetStack, mStartActivity, mOptions); + } + } else if (mStartActivity != null) { + mSupervisor.mRecentTasks.add(mStartActivity.getTaskRecord()); + } + mRootActivityContainer.updateUserStack(mStartActivity.mUserId, mTargetStack); + + mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTaskRecord(), + preferredWindowingMode, mPreferredDisplayId, mTargetStack); + + return START_SUCCESS; +} +``` + +startActivityUnchecked 方法主要处理与栈管理相关的逻辑。在标注①处我们得知,启动根Activity时会将Intent的Flag设置为FLAG\_ACTIVITY\_NEW\_TASK,这样注释1处的条件判断就会满足,接着执行注释2处的setTaskFromReuseOrCreateNewTask方法,其内部会创建一个新的TaskRecord,用来描述一个Activity任务栈,也就是说setTaskFromReuseOrCreateNewTask方法内部会创建一个新的Activity任务栈。Activity任务栈其实是一个假想的模型,并不真实存在,关于Activity 任务栈会在第6章进行介绍。在注释3处会调用RootActivityContainer的resumeFocusedStacksTopActivities方法,如下所示: + +### resumeFocusedStacksTopActivities\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java +boolean resumeFocusedStacksTopActivities() { + return resumeFocusedStacksTopActivities(null, null, null); +} +``` + +```java +//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java +boolean resumeFocusedStacksTopActivities( + ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) { + + if (!mStackSupervisor.readyToResume()) { + return false; + } + + boolean result = false; + if (targetStack != null && (targetStack.isTopStackOnDisplay() + || getTopDisplayFocusedStack() == targetStack)) { + result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); + } + + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + boolean resumedOnDisplay = false; + final ActivityDisplay display = mActivityDisplays.get(displayNdx); + for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = display.getChildAt(stackNdx); + //获取要启动的Activity所有栈的栈顶的不是处于停止状态的ActivityRecord + final ActivityRecord topRunningActivity = stack.topRunningActivityLocked(); + if (!stack.isFocusableAndVisible() || topRunningActivity == null) { + continue; + } + if (stack == targetStack) { + // Simply update the result for targetStack because the targetStack had + // already resumed in above. We don't want to resume it again, especially in + // some cases, it would cause a second launch failure if app process was dead. + resumedOnDisplay |= result; + continue; + } + if (display.isTopStack(stack) && topRunningActivity.isState(RESUMED)) { + // Kick off any lingering app transitions form the MoveTaskToFront operation, + // but only consider the top task and stack on that display. + stack.executeAppTransition(targetOptions); + } else { + resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target); + } + } + if (!resumedOnDisplay) { + // In cases when there are no valid activities (e.g. device just booted or launcher + // crashed) it's possible that nothing was resumed on a display. Requesting resume + // of top activity in focused stack explicitly will make sure that at least home + // activity is started and resumed, and no recursion occurs. + final ActivityStack focusedStack = display.getFocusedStack(); + if (focusedStack != null) { + //调用ActivityStack的resumeTopActivityUncheckedLocked方法 + focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions); + } + } + } + + return result; +} +``` + +### resumeTopActivityUncheckedLocked\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java +boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) { + if (mInResumeTopActivity) { + // Don't even start recursing. + return false; + } + boolean result = false; + try { + // Protect against recursion. + mInResumeTopActivity = true; + result = resumeTopActivityInnerLocked(prev, options); + // When resuming the top activity, it may be necessary to pause the top activity (for + // example, returning to the lock screen. We suppress the normal pause logic in + // {@link #resumeTopActivityUncheckedLocked}, since the top activity is resumed at the + // end. We call the {@link ActivityStackSupervisor#checkReadyForSleepLocked} again here + // to ensure any necessary pause logic occurs. In the case where the Activity will be + // shown regardless of the lock screen, the call to + // {@link ActivityStackSupervisor#checkReadyForSleepLocked} is skipped. + final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); + if (next == null || !next.canTurnScreenOn()) { + checkReadyForSleep(); + } + } finally { + mInResumeTopActivity = false; + } + return result; +} +``` + +### resumeTopActivityInnerLocked\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java +@GuardedBy("mService") +private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { + //... + mStackSupervisor.startSpecificActivity(next, true, true); + //... + return true; +} +``` + +### startSpecificActivity\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) { + // Is this activity's application already running? + //获取即将启动的Activity的所在的应用程序进程 + //① + final WindowProcessController wpc = + mService.getProcessController(r.processName, r.info.applicationInfo.uid); + + boolean knownToBeDead = false; + //② + if (wpc != null && wpc.hasThread()) { + try { + //③ + realStartActivityLocked(r, wpc, andResume, checkConfig); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting activity " + + r.intent.getComponent().flattenToShortString(), e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + knownToBeDead = true; + } + + // Suppress transition until the new activity becomes ready, otherwise the keyguard can + // appear for a short amount of time before the new process with the new activity had the + // ability to set its showWhenLocked flags. + if (getKeyguardController().isKeyguardLocked()) { + r.notifyUnknownVisibilityLaunched(); + } + + final boolean isTop = andResume && r.isTopRunningActivity(); + //创建进程 + mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity"); +} +``` + +* ①获取即将启动的`Activity`所在的应用程序进程, +* ②判断要启动的`Activity`所在的应用程序进程是否已经运行 +* 如果所在的进程已经运行,就会调用③处的`realStartActivityLocked`方法。 + +### realStartActivityLocked\(\) + +```java +//frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc, + boolean andResume, boolean checkConfig) throws RemoteException { + // Create activity launch transaction. + //创建ClientTransaction 传入WindowProcessController的IApplicationThread + //frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java + final ClientTransaction clientTransaction = ClientTransaction.obtain( + proc.getThread(), r.appToken); + + final DisplayContent dc = r.getDisplay().mDisplayContent; + clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent), + System.identityHashCode(r), r.info, + // TODO: Have this take the merged configuration instead of separate global + // and override configs. + mergedConfiguration.getGlobalConfiguration(), + mergedConfiguration.getOverrideConfiguration(), r.compat, + r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(), + r.icicle, r.persistentState, results, newIntents, + dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(), + r.assistToken)); + + // Set desired final state. + //创建ActivityLifecycleItem + //frameworks/base/core/java/android/app/servertransaction/ActivityLifecycleItem.java + final ActivityLifecycleItem lifecycleItem; + if (andResume) { + lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward()); + } else { + lifecycleItem = PauseActivityItem.obtain(); + } + clientTransaction.setLifecycleStateRequest(lifecycleItem); + + // Schedule transaction. + mService.getLifecycleManager().scheduleTransaction(clientTransaction); + + return true; +} +``` + +### scheduleTransaction\(\) + +```java +//ClientLifecycleManager.java +void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + final IApplicationThread client = transaction.getClient(); + transaction.schedule(); + if (!(client instanceof Binder)) { + // If client is not an instance of Binder - it's a remote call and at this point + // safe to recycle the object. All objects used for local calls will be recycled + // the transaction is executed on client in ActivityThread. + transaction.recycle(); + } +} +``` + +```java +//ClientTransaction.java +public void schedule() throws RemoteException { + //调用IApplicationThread的scheduleTransaction方法 + mClient.scheduleTransaction(this); +} +``` + +## ActivityThread启动Activity的过程 + +ActivityThread启动Activity过程的时序图 + +```java +@Override +public void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + ActivityThread.this.scheduleTransaction(transaction); +} +``` + +### scheduleTransaction\(\) + +```java +//ClientTransactionHandler.java +void scheduleTransaction(ClientTransaction transaction) { + transaction.preExecute(this); + sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); +} +``` + +### sendMessage\(\) + +```java +private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) { + if (DEBUG_MESSAGES) { + Slog.v(TAG, + "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj); + } + Message msg = Message.obtain(); + msg.what = what; + msg.obj = obj; + msg.arg1 = arg1; + msg.arg2 = arg2; + if (async) { + msg.setAsynchronous(true); + } + mH.sendMessage(msg); +} +``` + +这里mH指的是H,它是ActivityThread的内部类并继承自Handler,是应用程序进程中主线程的消息管理类。因为ApplicationThread是一个Binder,它的调用逻辑运行在Binder线程池中,所以这里需要用H将代码的逻辑切换到主线程中。 + +### H + +```java +class H extends Handler { + public void handleMessage(Message msg) { + if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); + switch (msg.what) { + case EXECUTE_TRANSACTION: + final ClientTransaction transaction = (ClientTransaction) msg.obj; + mTransactionExecutor.execute(transaction); + if (isSystem()) { + // Client transactions inside system process are recycled on the client side + // instead of ClientLifecycleManager to avoid being cleared before this + // message is handled. + transaction.recycle(); + } + // TODO(lifecycler): Recycle locally scheduled transactions. + break; + } + Object obj = msg.obj; + if (obj instanceof SomeArgs) { + ((SomeArgs) obj).recycle(); + } + if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); + } +} +``` + +### execute\(\) + +```java +//TransactionExecutor.java +public void execute(ClientTransaction transaction) { + if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction"); + final IBinder token = transaction.getActivityToken(); + if (token != null) { + final Map activitiesToBeDestroyed = + mTransactionHandler.getActivitiesToBeDestroyed(); + final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token); + if (destroyItem != null) { + if (transaction.getLifecycleStateRequest() == destroyItem) { + // It is going to execute the transaction that will destroy activity with the + // token, so the corresponding to-be-destroyed record can be removed. + activitiesToBeDestroyed.remove(token); + } + if (mTransactionHandler.getActivityClient(token) == null) { + // The activity has not been created but has been requested to destroy, so all + // transactions for the token are just like being cancelled. + Slog.w(TAG, tId(transaction) + "Skip pre-destroyed transaction:\n" + + transactionToString(transaction, mTransactionHandler)); + return; + } + } + } + if (DEBUG_RESOLVER) Slog.d(TAG, transactionToString(transaction, mTransactionHandler)); + executeCallbacks(transaction); + executeLifecycleState(transaction); + mPendingActions.clear(); + if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction"); +} +``` + +### executeCallbacks\(\) + +```java +/** Cycle through all states requested by callbacks and execute them at proper times. */ +@VisibleForTesting +public void executeCallbacks(ClientTransaction transaction) { + final List callbacks = transaction.getCallbacks(); + if (callbacks == null || callbacks.isEmpty()) { + // No callbacks to execute, return early. + return; + } + if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callbacks in transaction"); + final IBinder token = transaction.getActivityToken(); + ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + // In case when post-execution state of the last callback matches the final state requested + // for the activity in this transaction, we won't do the last transition here and do it when + // moving to final state instead (because it may contain additional parameters from server). + final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest(); + final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState() + : UNDEFINED; + // Index of the last callback that requests some post-execution state. + final int lastCallbackRequestingState = lastCallbackRequestingState(transaction); + final int size = callbacks.size(); + for (int i = 0; i < size; ++i) { + //获取item + final ClientTransactionItem item = callbacks.get(i); + if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item); + final int postExecutionState = item.getPostExecutionState(); + final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r, + item.getPostExecutionState()); + if (closestPreExecutionState != UNDEFINED) { + cycleToPath(r, closestPreExecutionState, transaction); + } + //执行execute + item.execute(mTransactionHandler, token, mPendingActions); + item.postExecute(mTransactionHandler, token, mPendingActions); + if (r == null) { + // Launch activity request will create an activity record. + r = mTransactionHandler.getActivityClient(token); + } + if (postExecutionState != UNDEFINED && r != null) { + // Skip the very last transition and perform it by explicit state request instead. + final boolean shouldExcludeLastTransition = + i == lastCallbackRequestingState && finalState == postExecutionState; + cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction); + } + } +} +``` + +### execute\(\) + +```java + @Override + public void execute(ClientTransactionHandler client, IBinder token, + PendingTransactionActions pendingActions) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); + ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, + mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, + mPendingResults, mPendingNewIntents, mIsForward, + mProfilerInfo, client, mAssistToken); + client.handleLaunchActivity(r, pendingActions, null /* customIntent */); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } +``` + +### handleLaunchActivity\(\) + +```java +/** + * Extended implementation of activity launch. Used when server requests a launch or relaunch. + */ +@Override +public Activity handleLaunchActivity(ActivityClientRecord r, + PendingTransactionActions pendingActions, Intent customIntent) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + mSomeActivitiesChanged = true; + + if (r.profilerInfo != null) { + mProfiler.setProfiler(r.profilerInfo); + mProfiler.startProfiling(); + } + + // Make sure we are running with the most recent config. + handleConfigurationChanged(null, null); + + if (localLOGV) Slog.v( + TAG, "Handling launch of " + r); + + // Initialize before creating the activity + if (!ThreadedRenderer.sRendererDisabled + && (r.activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) { + HardwareRenderer.preload(); + } + WindowManagerGlobal.initialize(); + + // Hint the GraphicsEnvironment that an activity is launching on the process. + GraphicsEnvironment.hintActivityLaunch(); + + final Activity a = performLaunchActivity(r, customIntent); + + if (a != null) { + r.createdConfig = new Configuration(mConfiguration); + reportSizeConfigurations(r); + if (!r.activity.mFinished && pendingActions != null) { + pendingActions.setOldState(r.state); + pendingActions.setRestoreInstanceState(true); + pendingActions.setCallOnPostCreate(true); + } + } else { + // If there was an error, for any reason, tell the activity manager to stop us. + try { + ActivityTaskManager.getService() + .finishActivity(r.token, Activity.RESULT_CANCELED, null, + Activity.DONT_FINISH_TASK_WITH_ACTIVITY); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + return a; +} +``` + +### performLaunchActivity\(\) + +```java +private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { + //获取ActivityInfo + ActivityInfo aInfo = r.activityInfo; + if (r.packageInfo == null) { + //获取Apk文件的描述类LoadedApk + r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, + Context.CONTEXT_INCLUDE_CODE); + } + + ComponentName component = r.intent.getComponent(); + if (component == null) { + component = r.intent.resolveActivity( + mInitialApplication.getPackageManager()); + r.intent.setComponent(component); + } + + if (r.activityInfo.targetActivity ! null) { + component = new ComponentName(r.activityInfo.packageName, + r.activityInfo.targetActivity); + } + //创建要启动Activity的上下文环境 + ContextImpl appContext = createBaseContextForActivity(r); + Activity activity = null; + try { + //获取类加载器 + java.lang.ClassLoader cl = appContext.getClassLoader(); + //用类加载器创建该Activity的实例 + activity = mInstrumentation.newActivity( + cl, component.getClassName(), r.intent); + StrictMode.incrementExpectedActivityCount(activity.getClass()); + r.intent.setExtrasClassLoader(cl); + r.intent.prepareToEnterProcess(); + if (r.state != null) { + r.state.setClassLoader(cl); + } + } catch (Exception e) { + //... + } + + try { + //创建Application + Application app = r.packageInfo.makeApplication(false, mInstrumentation); + + if (localLOGV) Slog.v(TAG, "Performing launch of " + r); + if (localLOGV) Slog.v( + TAG, r + ": app=" + app + + ", appName=" + app.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + + ", comp=" + r.intent.getComponent().toShortString() + + ", dir=" + r.packageInfo.getAppDir()); + + if (activity != null) { + CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); + Configuration config = new Configuration(mCompatConfiguration); + if (r.overrideConfig != null) { + config.updateFrom(r.overrideConfig); + } + if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + + r.activityInfo.name + " with config " + config); + Window window = null; + if (r.mPendingRemoveWindow != null && r.mPreserveWindow) { + window = r.mPendingRemoveWindow; + r.mPendingRemoveWindow = null; + r.mPendingRemoveWindowManager = null; + } + appContext.setOuterContext(activity); + //初始化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, window, r.configCallback, + r.assistToken); + + if (customIntent != null) { + activity.mIntent = customIntent; + } + r.lastNonConfigurationInstances = null; + checkAndBlockForNetworkAccess(); + activity.mStartedActivity = false; + int theme = r.activityInfo.getThemeResource(); + if (theme != 0) { + activity.setTheme(theme); + } + + activity.mCalled = false; + if (r.isPersistable()) { + //启动Activity + mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); + } else { + mInstrumentation.callActivityOnCreate(activity, r.state); + } + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onCreate()"); + } + r.activity = activity; + } + r.setState(ON_CREATE); + + // updatePendingActivityConfiguration() reads from mActivities to update + // ActivityClientRecord which runs in a different thread. Protect modifications to + // mActivities to avoid race. + synchronized (mResourcesManager) { + mActivities.put(r.token, r); + } + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to start activity " + component + + ": " + e.toString(), e); + } + } + + return activity; +} +``` + +### Instrumentation\#newActivity\(\) + +```java +public Activity newActivity(ClassLoader cl, String className, + Intent intent) + throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + String pkg = intent != null && intent.getComponent() != null + ? intent.getComponent().getPackageName() : null; + //调用AppComponentFactory的instantiateActivity方法 + return getFactory(pkg).instantiateActivity(cl, className, intent); +} +``` + +```java +//frameworks/base/core/java/android/app/AppComponentFactory.java +public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, + @Nullable Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + return (Activity) cl.loadClass(className).newInstance(); +} +``` + +```java +public void callActivityOnCreate(Activity activity, Bundle icicle, + PersistableBundle persistentState) { + prePerformCreate(activity); + activity.performCreate(icicle, persistentState); + postPerformCreate(activity); +} +``` + +```java +final void performCreate(Bundle icicle) { + performCreate(icicle, null); +} +``` + +```java +final void performCreate(Bundle icicle, PersistableBundle persistentState) { + dispatchActivityPreCreated(icicle); + mCanEnterPictureInPicture = true; + restoreHasCurrentPermissionRequest(icicle); + if (persistentState != null) { + onCreate(icicle, persistentState); + } else { + onCreate(icicle); + } + writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate"); + mActivityTransitionState.readState(icicle); + mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, false); + mFragments.dispatchActivityCreated(); + mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); + dispatchActivityPostCreated(icicle); +} +``` + +## 根Activity启动过程中涉及的进程 + +根Activity启动过程中会涉及4个进程,分别是Zygote进程、Launcher进程、AMS所在进程(SystemServer进程)、应用程序进程。它们之间的关系如图所示。 + +![](../../.gitbook/assets/image%20%2863%29.png) + +首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后,AMS 会请求创建应用程序进程并启动根Activity。图中步骤2采用的是Socket通信,步骤1和步骤4采用的是Binder通信。上图可能并不是很直观,为了更好理解,下面给出这4个进程调用的时序图,如下图所示。 + +![](../../.gitbook/assets/image%20%2865%29.png) + +如果是普通Activity启动过程会涉及几个进程呢?答案是两个,AMS所在进程和应用程序进程。实际上理解了根Activity的启动过程(根Activity的onCreate过程)。 + diff --git a/aosp/sparsearray.md b/aosp/sparsearray.md new file mode 100644 index 00000000..326c844f --- /dev/null +++ b/aosp/sparsearray.md @@ -0,0 +1,123 @@ +# SparseArray + +## 官方文档 + +> SparseArray maps integers to Objects and, unlike a normal array of Objects, its indices can contain gaps. SparseArray is intended to be more memory-efficient than a HashMap, because it avoids auto-boxing keys and its data structure doesn't rely on an extra entry object for each mapping. + +`SparseArray`将整数映射到`Objects`,与普通的`Objects`数组不同,它的索引可以包含间隙。`SparseArray`的目的是比`HashMap`更节省内存,因为它避免了自动装箱键,而且它的数据结构不依赖于每个映射的额外条目对象。 + +> Note that this container keeps its mappings in an array data structure, using a binary search to find keys. The implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a `HashMap` because lookups require a binary search, and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items, the performance difference is less than 50%. + +请注意,这个容器将其映射保存在数组数据结构中,使用二进制搜索来查找键。这个实现并不适合可能包含大量项目的数据结构。它通常比HashMap慢,因为查找需要二进制搜索,而添加和删除需要在数组中插入和删除条目。对于最多容纳数百个项目的容器,性能差异小于50%。 + +> To help with performance, the container includes an optimization when removing keys: instead of compacting its array immediately, it leaves the removed entry marked as deleted. The entry can then be re-used for the same key or compacted later in a single garbage collection of all removed entries. This garbage collection must be performed whenever the array needs to be grown, or when the map size or entry values are retrieved. + +为了帮助提高性能,容器在删除键时包含了一个优化:它不立即压缩其数组,而是将删除的条目标记为删除。然后,该条目可以为相同的键重新使用,或者在以后对所有删除的条目进行一次垃圾收集时进行压缩。每当需要增长数组,或者检索映射大小或条目值时,都必须执行这个垃圾收集。 + +> It is possible to iterate over the items in this container using keyAt\(int\) and valueAt\(int\). Iterating over the keys using keyAt\(int\) with ascending values of the index returns the keys in ascending order. In the case of valueAt\(int\), the values corresponding to the keys are returned in ascending order. + +可以使用`keyAt(int)`和`valueAt(int)`对这个容器中的项目进行迭代。使用keyAt\(int\)以索引的升序值对键进行迭代,按升序返回键。在valueAt\(int\)的情况下,按升序返回键对应的值。 + +## 源码分析 + +### 构造函数 + +```java +private int[] mKeys; +private Object[] mValues; +private int mSize; +public SparseArray() { + this(10); +} +public SparseArray(int initialCapacity) { + if (initialCapacity == 0) { + mKeys = EmptyArray.INT; + mValues = EmptyArray.OBJECT; + } else { + mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity); + mKeys = new int[mValues.length]; + } + mSize = 0; +} +``` + +### get + +```java +public E get(int key) { + return get(key, null); +} +``` + +```java +public E get(int key, E valueIfKeyNotFound) { + //二分查找从mKeys获取索引 + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + if (i < 0 || mValues[i] == DELETED) { + return valueIfKeyNotFound; + } else { + return (E) mValues[i]; + } +} +``` + +### put + +```java +public void put(int key, E value) { + //二分查找 + int i = ContainerHelpers.binarySearch(mKeys, mSize, key); + + if (i >= 0) { + //说明存在值 直接覆盖 + mValues[i] = value; + } else { + i = ~i; + // + if (i < mSize && mValues[i] == DELETED) { + mKeys[i] = key; + mValues[i] = value; + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + + // Search again because indices may have changed. + i = ~ContainerHelpers.binarySearch(mKeys, mSize, key); + } + mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, value); + mSize++; + } +} +``` + +```java +//frameworks/base/core/java/com/android/internal/util/GrowingArrayUtils.java +public static T[] insert(T[] array, int currentSize, int index, T element) { + assert currentSize <= array.length; + + if (currentSize + 1 <= array.length) { + //移动 + System.arraycopy(array, index, array, index + 1, currentSize - index); + array[index] = element; + return array; + } + + @SuppressWarnings("unchecked") + T[] newArray = ArrayUtils.newUnpaddedArray((Class)array.getClass().getComponentType(), + growSize(currentSize)); + System.arraycopy(array, 0, newArray, 0, index); + newArray[index] = element; + System.arraycopy(array, index, newArray, index + 1, array.length - index); + return newArray; +} +``` + +```java +public static int growSize(int currentSize) { + return currentSize <= 4 ? 8 : currentSize * 2; +} +``` + diff --git a/aosp/start-activity.md b/aosp/start-activity.md new file mode 100644 index 00000000..98e47315 --- /dev/null +++ b/aosp/start-activity.md @@ -0,0 +1,22 @@ +`Activity`的启动过程分为两种,一种是根`Activity`的启动过程,另一种是普通`Activity`的启动过程。根`Activity`指的是应用程序启动的第一个`Activity`,因此根`Activity`的启动过程一般情况下也可以理解为应用程序的启动过程。普通`Activity`指的是除应用程序启动的第一个`Activity`之外的其他`Activity`。 + + +Activity的启动过程比较复杂,因此这里分为3个部分来讲,分别是: + + +## **Launcher请求AMS过程** + +Launcher请求AMS的时序图如图所示 + +```mermaid +sequenceDiagram + Launcher->>BaseDraggingActivity: startActivitySafely() + BaseDraggingActivity->>Activity:startActivity() + Activity-->>Activity: startActivityForResult() + Activity-->>Instrumentation: execStartActivity() + Instrumentation-->>ActivityTaskManager: getService() + Note over Instrumentation,ActivityTaskManager: 获取IActivityTaskManager的代理类 + Instrumentation-->>IActivityTaskManager: startActivity() +``` + + diff --git a/aosp/window.md b/aosp/window.md new file mode 100644 index 00000000..3fb4606a --- /dev/null +++ b/aosp/window.md @@ -0,0 +1,2 @@ +# Window + diff --git a/aosp/windowmanagerservice.md b/aosp/windowmanagerservice.md new file mode 100644 index 00000000..e2ba0d8f --- /dev/null +++ b/aosp/windowmanagerservice.md @@ -0,0 +1,2 @@ +# WindowManagerService + diff --git a/aosp/wms.md b/aosp/wms.md new file mode 100644 index 00000000..4a57987e --- /dev/null +++ b/aosp/wms.md @@ -0,0 +1,2 @@ +# WMS + diff --git a/aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/README.md b/aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/README.md new file mode 100644 index 00000000..fc40d396 --- /dev/null +++ b/aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/README.md @@ -0,0 +1,2 @@ +# 应用程序进程启动过程 + diff --git a/aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng-jie-shao.md b/aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng-jie-shao.md new file mode 100644 index 00000000..42986089 --- /dev/null +++ b/aosp/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng/ying-yong-cheng-xu-jin-cheng-qi-dong-guo-cheng-jie-shao.md @@ -0,0 +1,2 @@ +# 应用程序进程启动过程介绍 + diff --git a/aosp/zygote.md b/aosp/zygote.md new file mode 100644 index 00000000..ff6e51de --- /dev/null +++ b/aosp/zygote.md @@ -0,0 +1,1469 @@ + +## Zygote启动流程 + +在Android系统中,应用程序进程以及运行系统的关键服务的`SystemServer`进程都是由`Zygote`进程来创建的,我们也将它称为孵化器。它通过`fork`(复制进程)的形式来创建应用程序进程和`SystemServer`进程。 + +Zygote进程是在init进程启动时创建的,起初Zygote进程的名称并不是叫“zygote”,而是叫“app_process”,这个名称是在Android.mk中定义的,Zygote进程启动后,Linux系统下的pctrl系统会调用app_process,将其名称换成了“zygote”。 + +```mermaid +sequenceDiagram + init.rc->>init.zygote32.rc:调用 +init.zygote32.rc->>app_main.cpp:main() +app_main.cpp->>AndroidRuntime:start() +AndroidRuntime->>ZygoteInit:main() +``` + +在`init.rc`文件中采用了`import`引入`Zygote`启动脚本。 + +```java +//system/core/rootdir/init.rc +import /init.${ro.hardware}.rc +import /init.usb.configfs.rc +import /init.${ro.zygote}.rc//导入zygote.rc +``` + +可以看出`init.rc`不会直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件。 + +从`Android 5.0`开始,Android开始支持64位程序,Zygote也就有了32位和64位的区别,所以在这里用`ro.zygote`属性来控制使用不同的`Zygote`启动脚本,从而也就启动了不同版本的`Zygote`进程,`ro.zygote`属性的取值有以下4种: + +* init.zygote32.rc + +* init.zygote32_64.rc + +* init.zygote64.rc + +* init.zygote64_32.rc + + +```java +//system/core/rootdir/init.zygote64.rc +//启动zygote service 路径/system/bin/app_process64 +service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server + class main + priority -20 + user root + group root readproc reserved_disk + socket zygote stream 660 root system + socket usap_pool_primary stream 660 root system + onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse + onrestart write /sys/power/state on + onrestart restart audioserver + onrestart restart cameraserver + onrestart restart media + onrestart restart netd + onrestart restart wificond + task_profiles ProcessCapacityHigh + critical window=${zygote.critical_window.minute:-off} target=zygote-fatal +``` + + +```java +//frameworks/base/cmds/app_process/Android.mk +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + app_main.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + liblog \ + libbinder \ + libandroid_runtime \ + $(app_process_common_shared_libs) \ + +LOCAL_WHOLE_STATIC_LIBRARIES := libsigchain + +LOCAL_LDFLAGS := -ldl -Wl,--version-script,art/sigchainlib/version-script.txt -Wl,--export-dynamic +LOCAL_CPPFLAGS := -std=c++11 + +LOCAL_MODULE := app_process__asan +LOCAL_MULTILIB := both //编译32位和64位 +LOCAL_MODULE_STEM_32 := app_process32 //32位 +LOCAL_MODULE_STEM_64 := app_process64//64位 +``` + + +### **main()** + +`app_main.cpp`的`main`方法会接收传进的来参数,并根据传进来的参数调用`AndroidRuntime`的`start`方法。 + +```c++ +//frameworks/base/cmds/app_process/app_main.cpp +int main(int argc, char* const argv[]) +{ + //... + while (i < argc) { + const char* arg = argv[i++]; + if (strcmp(arg, "--zygote") == 0) { + zygote = true; //传进来的参数--zygote 将布尔值zygote设置为true + niceName = ZYGOTE_NICE_NAME; + } else if (strcmp(arg, "--start-system-server") == 0) { + startSystemServer = true; //启动SystemServer进程 + } else if (strcmp(arg, "--application") == 0) { + application = true; + } else if (strncmp(arg, "--nice-name=", 12) == 0) { + niceName.setTo(arg + 12); + } else if (strncmp(arg, "--", 2) != 0) { + className.setTo(arg); + break; + } else { + --i; + break; + } + } + Vector args; + if (!className.isEmpty()) { + //... + } else { + //... + //start-system-server写入参数中 + if (startSystemServer) { + args.add(String8("start-system-server")); + } + //... + } + if (!niceName.isEmpty()) { //线程名字不是空 设置线程名字 + runtime.setArgv0(niceName.string()); + set_process_name(niceName.string()); + } + + if (zygote) { + //调用AppRuntime的start方法启动ZygoteInit + runtime.start("com.android.internal.os.ZygoteInit", args, zygote); + } else if (className) { + runtime.start("com.android.internal.os.RuntimeInit", args, zygote); + } else { + fprintf(stderr, "Error: no class name or --zygote supplied.\n"); + app_usage(); + LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); + return 10; + } +} +``` + + +### **start()** + +`AndroidRuntime`的`start`方法负责启动虚拟机,并根据传进来的类名,获取到对应的类,并执行该类的`main`方法。在`app_main.cpp`的`main`方法中,我们传进来的是`ZygoteInit`类, +所以执行的就是`ZygoteInit`的`main`方法。 + +```c++ +//frameworks/base/core/jni/AndroidRuntime.cpp +void AndroidRuntime::start(const char* className, const Vector& options, bool zygote) +{ //... + JniInvocation jni_invocation; + jni_invocation.Init(NULL); + JNIEnv* env; + //启动虚拟机 + if (startVm(&mJavaVM, &env, zygote) != 0) { + return; + } + onVmCreated(env); //启动虚拟机后的回调 + //... + + //将className中的. 替换为/ + char* slashClassName = toSlashClassName(className); + //寻找类 + jclass startClass = env->FindClass(slashClassName); + if (startClass == NULL) { + ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); + /* keep going */ + } else { + //获取main方法 + jmethodID startMeth = env->GetStaticMethodID(startClass, "main", + "([Ljava/lang/String;)V"); + if (startMeth == NULL) { + ALOGE("JavaVM unable to find main() in '%s'\n", className); + /* keep going */ + } else { + //调用main方法 + env->CallStaticVoidMethod(startClass, startMeth, strArray); + +#if 0 + if (env->ExceptionCheck()) + threadExitUncaughtException(env); +#endif + } + } + //... +} +``` + + +### **main()** + +在`ZygoteInit`的`main`方法中,主要做了四件事情: + +* 注册一个Socket + +* 预加载各种资源 + +* 启动SystemServer + +* 进入循环,等待AMS请求创建新的进程 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +private static final String SOCKET_NAME_ARG = "--socket-name=" +public static void main(String[] argv) { + ZygoteServer zygoteServer = null; + + // Mark zygote start. This ensures that thread creation will throw + // an error. + ZygoteHooks.startZygoteNoThreadCreation(); + + // Zygote goes into its own process group. + try { + Os.setpgid(0, 0); + } catch (ErrnoException ex) { + throw new RuntimeException("Failed to setpgid(0,0)", ex); + } + + Runnable caller; + try { + // Store now for StatsLogging later. + final long startTime = SystemClock.elapsedRealtime(); + final boolean isRuntimeRestarted = "1".equals( + SystemProperties.get("sys.boot_completed")); + + String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing"; + TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag, + Trace.TRACE_TAG_DALVIK); + bootTimingsTraceLog.traceBegin("ZygoteInit"); + RuntimeInit.preForkInit(); + + boolean startSystemServer = false; + String zygoteSocketName = "zygote"; + String abiList = null; + boolean enableLazyPreload = false; + for (int i = 1; i < argv.length; i++) { + if ("start-system-server".equals(argv[i])) { + startSystemServer = true; + } else if ("--enable-lazy-preload".equals(argv[i])) { + enableLazyPreload = true; + } else if (argv[i].startsWith(ABI_LIST_ARG)) { + abiList = argv[i].substring(ABI_LIST_ARG.length()); + } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { + zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length()); + } else { + throw new RuntimeException("Unknown command line argument: " + argv[i]); + } + } + //判断zygote的socketName是否是zygote + final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME); + if (!isRuntimeRestarted) { + if (isPrimaryZygote) { + FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, + BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__ZYGOTE_INIT_START, + startTime); + } else if (zygoteSocketName.equals(Zygote.SECONDARY_SOCKET_NAME)) { + FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED, + BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SECONDARY_ZYGOTE_INIT_START, + startTime); + } + } + + if (abiList == null) { + throw new RuntimeException("No ABI list supplied."); + } + + // In some configurations, we avoid preloading resources and classes eagerly. + // In such cases, we will preload things prior to our first fork. + if (!enableLazyPreload) { + bootTimingsTraceLog.traceBegin("ZygotePreload"); + EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, + SystemClock.uptimeMillis()); + //预加载 + preload(bootTimingsTraceLog); + EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, + SystemClock.uptimeMillis()); + bootTimingsTraceLog.traceEnd(); // ZygotePreload + } + + // Do an initial gc to clean up after startup + bootTimingsTraceLog.traceBegin("PostZygoteInitGC"); + gcAndFinalize(); + bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC + + bootTimingsTraceLog.traceEnd(); // ZygoteInit + + Zygote.initNativeState(isPrimaryZygote); + + ZygoteHooks.stopZygoteNoThreadCreation(); + //创建ZygoteServer + zygoteServer = new ZygoteServer(isPrimaryZygote); + + if (startSystemServer) { + //创建SystemServer + Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); + + // {@code r == null} in the parent (zygote) process, and {@code r != null} in the + // child (system_server) process. + if (r != null) { + r.run(); + return; + } + } + + Log.i(TAG, "Accepting command socket connections"); + + // The select loop returns early in the child process after a fork and + // loops forever in the zygote. + //执行runSelectLoop方法 + caller = zygoteServer.runSelectLoop(abiList); + } catch (Throwable ex) { + Log.e(TAG, "System zygote died with exception", ex); + throw ex; + } finally { + if (zygoteServer != null) { + zygoteServer.closeServerSocket(); + } + } + + // We're in the child process and have exited the select loop. Proceed to execute the + // command. + if (caller != null) { + caller.run(); + } +} +``` + + + +## **注册Socket** + + +### ZygoteServer构造函数 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteServer.java +ZygoteServer(boolean isPrimaryZygote) { + mUsapPoolEventFD = Zygote.getUsapPoolEventFD(); + + if (isPrimaryZygote) { + //调用Zygote的createManagedSocketFromInitSocket方法创建Socket + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME); + mUsapPoolSocket = + Zygote.createManagedSocketFromInitSocket( + Zygote.USAP_POOL_PRIMARY_SOCKET_NAME); + } else { + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME); + + mUsapPoolSocket = + Zygote.createManagedSocketFromInitSocket( + Zygote.USAP_POOL_SECONDARY_SOCKET_NAME); + } + + mUsapPoolSupported = true; + fetchUsapPoolPolicyProps(); +} +``` + + +### 创建Socket + +Zygote的createManagedSocketFromInitSocket方法负责创建Socket + +```java +//frameworks/base/core/java/com/android/internal/os/Zygote.java +static LocalServerSocket createManagedSocketFromInitSocket(String socketName) { + int fileDesc; + //拼接socket名字 + final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; + + try { + String env = System.getenv(fullSocketName); + fileDesc = Integer.parseInt(env); + } catch (RuntimeException ex) { + throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex); + } + + try { + FileDescriptor fd = new FileDescriptor(); + fd.setInt$(fileDesc); + return new LocalServerSocket(fd); + } catch (IOException ex) { + throw new RuntimeException( + "Error building socket from file descriptor: " + fileDesc, ex); + } +} +``` + + +## **启动SystemServer** + + +### **forkSystemServer()** + +forkSystemServer()会调用Zygote的forkSystemServer()方法fork一个SystemServer进程。并调用handleSystemServerProcess去启动SystemServer类。 + + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +private static Runnable forkSystemServer(String abiList, String socketName, + ZygoteServer zygoteServer) { + //... + //创建数组,保存启动SystemServer的启动参数 + /* Hardcoded command line to start the system server */ + String[] args = { + "--setuid=1000", + "--setgid=1000", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023," + + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011", + "--capabilities=" + capabilities + "," + capabilities, + "--nice-name=system_server", + "--runtime-args", + "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, + "com.android.server.SystemServer",//要启动的类 + }; + ZygoteArguments parsedArgs; + + int pid; + + try { + ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args); + try { + parsedArgs = ZygoteArguments.getInstance(commandBuffer); + } catch (EOFException e) { + throw new AssertionError("Unexpected argument error for forking system server", e); + } + commandBuffer.close(); + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); + + if (Zygote.nativeSupportsMemoryTagging()) { + /* The system server has ASYNC MTE by default, in order to allow + * system services to specify their own MTE level later, as you + * can't re-enable MTE once it's disabled. */ + String mode = SystemProperties.get("arm64.memtag.process.system_server", "async"); + if (mode.equals("async")) { + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_ASYNC; + } else if (mode.equals("sync")) { + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_SYNC; + } else if (!mode.equals("off")) { + /* When we have an invalid memory tag level, keep the current level. */ + parsedArgs.mRuntimeFlags |= Zygote.nativeCurrentTaggingLevel(); + Slog.e(TAG, "Unknown memory tag level for the system server: \"" + mode + "\""); + } + } else if (Zygote.nativeSupportsTaggedPointers()) { + /* Enable pointer tagging in the system server. Hardware support for this is present + * in all ARMv8 CPUs. */ + parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI; + } + + /* Enable gwp-asan on the system server with a small probability. This is the same + * policy as applied to native processes and system apps. */ + parsedArgs.mRuntimeFlags |= Zygote.GWP_ASAN_LEVEL_LOTTERY; + + if (shouldProfileSystemServer()) { + parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER; + } + + /* Request to fork the system server process */ + //fork一个新进程 + pid = Zygote.forkSystemServer( + parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, + parsedArgs.mRuntimeFlags, + null, + parsedArgs.mPermittedCapabilities, + parsedArgs.mEffectiveCapabilities); + } catch (IllegalArgumentException ex) { + throw new RuntimeException(ex); + } + + /* For child process */ + if (pid == 0) { + if (hasSecondZygote(abiList)) { + waitForSecondaryZygote(socketName); + } + + zygoteServer.closeServerSocket(); + //处理SystemServer + return handleSystemServerProcess(parsedArgs); + } + + return null; +} +``` + + +### **forkSystemServer()** + +```java +//frameworks/base/core/java/com/android/internal/os/Zygote.java +static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { + ZygoteHooks.preFork(); + + int pid = nativeForkSystemServer( + uid, gid, gids, runtimeFlags, rlimits, + permittedCapabilities, effectiveCapabilities); + + // Set the Java Language thread priority to the default value for new apps. + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + + ZygoteHooks.postForkCommon(); + return pid; +} +``` + + +### **handleSystemServerProcess()** + +handleSystemServerProcess()会调用ZygoteInit的zygoteInit方法 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) { + // set umask to 0077 so new files and directories will default to owner-only permissions. + Os.umask(S_IRWXG | S_IRWXO); + + if (parsedArgs.mNiceName != null) { + Process.setArgV0(parsedArgs.mNiceName); + } + + final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); + if (systemServerClasspath != null) { + // Capturing profiles is only supported for debug or eng builds since selinux normally + // prevents it. + if (shouldProfileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) { + try { + Log.d(TAG, "Preparing system server profile"); + prepareSystemServerProfile(systemServerClasspath); + } catch (Exception e) { + Log.wtf(TAG, "Failed to set up system server profile", e); + } + } + } + + if (parsedArgs.mInvokeWith != null) { + String[] args = parsedArgs.mRemainingArgs; + // If we have a non-null system server class path, we'll have to duplicate the + // existing arguments and append the classpath to it. ART will handle the classpath + // correctly when we exec a new process. + if (systemServerClasspath != null) { + String[] amendedArgs = new String[args.length + 2]; + amendedArgs[0] = "-cp"; + amendedArgs[1] = systemServerClasspath; + System.arraycopy(args, 0, amendedArgs, 2, args.length); + args = amendedArgs; + } + + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, + VMRuntime.getCurrentInstructionSet(), null, args); + + throw new IllegalStateException("Unexpected return from WrapperInit.execApplication"); + } else { + ClassLoader cl = getOrCreateSystemServerClassLoader(); + if (cl != null) { + Thread.currentThread().setContextClassLoader(cl); + } + + /* + * Pass the remaining arguments to SystemServer. + */ + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mDisabledCompatChanges, + parsedArgs.mRemainingArgs, cl); + } + + /* should never reach here */ +} +``` + + +### **zygoteInit()** + +ZygoteInit的zygoteInit()负责调用指定类的main方法。 + +```java +//frameworks/base/core/java/com/android/internal/os/RuntimeInit.java +public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, + String[] argv, ClassLoader classLoader) { + if (RuntimeInit.DEBUG) { + Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); + } + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit"); + RuntimeInit.redirectLogStreams(); + + RuntimeInit.commonInit(); + ZygoteInit.nativeZygoteInit(); + return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, + classLoader); +} +``` + + +### nativeZygoteInit() + +```java +//frameworks/base/core/jni/AndroidRuntime.cpp +static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz) +{ + gCurRuntime->onZygoteInit(); +} +``` + + + +### onZygoteInit() + +```java +//frameworks/base/cmds/app_process/app_main.cpp +virtual void onZygoteInit() +{ //打开驱动设备 + sp proc = ProcessState::self(); + ALOGV("App process: starting thread pool.\n"); + //创建一个新的binder线程,不断进行talkWithDriver(). + proc->startThreadPool(); +} +``` + + +### **applicationInit()** + +```java +//frameworks/base/core/java/com/android/internal/os/RuntimeInit.java +private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) + throws ZygoteInit.MethodAndArgsCaller { + // If the application calls System.exit(), terminate the process + // immediately without running any shutdown hooks. It is not possible to + // shutdown an Android application gracefully. Among other things, the + // Android runtime shutdown hooks close the Binder driver, which can cause + // leftover running threads to crash before the process actually exits. + nativeSetExitWithoutCleanup(true); + + // We want to be fairly aggressive about heap utilization, to avoid + // holding on to a lot of memory that isn't needed. + VMRuntime.getRuntime().setTargetHeapUtilization(0.75f); + VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); + + final Arguments args; + try { + args = new Arguments(argv); + } catch (IllegalArgumentException ex) { + Slog.e(TAG, ex.getMessage()); + // let the process exit + return; + } + + // The end of of the RuntimeInit event (see #zygoteInit). + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + + // Remaining arguments are passed to the start class's static main + invokeStaticMain(args.startClass, args.startArgs, classLoader); +} +``` + + +### **invokeStaticMain()** + +```java +/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java +private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader) + throws ZygoteInit.MethodAndArgsCaller { + Class cl; + + try { + cl = Class.forName(className, true, classLoader); + } catch (ClassNotFoundException ex) { + throw new RuntimeException( + "Missing class when invoking static main " + className, + ex); + } + + Method m; + try {//反射获取main方法 + m = cl.getMethod("main", new Class[] { String[].class }); + } catch (NoSuchMethodException ex) { + throw new RuntimeException( + "Missing static main on " + className, ex); + } catch (SecurityException ex) { + throw new RuntimeException( + "Problem getting static main on " + className, ex); + } + + int modifiers = m.getModifiers(); + if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) { + throw new RuntimeException( + "Main method is not public and static on " + className); + } + + /* + * This throw gets caught in ZygoteInit.main(), which responds + * by invoking the exception's run() method. This arrangement + * clears up all the stack frames that were required in setting + * up the process. + */ + throw new ZygoteInit.MethodAndArgsCaller(m, argv); +} +``` + + +## **进入等待状态** + + +### **runSelectLoop()** + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteServer.java +Runnable runSelectLoop(String abiList) { + ArrayList socketFDs = new ArrayList<>(); + ArrayList peers = new ArrayList<>(); + + socketFDs.add(mZygoteSocket.getFileDescriptor()); + peers.add(null); + + mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; + + while (true) { + fetchUsapPoolPolicyPropsWithMinInterval(); + mUsapPoolRefillAction = UsapPoolRefillAction.NONE; + + int[] usapPipeFDs = null; + StructPollfd[] pollFDs; + + // Allocate enough space for the poll structs, taking into account + // the state of the USAP pool for this Zygote (could be a + // regular Zygote, a WebView Zygote, or an AppZygote). + if (mUsapPoolEnabled) { + usapPipeFDs = Zygote.getUsapPipeFDs(); + pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length]; + } else { + pollFDs = new StructPollfd[socketFDs.size()]; + } + + /* + * For reasons of correctness the USAP pool pipe and event FDs + * must be processed before the session and server sockets. This + * is to ensure that the USAP pool accounting information is + * accurate when handling other requests like API deny list + * exemptions. + */ + + int pollIndex = 0; + for (FileDescriptor socketFD : socketFDs) { + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = socketFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + } + + final int usapPoolEventFDIndex = pollIndex; + + if (mUsapPoolEnabled) { + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = mUsapPoolEventFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + + // The usapPipeFDs array will always be filled in if the USAP Pool is enabled. + assert usapPipeFDs != null; + for (int usapPipeFD : usapPipeFDs) { + FileDescriptor managedFd = new FileDescriptor(); + managedFd.setInt$(usapPipeFD); + + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = managedFd; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + } + } + + + if (pollReturnValue == 0) { + // The poll returned zero results either when the timeout value has been exceeded + // or when a non-blocking poll is issued and no FDs are ready. In either case it + // is time to refill the pool. This will result in a duplicate assignment when + // the non-blocking poll returns zero results, but it avoids an additional + // conditional in the else branch. + mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; + mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED; + + } else { + boolean usapPoolFDRead = false; + + while (--pollIndex >= 0) { + if ((pollFDs[pollIndex].revents & POLLIN) == 0) { + continue; + } + + if (pollIndex == 0) { + // Zygote server socket + //创建ZygoteConnection + ZygoteConnection newPeer = acceptCommandPeer(abiList); + peers.add(newPeer); + socketFDs.add(newPeer.getFileDescriptor()); + } else if (pollIndex < usapPoolEventFDIndex) { + // Session socket accepted from the Zygote server socket + + try { + ZygoteConnection connection = peers.get(pollIndex); + boolean multipleForksOK = !isUsapPoolEnabled() + && ZygoteHooks.isIndefiniteThreadSuspensionSafe(); + final Runnable command = + connection.processCommand(this, multipleForksOK); + + // TODO (chriswailes): Is this extra check necessary? + if (mIsForkChild) { + // We're in the child. We should always have a command to run at + // this stage if processCommand hasn't called "exec". + if (command == null) { + throw new IllegalStateException("command == null"); + } + + return command; + } else { + // We're in the server - we should never have any commands to run. + if (command != null) { + throw new IllegalStateException("command != null"); + } + + // We don't know whether the remote side of the socket was closed or + // not until we attempt to read from it from processCommand. This + // shows up as a regular POLLIN event in our regular processing + // loop. + if (connection.isClosedByPeer()) { + connection.closeSocket(); + peers.remove(pollIndex); + socketFDs.remove(pollIndex); + } + } + } catch (Exception e) { + if (!mIsForkChild) { + // We're in the server so any exception here is one that has taken + // place pre-fork while processing commands or reading / writing + // from the control socket. Make a loud noise about any such + // exceptions so that we know exactly what failed and why. + + Slog.e(TAG, "Exception executing zygote command: ", e); + + // Make sure the socket is closed so that the other end knows + // immediately that something has gone wrong and doesn't time out + // waiting for a response. + ZygoteConnection conn = peers.remove(pollIndex); + conn.closeSocket(); + + socketFDs.remove(pollIndex); + } else { + // We're in the child so any exception caught here has happened post + // fork and before we execute ActivityThread.main (or any other + // main() method). Log the details of the exception and bring down + // the process. + Log.e(TAG, "Caught post-fork exception in child process.", e); + throw e; + } + } finally { + // Reset the child flag, in the event that the child process is a child- + // zygote. The flag will not be consulted this loop pass after the + // Runnable is returned. + mIsForkChild = false; + } + + } else { + // Either the USAP pool event FD or a USAP reporting pipe. + + // If this is the event FD the payload will be the number of USAPs removed. + // If this is a reporting pipe FD the payload will be the PID of the USAP + // that was just specialized. The `continue` statements below ensure that + // the messagePayload will always be valid if we complete the try block + // without an exception. + long messagePayload; + + try { + byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES]; + int readBytes = + Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length); + + if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) { + DataInputStream inputStream = + new DataInputStream(new ByteArrayInputStream(buffer)); + + messagePayload = inputStream.readLong(); + } else { + Log.e(TAG, "Incomplete read from USAP management FD of size " + + readBytes); + continue; + } + } catch (Exception ex) { + if (pollIndex == usapPoolEventFDIndex) { + Log.e(TAG, "Failed to read from USAP pool event FD: " + + ex.getMessage()); + } else { + Log.e(TAG, "Failed to read from USAP reporting pipe: " + + ex.getMessage()); + } + + continue; + } + + if (pollIndex > usapPoolEventFDIndex) { + Zygote.removeUsapTableEntry((int) messagePayload); + } + + usapPoolFDRead = true; + } + } + + if (usapPoolFDRead) { + int usapPoolCount = Zygote.getUsapPoolCount(); + + if (usapPoolCount < mUsapPoolSizeMin) { + // Immediate refill + mUsapPoolRefillAction = UsapPoolRefillAction.IMMEDIATE; + } else if (mUsapPoolSizeMax - usapPoolCount >= mUsapPoolRefillThreshold) { + // Delayed refill + mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis(); + } + } + } + + if (mUsapPoolRefillAction != UsapPoolRefillAction.NONE) { + int[] sessionSocketRawFDs = + socketFDs.subList(1, socketFDs.size()) + .stream() + .mapToInt(FileDescriptor::getInt$) + .toArray(); + + final boolean isPriorityRefill = + mUsapPoolRefillAction == UsapPoolRefillAction.IMMEDIATE; + + final Runnable command = + fillUsapPool(sessionSocketRawFDs, isPriorityRefill); + + if (command != null) { + return command; + } else if (isPriorityRefill) { + // Schedule a delayed refill to finish refilling the pool. + mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis(); + } + } + } +} +``` + + +## 接收数据 + + +### **acceptCommandPeer()** + + +```java +private static ZygoteConnection acceptCommandPeer(String abiList) { + try { + return new ZygoteConnection(sServerSocket.accept(), abiList); + } catch (IOException ex) { + throw new RuntimeException( + "IOException during accept()", ex); + } +} +``` + + + +### ZygoteConnection构造函数 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java +ZygoteConnection(LocalSocket socket, String abiList) throws IOException { + //赋值 + mSocket = socket; + this.abiList = abiList; + + mSocketOutStream + = new DataOutputStream(socket.getOutputStream()); + + mSocketReader = new BufferedReader( + new InputStreamReader(socket.getInputStream()), 256); + + mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS); + try { + peer = mSocket.getPeerCredentials(); + } catch (IOException ex) { + Log.e(TAG, "Cannot read peer credentials", ex); + throw ex; + } +} +``` + + +### **processCommand()** + +processCommand负责从socket读取数据,并调用Zygote的forkAndSpecialize方法创建新进程。 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java +Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { + ZygoteArguments parsedArgs; + + try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) { + while (true) { + try { + parsedArgs = ZygoteArguments.getInstance(argBuffer); + // Keep argBuffer around, since we need it to fork. + } catch (IOException ex) { + throw new IllegalStateException("IOException on command socket", ex); + } + //... + if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote + || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { + // Continue using old code for now. TODO: Handle these cases in the other path. + pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits, + parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, + fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, + parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList, + parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs, + parsedArgs.mBindMountAppStorageDirs); + + try { + if (pid == 0) { + // in child + zygoteServer.setForkChild(); + + zygoteServer.closeServerSocket(); + IoUtils.closeQuietly(serverPipeFd); + serverPipeFd = null; + //调用handleChildProc + return handleChildProc(parsedArgs, childPipeFd, + parsedArgs.mStartChildZygote); + } else { + // In the parent. A pid < 0 indicates a failure and will be handled in + // handleParentProc. + IoUtils.closeQuietly(childPipeFd); + childPipeFd = null; + handleParentProc(pid, serverPipeFd); + return null; + } + } finally { + IoUtils.closeQuietly(childPipeFd); + IoUtils.closeQuietly(serverPipeFd); + } + } else { + ZygoteHooks.preFork(); + Runnable result = Zygote.forkSimpleApps(argBuffer, + zygoteServer.getZygoteSocketFileDescriptor(), + peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName); + if (result == null) { + // parent; we finished some number of forks. Result is Boolean. + // We already did the equivalent of handleParentProc(). + ZygoteHooks.postForkCommon(); + // argBuffer contains a command not understood by forksimpleApps. + continue; + } else { + // child; result is a Runnable. + zygoteServer.setForkChild(); + Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary? + return result; + } + } + } + } + throw new AssertionError("Shouldn't get here"); +} +``` + +`zygote`需要为每个新启动的应用程序生成该自己独立的进程。不过`runOnce`并没有直接使用fork来完成这一工作,而是调用了`forkAndSpecialize`。另外,新创建的进程中一定需要运行应用程序本身的代码,这一部分工作是在`handleChildProc`中展开的。 + +执行完上述的任务后,父进程还需要做一些清尾工作才算“大功告成”。包括:将子进程加入进程组;正确关闭文件;调用方返回结果值等。 + + +### handleChildProc() + +```java +private Runnable handleChildProc(ZygoteArguments parsedArgs, + FileDescriptor pipeFd, boolean isZygote) { + /* + * By the time we get here, the native code has closed the two actual Zygote + * socket connections, and substituted /dev/null in their place. The LocalSocket + * objects still need to be closed properly. + */ + + closeSocket(); + + Zygote.setAppProcessName(parsedArgs, TAG); + + // End of the postFork event. + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + if (parsedArgs.mInvokeWith != null) { + WrapperInit.execApplication(parsedArgs.mInvokeWith, + parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion, + VMRuntime.getCurrentInstructionSet(), + pipeFd, parsedArgs.mRemainingArgs); + + // Should not get here. + throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned"); + } else { + if (!isZygote) { + //调用ZygoteInit方法 + return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mDisabledCompatChanges, + parsedArgs.mRemainingArgs, null /* classLoader */); + } else { + return ZygoteInit.childZygoteInit( + parsedArgs.mRemainingArgs /* classLoader */); + } + } +} +``` + + +### zygoteInit() + +ZygoteInit的zygoteInit()方法和RuntimeInit的zygoteInit方法差不多。 + +```java +//frameworks/base/core/java/com/android/internal/os/ZygoteInit.java +public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, + String[] argv, ClassLoader classLoader) { + if (RuntimeInit.DEBUG) { + Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); + } + + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit"); + RuntimeInit.redirectLogStreams(); + + RuntimeInit.commonInit(); + ZygoteInit.nativeZygoteInit(); + return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, + classLoader); +} +``` + + +## fork进程分析 + + +### **forkAndSpecialize()** + +forkAndSpecialize的处理分为3个阶段,即preFork、nativeForkAndSpecialize以及postForkCommon。 + +```java +//frameworks/base/core/java/com/android/internal/os/Zygote.java +static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, + int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, + boolean isTopApp, String[] pkgDataInfoList, String[] allowlistedDataInfoList, + boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) { + //① + ZygoteHooks.preFork(); + //② + int pid = nativeForkAndSpecialize( + uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, + pkgDataInfoList, allowlistedDataInfoList, bindMountAppDataDirs, + bindMountAppStorageDirs); + if (pid == 0) { + // Note that this event ends at the end of handleChildProc, + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + // If no GIDs were specified, don't make any permissions changes based on groups. + if (gids != null && gids.length > 0) { + NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); + } + } + + // Set the Java Language thread priority to the default value for new apps. + Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + //③ + ZygoteHooks.postForkCommon(); + return pid; +} +``` + + +### **nativeForkAndSpecialize()** + +`nativeForkAndSpecialize()`是一个native方法。 + +```c++ +//frameworks/base/core/jni/com_android_internal_os_Zygote.cpp +static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( + JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, + jint debug_flags, jobjectArray rlimits, + jint mount_external, jstring se_info, jstring se_name, + jintArray fdsToClose, jstring instructionSet, jstring appDataDir) { + jlong capabilities = 0; + + // Grant CAP_WAKE_ALARM to the Bluetooth process. + // Additionally, allow bluetooth to open packet sockets so it can start the DHCP client. + // TODO: consider making such functionality an RPC to netd. + if (multiuser_get_app_id(uid) == AID_BLUETOOTH) { + capabilities |= (1LL << CAP_WAKE_ALARM); + capabilities |= (1LL << CAP_NET_RAW); + capabilities |= (1LL << CAP_NET_BIND_SERVICE); + } + + // Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock" + bool gid_wakelock_found = false; + if (gid == AID_WAKELOCK) { + gid_wakelock_found = true; + } else if (gids != NULL) { + jsize gids_num = env->GetArrayLength(gids); + ScopedIntArrayRO ar(env, gids); + if (ar.get() == NULL) { + RuntimeAbort(env, __LINE__, "Bad gids array"); + } + for (int i = 0; i < gids_num; i++) { + if (ar[i] == AID_WAKELOCK) { + gid_wakelock_found = true; + break; + } + } + } + if (gid_wakelock_found) { + capabilities |= (1LL << CAP_BLOCK_SUSPEND); + } + //创建ForkAndSpecializeCommon + return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags, + rlimits, capabilities, capabilities, mount_external, se_info, + se_name, false, fdsToClose, instructionSet, appDataDir); +} +``` + + +### **preFork()** + +```c++ +//art/runtime/native/dalvik_system_ZygoteHooks.cc +static jlong ZygoteHooks_nativePreFork(JNIEnv* env, jclass) { + Runtime* runtime = Runtime::Current(); + CHECK(runtime->IsZygote()) << "runtime instance not started with -Xzygote"; + + runtime->PreZygoteFork(); + + if (Trace::GetMethodTracingMode() != TracingMode::kTracingInactive) { + // Tracing active, pause it. + Trace::Pause(); + } + + // Grab thread before fork potentially makes Thread::pthread_key_self_ unusable. + return reinterpret_cast(ThreadForEnv(env)); +} +``` + + +### ForkAndSpecializeCommon + +```c++ +//frameworks/base/core/jni/com_android_internal_os_Zygote.cpp +// Utility routine to fork zygote and specialize the child process. +static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, + jint debug_flags, jobjectArray javaRlimits, + jlong permittedCapabilities, jlong effectiveCapabilities, + jint mount_external, + jstring java_se_info, jstring java_se_name, + bool is_system_server, jintArray fdsToClose, + jstring instructionSet, jstring dataDir) { + SetSigChldHandler(); + +#ifdef ENABLE_SCHED_BOOST + SetForkLoad(true); +#endif + + sigset_t sigchld; + sigemptyset(&sigchld); + sigaddset(&sigchld, SIGCHLD); + + // Temporarily block SIGCHLD during forks. The SIGCHLD handler might + // log, which would result in the logging FDs we close being reopened. + // This would cause failures because the FDs are not whitelisted. + // + // Note that the zygote process is single threaded at this point. + if (sigprocmask(SIG_BLOCK, &sigchld, nullptr) == -1) { + ALOGE("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_BLOCK, { SIGCHLD }) failed."); + } + + // Close any logging related FDs before we start evaluating the list of + // file descriptors. + __android_log_close(); + + // If this is the first fork for this zygote, create the open FD table. + // If it isn't, we just need to check whether the list of open files has + // changed (and it shouldn't in the normal case). + if (gOpenFdTable == NULL) { + gOpenFdTable = FileDescriptorTable::Create(); + if (gOpenFdTable == NULL) { + RuntimeAbort(env, __LINE__, "Unable to construct file descriptor table."); + } + } else if (!gOpenFdTable->Restat()) { + RuntimeAbort(env, __LINE__, "Unable to restat file descriptor table."); + } + + pid_t pid = fork();//孵化出一个新进程 + + if (pid == 0) { + // The child process. + gMallocLeakZygoteChild = 1; + + // Clean up any descriptors which must be closed immediately + DetachDescriptors(env, fdsToClose); + + // Re-open all remaining open file descriptors so that they aren't shared + // with the zygote across a fork. + if (!gOpenFdTable->ReopenOrDetach()) { + RuntimeAbort(env, __LINE__, "Unable to reopen whitelisted descriptors."); + } + + if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { + ALOGE("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_UNBLOCK, { SIGCHLD }) failed."); + } + + // Keep capabilities across UID change, unless we're staying root. + if (uid != 0) { + EnableKeepCapabilities(env); + } + + DropCapabilitiesBoundingSet(env); + + bool use_native_bridge = !is_system_server && (instructionSet != NULL) + && android::NativeBridgeAvailable(); + if (use_native_bridge) { + ScopedUtfChars isa_string(env, instructionSet); + use_native_bridge = android::NeedsNativeBridge(isa_string.c_str()); + } + if (use_native_bridge && dataDir == NULL) { + // dataDir should never be null if we need to use a native bridge. + // In general, dataDir will never be null for normal applications. It can only happen in + // special cases (for isolated processes which are not associated with any app). These are + // launched by the framework and should not be emulated anyway. + use_native_bridge = false; + ALOGW("Native bridge will not be used because dataDir == NULL."); + } + + if (!MountEmulatedStorage(uid, mount_external, use_native_bridge)) { + ALOGW("Failed to mount emulated storage: %s", strerror(errno)); + if (errno == ENOTCONN || errno == EROFS) { + // When device is actively encrypting, we get ENOTCONN here + // since FUSE was mounted before the framework restarted. + // When encrypted device is booting, we get EROFS since + // FUSE hasn't been created yet by init. + // In either case, continue without external storage. + } else { + RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage"); + } + } + + if (!is_system_server) { + int rc = createProcessGroup(uid, getpid()); + if (rc != 0) { + if (rc == -EROFS) { + ALOGW("createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?"); + } else { + ALOGE("createProcessGroup(%d, %d) failed: %s", uid, pid, strerror(-rc)); + } + } + } + + SetGids(env, javaGids); + + SetRLimits(env, javaRlimits); + + if (use_native_bridge) { + ScopedUtfChars isa_string(env, instructionSet); + ScopedUtfChars data_dir(env, dataDir); + android::PreInitializeNativeBridge(data_dir.c_str(), isa_string.c_str()); + } + + int rc = setresgid(gid, gid, gid); + if (rc == -1) { + ALOGE("setresgid(%d) failed: %s", gid, strerror(errno)); + RuntimeAbort(env, __LINE__, "setresgid failed"); + } + + rc = setresuid(uid, uid, uid); + if (rc == -1) { + ALOGE("setresuid(%d) failed: %s", uid, strerror(errno)); + RuntimeAbort(env, __LINE__, "setresuid failed"); + } + + if (NeedsNoRandomizeWorkaround()) { + // Work around ARM kernel ASLR lossage (http://b/5817320). + int old_personality = personality(0xffffffff); + int new_personality = personality(old_personality | ADDR_NO_RANDOMIZE); + if (new_personality == -1) { + ALOGW("personality(%d) failed: %s", new_personality, strerror(errno)); + } + } + + SetCapabilities(env, permittedCapabilities, effectiveCapabilities); + + SetSchedulerPolicy(env); + + const char* se_info_c_str = NULL; + ScopedUtfChars* se_info = NULL; + if (java_se_info != NULL) { + se_info = new ScopedUtfChars(env, java_se_info); + se_info_c_str = se_info->c_str(); + if (se_info_c_str == NULL) { + RuntimeAbort(env, __LINE__, "se_info_c_str == NULL"); + } + } + const char* se_name_c_str = NULL; + ScopedUtfChars* se_name = NULL; + if (java_se_name != NULL) { + se_name = new ScopedUtfChars(env, java_se_name); + se_name_c_str = se_name->c_str(); + if (se_name_c_str == NULL) { + RuntimeAbort(env, __LINE__, "se_name_c_str == NULL"); + } + } + rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str); + if (rc == -1) { + ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid, + is_system_server, se_info_c_str, se_name_c_str); + RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed"); + } + + // Make it easier to debug audit logs by setting the main thread's name to the + // nice name rather than "app_process". + if (se_info_c_str == NULL && is_system_server) { + se_name_c_str = "system_server"; + } + if (se_info_c_str != NULL) { + SetThreadName(se_name_c_str); + } + + delete se_info; + delete se_name; + + UnsetSigChldHandler(); + + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags, + is_system_server, instructionSet); + if (env->ExceptionCheck()) { + RuntimeAbort(env, __LINE__, "Error calling post fork hooks."); + } + } else if (pid > 0) { + // the parent process + +#ifdef ENABLE_SCHED_BOOST + // unset scheduler knob + SetForkLoad(false); +#endif + + // We blocked SIGCHLD prior to a fork, we unblock it here. + if (sigprocmask(SIG_UNBLOCK, &sigchld, nullptr) == -1) { + ALOGE("sigprocmask(SIG_SETMASK, { SIGCHLD }) failed: %s", strerror(errno)); + RuntimeAbort(env, __LINE__, "Call to sigprocmask(SIG_UNBLOCK, { SIGCHLD }) failed."); + } + } + return pid; +} +``` + + +## 参考 + +* [Android系统启动-zygote篇](http://gityuan.com/2016/02/13/android-zygote/) + +* [Android系统进程Zygote启动过程的源代码分析](https://blog.csdn.net/luoshengyang/article/details/6768304) + diff --git a/assets/images/animation-linear.png b/assets/images/animation-linear.png new file mode 100644 index 00000000..08bd9fc3 Binary files /dev/null and b/assets/images/animation-linear.png differ diff --git a/assets/images/constraintlayout-2.jpeg b/assets/images/constraintlayout-2.jpeg new file mode 100644 index 00000000..57298150 Binary files /dev/null and b/assets/images/constraintlayout-2.jpeg differ diff --git a/assets/images/diagram_backstack.png b/assets/images/diagram_backstack.png new file mode 100644 index 00000000..5ddadcbe Binary files /dev/null and b/assets/images/diagram_backstack.png differ diff --git a/assets/images/diagram_backstack_singletask_multiactivity.png b/assets/images/diagram_backstack_singletask_multiactivity.png new file mode 100644 index 00000000..b9e5ed48 Binary files /dev/null and b/assets/images/diagram_backstack_singletask_multiactivity.png differ diff --git a/assets/images/diagram_multiple_instances.png b/assets/images/diagram_multiple_instances.png new file mode 100644 index 00000000..606f5409 Binary files /dev/null and b/assets/images/diagram_multiple_instances.png differ diff --git a/assets/images/diagram_multitasking.png b/assets/images/diagram_multitasking.png new file mode 100644 index 00000000..ffe5c651 Binary files /dev/null and b/assets/images/diagram_multitasking.png differ diff --git a/assets/images/fragment_lifecycle.png b/assets/images/fragment_lifecycle.png new file mode 100644 index 00000000..fcaa63b6 Binary files /dev/null and b/assets/images/fragment_lifecycle.png differ diff --git a/assets/images/hierarchy-linearlayout.png b/assets/images/hierarchy-linearlayout.png new file mode 100644 index 00000000..cac4caef Binary files /dev/null and b/assets/images/hierarchy-linearlayout.png differ diff --git a/assets/images/hierarchy-relativelayout.png b/assets/images/hierarchy-relativelayout.png new file mode 100644 index 00000000..b3408e58 Binary files /dev/null and b/assets/images/hierarchy-relativelayout.png differ diff --git a/assets/images/https-1.png b/assets/images/https-1.png new file mode 100644 index 00000000..3db07dca Binary files /dev/null and b/assets/images/https-1.png differ diff --git a/assets/images/https-2.png b/assets/images/https-2.png new file mode 100644 index 00000000..590f48cd Binary files /dev/null and b/assets/images/https-2.png differ diff --git a/assets/images/intent-chooser.png b/assets/images/intent-chooser.png new file mode 100644 index 00000000..8a8d3393 Binary files /dev/null and b/assets/images/intent-chooser.png differ diff --git a/assets/images/path-5.png b/assets/images/path-5.png new file mode 100644 index 00000000..d6fef098 Binary files /dev/null and b/assets/images/path-5.png differ diff --git a/assets/images/restore_instance.png b/assets/images/restore_instance.png new file mode 100644 index 00000000..698b83a8 Binary files /dev/null and b/assets/images/restore_instance.png differ diff --git a/assets/images/service_binding_tree_lifecycle.png b/assets/images/service_binding_tree_lifecycle.png new file mode 100644 index 00000000..8b826f67 Binary files /dev/null and b/assets/images/service_binding_tree_lifecycle.png differ diff --git a/book.json b/book.json deleted file mode 100755 index 048138cc..00000000 --- a/book.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "plugins": [ - "disqus" - ], - "pluginsConfig": { - "disqus": { - "shortName": "malinkang" - } - } -} \ No newline at end of file diff --git a/cha-jian-hua/README.md b/cha-jian-hua/README.md new file mode 100644 index 00000000..7e9d11d3 --- /dev/null +++ b/cha-jian-hua/README.md @@ -0,0 +1,2 @@ +# 插件化 + diff --git a/cha-jian-hua/cha-jian-hua-kuang-jia.md b/cha-jian-hua/cha-jian-hua-kuang-jia.md new file mode 100644 index 00000000..fbdd7ad3 --- /dev/null +++ b/cha-jian-hua/cha-jian-hua-kuang-jia.md @@ -0,0 +1,4 @@ +# 插件化框架 + +* [https://github.com/Tencent/Shadow](https://github.com/Tencent/Shadow) + diff --git a/cha-jian-hua/virtualapk-fen-xi.md b/cha-jian-hua/virtualapk-fen-xi.md new file mode 100644 index 00000000..8b5bb3a7 --- /dev/null +++ b/cha-jian-hua/virtualapk-fen-xi.md @@ -0,0 +1,555 @@ +# VirtualAPK分析 + +## VirtualAPK初始化流程 + +在使用VirtualAPK之前,我们需要多VirtualAPK进行初始化,如下所示: + +```java +@Override +protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + long start = System.currentTimeMillis(); + PluginManager.getInstance(base).init(); +} +``` + +### getInstance\(\) + +双重校验锁实现单例 + +```java +private static volatile PluginManager sInstance = null; +public static PluginManager getInstance(Context base) { + if (sInstance == null) { + synchronized (PluginManager.class) { + if (sInstance == null) { + //调用createInstance创建PluginManager + sInstance = createInstance(base); + } + } + } + return sInstance; +} +``` + +### createInstance\(\) + +```java + private static PluginManager createInstance(Context context) { + try { + //获取清单文件种的meta数据 + Bundle metaData = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA) + .metaData; + + if (metaData == null) { + return new PluginManager(context); + } + + String factoryClass = metaData.getString("VA_FACTORY"); + + if (factoryClass == null) { + return new PluginManager(context); + } + //调用清单文件中Factory创建PluginManager类 + PluginManager pluginManager = Reflector.on(factoryClass).method("create", Context.class).call(context); + + if (pluginManager != null) { + Log.d(TAG, "Created a instance of " + pluginManager.getClass()); + return pluginManager; + } + + } catch (Exception e) { + Log.w(TAG, "Created the instance error!", e); + } + //清单文件中没有声明 直接调用构造函数 + return new PluginManager(context); + } +``` + +### 构造函数 + +```java +protected PluginManager(Context context) { + if (context instanceof Application) { + this.mApplication = (Application) context; + this.mContext = mApplication.getBaseContext(); + } else { + final Context app = context.getApplicationContext(); + if (app == null) { + this.mContext = context; + this.mApplication = ActivityThread.currentApplication(); + } else { + this.mApplication = (Application) app; + this.mContext = mApplication.getBaseContext(); + } + } + //创建ComponentsHandler + mComponentsHandler = createComponentsHandler(); + //hook当前线程 + hookCurrentProcess(); +} +``` + +### hookCurrentProcess\(\) + +```java +protected void hookCurrentProcess() { + hookInstrumentationAndHandler(); + hookSystemServices(); + hookDataBindingUtil(); +} +``` + +### hookInstrumentationAndHandler\(\) + +```java +protected void hookInstrumentationAndHandler() { + try { + //获取ActivityThread + ActivityThread activityThread = ActivityThread.currentActivityThread(); + //获取ActivityThread的 Instrumentation 对象 + Instrumentation baseInstrumentation = activityThread.getInstrumentation(); + //如果是在平行空间中运行,则退出 + if (baseInstrumentation.getClass().getName().contains("lbe")) { + // reject executing in paralell space, for example, lbe. + System.exit(0); + } + //创建代理对象 + final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation); + + Reflector.with(activityThread).field("mInstrumentation").set(instrumentation); + Handler mainHandler = Reflector.with(activityThread).method("getHandler").call(); + Reflector.with(mainHandler).field("mCallback").set(instrumentation); + this.mInstrumentation = instrumentation; + Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation); + } catch (Exception e) { + Log.w(TAG, e); + } +} +``` + +```java +@Override +public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + try { + cl.loadClass(className); + Log.i(TAG, String.format("newActivity[%s]", className)); + + } catch (ClassNotFoundException e) { + //没有找到类,说明是插件中的类 + ComponentName component = PluginUtil.getComponent(intent); + + if (component == null) { + return newActivity(mBase.newActivity(cl, className, intent)); + } + + String targetClassName = component.getClassName(); + Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName)); + //获取插件 + LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component); + + if (plugin == null) { + // Not found then goto stub activity. + boolean debuggable = false; + try { + Context context = this.mPluginManager.getHostContext(); + debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Throwable ex) { + + } + + if (debuggable) { + throw new ActivityNotFoundException("error intent: " + intent.toURI()); + } + + Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class); + return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent)); + } + + Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); + activity.setIntent(intent); + + // for 4.1+ + Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources()); + + return newActivity(activity); + } + + return newActivity(mBase.newActivity(cl, className, intent)); +} +``` + +```java +//从Intent中获取 +public static ComponentName getComponent(Intent intent) { + if (intent == null) { + return null; + } + //Intent中isPlugin值为true说明是插件中的intent + if (isIntentFromPlugin(intent)) { + //插件的类名和包名通过Intent传递过来 + //奇怪,宿主工程并不知道插件里面的类名,如何传递Activity的类名 + return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE), + intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY)); + } + + return intent.getComponent(); +} +``` + +### hookSystemServices + +```java +protected void hookSystemServices() { + try { + Singleton defaultSingleton; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get(); + } else { + defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get(); + } + IActivityManager origin = defaultSingleton.get(); + //创建IActivityManager的代理类 + IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class }, + createActivityManagerProxy(origin)); + //将代理类设置给mInstance + // Hook IActivityManager from ActivityManagerNative + Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); + + if (defaultSingleton.get() == activityManagerProxy) { + this.mActivityManager = activityManagerProxy; + Log.d(TAG, "hookSystemServices succeed : " + mActivityManager); + } + } catch (Exception e) { + Log.w(TAG, e); + } +} +``` + +## VirtualAPK的的加载流程 + +VirtualAPK对于加载的APK文件没有额外的约束,只需要添加VirtualAPK的插件进行编译,如下所示: + +```java +apply plugin: 'com.didi.virtualapk.plugin' + +virtualApk { + packageId = 0x6f // the package id of Resources. + targetHost = '../../VirtualAPK/app' // the path of application module in host project. + applyHostMapping = true //optional, default value: true. +} +``` + +然后就可以调用PluginManager直接加载编译完成的APK,被加载的APK在PluginManager里是一个LoadedPlugin对象,VirtualAPK通过这些LoadedPlugin对象来管理APK,这些APK也可以 想在手机里直接安装的App一样运行。 + +```java +PluginManager pluginManager = PluginManager.getInstance(base); +File apk = new File(Environment.getExternalStorageDirectory(), "Test.apk"); +try { + pluginManager.loadPlugin(apk); + Log.i(TAG, "Loaded plugin from apk: " + apk); +} catch (Exception e) { + e.printStackTrace(); +} +``` + +APK的加载流程如下图所示: + +![](../.gitbook/assets/image%20%2888%29.png) + + + +```java +public void loadPlugin(File apk) throws Exception { + if (null == apk) { + throw new IllegalArgumentException("error : apk is null."); + } + + if (!apk.exists()) { + // throw the FileNotFoundException by opening a stream. + InputStream in = new FileInputStream(apk); + in.close(); + } + //创建LoadedPlugin + LoadedPlugin plugin = createLoadedPlugin(apk); + + if (null == plugin) { + throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath()); + } + //包名为key 插件为value + this.mPlugins.put(plugin.getPackageName(), plugin); + //回调 + synchronized (mCallbacks) { + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onAddedLoadedPlugin(plugin); + } + } +} +``` + +```java +//直接new一个LoadedPlugin对象 +protected LoadedPlugin createLoadedPlugin(File apk) throws Exception { + return new LoadedPlugin(this, this.mContext, apk); +} +``` + +这里调用了LoadedPlugin的create\(\)方法去构建一个LoadedPlugin对象,所以的初始化操作都是在LoadedPlugin的构造方法里完成的,如下所示: + +```java +public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception { + this.mPluginManager = pluginManager; + this.mHostContext = context; + this.mLocation = apk.getAbsolutePath(); + //1.调用PackageParser解析APK,获取PackageParse.Package对象 + this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK); + this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData; + //2.构建PackageInfo对象 + this.mPackageInfo = new PackageInfo(); + this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo; + this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath(); + + if (Build.VERSION.SDK_INT >= 28 + || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview + try { + this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures; + } catch (Throwable e) { + PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); + this.mPackageInfo.signatures = info.signatures; + } + } else { + this.mPackageInfo.signatures = this.mPackage.mSignatures; + } + + this.mPackageInfo.packageName = this.mPackage.packageName; + if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) { + throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName); + } + this.mPackageInfo.versionCode = this.mPackage.mVersionCode; + this.mPackageInfo.versionName = this.mPackage.mVersionName; + this.mPackageInfo.permissions = new PermissionInfo[0]; + //3.构建PluginPackageManager对象 + this.mPackageManager = createPluginPackageManager(); + this.mPluginContext = createPluginContext(null); + this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR); + this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath(); + //4.构建Resources对象 + this.mResources = createResources(context, getPackageName(), apk); + //5.构建ClassLoader对象 + this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader()); + //6.拷贝so库 + tryToCopyNativeLib(apk); + + // Cache instrumentations + //7.缓存Instrumentation对象 + Map instrumentations = new HashMap(); + for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) { + instrumentations.put(instrumentation.getComponentName(), instrumentation.info); + } + this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations); + this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]); + + // Cache activities + //8.缓存APK里的Activity信息 + Map activityInfos = new HashMap(); + for (PackageParser.Activity activity : this.mPackage.activities) { + activity.info.metaData = activity.metaData; + activityInfos.put(activity.getComponentName(), activity.info); + } + this.mActivityInfos = Collections.unmodifiableMap(activityInfos); + this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]); + + // Cache services + //9.缓存APK里的Service信息 + Map serviceInfos = new HashMap(); + for (PackageParser.Service service : this.mPackage.services) { + serviceInfos.put(service.getComponentName(), service.info); + } + this.mServiceInfos = Collections.unmodifiableMap(serviceInfos); + this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]); + + // Cache providers + //10.缓存APK里的Content Provider信息 + Map providers = new HashMap(); + Map providerInfos = new HashMap(); + for (PackageParser.Provider provider : this.mPackage.providers) { + providers.put(provider.info.authority, provider.info); + providerInfos.put(provider.getComponentName(), provider.info); + } + this.mProviders = Collections.unmodifiableMap(providers); + this.mProviderInfos = Collections.unmodifiableMap(providerInfos); + this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]); + + // Register broadcast receivers dynamically + //11.静态的广播转换为动态的 + Map receivers = new HashMap(); + for (PackageParser.Activity receiver : this.mPackage.receivers) { + receivers.put(receiver.getComponentName(), receiver.info); + + BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); + for (PackageParser.ActivityIntentInfo aii : receiver.intents) { + this.mHostContext.registerReceiver(br, aii); + } + } + this.mReceiverInfos = Collections.unmodifiableMap(receivers); + this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]); + + // try to invoke plugin's application + invokeApplication(); +} +``` + +整个LoadedPlugin对象构建的过程就是去解析APK里的组件信息,并缓存起来,具体说来: + +1. 调用PackageParser解析APK,获取PackageParser.Package对象。 +2. 构建PackageInfo对象。 +3. 构建PluginPackageManager对象。 +4. 构建Resouces对象。 +5. 构建ClassLoader对象。 +6. 拷贝so库。 +7. 缓存Instrumentation对象。 +8. 缓存APK里的Activity信息。 +9. 缓存APK里的Service信息。 +10. 缓存APK里的Content Provider信息。 +11. 将静态的广播转为动态的。 + +我们接着来看没有在宿主App的Manifest里注册的四大组件时如何被启动起来的。 + + +## VirtualAPK启动组件的流程 + +### Activity + +前面我们说过在初始化VirtualAPK的过程中使用自定义的VAInstrumentation在ActivityThread中替换掉了原生的Instrumentation对象,来达到hook到Activity启动流程的目的,绕开Instrumentation 启动Activity的校验流程。 + +那么VirtualAPK是如何绕过系统校验的呢? + + +Virtual是采用占坑的方式来绕过校验的,它在库里的Manifest文件里定义了占坑的文件,如下所示: + +```java + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +A、B、C、D分别代表standard、singleTop、singleTask和singleInstance四种启动模式。 + +VirtualAPK制造一些假的Activity替身在Manifest文件提前进行注册占坑,在启动真正的Activity时候,再将Activity填到坑里,以完成启动Activity。我们来看看具体的是实现流程: + +1. execStartActivity\(\) +2. realExecStartActivity\(\) +3. newActivity\(\) +4. callActivityOnCreate\(\) + +以上四个方法都是启动Activity的过程中必经的四个方法。 + +```java +protected void injectIntent(Intent intent) { + //将隐式Intent转换为显式Intent + mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent); + // null component is an implicitly intent + if (intent.getComponent() != null) { + // resolve intent with Stub Activity if needed + this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); + } +} +``` + +```java +public Intent transformIntentToExplicitAsNeeded(Intent intent) { + ComponentName component = intent.getComponent(); + if (component == null + || component.getPackageName().equals(mContext.getPackageName())) { + ResolveInfo info = mPluginManager.resolveActivity(intent); + if (info != null && info.activityInfo != null) { + component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name); + intent.setComponent(component); + } + } + + return intent; +} +``` + +```java +public void markIntentIfNeeded(Intent intent) { + if (intent.getComponent() == null) { + return; + } + + String targetPackageName = intent.getComponent().getPackageName(); + String targetClassName = intent.getComponent().getClassName(); + // search map and return specific launchmode stub activity + if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) { + intent.putExtra(Constants.KEY_IS_PLUGIN, true); + intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName); + intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName); + dispatchStubActivity(intent); + } +} +``` + +1. 将隐式Intent转换为显式Intent,Virtual是通过intent.setClassName\(this, "com.guoxiaoxing.plugin.MainActivity"\);这种 //方式来启动Activity的,这里将包名封装成真正的ComponentName对象。 + + + +## 参考 + +* [https://juejin.im/post/6844903561931784200](https://juejin.im/post/6844903561931784200) + + + diff --git a/components/README.md b/components/README.md new file mode 100644 index 00000000..5bce0d33 --- /dev/null +++ b/components/README.md @@ -0,0 +1,11 @@ +# 组件 + + + +## BroadcastReceiver + +* [广播概览](https://developer.android.com/guide/components/broadcasts) +* [隐式广播例外情况](https://developer.android.com/guide/components/broadcast-exceptions) + + + diff --git a/components/activities.md b/components/activities.md new file mode 100644 index 00000000..391f3e94 --- /dev/null +++ b/components/activities.md @@ -0,0 +1,38 @@ +# Activity + +## 生命周期 + +* [了解 Activity 生命周期](https://developer.android.com/guide/components/activities/activity-lifecycle) +* [面试题: 如何判断Activity是否在运行?](https://www.jianshu.com/p/f8a0c43b3dfe) +* [显示Dialog会调用onPause方法吗?](https://stackoverflow.com/questions/7240916/android-under-what-circumstances-would-a-dialog-appearing-cause-onpause-to-be) + +> 普通Dialog显示不会调用onPause,除非是Activity创建的Dialog + +* [面试官:为什么 Activity.finish\(\) 之后 10s 才 onDestroy ?](https://juejin.cn/post/6898588053451833351) + +## 启动模式 + +> 官方文档关于singleTask描述错误,可以看深入讲解Android中Activity launchMode +> +> singleTask: +> +> A和C都为standard +> +> B为singleTask +> +> A->B->C A、B、C在同一个栈内 +> +> B设置不同的taskAffinity +> +> A->B->C A在一个栈内 B、C在同一个栈内 +> +> C设置为singleTask、taskAffinity与A一致 +> +> A->B->C A、C在一个栈内 B在同一个栈内 + +* [了解任务和返回堆栈](https://developer.android.com/guide/components/activities/tasks-and-back-stack) +* [深入讲解Android中Activity launchMode](https://droidyue.com/blog/2015/08/16/dive-into-android-activity-launchmode/) +* [Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance](https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en) +* [Activity的四种launchMode](http://blog.csdn.net/liuhe688/article/details/6754323) +* [Activity的task相关](http://blog.csdn.net/liuhe688/article/details/6761337) + diff --git a/components/activities/activity-jian-jie.md b/components/activities/activity-jian-jie.md new file mode 100644 index 00000000..80cecd66 --- /dev/null +++ b/components/activities/activity-jian-jie.md @@ -0,0 +1,2 @@ +# Activity简介 + diff --git a/components/activities/le-jie-activity-sheng-ming-zhou-qi.md b/components/activities/le-jie-activity-sheng-ming-zhou-qi.md new file mode 100644 index 00000000..8aff147d --- /dev/null +++ b/components/activities/le-jie-activity-sheng-ming-zhou-qi.md @@ -0,0 +1,2 @@ +# 了解 Activity 生命周期 + diff --git a/components/broadcasts.md b/components/broadcasts.md new file mode 100644 index 00000000..b3827055 --- /dev/null +++ b/components/broadcasts.md @@ -0,0 +1,2 @@ +# Broadcasts + diff --git a/components/contentprovider.md b/components/contentprovider.md new file mode 100644 index 00000000..1e44caa0 --- /dev/null +++ b/components/contentprovider.md @@ -0,0 +1,2 @@ +# ContentProvider + diff --git a/components/fragment.md b/components/fragment.md new file mode 100644 index 00000000..0fea27fd --- /dev/null +++ b/components/fragment.md @@ -0,0 +1,533 @@ +# Fragment + +Fragment 表示 Activity 中的行为或用户界面部分。您可以将多个片段组合在一个 Activity 中来构建多窗格 UI,以及在多个 Activity 中重复使用某个片段。您可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除片段(有点像您可以在不同 Activity 中重复使用的“子 Activity”)。 + +片段必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。 例如,当 Activity 暂停时,其中的所有片段也会暂停;当 Activity 被销毁时,所有片段也会被销毁。 不过,当 Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个片段,如添加或移除它们。 当您执行此类片段事务时,您也可以将其添加到由 Activity 管理的返回栈 — Activity 中的每个返回栈条目都是一条已发生片段事务的记录。 返回栈让用户可以通过按返回按钮撤消片段事务(后退)。 + +当您将片段作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 ViewGroup 内部,并且片段会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明片段,将其作为 元素插入您的 Activity 布局中,或者通过将其添加到某个现有 ViewGroup,利用应用代码进行插入。不过,片段并非必须成为 Activity 布局的一部分; + +本文描述如何在开发您的应用时使用片段,包括将片段添加到 Activity 返回栈时如何保持其状态、如何与 Activity 及 Activity 中的其他片段共享事件、如何为 Activity 的操作栏发挥作用等等。 + +## 1. 设计原理 + +Android 在 Android 3.0(API 级别 11)中引入了片段,主要是为了给大屏幕(如平板电脑)上更加动态和灵活的 UI 设计提供支持。由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 UI 组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。 + +例如,新闻应用可以使用一个片段在左侧显示文章列表,使用另一个片段在右侧显示文章 — 两个片段并排显示在一个 Activity 中,每个片段都具有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。 因此,用户不需要使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,如图 1 中的平板电脑布局所示。 + +您应该将每个片段都设计为可重复使用的模块化 Activity 组件。也就是说,由于每个片段都会通过各自的生命周期回调来定义其自己的布局和行为,您可以将一个片段加入多个 Activity,因此,您应该采用可复用式设计,避免直接从某个片段直接操纵另一个片段。 这特别重要,因为模块化片段让您可以通过更改片段的组合方式来适应不同的屏幕尺寸。 在设计可同时支持平板电脑和手机的应用时,您可以在不同的布局配置中重复使用您的片段,以根据可用的屏幕空间优化用户体验。 例如,在手机上,如果不能在同一 Activity 内储存多个片段,可能必须利用单独片段来实现单窗格 UI。 + +![图 1. 有关由片段定义的两个 UI 模块如何适应不同设计的示例:通过组合成一个 Activity 来适应平板电脑设计,通过单独片段来适应手机设计。](<../.gitbook/assets/fragments (1) (1) (1) (1) (1) (1).png>) + +例如 — 仍然以新闻应用为例 — 在平板电脑尺寸的设备上运行时,该应用可以在 Activity A 中嵌入两个片段。 不过,在手机尺寸的屏幕上,没有足以储存两个片段的空间,因此Activity A 只包括用于显示文章列表的片段,当用户选择文章时,它会启动Activity B,其中包括用于阅读文章的第二个片段。 因此,应用可通过重复使用不同组合的片段来同时支持平板电脑和手机,如图 1 所示。 + +## 2. 创建Fragment + +要想创建片段,您必须创建 Fragment 的子类(或已有其子类)。Fragment 类的代码与 Activity 非常相似。它包含与 Activity 类似的回调方法,如 onCreate()、onStart()、onPause() 和 onStop()。实际上,如果您要将现有 Android 应用转换为使用片段,可能只需将代码从 Activity 的回调方法移入片段相应的回调方法中。 + +通常,您至少应实现以下生命周期方法: + +* onCreate():系统会在创建片段时调用此方法。您应该在实现内初始化您想在片段暂停或停止后恢复时保留的必需片段组件。 +* onCreateView():系统会在片段首次绘制其用户界面时调用此方法。 要想为您的片段绘制 UI,您从此方法中返回的 View 必须是片段布局的根视图。如果片段未提供 UI,您可以返回 null。 +* onPause():系统将此方法作为用户离开片段的第一个信号(但并不总是意味着此片段会被销毁)进行调用。 您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。 + + 大多数应用都应该至少为每个片段实现这三个方法,但您还应该使用几种其他回调方法来处理片段生命周期的各个阶段。 处理片段生命周期部分对所有生命周期回调方法做了更详尽的阐述。 + +![图 2. 片段的生命周期(其 Activity 运行时)。](<../.gitbook/assets/fragment\_lifecycle (1).png>) + +您可能还想扩展几个子类,而不是 Fragment 基类: + +* DialogFragment:显示浮动对话框。使用此类创建对话框可有效地替代使用 Activity 类中的对话框帮助程序方法,因为您可以将片段对话框纳入由 Activity 管理的片段返回栈,从而使用户能够返回清除的片段。 +* ListFragment:显示由适配器(如 SimpleCursorAdapter)管理的一系列项目,类似于 ListActivity。它提供了几种管理列表视图的方法,如用于处理点击事件的 onListItemClick() 回调。 +* PreferenceFragment:以列表形式显示 Preference 对象的层次结构,类似于 PreferenceActivity。这在为您的应用创建“设置” Activity 时很有用处。 + +### 2.1 添加用户界面 + +fragment通常用作 Activity 用户界面的一部分,将其自己的布局融入 Activity。 + +要想为片段提供布局,您必须实现 onCreateView() 回调方法,Android 系统会在`fragment`需要绘制其布局时调用该方法。您对此方法的实现返回的 View 必须是片段布局的根视图。 + +> 注:如果您的片段是 ListFragment 的子类,则默认实现会从 onCreateView() 返回一个 ListView,因此您无需实现它。 + +要想从 onCreateView() 返回布局,您可以通过 XML 中定义的布局资源来扩展布局。为帮助您执行此操作,onCreateView() 提供了一个 LayoutInflater 对象。 + +例如,以下这个 Fragment 子类从 example\_fragment.xml 文件加载布局: + +```java +public static class ExampleFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.example_fragment, container, false); + } +} +``` + +传递至 onCreateView() 的 container 参数是您的片段布局将插入到的父 ViewGroup(来自 Activity 的布局)。savedInstanceState 参数是在恢复fragment时,提供上一fragment实例相关数据的 Bundle。 + +inflate() 方法带有三个参数: + +* 您想要扩展的布局的资源 ID; +* 将作为扩展布局父项的 ViewGroup。传递 container 对系统向扩展布局的根视图(由其所属的父视图指定)应用布局参数具有重要意义; +* 指示是否应该在扩展期间将扩展布局附加至 ViewGroup(第二个参数)的布尔值。(在本例中,其值为 false,因为系统已经将扩展布局插入`container` — 传递 true 值会在最终布局中创建一个多余的视图组。) + + 现在,您已经了解了如何创建提供布局的片段。接下来,您需要将该片段添加到您的 Activity 中。 + +### 2.2 向 Activity 添加片段 + +通常,片段向宿主 Activity 贡献一部分 UI,作为 Activity 总体视图层次结构的一部分嵌入到 Activity 中。可以通过两种方式向 Activity 布局添加片段: + +#### 1. 在 Activity 的布局文件内声明片段 + +在本例中,您可以将片段当作视图来为其指定布局属性。 例如,以下是一个具有两个片段的 Activity 的布局文件: + +```markup + + + + + +``` + +中的 android:name 属性指定要在布局中实例化的 Fragment 类。 当系统创建此 Activity 布局时,会实例化在布局中指定的每个片段,并为每个片段调用 onCreateView() 方法,以检索每个片段的布局。系统会直接插入片段返回的 View 来替代 元素。 + +> 注:每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其移除)。 可以通过三种方式为片段提供 ID: +> +> * 为 android:id 属性提供唯一 ID。 +> * 为 android:tag 属性提供唯一字符串。 +> * 如果您未给以上两个属性提供值,系统会使用容器视图的 ID。 + +#### 2.或者通过编程方式将片段添加到某个现有 ViewGroup + +您可以在 Activity 运行期间随时将片段添加到 Activity 布局中。您只需指定要将片段放入哪个 ViewGroup。 要想在您的 Activity 中执行片段事务(如添加、移除或替换片段),您必须使用 FragmentTransaction 中的 API。您可以像下面这样从 Activity 获取一个 FragmentTransaction 实例: + +```java +FragmentManager fragmentManager = getFragmentManager(); +FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); +``` + +然后,您可以使用 add() 方法添加一个片段,指定要添加的片段以及将其插入哪个视图。例如: + +```java +ExampleFragment fragment = new ExampleFragment(); +fragmentTransaction.add(R.id.fragment_container, fragment); +fragmentTransaction.commit(); +``` + +传递到 add() 的第一个参数是 ViewGroup,即应该放置片段的位置,由资源 ID 指定,第二个参数是要添加的片段。 一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。 + +#### 添加没有 UI 的片段 + +上例展示了如何向您的 Activity 添加片段以提供 UI。不过,您还可以使用片段为 Activity 提供后台行为,而不显示额外 UI。 + +要想添加没有 UI 的片段,请使用 add(Fragment, String) 从 Activity 添加片段(为片段提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加片段,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 onCreateView() 的调用。因此,您不需要实现该方法。 + +并非只能为非 UI 片段提供字符串标记 — 您也可以为具有 UI 的片段提供字符串标记 — 但如果片段没有 UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 findFragmentByTag()。 + +如需查看将没有 UI 的片段用作后台工作线程的示例 Activity,请参阅 FragmentRetainInstance.java 示例,该示例包括在 SDK 示例(通过 Android SDK 管理器提供)中,以 /APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java 形式位于您的系统中。 + +## 3. 管理片段 + +要想管理您的 Activity 中的片段,您需要使用 FragmentManager。要想获取它,请从您的 Activity 调用 getFragmentManager()。 + +您可以使用 FragmentManager 执行的操作包括: + +通过 findFragmentById()(对于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段。 通过 popBackStack()(模拟用户发出的返回命令)将片段从返回栈中弹出。 通过 addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。 如需了解有关这些方法以及其他方法的详细信息,请参阅 FragmentManager 类文档。 + +如上文所示,您也可以使用 FragmentManager 打开一个 FragmentTransaction,通过它来执行某些事务,如添加和移除片段。 + +## 4. 执行片段事务 + +在 Activity 中使用片段的一大优点是,可以根据用户行为通过它们执行添加、移除、替换以及其他操作。 您提交给 Activity 的每组更改都称为事务,您可以使用 FragmentTransaction 中的 API 来执行一项事务。您也可以将每个事务保存到由 Activity 管理的返回栈内,从而让用户能够回退片段更改(类似于回退 Activity)。 + +您可以像下面这样从 FragmentManager 获取一个 FragmentTransaction 实例: + +```java +FragmentManager fragmentManager = getFragmentManager(); +FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); +``` + +每个事务都是您想要同时执行的一组更改。您可以使用 add()、remove() 和 replace() 等方法为给定事务设置您想要执行的所有更改。然后,要想将事务应用到 Activity,您必须调用 commit()。 + +不过,在您调用 commit() 之前,您可能想调用 addToBackStack(),以将事务添加到片段事务返回栈。 该返回栈由 Activity 管理,允许用户通过按返回按钮返回上一片段状态。 + +例如,以下示例说明了如何将一个片段替换成另一个片段,以及如何在返回栈中保留先前状态: + +```java +// Create new fragment and transaction +Fragment newFragment = new ExampleFragment(); +FragmentTransaction transaction = getFragmentManager().beginTransaction(); + +// Replace whatever is in the fragment_container view with this fragment, +// and add the transaction to the back stack +transaction.replace(R.id.fragment_container, newFragment); +transaction.addToBackStack(null); + +// Commit the transaction +transaction.commit(); +``` + +在上例中,newFragment 会替换目前在 R.id.fragment\_container ID 所标识的布局容器中的任何片段(如有)。通过调用 addToBackStack() 可将替换事务保存到返回栈,以便用户能够通过按返回按钮撤消事务并回退到上一片段。 + +如果您向事务添加了多个更改(如又一个 add() 或 remove()),并且调用了 addToBackStack(),则在调用 commit() 前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。 + +向 FragmentTransaction 添加更改的顺序无关紧要,不过: + +* 您必须最后调用 commit() +* 如果您要向同一容器添加多个片段,则您添加片段的顺序将决定它们在视图层次结构中的出现顺序 + +如果您没有在执行移除片段的事务时调用 addToBackStack(),则事务提交时该片段会被销毁,用户将无法回退到该片段。 不过,如果您在删除片段时调用了 addToBackStack(),则系统会停止该片段,并在用户回退时将其恢复。 + +> 提示:对于每个片段事务,您都可以通过在提交前调用 setTransition() 来应用过渡动画。 + +。 + +> 。 + +## 5. 与 Activity 通信 + +尽管 Fragment 是作为独立于 Activity 的对象实现,并且可在多个 Activity 内使用,但片段的给定实例会直接绑定到包含它的 Activity。 + +具体地说,片段可以通过 getActivity() 访问 Activity 实例,并轻松地执行在 Activity 布局中查找视图等任务。 + +View listView = getActivity().findViewById(R.id.list); 同样地,您的 Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通过从 FragmentManager 获取对 Fragment 的引用来调用片段中的方法。例如: + +ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example\_fragment); 创建对 Activity 的事件回调 在某些情况下,您可能需要通过片段与 Activity 共享事件。执行此操作的一个好方法是,在片段内定义一个回调接口,并要求宿主 Activity 实现它。 当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他片段共享这些信息。 + +例如,如果一个新闻应用的 Activity 有两个片段 — 一个用于显示文章列表(片段 A),另一个用于显示文章(片段 B)— 那么片段 A 必须在列表项被选定后告知 Activity,以便它告知片段 B 显示该文章。 在本例中,OnArticleSelectedListener 接口在片段 A 内声明: + +```java +public static class FragmentA extends ListFragment { + ... + // Container Activity must implement this interface + public interface OnArticleSelectedListener { + public void onArticleSelected(Uri articleUri); + } + ... +} +``` + +然后,该片段的宿主 Activity 会实现 OnArticleSelectedListener 接口并替代 onArticleSelected(),将来自片段 A 的事件通知片段 B。为确保宿主 Activity 实现此接口,片段 A 的 onAttach() 回调方法(系统在向 Activity 添加片段时调用的方法)会通过转换传递到 onAttach() 中的 Activity 来实例化 OnArticleSelectedListener 的实例: + +```java +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() + " must implement OnArticleSelectedListener"); + } + } + ... +} +``` + +如果 Activity 未实现接口,则片段会引发 ClassCastException。实现时,mListener 成员会保留对 Activity 的 OnArticleSelectedListener 实现的引用,以便片段 A 可以通过调用 OnArticleSelectedListener 接口定义的方法与 Activity 共享事件。例如,如果片段 A 是 ListFragment 的一个扩展,则用户每次点击列表项时,系统都会调用片段中的 onListItemClick(),然后该方法会调用 onArticleSelected() 以与 Activity 共享事件: + +```java +public static class FragmentA extends ListFragment { + OnArticleSelectedListener mListener; + ... + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + // Append the clicked item's row ID with the content provider Uri + Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id); + // Send the event and Uri to the host activity + mListener.onArticleSelected(noteUri); + } + ... +} +``` + +传递到 onListItemClick() 的 id 参数是被点击项的行 ID,即 Activity(或其他片段)用来从应用的 ContentProvider 获取文章的 ID。 + +内容提供程序文档中提供了有关内容提供程序用法的更多详情。 + +### 向应用栏添加项目 + +您的片段可以通过实现 onCreateOptionsMenu() 向 Activity 的选项菜单(并因此向应用栏)贡献菜单项。 + +。 + +您之后从片段添加到选项菜单的任何菜单项都将追加到现有菜单项之后。 选定菜单项时,片段还会收到对 onOptionsItemSelected() 的回调。 + +您还可以通过调用 registerForContextMenu(),在片段布局中注册一个视图来提供上下文菜单。用户打开上下文菜单时,片段会收到对 onCreateContextMenu() 的调用。当用户选择某个菜单项时,片段会收到对 onContextItemSelected() 的调用。 + +> 注:尽管您的片段会收到与其添加的每个菜单项对应的菜单项选定回调,但当用户选择菜单项时,Activity 会首先收到相应的回调。 如果 Activity 对菜单项选定回调的实现不会处理选定的菜单项,则系统会将事件传递到片段的回调。 这适用于选项菜单和上下文菜单。 + +## 6. 处理片段生命周期 + +![图 3. Activity 生命周期对片段生命周期的影响。](https://github.com/malinkang/AndroidNote/tree/0934e36146072be0da558bff25ca14b172cdc515/assets/images/activity\_fragment\_lifecycle.png) + +管理片段生命周期与管理 Activity 生命周期很相似。和 Activity 一样,片段也以三种状态存在: + +* 继续:片段在运行中的 Activity 中可见。 +* 暂停:另一个 Activity 位于前台并具有焦点,但此片段所在的 Activity 仍然可见(前台 Activity 部分透明,或未覆盖整个屏幕)。 +* 停止:片段不可见。宿主 Activity 已停止,或片段已从 Activity 中移除,但已添加到返回栈。 停止片段仍然处于活动状态(系统会保留所有状态和成员信息)。 不过,它对用户不再可见,如果 Activity 被终止,它也会被终止。 + +同样与 Activity 一样,假使 Activity 的进程被终止,而您需要在重建 Activity 时恢复片段状态,您也可以使用 Bundle 保留片段的状态。您可以在片段的 onSaveInstanceState() 回调期间保存状态,并可在 onCreate()、onCreateView() 或 onActivityCreated() 期间恢复状态。如需了解有关保存状态的详细信息,请参阅 Activity 文档。 + +Activity 生命周期与片段生命周期之间的最显著差异在于它们在其各自返回栈中的存储方式。 默认情况下,Activity 停止时会被放入由系统管理的 Activity 返回栈(以便用户通过返回按钮回退到 Activity,任务和返回栈对此做了阐述)。不过,仅当您在移除片段的事务执行期间通过调用 addToBackStack() 显式请求保存实例时,系统才会将片段放入由宿主 Activity 管理的返回栈。 + +在其他方面,管理片段生命周期与管理 Activity 生命周期非常相似。 因此,管理 Activity 生命周期的做法同样适用于片段。 但您还需要了解 Activity 的生命周期对片段生命周期的影响。 + +> 注意:如需 Fragment 内的某个 Context 对象,可以调用 getActivity()。但要注意,请仅在片段附加到 Activity 时调用 getActivity()。如果片段尚未附加,或在其生命周期结束期间分离,则 getActivity() 将返回 null。 + +### 与 Activity 生命周期协调一致 + +片段所在的 Activity 的生命周期会直接影响片段的生命周期,其表现为,Activity 的每次生命周期回调都会引发每个片段的类似回调。 例如,当 Activity 收到 onPause() 时,Activity 中的每个片段也会收到 onPause()。 + +不过,片段还有几个额外的生命周期回调,用于处理与 Activity 的唯一交互,以执行构建和销毁片段 UI 等操作。 这些额外的回调方法是: + +* onAttach():在片段已与 Activity 关联时调用(Activity 传递到此方法内)。 +* onCreateView():调用它可创建与片段关联的视图层次结构。 +* onActivityCreated():在 Activity 的 onCreate() 方法已返回时调用。 +* onDestroyView():在移除与片段关联的视图层次结构时调用。 +* onDetach():在取消片段与 Activity 的关联时调用。 + + 图 3 图示说明了受其宿主 Activity 影响的片段生命周期流。在该图中,您可以看到 Activity 的每个连续状态如何决定片段可以收到的回调方法。 例如,当 Activity 收到其 onCreate() 回调时,Activity 中的片段只会收到 onActivityCreated() 回调。 + +一旦 Activity 达到恢复状态,您就可以随意向 Activity 添加片段和移除其中的片段。 因此,只有当 Activity 处于恢复状态时,片段的生命周期才能独立变化。 + +不过,当 Activity 离开恢复状态时,片段会在 Activity 的推动下再次经历其生命周期。 + +## 7. 示例 + +为了将本文阐述的所有内容融会贯通,以下提供了一个示例,其中的 Activity 使用两个片段来创建一个双窗格布局。 下面的 Activity 包括两个片段:一个用于显示莎士比亚戏剧标题列表,另一个用于从列表中选定戏剧时显示其摘要。 此外,它还展示了如何根据屏幕配置提供不同的片段配置。 + +注:FragmentLayout.java 中提供了此 Activity 的完整源代码。 + +主 Activity 会在 onCreate() 期间以常规方式应用布局: + +```java +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.fragment_layout); +} +``` + +应用的布局为 fragment\_layout.xml: + +```markup + + + + + + + +``` + +通过使用此布局,系统可在 Activity 加载布局时立即实例化 TitlesFragment(列出戏剧标题),而 FrameLayout(用于显示戏剧摘要的片段所在位置)则会占用屏幕右侧的空间,但最初处于空白状态。 正如您将在下文所见的那样,用户从列表中选择某个项目后,系统才会将片段放入 FrameLayout。 + +不过,并非所有屏幕配置都具有足够的宽度,可以并排显示戏剧列表和摘要。 因此,以上布局仅用于横向屏幕配置(布局保存在 res/layout-land/fragment\_layout.xml)。 + +因此,当屏幕纵向显示时,系统会应用以下布局(保存在 res/layout/fragment\_layout.xml): + +```markup + + + +``` + +此布局仅包括 TitlesFragment。这意味着,当设备纵向显示时,只有戏剧标题列表可见。 因此,当用户在此配置中点击某个列表项时,应用会启动一个新 Activity 来显示摘要,而不是加载另一个片段。 + +接下来,您可以看到如何在片段类中实现此目的。第一个片段是 TitlesFragment,它显示莎士比亚戏剧标题列表。该片段扩展了 ListFragment,并依靠它来处理大多数列表视图工作。 + +当您检查此代码时,请注意,用户点击列表项时可能会出现两种行为:系统可能会创建并显示一个新片段,从而在同一 Activity 中显示详细信息(将片段添加到 FrameLayout),也可能会启动一个新 Activity(在该 Activity 中可显示片段),具体取决于这两个布局中哪一个处于活动状态。 + +```java +public static class TitlesFragment extends ListFragment { + boolean mDualPane; + int mCurCheckPosition = 0; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Populate list with our static array of titles. + setListAdapter(new ArrayAdapter(getActivity(), + android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES)); + + // Check to see if we have a frame in which to embed the details + // fragment directly in the containing UI. + View detailsFrame = getActivity().findViewById(R.id.details); + mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE; + + if (savedInstanceState != null) { + // Restore last state for checked position. + mCurCheckPosition = savedInstanceState.getInt("curChoice", 0); + } + + if (mDualPane) { + // In dual-pane mode, the list view highlights the selected item. + getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); + // Make sure our UI is in the correct state. + showDetails(mCurCheckPosition); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("curChoice", mCurCheckPosition); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + showDetails(position); + } + + /** + * Helper function to show the details of a selected item, either by + * displaying a fragment in-place in the current UI, or starting a + * whole new activity in which it is displayed. + */ + void showDetails(int index) { + mCurCheckPosition = index; + + if (mDualPane) { + // We can display everything in-place with fragments, so update + // the list to highlight the selected item and show the data. + getListView().setItemChecked(index, true); + + // Check what fragment is currently shown, replace if needed. + DetailsFragment details = (DetailsFragment) + getFragmentManager().findFragmentById(R.id.details); + if (details == null || details.getShownIndex() != index) { + // Make new fragment to show this selection. + details = DetailsFragment.newInstance(index); + + // Execute a transaction, replacing any existing fragment + // with this one inside the frame. + FragmentTransaction ft = getFragmentManager().beginTransaction(); + if (index == 0) { + ft.replace(R.id.details, details); + } else { + ft.replace(R.id.a_item, details); + } + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.commit(); + } + + } else { + // Otherwise we need to launch a new activity to display + // the dialog fragment with selected text. + Intent intent = new Intent(); + intent.setClass(getActivity(), DetailsActivity.class); + intent.putExtra("index", index); + startActivity(intent); + } + } +} +``` + +第二个片段 DetailsFragment 显示从 TitlesFragment 的列表中选择的项目的戏剧摘要: + +```java +public static class DetailsFragment extends Fragment { + /** + * Create a new instance of DetailsFragment, initialized to + * show the text at 'index'. + */ + public static DetailsFragment newInstance(int index) { + DetailsFragment f = new DetailsFragment(); + + // Supply index input as an argument. + Bundle args = new Bundle(); + args.putInt("index", index); + f.setArguments(args); + + return f; + } + + public int getShownIndex() { + return getArguments().getInt("index", 0); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + if (container == null) { + // We have different layouts, and in one of them this + // fragment's containing frame doesn't exist. The fragment + // may still be created from its saved state, but there is + // no reason to try to create its view hierarchy because it + // won't be displayed. Note this is not needed -- we could + // just run the code below, where we would create and return + // the view hierarchy; it would just never be used. + return null; + } + + ScrollView scroller = new ScrollView(getActivity()); + TextView text = new TextView(getActivity()); + int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 4, getActivity().getResources().getDisplayMetrics()); + text.setPadding(padding, padding, padding, padding); + scroller.addView(text); + text.setText(Shakespeare.DIALOGUE[getShownIndex()]); + return scroller; + } +} +``` + +从 TitlesFragment 类中重新调用,如果用户点击某个列表项,且当前布局“根本不”包括 R.id.details 视图(即 DetailsFragment 所属视图),则应用会启动 DetailsActivity Activity 以显示该项目的内容。 + +以下是 DetailsActivity,它简单地嵌入了 DetailsFragment,以在屏幕为纵向时显示所选的戏剧摘要: + +```java +public static class DetailsActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE) { + // If the screen is now in landscape mode, we can show the + // dialog in-line with the list so we don't need this activity. + finish(); + return; + } + + if (savedInstanceState == null) { + // During initial setup, plug in the details fragment. + DetailsFragment details = new DetailsFragment(); + details.setArguments(getIntent().getExtras()); + getFragmentManager().beginTransaction().add(android.R.id.content, details).commit(); + } + } +} +``` + +请注意,如果配置为横向,则此 Activity 会自行完成,以便主 Activity 可以接管并沿 TitlesFragment 显示 DetailsFragment。如果用户在纵向显示时启动 DetailsActivity,但随后旋转为横向(这会重启当前 Activity),就可能出现这种情况。 + +## 参考 + +* [大话Fragment管理](http://blog.csdn.net/mobilexu/article/details/11711865) diff --git a/components/intent.md b/components/intent.md new file mode 100644 index 00000000..f6e7b807 --- /dev/null +++ b/components/intent.md @@ -0,0 +1,360 @@ +# Intent + +Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个: + +* 启动 Activity: + + Activity 表示应用中的一个屏幕。通过将 Intent 传递给 `startActivity()`,您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。 + + 如果您希望在 Activity 完成后收到结果,请调用 `startActivityForResult()`。在 Activity 的 onActivityResult() 回调中,您的 Activity 将结果作为单独的 Intent 对象接收。 +* 启动服务: + + Service 是一个不使用用户界面而在后台执行操作的组件。通过将 Intent 传递给 `startService()`,您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。 + + 如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。如需了解详细信息,请参阅服务指南。 +* 传递广播: + + 广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 `sendBroadcast()`、`sendOrderedBroadcast()` 或 `sendStickyBroadcast()`,您可以将广播传递给其他应用。 + +## Intent 类型 + +Intent 分为两种类型: + +* 显式 Intent:按名称(完全限定类名)指定要启动的组件。 通常,您会在自己的应用中使用显式 Intent 来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,启动新 Activity 以响应用户操作,或者启动服务以在后台下载文件。 +* 隐式 Intent :不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。 例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用在地图上显示指定的位置。 + +创建显式 Intent 启动 Activity 或服务时,系统将立即启动 Intent 对象中指定的应用组件。 + +创建隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。 + +Intent 过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent 类型。 例如,通过为 Activity 声明 Intent 过滤器,您可以使其他应用能够直接使用某一特定类型的 Intent 启动 Activity。同样,如果您没有为 Activity 声明任何 Intent 过滤器,则 Activity 只能通过显式 Intent 启动。 + +![图 1. 隐式 Intent 如何通过系统传递以启动其他 Activity 的图解:\[1\] Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。\[2\] Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。 找到匹配项之后,\[3\] 该系统通过调用匹配 Activity(Activity B)的 onCreate() 方法并将其传递给 Intent,以此启动匹配 Activity。](<../.gitbook/assets/intent-filters@2x (1) (1).png>) + +> 注意:为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会引发异常。 + +## 构建Intent + +Intent 对象携带了 Android 系统用来确定要启动哪个组件的信息(例如,准确的组件名称或应当接收该 Intent 的组件类别),以及收件人组件为了正确执行操作而使用的信息(例如,要采取的操作以及要处理的数据)。 + +Intent 中包含的主要信息如下: + +* 组件名称 + +要启动的组件名称。 这是可选项,但也是构建显式 Intent 的一项重要信息,这意味着 Intent 应当仅传递给由组件名称定义的应用组件。 如果没有组件名称,则 Intent 是隐式的,且系统将根据其他 Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 Intent。 因此,如需在应用中启动特定的组件,则应指定该组件的名称。 + +> 注:启动 Service 时,您应始终指定组件名称。 否则,您无法确定哪项服务会响应 Intent,且用户无法看到哪项服务已启动。 + +Intent 的这一字段是一个 ComponentName 对象,您可以使用目标组件的完全限定类名指定此对象,其中包括应用的软件包名称。 例如, com.example.ExampleActivity。您可以使用 `setComponent()`、`setClass()`、`setClassName()` 或 `Intent 构造函数`设置组件名称。 + +* 操作 + +指定要执行的通用操作(例如,“查看”或“选取”)的字符串。 对于广播 Intent,这是指已发生且正在报告的操作。操作在很大程度上决定了其余 Intent 的构成,特别是数据和 extra 中包含的内容。 + +您可以指定自己的操作,供 Intent 在您的应用内使用(或者供其他应用在您的应用中调用组件)。但是,您通常应该使用由 Intent 类或其他框架类定义的操作常量。以下是一些用于启动 Activity 的常见操作: + +* ACTION\_VIEW + +如果您拥有一些某项 Activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查看的地址),请使用 Intent 将此操作与 startActivity() 结合使用。 + +* ACTION\_SEND + +这也称为“共享”Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 将此操作与 startActivity() 结合使用。 有关更多定义通用操作的常量,请参阅 Intent 类参考文档。 其他操作在 Android 框架中的其他位置定义。例如,对于在系统的设置应用中打开特定屏幕的操作,将在 Settings 中定义。 + +您可以使用 `setAction()` 或 `Intent 构造函数`为 Intent 指定操作。 + +如果定义自己的操作,请确保将应用的软件包名称作为前缀。 例如: + +static final String ACTION\_TIMETRAVEL = "com.example.action.TIMETRAVEL"; + +* 数据 + +引用待操作数据和/或该数据 MIME 类型的 URI(Uri 对象)。提供的数据类型通常由 Intent 的操作决定。例如,如果操作是 ACTION\_EDIT,则数据应包含待编辑文档的 URI。 创建 Intent 时,除了指定 URI 以外,指定数据类型(其 MIME 类型)往往也很重要。例如,能够显示图像的 Activity 可能无法播放音频文件,即便 URI 格式十分类似时也是如此。因此,指定数据的 MIME 类型有助于 Android 系统找到接收 Intent 的最佳组件。但有时,MIME 类型可以从 URI 中推断得出,特别当数据是 content: URI 时尤其如此。这表明数据位于设备中,且由 ContentProvider 控制,这使得数据 MIME 类型对系统可见。 + +要仅设置数据 URI,请调用 setData()。 要仅设置 MIME 类型,请调用 setType()。如有必要,您可以使用 setDataAndType() 同时显式设置二者。 + +> 注意:若要同时设置 URI 和 MIME 类型,请勿调用 setData() 和 setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。 + +* 类别 + +一个包含应处理 Intent 组件类型的附加信息的字符串。 您可以将任意数量的类别描述放入一个 Intent 中,但大多数 Intent 均不需要类别。 以下是一些常见类别: + +* CATEGORY\_BROWSABLE + + 目标 Activity 允许本身通过网络浏览器启动,以显示链接引用的数据,如图像或电子邮件。 +* CATEGORY\_LAUNCHER + + 该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。 + +您可以使用 addCategory() 指定类别。 + +以上列出的这些属性(组件名称、操作、数据和类别)表示 Intent 的既定特征。 通过读取这些属性,Android 系统能够解析应当启动哪个应用组件。 + +但是,Intent 也有可能会一些携带不影响其如何解析为应用组件的信息。 Intent 还可以提供: + +* Extra + +携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。 您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将Bundle 插入 Intent 中。 + +例如,使用 ACTION\_SEND 创建用于发送电子邮件的 Intent 时,可以使用 EXTRA\_EMAIL 键指定“目标”收件人,并使用 EXTRA\_SUBJECT 键指定“主题”。 + +Intent 类将为标准化的数据类型指定多个 EXTRA\_\* 常量。如需声明自己的 extra 键(对于应用接收的 Intent),请确保将应用的软件包名称作为前缀。 例如: + +```java +static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"; +``` + +* 标志 + +在 Intent 类中定义的、充当 Intent 元数据的标志。 + +。 + +### 显式 Intent 示例 + +显式 Intent 是指用于启动某个特定应用组件(例如,应用中的某个特定 Activity 或服务)的 Intent。 要创建显式 Intent,请为 Intent 对象定义组件名称 — Intent 的所有其他属性均为可选属性。 + +例如,如果在应用中构建了一个名为 DownloadService、旨在从网页下载文件的服务,则可使用以下代码启动该服务: + +```java +// Executed in an Activity, so 'this' is the Context +// The fileUrl is a string URL, such as "http://www.example.com/image.png" +Intent downloadIntent = new Intent(this, DownloadService.class); +downloadIntent.setData(Uri.parse(fileUrl)); +startService(downloadIntent); +``` + +Intent(Context, Class) 构造函数分别为应用和组件提供 Context 和 Class 对象。因此,此 Intent 将显式启动该应用中的 DownloadService 类。 + +### 隐式 Intent 示例 + +隐式 Intent 指定能够在可以执行相应操作的设备上调用任何应用的操作。 如果您的应用无法执行该操作而其他应用可以,且您希望用户选取要使用的应用,则使用隐式 Intent 非常有用。 + +例如,如果您希望用户与他人共享您的内容,请使用 ACTION\_SEND 操作创建 Intent,并添加指定共享内容的 extra。 使用该 Intent 调用 startActivity() 时,用户可以选取共享内容所使用的应用。 + +注意:用户可能没有任何应用处理您发送到 startActivity() 的隐式 Intent。如果出现这种情况,则调用将会失败,且应用会崩溃。要验证 Activity 是否会接收 Intent,请对 Intent 对象调用 resolveActivity()。如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()。 如果结果为空,则不应使用该 Intent。如有可能,您应停用发出该 Intent 的功能。 + +```java +// Create the text message with a string +Intent sendIntent = new Intent(); +sendIntent.setAction(Intent.ACTION_SEND); +sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage); +sendIntent.setType("text/plain"); + +// Verify that the intent will resolve to an activity +if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(sendIntent); +} +``` + +注:在这种情况下,系统并没有使用 URI,但已声明 Intent 的数据类型,用于指定 extra 携带的内容。 + +调用 startActivity() 时,系统将检查已安装的所有应用,确定哪些应用能够处理这种 Intent(即:含 ACTION\_SEND 操作并携带“text/plain”数据的 Intent )。 如果只有一个应用能够处理,则该应用将立即打开并为其提供 Intent。 如果多个 Activity 接受 Intent,则系统将显示一个对话框,使用户能够选取要使用的应用。 + +### 强制使用应用选择器 + +如果有多个应用响应隐式 Intent,则用户可以选择要使用的应用,并将其设置为该操作的默认选项。 如果用户可能希望今后一直使用相同的应用执行某项操作(例如,打开网页时,用户往往倾向于仅使用一种网络浏览器),则这一点十分有用。 + +但是,如果多个应用可以响应 Intent,且用户可能希望每次使用不同的应用,则应采用显式方式显示选择器对话框。 选择器对话框每次都会要求用户选择用于操作的应用(用户无法为该操作选择默认应用)。 例如,当应用使用 ACTION\_SEND 操作执行“共享”时,用户根据目前的状况可能需要使用另一不同的应用,因此应当始终使用选择器对话框,如图 2 中所示。 + +要显示选择器,请使用 createChooser() 创建 Intent,并将其传递给 startActivity()。例如: + +Intent sendIntent = new Intent(Intent.ACTION\_SEND); ... + +// Always use string resources for UI text. // This says something like "Share this photo with" String title = getResources().getString(R.string.chooser\_title); // Create intent to show the chooser dialog Intent chooser = Intent.createChooser(sendIntent, title); + +// Verify the original intent will resolve to at least one activity if (sendIntent.resolveActivity(getPackageManager()) != null) { startActivity(chooser); } 这将显示一个对话框,其中有响应传递给 createChooser() 方法的 Intent 的应用列表,并且将提供的文本用作对话框标题。 + +![图 2. 选择器对话框。](../.gitbook/assets/intent-chooser.png) + +## 接收隐式 Intent + +要公布应用可以接收哪些隐式 Intent,请在清单文件中使用 元素为每个应用组件声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 Intent 类型。 仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。 + +> 注:显式 Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器如何均是如此。 + +应用组件应当为自身可执行的每个独特作业声明单独的过滤器。例如,图像库应用中的一个 Activity 可能会有两个过滤器,分别用于查看图像和编辑图像。 当 Activity 启动时,它将检查 Intent 并根据 Intent 中的信息决定具体的行为(例如,是否显示编辑器控件)。 + +每个 Intent 过滤器均由应用清单文件中的 元素定义,并嵌套在相应的应用组件(例如, 元素)中。 在 内部,您可以使用以下三个元素中的一个或多个指定要接受的 Intent 类型: + +* 在 name 属性中,声明接受的 Intent 操作。该值必须是操作的文本字符串值,而不是类常量。 +* + +使用一个或多个指定数据 URI 各个方面(scheme、host、port、path 等)和 MIME 类型的属性,声明接受的数据类型。 + +* + +在 name 属性中,声明接受的 Intent 类别。该值必须是操作的文本字符串值,而不是类常量。 + +> 注:为了接收隐式 Intent,必须将 CATEGORY\_DEFAULT 类别包括在 Intent 过滤器中。 方法 startActivity() 和 startActivityForResult() 将按照已申明 CATEGORY\_DEFAULT 类别的方式处理所有 Intent。 如果未在 Intent 过滤器中声明此类别,则隐式 Intent 不会解析为您的 Activity。 + +例如,以下是一个使用包含 Intent 过滤器的 Activity 声明,当数据类型为文本时,系统将接收 ACTION\_SEND Intent : + +```markup + + + + + + + +``` + +您可以创建一个包括多个 、 或 实例的过滤器。创建时,仅需确定组件能够处理这些过滤器元素的任何及所有组合即可。 + +如需仅以操作、数据和类别类型的特定组合来处理多种 Intent,则需创建多个 Intent 过滤器。 + +> **限制对组件的访问** +> +> 使用 Intent 过滤器时,无法安全地防止其他应用启动组件。 尽管 Intent 过滤器将组件限制为仅响应特定类型的隐式 Intent,但如果开发者确定您的组件名称,则其他应用有可能通过使用显式 Intent 启动您的应用组件。如果必须确保只有您自己的应用才能启动您的某一组件,请针对该组件将 exported 属性设置为 "false"。 系统通过将 Intent 与所有这三个元素进行比较,根据过滤器测试隐式 Intent。 隐式 Intent 若要传递给组件,必须通过所有这三项测试。如果 Intent 甚至无法匹配其中任何一项测试,则 Android 系统不会将其传递给组件。 但是,由于一个组件可能有多个 Intent 过滤器,因此未能通过某一组件过滤器的 Intent 可能会通过另一过滤器。如需了解有关系统如何解析 Intent 的详细信息,请参阅下文的 Intent 解析部分。 +> +> 注意:为了避免无意中运行不同应用的 Service,请始终使用显式 Intent 启动您自己的服务,且不必为该服务声明 Intent 过滤器。 +> +> 注:对于所有 Activity,您必须在清单文件中声明 Intent 过滤器。但是,广播接收器的过滤器可以通过调用 registerReceiver() 动态注册。 稍后,您可以使用 unregisterReceiver() 注销该接收器。这样一来,应用便可仅在应用运行时的某一指定时间段内侦听特定的广播。 + +### 过滤器示例 + +为了更好地了解一些 Intent 过滤器的行为,我们一起来看看从社交共享应用的清单文件中截取的以下片段。 + +```markup + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +第一个 Activity MainActivity 是应用的主要入口点。当用户最初使用启动器图标启动应用时,该 Activity 将打开: + +ACTION\_MAIN 操作指示这是主要入口点,且不要求输入任何 Intent 数据。 CATEGORY\_LAUNCHER 类别指示此 Activity 的图标应放入系统的应用启动器。 如果 元素未使用 icon 指定图标,则系统将使用 元素中的图标。 这两个元素必须配对使用,Activity 才会显示在应用启动器中。 + +第二个 Activity ShareActivity 旨在便于共享文本和媒体内容。 尽管用户可以通过从 MainActivity 导航进入此 Activity,但也可以从发出隐式 Intent(与两个 Intent 过滤器之一匹配)的另一应用中直接进入 ShareActivity。 + +> 注:MIME 类型 application/vnd.google.panorama360+jpg 是一个指定全景照片的特殊数据类型,您可以使用 Google panorama API 对其进行处理。 + +## 使用PendingIntent + +待定 Intent 的主要用例包括: + +* 声明用户使用您的通知执行操作时所要执行的 Intent(Android 系统的 NotificationManager 执行 Intent)。 +* 声明用户使用您的 应用小部件执行操作时要执行的 Intent(主屏幕应用执行 Intent)。 +* 声明未来某一特定时间要执行的 Intent(Android 系统的 AlarmManager 执行 Intent)。 由于每个 Intent 对象均设计为由特定类型的应用组件(Activity、Service 或 BroadcastReceiver)进行处理,因此还必须基于相同的考虑因素创建 PendingIntent。使用待定 Intent 时,应用不会使用调用(如 startActivity())执行该 Intent。相反,通过调用相应的创建器方法创建 PendingIntent 时,您必须声明所需的组件类型: +* PendingIntent.getActivity(),适用于启动 Activity 的 Intent。 +* PendingIntent.getService(),适用于启动 Service 的 Intent。 +* PendingIntent.getBroadcast(),适用于启动 BroadcastReceiver 的 Intent。 + +除非您的应用正在从其他应用中接收待定 Intent,否则上述用于创建 PendingIntent 的方法可能是您所需的唯一 PendingIntent 方法。 + +每种方法均会提取当前的应用 Context、您要包装的 Intent 以及一个或多个指定应如何使用该 Intent 的标志(例如,是否可以多次使用该 Intent)。 + +## Intent 解析 + +当系统收到隐式 Intent 以启动 Activity 时,它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较,搜索该 Intent 的最佳 Activity: + +Intent 操作 Intent 数据(URI 和数据类型) Intent 类别 下文根据如何在应用的清单文件中声明 Intent 过滤器,描述 Intent 如何与相应的组件匹配。 + +操作测试 要指定接受的 Intent 操作,Intent 过滤器既可以不声明任何 元素,也可以声明多个此类元素。例如: + +```markup + + + + ... + +``` + +要通过此过滤器,您在 Intent 中指定的操作必须与过滤器中列出的某一操作匹配。 + +如果该过滤器未列出任何操作,则 Intent 没有任何匹配项,因此所有 Intent 均无法通过测试。 但是,如果 Intent 未指定操作,则会通过测试(只要过滤器至少包含一个操作)。 + +类别测试 要指定接受的 Intent 类别, Intent 过滤器既可以不声明任何 元素,也可以声明多个此类元素。 例如: + +```markup + + + + ... + +``` + +若要使 Intent 通过类别测试,则 Intent 中的每个类别均必须与过滤器中的类别匹配。反之则未必然,Intent 过滤器声明的类别可以超出 Intent 中指定的数量,且 Intent 仍会通过测试。 因此,不含类别的 Intent 应当始终会通过此测试,无论过滤器中声明何种类别均是如此。 + +注:Android 会自动将 CATEGORY\_DEFAULT 类别应用于传递给 startActivity() 和 startActivityForResult() 的所有隐式 Intent。因此,如需 Activity 接收隐式 Intent,则必须将 "android.intent.category.DEFAULT" 的类别包括在其 Intent 过滤器中(如上文的 示例所示)。 + +数据测试 要指定接受的 Intent 数据, Intent 过滤器既可以不声明任何 元素,也可以声明多个此类元素。 例如: + +```markup + + + + ... + +``` + +每个 元素均可指定 URI 结构和数据类型(MIME 媒体类型)。 URI 的每个部分均包含单独的 scheme、host、port 和 path 属性: + +``` +://:/ +``` + +例如: + +``` +content://com.example.project:200/folder/subfolder/etc +``` + +在此 URI 中,架构是 content,主机是 com.example.project,端口是 200,路径是 folder/subfolder/etc。 + +在 元素中,上述每个属性均为可选,但存在线性依赖关系: + +如果未指定架构,则会忽略主机。 如果未指定主机,则会忽略端口。 如果未指定架构和主机,则会忽略路径。 将 Intent 中的 URI 与过滤器中的 URI 规范进行比较时,它仅与过滤器中包含的部分 URI 进行比较。 例如: + +如果过滤器仅指定架构,则具有该架构的所有 URI 均与该过滤器匹配。 如果过滤器指定架构和权限,但未指定路径,则具有相同架构和权限的所有 URI 都会通过过滤器,无论其路径如何均是如此。 如果过滤器指定架构、权限和路径,则仅具有相同架构、权限和路径的 URI 才会通过过滤器。 注:路径规范可以包含星号通配符 (\*),因此仅需部分匹配路径名即可。 + +数据测试会将 Intent 中的 URI 和 MIME 类型与过滤器中指定的 URI 和 MIME 类型进行比较。 规则如下: + +仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 Intent 才会通过测试。 对于包含 URI 但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 Intent,仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型、但不含 URI 的 Intent 才会通过测试。 仅当 MIME 类型与过滤器中列出的类型匹配时,同时包含 URI 类型和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。 如果 Intent 的 URI 与过滤器中的 URI 匹配,或者如果 Intent 具有 content: 或 file: URI 且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。 换言之,如果过滤器只是列出 MIME 类型,则假定组件支持 content: 和 file: 数据。 最后一条规则,即规则 (d),反映了期望组件能够从文件中或内容提供程序获得本地数据。因此,其过滤器可以仅列出数据类型,而不必显式命名 content: 和 file: 架构。这是一个典型的案例。 例如,下文中的 元素向 Android 指出,组件可从内容提供商处获得并显示图像数据: + +```markup + + + ... + +``` + +由于大部分可用数据均由内容提供商分发,因此指定数据类型(而非 URI)的过滤器也许最为常见。 + +另一常见的配置是具有架构和数据类型的过滤器。例如,下文中的 元素向 Android 指出,组件可从网络中检索视频数据以执行操作: + +```markup + + + ... + +``` + +## Intent 匹配 + +通过 Intent 过滤器匹配 Intent,这不仅有助于发现要激活的目标组件,还有助于发现设备上组件集的相关信息。 例如,主页应用通过使用指定 ACTION\_MAIN 操作和 CATEGORY\_LAUNCHER 类别的 Intent 过滤器查找所有 Activity,以此填充应用启动器。 + +您的应用可以采用类似的方式使用 Intent 匹配。PackageManager 提供了一整套 query...() 方法来返回所有能够接受特定 Intent 的组件。此外,它还提供了一系列类似的 resolve...() 方法来确定响应 Intent 的最佳组件。 例如,queryIntentActivities() 将返回能够执行那些作为参数传递的 Intent 的所有 Activity 列表,而 queryIntentServices() 则可返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。 对于广播接收器,有一种类似的方法: queryBroadcastReceivers()。 diff --git "a/activity/\351\241\265\351\235\242\345\210\207\346\215\242\344\271\213\351\227\264\347\232\204\346\225\260\346\215\256\344\274\240\351\200\222.md" b/components/parceable.md similarity index 71% rename from "activity/\351\241\265\351\235\242\345\210\207\346\215\242\344\271\213\351\227\264\347\232\204\346\225\260\346\215\256\344\274\240\351\200\222.md" rename to components/parceable.md index 0ad67f19..56d4b446 100755 --- "a/activity/\351\241\265\351\235\242\345\210\207\346\215\242\344\271\213\351\227\264\347\232\204\346\225\260\346\215\256\344\274\240\351\200\222.md" +++ b/components/parceable.md @@ -3,69 +3,56 @@ Android中,Activity和Fragment之间传递对象,可以通过将对象序列 序列化对象可以使用Java的`Serializable`的接口`Parcelable`接口。转化成JSON字符串,可以使用`Gson`等库。 -## 1.Serializable +### 1.Serializable Model ```java - public class Author implements Serializable{ private int id; - private String name; //... } - ``` ```java - public class Book implements Serializable{ private String title; private Author author; //... } - ``` 传递数据 ```java - - Book book=new Book(); - book.setTitle("Java编程思想"); - Author author=new Author(); - author.setId(1); - author.setName("Bruce Eckel"); - book.setAuthor(author); - Intent intent=new Intent(this,SecondActivity.class); - intent.putExtra("book",book); - startActivity(intent); - +Book book=new Book(); +book.setTitle("Java编程思想"); +Author author=new Author(); +author.setId(1); +author.setName("Bruce Eckel"); +book.setAuthor(author); +Intent intent=new Intent(this,SecondActivity.class); +intent.putExtra("book",book); +startActivity(intent); ``` 接收数据 ```java - Book book= (Book) getIntent().getSerializableExtra("book"); - Log.d(TAG,"book title->"+book.getTitle()); - Log.d(TAG,"book author name->"+book.getAuthor().getName()); - - +Book book= (Book) getIntent().getSerializableExtra("book"); +Log.d(TAG,"book title->"+book.getTitle()); +Log.d(TAG,"book author name->"+book.getAuthor().getName()); ``` -## 2.转化为JSON字符串 +### 2.转化为JSON字符串 Model ```java - public class Author{ private int id; - private String name; - //... } - ``` ```java @@ -74,34 +61,31 @@ public class Book{ private Author author; //... } - - ``` 传递数据 ```java - Book book=new Book(); - book.setTitle("Java编程思想"); - Author author=new Author(); - author.setId(1); - author.setName("Bruce Eckel"); - book.setAuthor(author); - Intent intent=new Intent(this,SecondActivity.class); - intent.putExtra("book",new Gson().toJson(book)); - startActivity(intent); - +Book book=new Book(); +book.setTitle("Java编程思想"); +Author author=new Author(); +author.setId(1); +author.setName("Bruce Eckel"); +book.setAuthor(author); +Intent intent=new Intent(this,SecondActivity.class); +intent.putExtra("book",new Gson().toJson(book)); +startActivity(intent); ``` 接收数据 ```java - String bookJson=getIntent().getStringExtra("book"); - Book book=new Gson().fromJson(bookJson,Book.class); - Log.d(TAG,"book title->"+book.getTitle()); - Log.d(TAG,"book author name->"+book.getAuthor().getName()); +String bookJson=getIntent().getStringExtra("book"); +Book book=new Gson().fromJson(bookJson,Book.class); +Log.d(TAG,"book title->"+book.getTitle()); +Log.d(TAG,"book author name->"+book.getAuthor().getName()); ``` -## 3.使用Parcelable +### 3.使用Parcelable 实现`Parcelable`接口需要实现两个方法 @@ -114,8 +98,6 @@ public class Book{ Model ```java - - public class Author implements Parcelable{ private int id; @@ -197,46 +179,46 @@ public class Book implements Parcelable{ ```java - Book book=new Book(); - book.setTitle("Java编程思想"); - Author author=new Author(); - author.setId(1); - author.setName("Bruce Eckel"); - book.setAuthor(author); - Intent intent=new Intent(this,SecondActivity.class); - intent.putExtra("book",book); - startActivity(intent); +Book book=new Book(); +book.setTitle("Java编程思想"); +Author author=new Author(); +author.setId(1); +author.setName("Bruce Eckel"); +book.setAuthor(author); +Intent intent=new Intent(this,SecondActivity.class); +intent.putExtra("book",book); +startActivity(intent); ``` 接收数据 ```java - Book book=getIntent().getParcelableExtra("book"); - Log.d(TAG,"book title->"+book.getTitle()); - Log.d(TAG,"book author name->"+book.getAuthor().getName()); +Book book=getIntent().getParcelableExtra("book"); +Log.d(TAG,"book title->"+book.getTitle()); +Log.d(TAG,"book author name->"+book.getAuthor().getName()); ``` -## 4.性能分析 +### 4.性能分析 [经过测试][why-we-love-parcelable],我们得到下图的效果 -![](images/parcelable-vs-seralizable.png) +![](/assets/images/parcelable-vs-seralizable.png) 可以看出,通过转换为字符串的速度是最慢的。Seralizable次之,Parcelable比Seralizable快10倍。所以从性能上考虑,我们必定优先选择Parcelable。但是Parcelable有大量重复的模板代码,如何简化这些操作,将是下面主要讲解的内容。 -## 5.简化Parcel操作 +### 5.简化Parcel操作 如果你使用android Studio 可以通过安装[android-parcelable-intellij-plugin]插件,或者自己[配置模板][How templates can save your time?]进行操作。 -### 5.1 parceler +#### 5.1 parceler 除了上面的操作,还有大量的第三方库来简化Parcelable操作。当然使用这些库也许会降低Parcelable的性能。[Parceler][Parceler]就是这样一个库。 @@ -245,68 +227,54 @@ Parceler使用非常简单,在定义Model时用`@Parcel`进行注解,在传 Model ```java - -<<<<<<< HEAD - @Parcel - public class Author { - - - int id; - + int id; String name; - //setter & getter... } - ``` ```java - @Parcel public class Book { String title; Author author; //setter & getter } - - ``` 传递对象 ```java - Book book=new Book(); book.setTitle("Java编程思想"); Author author=new Author(); author.setId(1); author.setName("Bruce Eckel"); book.setAuthor(author); - Intent intent=new Intent(this,SecondActivity.class); - intent.putExtra("book", Parcels.wrap(book)); - startActivity(intent); - - +Intent intent=new Intent(this,SecondActivity.class); +intent.putExtra("book", Parcels.wrap(book)); +startActivity(intent); ``` 接收对象 ```java - - Book book= Parcels.unwrap(getIntent().getParcelableExtra("book")); - Log.d(TAG,"book title->"+book.getTitle()); - Log.d(TAG,"book author name->"+book.getAuthor().getName()); - - +Book book= Parcels.unwrap(getIntent().getParcelableExtra("book")); +Log.d(TAG,"book title->"+book.getTitle()); +Log.d(TAG,"book author name->"+book.getAuthor().getName()); ``` 除了Parceler之外,还有如[auto-parcel][auto-parcel],[ParcelableCodeGenerator][ParcelableCodeGenerator],[ParcelableGenerator][ParcelableGenerator]等第三方库,这里我将不进行讲解,有兴趣的朋友,可以自行研究。 +### 常见问题 + +* [Parcelable传递Drawable](https://stackoverflow.com/questions/10070974/how-to-pass-drawable-using-parcelable) + [why-we-love-parcelable]: http://prolificinteractive.com/blog/2014/07/18/why-we-love-parcelable/ [android-parcelable-intellij-plugin]: https://github.com/mcharmas/android-parcelable-intellij-plugin [How templates can save your time?]: http://dmytrodanylyk.com/pages/blog/templates.html diff --git a/components/processes-and-threads.md b/components/processes-and-threads.md new file mode 100644 index 00000000..0561e790 --- /dev/null +++ b/components/processes-and-threads.md @@ -0,0 +1,151 @@ + + +当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。 如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。 + +本文档介绍进程和线程在 Android 应用中的工作方式。 + +### 进程 +默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 但是,如果您发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。 + +各类组件元素的清单文件条目——均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,您还可以设置 android:process,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。 + +此外, 元素还支持 android:process 属性,以设置适用于所有组件的默认值。 + +如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 当这些组件需要再次运行时,系统将为它们重启进程。 + +决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 的进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 下面,我们介绍决定终止进程所用的规则。 + +### 进程生命周期 +Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。 + +重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程): + +#### 1. 前台进程 +用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程: +托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法) +托管某个 Service,后者绑定到用户正在交互的 Activity +托管正在“前台”运行的 Service(服务已调用 startForeground()) +托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy()) +托管正执行其 onReceive() 方法的 BroadcastReceiver +通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。 +#### 2.可见进程 +没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程: +托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。 +托管绑定到可见(或前台)Activity 的 Service。 +可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。 +#### 3.服务进程 +正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。 +#### 4.后台进程 +包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅 Activity文档。 +#### 5.空进程 +不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。 +根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。 + +此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 例如,如果进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 B 同样重要。 + +由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动服务,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。 + +### 线程 +应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widget 和 android.view 软件包的组件)进行交互的线程。因此,主线程有时也称为 UI 线程。 + +系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。 + +例如,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给小部件,而小部件反过来又设置其按下状态,并将失效请求发布到事件队列中。 UI 线程从队列中取消该请求并通知小部件应该重绘自身。 + +在应用执行繁重的任务以响应用户交互时,除非正确实现应用,否则这种单线程模式可能会导致性能低下。 具体地讲,如果 UI 线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 UI。 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。 从用户的角度来看,应用显示为挂起。 更糟糕的是,如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“应用无响应”(ANR) 对话框。如果引起用户不满,他们可能就会决定退出并卸载此应用。 + +此外,Android UI 工具包并非线程安全工具包。因此,您不得通过工作线程操纵 UI,而只能通过 UI 线程操纵用户界面。 因此,Android 的单线程模式必须遵守两条规则: + +不要阻塞 UI 线程 +不要在 UI 线程之外访问 Android UI 工具包 +#### 工作线程 +根据上述单线程模式,要保证应用 UI 的响应能力,关键是不能阻塞 UI 线程。 如果执行的操作不能很快完成,则应确保它们在单独的线程(“后台”或“工作”线程)中运行。 + +例如,以下代码演示了一个点击侦听器从单独的线程下载图像并将其显示在 ImageView 中: +```java +public void onClick(View v) { + new Thread(new Runnable() { + public void run() { + Bitmap b = loadImageFromNetwork("http://example.com/image.png"); + mImageView.setImageBitmap(b); + } + }).start(); +} +``` +乍看起来,这段代码似乎运行良好,因为它创建了一个新线程来处理网络操作。 但是,它违反了单线程模式的第二条规则:不要在 UI 线程之外访问 Android UI 工具包 — 此示例从工作线程(而不是 UI 线程)修改了 ImageView。 这可能导致出现不明确、不可预见的行为,但要跟踪此行为困难而又费时。 + +为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程。 以下列出了几种有用的方法: + +Activity.runOnUiThread(Runnable) +View.post(Runnable) +View.postDelayed(Runnable, long) +例如,您可以通过使用 View.post(Runnable) 方法修复上述代码: +```java +public void onClick(View v) { + new Thread(new Runnable() { + public void run() { + final Bitmap bitmap = + loadImageFromNetwork("http://example.com/image.png"); + mImageView.post(new Runnable() { + public void run() { + mImageView.setImageBitmap(bitmap); + } + }); + } + }).start(); +} +``` +现在,上述实现属于线程安全型:在单独的线程中完成网络操作,而在 UI 线程中操纵 ImageView。 + +但是,随着操作日趋复杂,这类代码也会变得复杂且难以维护。 要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 Handler 处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 AsyncTask 类,此类简化了与 UI 进行交互所需执行的工作线程任务。 + +使用 AsyncTask +AsyncTask 允许对用户界面执行异步操作。 它会先阻塞工作线程中的操作,然后在 UI 线程中发布结果,而无需您亲自处理线程和/或处理程序。 + +要使用它,必须创建 AsyncTask 的子类并实现 doInBackground() 回调方法,该方法将在后台线程池中运行。 要更新 UI,应该实现 onPostExecute() 以传递 doInBackground() 返回的结果并在 UI 线程中运行,以便您安全地更新 UI。 稍后,您可以通过从 UI 线程调用 execute() 来运行任务。 + +例如,您可以通过以下方式使用 AsyncTask 来实现上述示例: +```java +public void onClick(View v) { + new DownloadImageTask().execute("http://example.com/image.png"); +} + +private class DownloadImageTask extends AsyncTask { + /** The system calls this to perform work in a worker thread and + * delivers it the parameters given to AsyncTask.execute() */ + protected Bitmap doInBackground(String... urls) { + return loadImageFromNetwork(urls[0]); + } + + /** The system calls this to perform work in the UI thread and delivers + * the result from doInBackground() */ + protected void onPostExecute(Bitmap result) { + mImageView.setImageBitmap(result); + } +} +``` +现在 UI 是安全的,代码也得到简化,因为任务分解成了两部分:一部分应在工作线程内完成,另一部分应在 UI 线程内完成。 + + +可以使用泛型指定参数类型、进度值和任务最终值 +方法 doInBackground() 会在工作线程上自动执行 +onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 线程中调用 +doInBackground() 返回的值将发送到 onPostExecute() +您可以随时在 doInBackground() 中调用publishProgress(),以在 UI 线程中执行 onProgressUpdate() +您可以随时取消任何线程中的任务 +注意:使用工作线程时可能会遇到另一个问题,即:运行时配置变更(例如,用户更改了屏幕方向)导致 Activity 意外重启,这可能会销毁工作线程。 要了解如何在这种重启情况下坚持执行任务,以及如何在 Activity 被销毁时正确地取消任务,请参阅书架示例应用的源代码。 + +#### 线程安全方法 + +在某些情况下,您实现的方法可能会从多个线程调用,因此编写这些方法时必须确保其满足线程安全的要求。 + +这一点主要适用于可以远程调用的方法,如绑定服务中的方法。如果对 IBinder 中所实现方法的调用源自运行 IBinder 的同一进程,则该方法在调用方的线程中执行。但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统在与 IBinder 相同的进程中维护。 例如,即使服务的 onBind() 方法将从服务进程的 UI 线程调用,在 onBind() 返回的对象中实现的方法(例如,实现 RPC 方法的子类)仍会从线程池中的线程调用。 由于一个服务可以有多个客户端,因此可能会有多个池线程在同一时间使用同一 IBinder 方法。因此,IBinder 方法必须实现为线程安全方法。 + +同样,内容提供程序也可接收来自其他进程的数据请求。尽管 ContentResolver 和 ContentProvider 类隐藏了如何管理进程间通信的细节,但响应这些请求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 UI 线程调用。 由于这些方法可能会同时从任意数量的线程调用,因此它们也必须实现为线程安全方法。 + +### 进程间通信 + +Android 利用远程过程调用 (RPC) 提供了一种进程间通信 (IPC) 机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。 这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 然后,返回值将沿相反方向传输回来。 Android 提供了执行这些 IPC 事务所需的全部代码,因此您只需集中精力定义和实现 RPC 编程接口即可。 + + + diff --git a/components/services/README.md b/components/services/README.md new file mode 100644 index 00000000..a41238fd --- /dev/null +++ b/components/services/README.md @@ -0,0 +1,2 @@ +# 服务 + diff --git a/components/services/aidl.md b/components/services/aidl.md new file mode 100644 index 00000000..b75e3915 --- /dev/null +++ b/components/services/aidl.md @@ -0,0 +1,528 @@ +# AIDL + +Android 接口定义语言 \(AIDL\) 与您可能使用过的其他接口语言 \(IDL\) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 \(IPC\) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。 + +{% hint style="info" %} +**注意:**只有在需要不同应用的客户端通过 IPC 方式访问服务,并且希望在服务中进行多线程处理时,您才有必要使用 AIDL。如果您无需跨不同应用执行并发 IPC,则应通过[实现 Binder](https://developer.android.com/guide/components/bound-services#Binder) 来创建接口;或者,如果您想执行 IPC,但_不_需要处理多线程,请[使用 Messenger ](https://developer.android.com/guide/components/bound-services#Messenger)来实现接口。无论如何,在实现 AIDL 之前,请您务必理解[绑定服务](https://developer.android.com/guide/components/bound-services)。 +{% endhint %} + +在开始设计 AIDL 接口之前,请注意,AIDL 接口的调用是直接函数调用。您无需对发生调用的线程做任何假设。实际情况的差异取决于调用是来自本地进程中的线程,还是远程进程中的线程。具体而言: + +* 来自本地进程的调用在发起调用的同一线程内执行。如果该线程是您的主界面线程,则其将继续在 AIDL 接口中执行。如果该线程是其他线程,则其便是在服务中执行代码的线程。因此,只有在本地线程访问服务时,您才能完全控制哪些线程在服务中执行(但若出现此情况,您根本无需使用 AIDL,而应通过[实现 Binder 类](https://developer.android.com/guide/components/bound-services#Binder)来创建接口)。 +* 远程进程的调用分派自线程池,且平台会在您自己的进程内部维护该线程池。您必须为来自未知线程,且多次调用同时发生的传入调用做好准备。换言之,AIDL 接口的实现必须基于完全的线程安全。如果调用来自同一远程对象上的某个线程,则该调用将**依次**抵达接收器端。 +* `oneway` 关键字用于修改远程调用的行为。使用此关键字后,远程调用不会屏蔽,而只是发送事务数据并立即返回。最终接收该数据时,接口的实现会将其视为来自 [`Binder`](https://developer.android.com/reference/android/os/Binder) 线程池的常规调用(普通的远程调用)。如果 `oneway` 用于本地调用,则不会有任何影响,且调用仍为同步调用。 + +## 定义 AIDL 接口 + +您必须在 `.aidl` 文件中使用 Java 编程语言的语法定义 AIDL 接口,然后将其保存至应用的源代码(在 `src/` 目录中)内,这类应用会托管服务或与服务进行绑定。 + +在构建每个包含 `.aidl` 文件的应用时,Android SDK 工具会生成基于该 `.aidl` 文件的 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口,并将其保存到项目的 `gen/` 目录中。服务必须视情况实现 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口。然后,客户端应用便可绑定到该服务,并调用 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 中的方法来执行 IPC。 + +如要使用 AIDL 创建绑定服务,请执行以下步骤: + +1. [创建 .aidl 文件](https://developer.android.com/guide/components/aidl#Create) + + 此文件定义带有方法签名的编程接口。 + +2. [实现接口](https://developer.android.com/guide/components/aidl#ImplementTheInterface) + + Android SDK 工具会基于您的 `.aidl` 文件,使用 Java 编程语言生成接口。此接口拥有一个名为 `Stub` 的内部抽象类,用于扩展 [`Binder`](https://developer.android.com/reference/android/os/Binder) 类并实现 AIDL 接口中的方法。您必须扩展 `Stub` 类并实现这些方法。 + +3. [向客户端公开接口](https://developer.android.com/guide/components/aidl#ExposeTheInterface) + + 实现 [`Service`](https://developer.android.com/reference/android/app/Service) 并重写 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29),从而返回 `Stub` 类的实现。 + +{% hint style="info" %} +**注意:**如果您在首次发布 AIDL 接口后对其进行更改,则每次更改必须保持向后兼容性,以免中断其他应用使用您的服务。换言之,由于只有在将您的 `.aidl` 文件复制到其他应用后,才能使其访问服务接口,因而您必须保留对原始接口的支持。 +{% endhint %} + +### 1. 创建 .aidl 文件 + +AIDL 使用一种简单语法,允许您通过一个或多个方法(可接收参数和返回值)来声明接口。参数和返回值可为任意类型,甚至是 AIDL 生成的其他接口。 + +您必须使用 Java 编程语言构建 `.aidl` 文件。每个 `.aidl` 文件均须定义单个接口,并且只需要接口声明和方法签名。 + +默认情况下,AIDL 支持下列数据类型: + +* Java 编程语言中的所有原语类型(如 `int`、`long`、`char`、`boolean` 等) +* [`String`](https://developer.android.com/reference/java/lang/String) +* [`CharSequence`](https://developer.android.com/reference/java/lang/CharSequence) +* [`List`](https://developer.android.com/reference/java/util/List) + + [`List`](https://developer.android.com/reference/java/util/List) 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 [`List`](https://developer.android.com/reference/java/util/List) 用作“泛型”类(例如,`List`)。尽管生成的方法旨在使用 [`List`](https://developer.android.com/reference/java/util/List) 接口,但另一方实际接收的具体类始终是 [`ArrayList`](https://developer.android.com/reference/java/util/ArrayList)。 + +* [`Map`](https://developer.android.com/reference/java/util/Map) + + [`Map`](https://developer.android.com/reference/java/util/Map) 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 `Map` 形式的 Map)。尽管生成的方法旨在使用 [`Map`](https://developer.android.com/reference/java/util/Map) 接口,但另一方实际接收的具体类始终是 [`HashMap`](https://developer.android.com/reference/java/util/HashMap)。 + +即使您在与接口相同的包内定义上方未列出的附加类型,亦须为其各自加入一条 `import` 语句。 + +定义服务接口时,请注意: + +* 方法可带零个或多个参数,返回值或空值。 +* 所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 `in`、`out` 或 `inout`(见下方示例)。 + + 原语默认为 `in`,不能是其他方向。 + +{% hint style="info" %} +**注意:**您应将方向限定为真正需要的方向,因为编组参数的开销较大。 +{% endhint %} + +* 生成的 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口内包含 `.aidl` 文件中的所有代码注释(import 和 package 语句之前的注释除外)。 +* 您可以在 ADL 接口中定义 String 常量和 int 字符串常量。例如:`const int VERSION = 1;`。 +* 方法调用由 [transact\(\) 代码](https://developer.android.com/reference/android/os/IBinder#transact%28int,%20android.os.Parcel,%20android.os.Parcel,%20int%29)分派,该代码通常基于接口中的方法索引。由于这会增加版本控制的难度,因此您可以向方法手动配置事务代码:`void method() = 10;`。 +* 使用 `@nullable` 注释可空参数或返回类型。 + +以下是 `.aidl` 文件示例: + +```java +/** + * Example of defining an interface for calling on to a remote service + * (running in another process). + */ +interface IRemoteService { + /** + * Often you want to allow a service to call back to its clients. + * This shows how to do so, by registering a callback interface with + * the service. + */ + void registerCallback(IRemoteServiceCallback cb); + + /** + * Remove a previously registered callback interface. + */ + void unregisterCallback(IRemoteServiceCallback cb); +} +``` + +您只需将 `.aidl` 文件保存至项目的 `src/` 目录内,这样在构建应用时,SDK 工具便会在项目的 `gen/` 目录中生成 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口文件。生成文件的名称与 `.aidl` 文件的名称保持一致,区别在于其使用 `.java` 扩展名(例如,`IRemoteService.aidl` 生成的文件名是 `IRemoteService.java`)。 + +如果您使用 Android Studio,增量构建几乎会立即生成 Binder 类。如果您不使用 Android Studio,则 Gradle 工具会在您下一次开发应用时生成 Binder 类。因此,在编写完 `.aidl` 文件后,您应立即使用 `gradle assembleDebug`(或 `gradle assembleRelease`)构建项目,以便您的代码能够链接到生成的类。 + +### 2. 实现接口 + +当您构建应用时,Android SDK 工具会生成以 `.aidl` 文件命名的 `.java` 接口文件。生成的接口包含一个名为 `Stub` 的子类(例如,`YourInterface.Stub`),该子类是其父接口的抽象实现,并且会声明 `.aidl` 文件中的所有方法。 + +```java +public interface IRemoteService extends android.os.IInterface +{ + /** Default implementation for IRemoteService. */ + public static class Default implements cn.malinkang.servicesamples.IRemoteService + { + /** + * Often you want to allow a service to call back to its clients. + * This shows how to do so, by registering a callback interface with + * the service. + */ + @Override public void registerCallback(cn.malinkang.servicesamples.IRemoteServiceCallback cb) throws android.os.RemoteException + { + } + /** + * Remove a previously registered callback interface. + */ + @Override public void unregisterCallback(cn.malinkang.servicesamples.IRemoteServiceCallback cb) throws android.os.RemoteException + { + } + @Override + public android.os.IBinder asBinder() { + return null; + } + } + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements cn.malinkang.servicesamples.IRemoteService + { + private static final java.lang.String DESCRIPTOR = "cn.malinkang.servicesamples.IRemoteService"; + /** Construct the stub at attach it to the interface. */ + public Stub() + { + this.attachInterface(this, DESCRIPTOR); + } + /** + * Cast an IBinder object into an cn.malinkang.servicesamples.IRemoteService interface, + * generating a proxy if needed. + */ + //onServiceConnected调用 传入onServiceConnected返回的Binder + public static cn.malinkang.servicesamples.IRemoteService asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + //查询 + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + //同一个进程 + if (((iin!=null)&&(iin instanceof cn.malinkang.servicesamples.IRemoteService))) { + return ((cn.malinkang.servicesamples.IRemoteService)iin); + } + //不同进程 + return new cn.malinkang.servicesamples.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_registerCallback: + { + data.enforceInterface(descriptor); + cn.malinkang.servicesamples.IRemoteServiceCallback _arg0; + _arg0 = cn.malinkang.servicesamples.IRemoteServiceCallback.Stub.asInterface(data.readStrongBinder()); + this.registerCallback(_arg0); + reply.writeNoException(); + return true; + } + case TRANSACTION_unregisterCallback: + { + data.enforceInterface(descriptor); + cn.malinkang.servicesamples.IRemoteServiceCallback _arg0; + _arg0 = cn.malinkang.servicesamples.IRemoteServiceCallback.Stub.asInterface(data.readStrongBinder()); + this.unregisterCallback(_arg0); + reply.writeNoException(); + return true; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + } + private static class Proxy implements cn.malinkang.servicesamples.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; + } + /** + * Often you want to allow a service to call back to its clients. + * This shows how to do so, by registering a callback interface with + * the service. + */ + @Override public void registerCallback(cn.malinkang.servicesamples.IRemoteServiceCallback cb) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStrongBinder((((cb!=null))?(cb.asBinder()):(null))); + boolean _status = mRemote.transact(Stub.TRANSACTION_registerCallback, _data, _reply, 0); + if (!_status && getDefaultImpl() != null) { + getDefaultImpl().registerCallback(cb); + return; + } + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + /** + * Remove a previously registered callback interface. + */ + @Override public void unregisterCallback(cn.malinkang.servicesamples.IRemoteServiceCallback cb) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStrongBinder((((cb!=null))?(cb.asBinder()):(null))); + boolean _status = mRemote.transact(Stub.TRANSACTION_unregisterCallback, _data, _reply, 0); + if (!_status && getDefaultImpl() != null) { + getDefaultImpl().unregisterCallback(cb); + return; + } + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + public static cn.malinkang.servicesamples.IRemoteService sDefaultImpl; + } + static final int TRANSACTION_registerCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + static final int TRANSACTION_unregisterCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + public static boolean setDefaultImpl(cn.malinkang.servicesamples.IRemoteService impl) { + // Only one user of this interface can use this function + // at a time. This is a heuristic to detect if two different + // users in the same process use this function. + if (Stub.Proxy.sDefaultImpl != null) { + throw new IllegalStateException("setDefaultImpl() called twice"); + } + if (impl != null) { + Stub.Proxy.sDefaultImpl = impl; + return true; + } + return false; + } + public static cn.malinkang.servicesamples.IRemoteService getDefaultImpl() { + return Stub.Proxy.sDefaultImpl; + } + } + /** + * Often you want to allow a service to call back to its clients. + * This shows how to do so, by registering a callback interface with + * the service. + */ + public void registerCallback(cn.malinkang.servicesamples.IRemoteServiceCallback cb) throws android.os.RemoteException; + /** + * Remove a previously registered callback interface. + */ + public void unregisterCallback(cn.malinkang.servicesamples.IRemoteServiceCallback cb) throws android.os.RemoteException; +} +``` + +#### DESCRIPTOR + +Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的`cn.malinkang.servicesamples.IRemoteService`。 + + +#### asInterface + +asInterface\(android.os.IBinder obj\)用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。 + +#### asBinder + +asBinder此方法用于返回当前Binder对象。 + +#### onTransact + +onTransact这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。 + +#### registerCallback + +这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象\_data、输出型Parcel对象\_reply;然后把该方法的参数信息写入\_data中(如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从\_reply中取出RPC过程的返回结果;最后返回\_reply中的数据。 + +\*\*\*\* + +如要实现 `.aidl` 生成的接口,请扩展生成的 [`Binder`](https://developer.android.com/reference/android/os/Binder) 接口(例如,`YourInterface.Stub`),并实现继承自 `.aidl` 文件的方法。 + +以下示例展示使用匿名实例实现 `IRemoteService` 接口(由以上 `IRemoteService.aidl` 示例定义)的过程: + +```java +/** + * The IRemoteInterface is defined through IDL + */ +private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { + public void registerCallback(IRemoteServiceCallback cb) { + if (cb != null) mCallbacks.register(cb); + } + public void unregisterCallback(IRemoteServiceCallback cb) { + if (cb != null) mCallbacks.unregister(cb); + } +}; +``` + +现在,`binder` 是 `Stub` 类的一个实例(一个 [`Binder`](https://developer.android.com/reference/android/os/Binder)),其定义了服务的远程过程调用 \(RPC\) 接口。在下一步中,我们会向客户端公开此实例,以便客户端能与服务进行交互。 + +在实现 AIDL 接口时,您应注意遵守以下规则: + +* 由于无法保证在主线程上执行传入调用,因此您一开始便需做好多线程处理的准备,并对您的服务进行适当构建,使其达到线程安全的标准。 +* 默认情况下,RPC 调用是同步调用。如果您知道服务完成请求的时间不止几毫秒,则不应从 Activity 的主线程调用该服务,因为这可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 通常,您应从客户端内的单独线程调用服务。 +* 您引发的任何异常都不会回传给调用方。 + +### 3. 向客户端公开接口 + +在为服务实现接口后,您需要向客户端公开该接口,以便客户端进行绑定。如要为您的服务公开该接口,请扩展 [`Service`](https://developer.android.com/reference/android/app/Service) 并实现 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29),从而返回实现生成的 `Stub` 的类实例(如前文所述)。以下是向客户端公开 `IRemoteService` 示例接口的服务示例。 + +```java +public class RemoteService extends Service { + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public IBinder onBind(Intent intent) { + // Return the interface + return binder; + } + + private final IRemoteService.Stub binder = new IRemoteService.Stub() { + public int getPid(){ + return Process.myPid(); + } + public void basicTypes(int anInt, long aLong, boolean aBoolean, + float aFloat, double aDouble, String aString) { + // Does nothing + } + }; +} +``` + +现在,当客户端(如 Activity)调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 以连接此服务时,客户端的 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29) 回调会接收服务的 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 方法所返回的 `binder` 实例。 + +客户端还必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的 `src/` 目录内必须包含 `.aidl` 文件(该文件会生成 `android.os.Binder` 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。 + +当客户端在 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29) 回调中收到 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 时,它必须调用 _`YourServiceInterface`_`.Stub.asInterface(service)`,以将返回的参数转换成 _`YourServiceInterface`_ 类型。例如: + +```java +IRemoteService iRemoteService; +private ServiceConnection mConnection = new ServiceConnection() { + // Called when the connection with the service is established + public void onServiceConnected(ComponentName className, IBinder service) { + // Following the example above for an AIDL interface, + // this gets an instance of the IRemoteInterface, which we can use to call on the service + iRemoteService = IRemoteService.Stub.asInterface(service); + } + + // Called when the connection with the service disconnects unexpectedly + public void onServiceDisconnected(ComponentName className) { + Log.e(TAG, "Service has unexpectedly disconnected"); + iRemoteService = null; + } +}; +``` + +如需查看更多示例代码,请参阅 [ApiDemos](https://android.googlesource.com/platform/development/+/master/samples/ApiDemos) 中的 [`RemoteService.java`](https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.java) 类。 + +## 通过 IPC 传递对象 + +您可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。不过,您必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 [`Parcelable`](https://developer.android.com/reference/android/os/Parcelable) 接口。支持 [`Parcelable`](https://developer.android.com/reference/android/os/Parcelable) 接口很重要,因为 Android 系统能通过该接口将对象分解成可编组至各进程的原语。 + +如要创建支持 [`Parcelable`](https://developer.android.com/reference/android/os/Parcelable) 协议的类,您必须执行以下操作: + +1. 让您的类实现 [`Parcelable`](https://developer.android.com/reference/android/os/Parcelable) 接口。 +2. 实现 [`writeToParcel`](https://developer.android.com/reference/android/os/Parcelable#writeToParcel%28android.os.Parcel,%20int%29),它会获取对象的当前状态并将其写入 [`Parcel`](https://developer.android.com/reference/android/os/Parcel)。 +3. 为您的类添加 `CREATOR` 静态字段,该字段是实现 [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator) 接口的对象。 +4. 最后,创建声明 Parcelable 类的 `.aidl` 文件(遵照下文 `Rect.aidl` 文件所示步骤)。 + + 如果您使用的是自定义编译进程,_请勿_在您的构建中添加 `.aidl` 文件。此 `.aidl` 文件与 C 语言中的头文件类似,并未经过编译。 + +AIDL 会在其生成的代码中使用这些方法和字段,以对您的对象进行编组和解编。 + +例如,下方的 `Rect.aidl` 文件可创建 Parcelable 类型的 `Rect` 类: + +```java +package android.graphics; + +// Declare Rect so AIDL can find it and knows that it implements +// the parcelable protocol. +parcelable Rect; +``` + +以下示例展示 [`Rect`](https://developer.android.com/reference/android/graphics/Rect) 类如何实现 [`Parcelable`](https://developer.android.com/reference/android/os/Parcelable) 协议。 + +```java +import android.os.Parcel; +import android.os.Parcelable; + +public final class Rect implements Parcelable { + public int left; + public int top; + public int right; + public int bottom; + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Rect createFromParcel(Parcel in) { + return new Rect(in); + } + + public Rect[] newArray(int size) { + return new Rect[size]; + } + }; + + public Rect() { + } + + private Rect(Parcel in) { + readFromParcel(in); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(left); + out.writeInt(top); + out.writeInt(right); + out.writeInt(bottom); + } + + public void readFromParcel(Parcel in) { + left = in.readInt(); + top = in.readInt(); + right = in.readInt(); + bottom = in.readInt(); + } + + public int describeContents() { + return 0; + } +} +``` + +`Rect` 类中的编组相当简单。请查看 [`Parcel`](https://developer.android.com/reference/android/os/Parcel) 的其他相关方法,了解您可以向 Parcel 写入哪些其他类型的值。 + +{% hint style="info" %} +**警告:**请勿忘记从其他进程中接收数据的安全问题。在本例中,`Rect` 从 [`Parcel`](https://developer.android.com/reference/android/os/Parcel) 读取四个数字,但您需确保:无论调用方目的为何,这些数字均在可接受的值范围内。如需详细了解如何防止应用受到恶意软件侵害、保证应用安全,请参阅[安全与权限](https://developer.android.com/guide/topics/security/security)。 +{% endhint %} + +## 带软件包参数(包含 Parcelable 类型)的方法 + +如果您的 AIDL 接口包含接收软件包作为参数(预计包含 Parcelable 类型)的方法,则在尝试从软件包读取之前,请务必通过调用 [`Bundle.setClassLoader(ClassLoader)`](https://developer.android.com/reference/android/os/Bundle?hl=en#setClassLoader%28java.lang.ClassLoader%29) 设置软件包的类加载器。否则,即使您在应用中正确定义 Parcelable 类型,也会遇到 [`ClassNotFoundException`](https://developer.android.com/reference/java/lang/ClassNotFoundException)。例如, + +如果您有 `.aidl` 文件: + +```java +// IRectInsideBundle.aidl +package com.example.android; + +/** Example service interface */ +interface IRectInsideBundle { + /** Rect parcelable is stored in the bundle with key "rect" */ + void saveRect(in Bundle bundle); +} +``` + +如下方实现所示,在读取 `Rect` 之前,`ClassLoader` 已在 `Bundle` 中完成显式设置 + +```java +private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() { + public void saveRect(Bundle bundle){ + bundle.setClassLoader(getClass().getClassLoader()); + Rect rect = bundle.getParcelable("rect"); + process(rect); // Do more with the parcelable. + } +}; +``` + +## 调用 IPC 方法 + +如要调用通过 AIDL 定义的远程接口,调用类必须执行以下步骤: + +1. 在项目的 `src/` 目录中加入 `.aidl` 文件。 +2. 声明一个 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口实例(基于 AIDL 生成)。 +3. 实现 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection)。 +4. 调用 [`Context.bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29),从而传入您的 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection) 实现。 +5. 在 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29) 实现中,您将收到一个 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 实例(名为 `service`)。调用 _`YourInterfaceName`_`.Stub.asInterface((IBinder)`_`service`_`)`,以将返回的参数转换为 _YourInterface_ 类型。 +6. 调用您在接口上定义的方法。您应始终捕获 [`DeadObjectException`](https://developer.android.com/reference/android/os/DeadObjectException) 异常,系统会在连接中断时抛出此异常。您还应捕获 [`SecurityException`](https://developer.android.com/reference/java/lang/SecurityException) 异常,当 IPC 方法调用中两个进程的 AIDL 定义发生冲突时,系统会抛出此异常。 +7. 如要断开连接,请使用您的接口实例调用 [`Context.unbindService()`](https://developer.android.com/reference/android/content/Context#unbindService%28android.content.ServiceConnection%29)。 + +有关调用 IPC 服务的几点说明: + +* 对象是跨进程计数的引用。 +* 您可以方法参数的形式发送匿名对象。 + +如需了解有关绑定服务的详细信息,请阅读[绑定服务](https://developer.android.com/guide/components/bound-services#Binding)文档。 + +## 参考 + +* [Android 接口定义语言 \(AIDL\)](https://developer.android.com/guide/components/aidl) + diff --git a/components/services/bound-services.md b/components/services/bound-services.md new file mode 100644 index 00000000..b6ff8c71 --- /dev/null +++ b/components/services/bound-services.md @@ -0,0 +1,435 @@ +# 绑定服务 + +绑定服务是客户端-服务器接口中的服务器。借助绑定服务,组件(例如 Activity)可以绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。 + +本文介绍如何创建绑定服务,包括如何绑定到来自其他应用组件的服务。如需了解一般服务的更多信息(例如:如何利用服务传送通知、如何将服务设置为在前台运行等),请参阅[服务](https://developer.android.com/guide/components/services)文档。 + +## 基础知识 + +绑定服务是 [`Service`](https://developer.android.com/reference/android/app/Service?hl=zh-cn) 类的实现,可让其他应用与其进行绑定和交互。如要为服务提供绑定,您必须实现 [`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) 回调方法。该方法会返回 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn) 对象,该对象定义的编程接口可供客户端用来与服务进行交互。 + +### 绑定到已启动服务 + +如[服务](https://developer.android.com/guide/components/services)文档中所述,您可以创建同时具有已启动和已绑定两种状态的服务。换言之,可通过调用 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 启动服务,让服务无限期运行;此外,还可通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 让客户端绑定到该服务。 + +如果您确实允许服务同时具有已启动和已绑定状态,则在启动服务后,如果所有客户端均解绑服务,则系统\_不会\_销毁该服务。为此,您必须通过调用 [`stopSelf()`](https://developer.android.com/reference/android/app/Service#stopSelf%28%29) 或 [`stopService()`](https://developer.android.com/reference/android/content/Context#stopService%28android.content.Intent%29) 显式停止服务。 + +尽管您通常应实现 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) _或_ [`onStartCommand()`](https://developer.android.com/reference/android/app/Service#onStartCommand%28android.content.Intent,%20int,%20int%29),但有时需同时实现这两种方法。例如,音乐播放器可能认为,让其服务无限期运行并同时提供绑定会很有用处。如此一来,Activity 便可通过启动服务来播放音乐,并且即使用户离开应用,音乐也不会停止。然后,当用户返回应用时,Activity 便能绑定到服务,重新获得回放控制权。 + +如需详细了解为已启动服务添加绑定时的服务生命周期,请参阅[管理绑定服务的生命周期](https://developer.android.com/guide/components/bound-services#Lifecycle)部分。 + +客户端通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 绑定到服务。调用时,它必须提供 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection) 的实现,后者会监控与服务的连接。[`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 的返回值表明所请求的服务是否存在,以及是否允许客户端访问该服务。当创建客户端与服务之间的连接时,Android 系统会调用 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection) 上的 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29)。`onServiceConnected()` 方法包含 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 参数,客户端随后会使用该参数与绑定服务进行通信。 + +您可以同时将多个客户端连接到服务。但是,系统会缓存 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 服务通信通道。换言之,只有在第一个客户端绑定服务时,系统才会调用服务的 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 方法来生成 [`IBinder`](https://developer.android.com/reference/android/os/IBinder)。然后,系统会将同一 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 传递至绑定到相同服务的所有其他客户端,无需再次调用 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29)。 + +当最后一个客户端取消与服务的绑定时,系统会销毁服务(除非 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 也启动了该服务)。 + +针对您的绑定服务实现,其最重要的环节是定义 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 回调方法所返回的接口。下文将为您介绍几种不同方法,以便您定义服务的 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口。 + +## 创建绑定服务 + +创建提供绑定的服务时,您必须提供 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn),进而提供编程接口,以便客户端使用此接口与服务进行交互。您可以通过三种方法定义接口: + +[扩展 Binder 类](https://developer.android.com/guide/components/bound-services?hl=zh-cn#Binder) + +如果服务是供您的自有应用专用,并且在与客户端相同的进程中运行(常见情况),则应通过扩展 [`Binder`](https://developer.android.com/reference/android/os/Binder?hl=zh-cn) 类并从 [`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) 返回该类的实例来创建接口。收到 [`Binder`](https://developer.android.com/reference/android/os/Binder?hl=zh-cn) 后,客户端可利用其直接访问 [`Binder`](https://developer.android.com/reference/android/os/Binder?hl=zh-cn) 实现或 [`Service`](https://developer.android.com/reference/android/app/Service?hl=zh-cn) 中可用的公共方法。 + +如果服务只是您自有应用的后台工作线程,则优先采用这种方法。只有当其他应用或不同进程占用您的服务时,您可以不必使用此方法创建接口。 + +[使用 Messenger](https://developer.android.com/guide/components/bound-services?hl=zh-cn#Messenger) + +如需让接口跨不同进程工作,您可以使用 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 为服务创建接口。服务可借此方式定义 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn),以响应不同类型的 [`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn) 对象。此 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn) 是 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 的基础,后者随后可与客户端分享一个 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn),以便客户端能利用 [`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn) 对象向服务发送命令。此外,客户端还可定义自有 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn),以便服务回传消息。 + +这是执行进程间通信 (IPC) 最为简单的方法,因为 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 会在单个线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。 + +[使用 AIDL](https://developer.android.com/guide/components/aidl?hl=zh-cn) + +Android 接口定义语言 (AIDL) 会将对象分解成原语,操作系统可通过识别这些原语并将其编组到各进程中来执行 IPC。对于之前采用 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 的方法而言,其实际上是以 AIDL 作为其底层结构。如上所述,[`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 会在单个线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。在此情况下,您的服务必须达到线程安全的要求,并且能够进行多线程处理。 + +如要直接使用 AIDL,您必须创建定义编程接口的 `.aidl` 文件。Android SDK 工具会利用该文件生成实现接口和处理 IPC 的抽象类,您随后可在服务内对该类进行扩展。 + +### 扩展Binder类 + +如果您的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让您的客户端通过该类直接访问服务中的公共方法。 + +> 注:此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。 + +以下是具体的设置方法: + +1. 在您的服务中,创建一个可满足下列任一要求的 Binder 实例: + * 包含客户端可调用的公共方法 + * 返回当前 Service 实例,其中包含客户端可调用的公共方法 + * 或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法 +2. 从 onBind() 回调方法返回此 Binder 实例。 +3. 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务。 + +> 注:之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 API。服务和客户端还必须在同一进程内,因为此方法不执行任何跨进程编组。 + +例如,以下这个服务可让客户端通过 Binder 实现访问服务中的方法: + +```java +public class LocalService extends Service { + // Binder given to clients + private final IBinder mBinder = new LocalBinder(); + // Random number generator + private final Random mGenerator = new Random(); + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + LocalService getService() { + // Return this instance of LocalService so clients can call public methods + return LocalService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + /** method for clients */ + public int getRandomNumber() { + return mGenerator.nextInt(100); + } +} +``` + +LocalBinder 为客户端提供 getService() 方法,以检索 LocalService 的当前实例。这样,客户端便可调用服务中的公共方法。 例如,客户端可调用服务中的 getRandomNumber()。 + +点击按钮时,以下这个 Activity 会绑定到 LocalService 并调用 getRandomNumber() : + +```java +public class BindingActivity extends Activity { + LocalService mService; + boolean mBound = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + + @Override + protected void onStart() { + super.onStart(); + // Bind to LocalService + Intent intent = new Intent(this, LocalService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (mBound) { + unbindService(mConnection); + mBound = false; + } + } + + /** Called when a button is clicked (the button in the layout file attaches to + * this method with the android:onClick attribute) */ + public void onButtonClick(View v) { + if (mBound) { + // Call a method from the LocalService. + // However, if this call were something that might hang, then this request should + // occur in a separate thread to avoid slowing down the activity performance. + int num = mService.getRandomNumber(); + Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); + } + } + + /** Defines callbacks for service binding, passed to bindService() */ + private ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + LocalBinder binder = (LocalBinder) service; + mService = binder.getService(); + mBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mBound = false; + } + }; +} +``` + +### 使用 Messenger + +如需让服务与远程进程通信,则可使用 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 为您的服务提供接口。借助此方法,您无需使用 AIDL 便可执行进程间通信 (IPC)。 + +为接口使用 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 比使用 AIDL 更简单,因为 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 会将所有服务调用加入队列。纯 AIDL 接口会同时向服务发送多个请求,服务随后必须执行多线程处理。 + +对于大多数应用,服务无需执行多线程处理,因此使用 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 即可让服务一次处理一个调用。如果您的服务必须执行多线程处理,请使用 [AIDL](https://developer.android.com/guide/components/aidl?hl=zh-cn) 来定义接口。 + +以下是 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 的使用方法摘要: + +1. 服务实现一个 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn),由该类为每个客户端调用接收回调。 +2. 服务使用 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn) 来创建 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 对象(对 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn) 的引用)。 +3. [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 创建一个 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn),服务通过 [`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) 使其返回客户端。 +4. 客户端使用 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn) 将 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn)(其引用服务的 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn))实例化,然后使用后者将 [`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn) 对象发送给服务。 +5. 服务在其 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn) 中(具体地讲,是在 [`handleMessage()`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn#handleMessage%28android.os.Message%29) 方法中)接收每个 [`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn)。 + +这样,客户端便没有\_方法\_来调用服务。相反,客户端会传递服务在其 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn) 中接收的\_消息\_([`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn) 对象)。 + +以下简单服务实例展示如何使用 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn) 接口: + +```java +public class MessengerService extends Service { + /** For showing and hiding our notification. */ + NotificationManager mNM; + /** Keeps track of all current registered clients. */ + ArrayList mClients = new ArrayList(); + /** Holds last value set by a client. */ + int mValue = 0; + + /** + * Command to the service to register a client, receiving callbacks + * from the service. The Message's replyTo field must be a Messenger of + * the client where callbacks should be sent. + */ + static final int MSG_REGISTER_CLIENT = 1; + + /** + * Command to the service to unregister a client, ot stop receiving callbacks + * from the service. The Message's replyTo field must be a Messenger of + * the client as previously given with MSG_REGISTER_CLIENT. + */ + static final int MSG_UNREGISTER_CLIENT = 2; + + /** + * Command to service to set a new value. This can be sent to the + * service to supply a new value, and will be sent by the service to + * any registered clients with the new value. + */ + static final int MSG_SET_VALUE = 3; + + /** + * Handler of incoming messages from clients. + */ + class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_CLIENT: + mClients.add(msg.replyTo); + break; + case MSG_UNREGISTER_CLIENT: + mClients.remove(msg.replyTo); + break; + case MSG_SET_VALUE: + mValue = msg.arg1; + for (int i=mClients.size()-1; i>=0; i--) { + try { + mClients.get(i).send(Message.obtain(null, + MSG_SET_VALUE, mValue, 0)); + } catch (RemoteException e) { + // The client is dead. Remove it from the list; + // we are going through the list from back to front + // so this is safe to do inside the loop. + mClients.remove(i); + } + } + break; + default: + super.handleMessage(msg); + } + } + } + + /** + * Target we publish for clients to send messages to IncomingHandler. + */ + final Messenger mMessenger = new Messenger(new IncomingHandler()); + + @Override + public void onCreate() { + mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + + // Display a notification about us starting. + showNotification(); + } + + @Override + public void onDestroy() { + // Cancel the persistent notification. + mNM.cancel(R.string.remote_service_started); + + // Tell the user we stopped. + Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show(); + } + + /** + * When binding to the service, we return an interface to our messenger + * for sending messages to the service. + */ + @Override + public IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } + + /** + * Show a notification while this service is running. + */ + private void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(R.string.remote_service_started); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, RemoteService.Controller.class), 0); + + // Set the info for the views that show in the notification panel. + Notification notification = new Notification.Builder(this) + .setSmallIcon(R.drawable.stat_sample) // the status icon + .setTicker(text) // the status text + .setWhen(System.currentTimeMillis()) // the time stamp + .setContentTitle(getText(R.string.local_service_label)) // the label of the entry + .setContentText(text) // the contents of the entry + .setContentIntent(contentIntent) // The intent to send when the entry is clicked + .build(); + + // Send the notification. + // We use a string id because it is a unique number. We use it later to cancel. + mNM.notify(R.string.remote_service_started, notification); + } +} +//END_INCLUDE(service) +``` + +请注意,服务会在 [`Handler`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn) 的 [`handleMessage()`](https://developer.android.com/reference/android/os/Handler?hl=zh-cn#handleMessage%28android.os.Message%29) 方法中接收传入的 [`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn),并根据 [`what`](https://developer.android.com/reference/android/os/Message?hl=zh-cn#what) 成员决定下一步操作。 + +客户端只需根据服务返回的 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn) 创建 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn),然后利用 [`send()`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn#send%28android.os.Message%29) 发送消息。 + +如果您想让服务作出响应,需在客户端中创建一个 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn)。收到 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29) 回调时,客户端会向服务发送 [`Message`](https://developer.android.com/reference/android/os/Message?hl=zh-cn),并在其 [`send()`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn#send%28android.os.Message%29) 方法的 [`replyTo`](https://developer.android.com/reference/android/os/Message?hl=zh-cn#replyTo) 参数中加入客户端的 [`Messenger`](https://developer.android.com/reference/android/os/Messenger?hl=zh-cn)。 + +```java +public class ActivityMessenger extends Activity { + /** Messenger for communicating with the service. */ + Messenger mService = null; + + /** Flag indicating whether we have called bind on the service. */ + boolean mBound; + + /** + * Class for interacting with the main interface of the service. + */ + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the object we can use to + // interact with the service. We are communicating with the + // service using a Messenger, so here we get a client-side + // representation of that from the raw IBinder object. + mService = new Messenger(service); + mBound = true; + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + mService = null; + mBound = false; + } + }; + + public void sayHello(View v) { + if (!mBound) return; + // Create and send a message to the service, using a supported 'what' value + Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); + try { + mService.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + + @Override + protected void onStart() { + super.onStart(); + // Bind to the service + bindService(new Intent(this, MessengerService.class), mConnection, + Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (mBound) { + unbindService(mConnection); + mBound = false; + } + } +} +``` + +## 绑定到服务 + +应用组件(客户端)可通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 绑定到服务。然后,Android 系统会调用服务的 [`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) 方法,该方法会返回用于与服务交互的 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn)。 + +绑定为异步操作,并且 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) \_无需\_将 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn) 返回至客户端即可立即返回。如要接收 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn),客户端必须创建一个 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn) 实例,并将其传递给 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29)。[`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn) 包含一个回调方法,系统通过调用该方法来传递 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn)。 + +{% hint style="info" %} +**注意:只有 Activity、服务和内容提供程序可以绑定到服务,您无法**从广播接收器绑定到服务。 +{% endhint %} + +如要从您的客户端绑定到服务,请执行以下步骤: + +1. 实现 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn)。 + + 您的实现必须重写两个回调方法: + + * [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29)系统会调用该方法,进而传递服务的 [`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) 方法所返回的 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn)。 + * [`onServiceDisconnected()`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn#onServiceDisconnected%28android.content.ComponentName%29)当与服务的连接意外中断(例如服务崩溃或被终止)时,Android 系统会调用该方法。当客户端取消绑定时,系统\_不会\_调用该方法。 +2. 调用 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29),从而传递 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn) 实现。 +3. 当系统调用 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29) 回调方法时,您可以使用接口定义的方法开始调用服务。 +4. 如要断开与服务的连接,请调用 [`unbindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#unbindService%28android.content.ServiceConnection%29)。 + + 当应用销毁客户端时,如果该客户端仍与服务保持绑定状态,则该销毁会导致客户端取消绑定。更好的做法是在客户端与服务交互完成后,立即取消与该客户端的绑定。这样可以关闭空闲服务。如需详细了解有关绑定和取消绑定的适当时机,请参阅[附加说明](https://developer.android.com/guide/components/bound-services?hl=zh-cn#Additional\_Notes)。 +5. [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 的第一个参数是一个 [`Intent`](https://developer.android.com/reference/android/content/Intent?hl=zh-cn),用于显式命名要绑定的服务。 + +{% hint style="info" %} +\*\*注意:\*\*如果您使用 Intent 来绑定到 [`Service`](https://developer.android.com/reference/android/app/Service?hl=zh-cn),请务必使用[显式](https://developer.android.com/guide/components/intents-filters?hl=zh-cn#Types) Intent 来确保应用的安全性。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务会响应该 Intent,并且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29),则系统会抛出异常。 +{% endhint %} + +* 第二个参数是 [`ServiceConnection`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn) 对象。 +* 第三个参数是指示绑定选项的标记。如要创建尚未处于活动状态的服务,此参数应为 [`BIND_AUTO_CREATE`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#BIND\_AUTO\_CREATE)。其他可能的值为 [`BIND_DEBUG_UNBIND`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#BIND\_DEBUG\_UNBIND) 和 [`BIND_NOT_FOREGROUND`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#BIND\_NOT\_FOREGROUND),或者 `0`(表示无此参数)。 + +以下是一些有关绑定到服务的重要说明: + +### 附加说明 + +* 您应该始终捕获 [`DeadObjectException`](https://developer.android.com/reference/android/os/DeadObjectException?hl=zh-cn) 异常,系统会在连接中断时抛出此异常。这是远程方法抛出的唯一异常。 +* 对象是跨进程计数的引用。 +* 如以下示例所述,在匹配客户端生命周期的引入 (bring-up) 和退出 (tear-down) 时刻期间,您通常需配对绑定和取消绑定: + * 如果您只需在 Activity 可见时与服务交互,则应在 [`onStart()`](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onStart%28%29) 期间进行绑定,在 [`onStop()`](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onStop%28%29) 期间取消绑定。 + * 当 Activity 在后台处于停止运行状态时,若您仍希望其能接收响应,则可在 [`onCreate()`](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onCreate%28android.os.Bundle%29) 期间进行绑定,在 [`onDestroy()`](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onDestroy%28%29) 期间取消绑定。请注意,这意味着您的 Activity 在整个运行过程中(甚至包括后台运行期间)均需使用服务,因此如果服务位于其他进程内,则当您提高该进程的权重时,系统便更有可能会将其终止。 + +{% hint style="info" %} +\*\*注意:\*\*通常情况下,\_不应\_在 Activity 的 [`onResume()`](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onResume%28%29) 和 [`onPause()`](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onPause%28%29) 期间绑定和取消绑定,因为每次切换生命周期状态时都会发生这些回调,并且您应让这些转换期间的处理工作保持最少。此外,如果您将应用内的多个 Activity 绑定到同一个 Service,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一个 Activity 绑定(恢复期间)之前取消绑定(暂停期间),则系统可能会销毁并重建服务。如需了解 Activity 如何协调其生命周期的 Activity 转换,请参阅 [Activity](https://developer.android.com/guide/components/activities?hl=zh-cn#CoordinatingActivities) 文档。 +{% endhint %} + +## 管理绑定服务的生命周期 + +当取消服务与所有客户端之间的绑定时,Android 系统会销毁该服务(除非您还使用 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29) 启动了该服务)。因此,如果您的服务完全是绑定服务,则您无需管理其生命周期,Android 系统会根据它是否绑定到任何客户端代您管理。 + +不过,如果您选择实现 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29) 回调方法,则您必须显式停止服务,因为系统现已将其视为\_已启动\_状态。在此情况下,服务将一直运行,直到其通过 [`stopSelf()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#stopSelf%28%29) 自行停止,或其他组件调用 [`stopService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#stopService%28android.content.Intent%29)(与该服务是否绑定到任何客户端无关)。 + +此外,如果您的服务已启动并接受绑定,则当系统调用您的 [`onUnbind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onUnbind%28android.content.Intent%29) 方法时,如果您想在客户端下一次绑定到服务时接收 [`onRebind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onRebind%28android.content.Intent%29) 调用,则可选择返回 `true`。[`onRebind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onRebind%28android.content.Intent%29) 返回空值,但客户端仍在其 [`onServiceConnected()`](https://developer.android.com/reference/android/content/ServiceConnection?hl=zh-cn#onServiceConnected%28android.content.ComponentName,%20android.os.IBinder%29) 回调中接收 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn)。下图说明这种生命周期的逻辑。 + +![已启动且允许绑定的服务的生命周期](../../assets/images/service\_binding\_tree\_lifecycle.png) diff --git a/components/services/services.md b/components/services/services.md new file mode 100644 index 00000000..346f98fe --- /dev/null +++ b/components/services/services.md @@ -0,0 +1,482 @@ +# 服务 + +[`Service`](https://developer.android.com/reference/android/app/Service) 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。 + +以下是**三种**不同的服务类型: + +**前台** + +前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示[通知](https://developer.android.com/guide/topics/ui/notifiers/notifications)。即使用户停止与应用的交互,前台服务仍会继续运行。 + +**后台** + +后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。 + +{% hint style="success" %} +注意:如果您的应用面向 API 级别 26 或更高版本,当应用本身未在前台运行时,系统会对[运行后台服务施加限制](https://developer.android.com/about/versions/oreo/background)。在诸如此类的大多数情况下,您的应用应改为使用[计划作业](https://developer.android.com/topic/performance/scheduling)。 +{% endhint %} + +**绑定** + +当应用组件通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 绑定到服务时,服务即处于\_绑定\_状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。 + +虽然本文档分开概括讨论启动服务和绑定服务,但您的服务可同时以这两种方式运行,换言之,它既可以是启动服务(以无限期运行),亦支持绑定。唯一的问题在于您是否实现一组回调方法:[`onStartCommand()`](https://developer.android.com/reference/android/app/Service#onStartCommand%28android.content.Intent,%20int,%20int%29)(让组件启动服务)和 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29)(实现服务绑定)。 + +无论服务是处于启动状态还是绑定状态(或同时处于这两种状态),任何应用组件均可像使用 Activity 那样,通过调用 [`Intent`](https://developer.android.com/reference/android/content/Intent) 来使用服务(即使此服务来自另一应用)。不过,您可以通过清单文件将服务声明为\_私有\_服务,并阻止其他应用访问该服务。[使用清单文件声明服务](https://developer.android.com/guide/components/services#Declaring)部分将对此做更详尽的阐述。 + +{% hint style="success" %} +**注意:服务在其托管进程的主线程中运行,它既不**创建自己的线程,也**不**在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。通过使用单独的线程,您可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。 +{% endhint %} + +## 在服务和线程之间进行选择 + +简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,因此,只有在需要服务时才应创建服务。 + +如果您必须在主线程之外执行操作,但只在用户与您的应用交互时执行此操作,则应创建新线程。例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 [`onCreate()`](https://developer.android.com/reference/android/app/Activity#onCreate%28android.os.Bundle%29) 中创建线程,在 [`onStart()`](https://developer.android.com/reference/android/app/Activity#onStart%28%29) 中启动线程运行,然后在 [`onStop()`](https://developer.android.com/reference/android/app/Activity#onStop%28%29) 中停止线程。您还可考虑使用 [`AsyncTask`](https://developer.android.com/reference/android/os/AsyncTask) 或 [`HandlerThread`](https://developer.android.com/reference/android/os/HandlerThread),而非传统的 [`Thread`](https://developer.android.com/reference/java/lang/Thread) 类。如需了解有关线程的详细信息,请参阅[进程和线程](https://developer.android.com/guide/components/processes-and-threads#Threads)文档。 + +请记住,如果您确实要使用服务,则默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集型或阻止性操作,则您仍应在服务内创建新线程。 + +## 基础知识 + +如要创建服务,您必须创建 [`Service`](https://developer.android.com/reference/android/app/Service?hl=zh-cn) 的子类(或使用它的一个现有子类)。在实现中,您必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务(如适用)。以下是您应重写的最重要的回调方法: + +[`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29) + +当另一个组件(如 Activity)请求启动服务时,系统会通过调用 [`startService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#startService%28android.content.Intent%29) 来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。如果您实现此方法,则在服务工作完成后,您需负责通过调用 [`stopSelf()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#stopSelf%28%29) 或 [`stopService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#stopService%28android.content.Intent%29) 来停止服务。(如果您只想提供绑定,则无需实现此方法。) + +[`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) + +当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 来调用此方法。在此方法的实现中,您必须通过返回 [`IBinder`](https://developer.android.com/reference/android/os/IBinder?hl=zh-cn) 提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果您并不希望允许绑定,则应返回 null。 + +[`onCreate()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onCreate%28%29) + +首次创建服务时,系统会(在调用 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29) 或 [`onBind()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onBind%28android.content.Intent%29) 之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。 + +[`onDestroy()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onDestroy%28%29) + +当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。 + +如果组件通过调用 [`startService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#startService%28android.content.Intent%29) 启动服务(这会引起对 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29) 的调用),则服务会一直运行,直到其使用 [`stopSelf()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#stopSelf%28%29) 自行停止运行,或由其他组件通过调用 [`stopService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#stopService%28android.content.Intent%29) 将其停止为止。 + +如果组件通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 来创建服务,且\_未\_调用 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29),则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。 + +只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务。如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止;如果将服务声明为[在前台运行](https://developer.android.com/guide/components/services?hl=zh-cn#Foreground),则其几乎永远不会终止。如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升—如果服务是启动服务,则您必须将其设计为能够妥善处理系统执行的重启。如果系统终止服务,则其会在资源可用时立即重启服务,但这还取决于您从 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service?hl=zh-cn#onStartCommand%28android.content.Intent,%20int,%20int%29) 返回的值。如需了解有关系统会在何时销毁服务的详细信息,请参阅[进程和线程](https://developer.android.com/guide/components/processes-and-threads?hl=zh-cn)文档。 + +下文将介绍如何创建 [`startService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#startService%28android.content.Intent%29) 和 [`bindService()`](https://developer.android.com/reference/android/content/Context?hl=zh-cn#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 服务方法,以及如何通过其他应用组件使用这些方法。 + +## 使用清单文件声明服务 + +如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。 + +要声明服务,请添加 元素作为 元素的子元素。例如: + +```java + + ... + + + ... + + +``` + +您还可将其他属性包括在 元素中,以定义一些特性,如启动服务及其运行所在进程所需的权限。android:name 属性是唯一必需的属性,用于指定服务的类名。应用一旦发布,即不应更改此类名,如若不然,可能会存在因依赖显式 Intent 启动或绑定服务而破坏代码的风险(请阅读博客文章Things That Cannot Change\[不能更改的内容])。 + +{% hint style="info" %} +为了确保应用的安全性,请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。 启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 Intent 中排除相应的组件名称,但随后必须使用 setPackage() 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性。 +{% endhint %} + +此外,还可以通过添加 android:exported 属性并将其设置为 "false",确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。 + +{% hint style="info" %} +**注意**:用户可以查看其设备上正在运行的服务。如果他们发现自己无法识别或信任的服务,则可以停止该服务。为避免用户意外停止您的服务,您需要在应用清单的 [``](https://developer.android.com/guide/topics/manifest/service-element) 元素中添加 [`android:description`](https://developer.android.com/guide/topics/manifest/service-element#desc)。请在描述中用一个短句解释服务的作用及其提供的好处。 +{% endhint %} + +## 创建启动服务 + +启动服务由另一个组件通过调用 startService() 启动,这会导致调用服务的 onStartCommand() 方法。 + +服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。 因此,服务应通过调用 stopSelf() 结束工作来自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。 + +应用组件(如 Activity)可以通过调用 startService() 方法并传递 Intent 对象(指定服务并包含待使用服务的所有数据)来启动服务。服务通过 onStartCommand() 方法接收此 Intent。 + +例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向 startService() 传递一个 Intent,为该服务提供要保存的数据。服务通过 onStartCommand() 接收 Intent,连接到互联网并执行数据库事务。事务完成之后,服务会自行停止运行并随即被销毁。 + +注意:默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。 因此,如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。 为了避免影响应用性能,您应在服务内启动新线程。 + +从传统上讲,您可以扩展两个类来创建启动服务: + +* Service + + 这是适用于所有服务的基类。扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主~~线~~程,这会降低应用正在运行的所有 Activity 的性能。 +* IntentService + + 这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。 + +### 扩展 IntentService 类 + +该类已经被标记为过期。IntentService受制于Android 8.0规定的所有[后台执行限制](https://developer.android.com/about/versions/oreo/background)(API级别26)。 当在`Android 8.0`或更高版本上运行时,可以考虑使用`WorkManager`或`JobIntentService`,它使用作业而不是服务。 + +由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 `IntentService` 类实现服务也许是最好的选择。 + +`IntentService`执行以下操作: + +* 创建默认的工作线程,用于在应用的**主线程外**执行传递给`onStartCommand()`的所有 Intent。 +* 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。 +* 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。 +* 提供`onBind()`的默认实现(返回 null)。 +* 提供 `onStartCommand()`的默认实现,可将 `Intent`依次发送到工作队列和 `onHandleIntent()` 实现。 + +`IntentService` 源码 + +```java +@Override +public void onCreate() { + // TODO: It would be nice to have an option to hold a partial wakelock + // during processing, and to have a static startService(Context, Intent) + // method that would launch the service & hand off a wakelock. + super.onCreate(); + //在onCreate中创建HandlerThread 和 Handler + HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); + thread.start(); + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); +} +private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + //在子线程中处理intent 并在处理完成后停止服务。 + onHandleIntent((Intent)msg.obj); + stopSelf(msg.arg1); + } +} +@Override +public void onStart(@Nullable Intent intent, int startId) { + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = intent; + //在主线程中发送消息 + mServiceHandler.sendMessage(msg); +} +``` + +综上所述,您只需实现 `onHandleIntent()` 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。) + +以下是 `IntentService` 的实现示例: + +```java +public class HelloIntentService extends IntentService { + + /** + * A constructor is required, and must call the super IntentService(String) + * constructor with a name for the worker thread. + */ + public HelloIntentService() { + super("HelloIntentService"); + } + + /** + * The IntentService calls this method from the default worker thread with + * the intent that started the service. When this method returns, IntentService + * stops the service, as appropriate. + */ + @Override + protected void onHandleIntent(Intent intent) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // Restore interrupt status. + Thread.currentThread().interrupt(); + } + } +} +``` + +您只需要一个构造函数和一个 `onHandleIntent()` 实现即可。 + +如果您决定还重写其他回调方法(如 onCreate()、onStartCommand() 或 onDestroy()),请确保调用超类实现,以便 `IntentService` 能够妥善处理工作线程的生命周期。 + +例如,`onStartCommand()`必须返回默认实现(即,如何将 `Intent` 传递给 `onHandleIntent()`): + +```java +@Override +public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + return super.onStartCommand(intent,flags,startId); +} +``` + +除 `onHandleIntent()` 之外,您无需从中调用超类的唯一方法就是 `onBind()`(仅当服务允许绑定时,才需要实现该方法)。 + +在下一部分中,您将了解如何在扩展 `Service` 基类时实现同类服务。该基类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类。 + +### 扩展服务类 + +借助 [`IntentService`](https://developer.android.com/reference/android/app/IntentService),您可以非常轻松地实现启动服务。但是,若要求服务执行多线程(而非通过工作队列处理启动请求),则可通过扩展 [`Service`](https://developer.android.com/reference/android/app/Service) 类来处理每个 Intent。 + +为进行比较,以下示例代码展示了 [`Service`](https://developer.android.com/reference/android/app/Service) 类的实现,该类执行的工作与上述使用 [`IntentService`](https://developer.android.com/reference/android/app/IntentService) 的示例完全相同。换言之,对于每个启动请求,其均使用工作线程来执行作业,且每次仅处理一个请求。 + +```java +public class HelloService extends Service { + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + + // Handler that receives messages from the thread + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // Restore interrupt status. + Thread.currentThread().interrupt(); + } + // Stop the service using the startId, so that we don't stop + // the service in the middle of handling another job + stopSelf(msg.arg1); + } + } + + @Override + public void onCreate() { + // Start up the thread running the service. Note that we create a + // separate thread because the service normally runs in the process's + // main thread, which we don't want to block. We also make it + // background priority so CPU-intensive work will not disrupt our UI. + HandlerThread thread = new HandlerThread("ServiceStartArguments", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + // Get the HandlerThread's Looper and use it for our Handler + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + + // For each start request, send a message to start a job and deliver the + // start ID so we know which request we're stopping when we finish the job + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + mServiceHandler.sendMessage(msg); + + // If we get killed, after returning from here, restart + return START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + // We don't provide binding, so return null + return null; + } + + @Override + public void onDestroy() { + Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); + } +} +``` + +正如您所见,与使用 IntentService 相比,这需要执行更多工作。 + +但是,因为是由您自己处理对 onStartCommand() 的每个调用,因此可以同时执行多个请求。此示例并未这样做,但如果您希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。 + +请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一: + +* START\_NOT\_STICKY + + 如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 +* START\_STICKY + + 如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 +* START\_REDELIVER\_INTENT + + 如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。 + +### 启动服务 + +您可以通过将 Intent(指定要启动的服务)传递给 startService(),从 Activity 或其他应用组件启动服务。Android 系统调用服务的 onStartCommand() 方法,并向其传递 Intent。(切勿直接调用 onStartCommand()。) + +例如,Activity 可以结合使用显式 Intent 与 startService(),启动上文中的示例服务 (HelloService): + +```java +Intent intent = new Intent(this, HelloService.class); +startService(intent); +``` + +startService() 方法将立即返回,且 Android 系统调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统会先调用 onCreate(),然后再调用 onStartCommand()。 + +如果服务亦未提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent 传递给服务。然后,服务就可以使用广播传递结果。 + +多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,要停止服务,只需一个服务停止请求(使用 stopSelf() 或 stopService())即可。 + +### 停止服务 + +启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。因此,服务必须通过调用 stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。 + +一旦请求使用 stopSelf() 或 stopService() 停止服务,系统就会尽快销毁服务。 + +但是,如果服务同时处理多个 onStartCommand() 请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int) 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 onStartCommand() 的 startId)。然后,如果在您能够调用 stopSelf(int) 之前服务收到了新的启动请求,ID 就不匹配,服务也就不会停止。 + +{% hint style="info" %} +注意:为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 如有必要,其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。 +{% endhint %} + +## 创建绑定服务 + +绑定服务允许应用组件通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 来\_启动\_它。 + +如需与 Activity 和其他应用组件中的服务进行交互,或需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。 + +如要创建绑定服务,您需通过实现 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 回调方法返回 [`IBinder`](https://developer.android.com/reference/android/os/IBinder),从而定义与服务进行通信的接口。然后,其他应用组件可通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 来检索该接口,并开始调用与服务相关的方法。服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。您\_不必\_像通过 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service#onStartCommand%28android.content.Intent,%20int,%20int%29) 启动的服务那样,以相同方式停止绑定服务。 + +如要创建绑定服务,您必须定义指定客户端如何与服务进行通信的接口。服务与客户端之间的这个接口必须是 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 的实现,并且服务必须从 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 回调方法返回该接口。收到 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 后,客户端便可开始通过该接口与服务进行交互。 + +多个客户端可以同时绑定到服务。完成与服务的交互后,客户端会通过调用 [`unbindService()`](https://developer.android.com/reference/android/content/Context#unbindService%28android.content.ServiceConnection%29) 来取消绑定。如果没有绑定到服务的客户端,则系统会销毁该服务。 + +实现绑定服务有多种方法,并且此实现比启动服务更为复杂。出于这些原因,请参阅另一份文档[绑定服务](https://developer.android.com/guide/components/bound-services),了解关于绑定服务的详细介绍。 + +绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activity)绑定到服务、发送请求、接收响应,甚至执行进程间通信 (IPC)。 绑定服务通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行。 + +## 向用户发送通知 + +处于运行状态时,服务可以使用 [Toast 通知](https://developer.android.com/guide/topics/ui/notifiers/toasts)或[状态栏通知](https://developer.android.com/guide/topics/ui/notifiers/notifications)来通知用户所发生的事件。 + +Toast 通知是指仅在当前窗口界面显示片刻的消息。状态栏通知在状态栏中提供内含消息的图标,用户可通过选择该图标来执行操作(例如启动 Activity)。 + +通常,当某些后台工作(例如文件下载)已经完成,并且用户可对其进行操作时,状态栏通知是最佳方法。当用户从展开视图中选定通知时,该通知即可启动 Activity(例如显示已下载的文件)。 + +如需了解详细信息,请参阅 [Toast 通知](https://developer.android.com/guide/topics/ui/notifiers/toasts)或[状态栏通知](https://developer.android.com/guide/topics/ui/notifiers/notifications)开发者指南。 + +## 在前台运行服务 + +前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在\_运行中的\_标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。 + +{% hint style="info" %} +\*\*注意:\*\*请限制应用使用前台服务。 + +只有当应用执行的任务需供用户查看(即使该任务未直接与应用交互)时,您才应使用前台服务。因此,前台服务必须显示优先级为 [`PRIORITY_LOW`](https://developer.android.com/reference/androidx/core/app/NotificationCompat#PRIORITY\_LOW) 或更高的[状态栏通知](https://developer.android.com/guide/topics/ui/notifiers/notifications),这有助于确保用户知道应用正在执行的任务。如果某操作不是特别重要,因而您希望使用最低优先级通知,则可能不适合使用服务;相反,您可以考虑使用[计划作业](https://developer.android.com/topic/performance/scheduling)。 + +每个运行服务的应用都会给系统带来额外负担,从而消耗系统资源。如果应用尝试使用低优先级通知隐藏其服务,则可能会降低用户正在主动交互的应用的性能。因此,如果某个应用尝试运行拥有最低优先级通知的服务,则系统会在抽屉式通知栏的底部调用出该应用的行为。 +{% endhint %} + +例如,应将通过服务播放音乐的音乐播放器设置为在前台运行,因为用户会明确意识到其操作。状态栏中的通知可能表示正在播放的歌曲,并且其允许用户通过启动 Activity 与音乐播放器进行交互。同样,如果应用允许用户追踪其运行,则需通过前台服务来追踪用户的位置。 + +{% hint style="info" %} +\*\*注意:\*\*如果应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 [`FOREGROUND_SERVICE`](https://developer.android.com/reference/android/Manifest.permission#FOREGROUND\_SERVICE) 权限。这是一种[普通权限](https://developer.android.com/guide/topics/permissions/overview#normal-dangerous),因此,系统会自动为请求权限的应用授予此权限。 + +如果面向 API 级别 28 或更高版本的应用试图创建前台服务但未请求 `FOREGROUND_SERVICE`,则系统会抛出 [`SecurityException`](https://developer.android.com/reference/java/lang/SecurityException)。 +{% endhint %} + +如要请求让服务在前台运行,请调用 [`startForeground()`](https://developer.android.com/reference/android/app/Service#startForeground%28int,%20android.app.Notification%29)。此方法采用两个参数:唯一标识通知的整型数和用于状态栏的 [`Notification`](https://developer.android.com/reference/android/app/Notification)。此通知必须拥有 [`PRIORITY_LOW`](https://developer.android.com/reference/androidx/core/app/NotificationCompat#PRIORITY\_LOW) 或更高的优先级。下面是示例: + +```java +Intent notificationIntent = new Intent(this, ExampleActivity.class); +PendingIntent pendingIntent = + PendingIntent.getActivity(this, 0, notificationIntent, 0); + +Notification notification = + new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE) + .setContentTitle(getText(R.string.notification_title)) + .setContentText(getText(R.string.notification_message)) + .setSmallIcon(R.drawable.icon) + .setContentIntent(pendingIntent) + .setTicker(getText(R.string.ticker_text)) + .build(); + +startForeground(ONGOING_NOTIFICATION_ID, notification); +``` + +如要从前台移除服务,请调用 [`stopForeground()`](https://developer.android.com/reference/android/app/Service#stopForeground%28boolean%29)。此方法采用布尔值,指示是否需同时移除状态栏通知。此方法\_不会\_停止服务。但是,如果您在服务仍运行于前台时将其停止,则通知也会随之移除。 + +如需了解有关通知的详细信息,请参阅[创建状态栏通知](https://developer.android.com/guide/topics/ui/notifiers/notifications)。 + +## 管理服务的生命周期 + +服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户未意识到的情况下运行于后台。 + +服务生命周期(从创建到销毁)可遵循以下任一路径: + +* 启动服务 + + 该服务在其他组件调用 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 时创建,然后无限期运行,且必须通过调用 [`stopSelf()`](https://developer.android.com/reference/android/app/Service#stopSelf%28%29) 来自行停止运行。此外,其他组件也可通过调用 [`stopService()`](https://developer.android.com/reference/android/content/Context#stopService%28android.content.Intent%29) 来停止此服务。服务停止后,系统会将其销毁。 +* 绑定服务 + + 该服务在其他组件(客户端)调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 时创建。然后,客户端通过 [`IBinder`](https://developer.android.com/reference/android/os/IBinder) 接口与服务进行通信。客户端可通过调用 [`unbindService()`](https://developer.android.com/reference/android/content/Context#unbindService%28android.content.ServiceConnection%29) 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。(服务\_不必\_自行停止运行。) + +这两条路径并非完全独立。您可以绑定到已使用 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 启动的服务。例如,您可以使用 [`Intent`](https://developer.android.com/reference/android/content/Intent)(标识要播放的音乐)来调用 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29),从而启动后台音乐服务。随后,当用户需稍加控制播放器或获取有关当前所播放歌曲的信息时,Activity 可通过调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 绑定到服务。此类情况下,在所有客户端取消绑定之前,[`stopService()`](https://developer.android.com/reference/android/content/Context#stopService%28android.content.Intent%29) 或 [`stopSelf()`](https://developer.android.com/reference/android/app/Service#stopSelf%28%29) 实际不会停止服务。 + +### 实现生命周期回调 + +与 Activity 类似,服务也拥有生命周期回调方法,您可通过实现这些方法来监控服务状态的变化并适时执行工作。以下框架服务展示了每种生命周期方法: + +```java +public class ExampleService extends Service { + int startMode; // indicates how to behave if the service is killed + IBinder binder; // interface for clients that bind + boolean allowRebind; // indicates whether onRebind should be used + + @Override + public void onCreate() { + // The service is being created + } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // The service is starting, due to a call to startService() + return mStartMode; + } + @Override + public IBinder onBind(Intent intent) { + // A client is binding to the service with bindService() + return mBinder; + } + @Override + public boolean onUnbind(Intent intent) { + // All clients have unbound with unbindService() + return mAllowRebind; + } + @Override + public void onRebind(Intent intent) { + // A client is binding to the service with bindService(), + // after onUnbind() has already been called + } + @Override + public void onDestroy() { + // The service is no longer used and is being destroyed + } +} +``` + +\*\*注意:\*\*与 Activity 生命周期回调方法不同,您\_不\_需要调用这些回调方法的超类实现。 + +![](../../.gitbook/assets/service\_lifecycle.png) + +图 2 展示服务的典型回调方法。尽管该图分开介绍通过 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 创建的服务和通过 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 创建的服务,但请记住,无论启动方式如何,任何服务均有可能允许客户端与其绑定。因此,最初使用 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service#onStartCommand%28android.content.Intent,%20int,%20int%29)(通过客户端调用 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29))启动的服务仍可接收对 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 的调用(当客户端调用 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 时)。 + +通过实现这些方法,您可以监控服务生命周期的以下两种嵌套循环: + +* 服务的**整个生命周期**贯穿调用 [`onCreate()`](https://developer.android.com/reference/android/app/Service#onCreate%28%29) 和返回 [`onDestroy()`](https://developer.android.com/reference/android/app/Service#onDestroy%28%29) 之间的这段时间。与 Activity 类似,服务也在 [`onCreate()`](https://developer.android.com/reference/android/app/Service#onCreate%28%29) 中完成初始设置,并在 [`onDestroy()`](https://developer.android.com/reference/android/app/Service#onDestroy%28%29) 中释放所有剩余资源。例如,音乐播放服务可以在 [`onCreate()`](https://developer.android.com/reference/android/app/Service#onCreate%28%29) 中创建用于播放音乐的线程,然后在 [`onDestroy()`](https://developer.android.com/reference/android/app/Service#onDestroy%28%29) 中停止该线程。 + +{% hint style="info" %} +**注意**:无论所有服务是通过 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 还是 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 创建,系统均会为其调用 [`onCreate()`](https://developer.android.com/reference/android/app/Service#onCreate%28%29) 和 [`onDestroy()`](https://developer.android.com/reference/android/app/Service#onDestroy%28%29) 方法。 +{% endhint %} + +* 服务的**活动生命周期**从调用 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service#onStartCommand%28android.content.Intent,%20int,%20int%29) 或 [`onBind()`](https://developer.android.com/reference/android/app/Service#onBind%28android.content.Intent%29) 开始。每种方法均会获得 [`Intent`](https://developer.android.com/reference/android/content/Intent) 对象,该对象会传递至 [`startService()`](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 或 [`bindService()`](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29)。 + + 对于启动服务,活动生命周期与整个生命周期会同时结束(即便是在 [`onStartCommand()`](https://developer.android.com/reference/android/app/Service#onStartCommand%28android.content.Intent,%20int,%20int%29) 返回之后,服务仍然处于活动状态)。对于绑定服务,活动生命周期会在 [`onUnbind()`](https://developer.android.com/reference/android/app/Service#onUnbind%28android.content.Intent%29) 返回时结束。 + +{% hint style="info" %} +\*\*注意:\*\*尽管您需通过调用 [`stopSelf()`](https://developer.android.com/reference/android/app/Service#stopSelf%28%29) 或 [`stopService()`](https://developer.android.com/reference/android/content/Context#stopService%28android.content.Intent%29) 来停止绑定服务,但该服务并没有相应的回调(没有 `onStop()` 回调)。除非服务绑定到客户端,否则在服务停止时,系统会将其销毁([`onDestroy()`](https://developer.android.com/reference/android/app/Service#onDestroy%28%29) 是接收到的唯一回调)。 +{% endhint %} diff --git a/data-binding.md b/data-binding.md new file mode 100644 index 00000000..417869be --- /dev/null +++ b/data-binding.md @@ -0,0 +1,721 @@ +###### 构建环境 + +要使用Data Binding,需要添加`dataBinding`元素到app模块下的`build.gradle`文件下。 + +```java +android { + .... + dataBinding { + enabled = true + } +} +``` + +### 创建数据绑定布局文件 + +数据绑定的布局文件和普通的布局文件对比有如下不同:根标签是`layout`,接下来是一个`data`元素和一个view的根元素。 + +```xml + + + + + + + + + +``` + + +`variable`元素用来定义变量,`type`是变量类型,name是变量名。 + +在布局中通过`@{}`语法将变量的属性值设置给View的属性。 + +数据绑定中的data可以是POJO对象,也可以是JavaBean对象。在POJO对象中`@{}`获取的值是直接调用属性的值,而JavaBean则是通过属性的get方法获取值。例如上面的例子中`android:text="@{user.firstName}`,如果User是POJO对象,则访问的是`firstName`属性,如果是JavaBean,则是通过调用`getFirstName()`方法获取值。 + + +POJO对象: + +```java +public class User { + public final String firstName; + public final String lastName; + public User(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } +} +``` + +JavaBean对象: + +```java +public class User { + private final String firstName; + private final String lastName; + public User(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + public String getFirstName() { + return this.firstName; + } + public String getLastName() { + return this.lastName; + } +} +``` + +#### 导入 + +数据绑定的布局文件允许利用`import`元素像Java一样导入其他数据类型。例如下面代码就导入一个View。 + +```xml + + + +``` + +现在,View对象可以在绑定表达式中使用。 + +```java + +``` + +当类名出现冲突,可以指定一个别名 + +```java + + + +``` + +除了可以在表达式中使用,也可以在`variable`中使用。 + +```xml + + + + + + + +``` + +导入的类型还可以在表达式中使用static属性和方法 + +```xml + + + + +… + +``` +#### 引入 + +如果相同的布局被重复使用,我们会把他单独放在一个布局文件中使用`include`标签引入。数据绑定可以使用应用命名空间和变量名将变量从容器布局中传递到被包含的布局中。 + +被包含的布局文件`user.xml` + +```xml + + + + + + + + + + + +``` + +容器布局文件`activity_main.xml` + +```xml + + + + + + + + + + +``` + + + + +### 绑定数据 + +数据绑定是通过`Binding`类进行绑定的,创建一个数据绑定布局,编译器会自动生成一个`Binding`类。该类包含了设置变量的方法,比如上面的布局生成的绑定类会有一个`setUser(User User)`方法。 + +默认情况下,一个Binding类会基于布局文件的名称而产生,将布局文件名改为驼峰命名,并添加“Binding”后缀。例如布局文件为`activity_main.xml`,则会生的类名为`ActivityMainBinding`。 + + +在Activity中创建`Binding`类的实例可以通过`DataBindingUtil`的`setContentView`方法来创建。 + +```java +ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); +User user = new User("linkang","ma"); +binding.setUser(user); +``` +也可以通过生成的Binding类的inflate方法获取View,该方法只会生成View不会添加到Activity上。 + +```java +ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); +User user = new User("linkang","ma"); +binding.setUser(user); +setContentView(binding.getRoot()); +``` + +在Fragment中创建Binding类。 + +```java +FragmentBinding binding = + FragmentBinding.inflate(inflater, container, false); +//或者 +FragmentBinding binding = + DataBindingUtil.inflate(inflater, R.layout.fragment_my, container, false); +User user = new User("linkang", "ma"); +binding.setUser(user); +return binding.getRoot(); +``` + + +上面已经了解了`Binding`类名的生成规则,我们也可以自定义生成的`Binding`类的类名。`data`元素的class属性可以指定生成的类名。 + +```xml + +... + +``` + +生成的MainBinding类位于MainActivity包下的`databinding`包中,比如MainActivity位于`cn.malinkang.databinding`的包中,MainBinding位于`cn.malinkang.databinding.databinding`包中。还可以通过`.`前缀指定相对包名,例如在`MainBinding`前面添加`.`,生成的MainBinding类位于`cn.malinkang.databinding`包中。 + +```xml + +... + +``` + +也可以指定全包名 + +```java + +... + +``` + +### 事件处理 + +数据绑定允许写表达式来处理views分发的事件,比如`onClick`事件。事件属性名称与监听器方法的名称一致,例如`View.OnLongClickListener`监听器方法为`onLongClick()`,对应属性为`android:onLongClick`。 + +有两种处理事件的方法: + +* 方法引用 +* 监听器绑定 + +#### 方法引用 + +当设置的方法的参数签名和监听器实现方法的签名一致时,则采用方法引用。数据绑定将方法引用和当前View对象一起传递给一个监听器,并将这个监听器设置给当前View对象。 + +```java +public class MyHandlers { + public static final String TAG = MyHandlers.class.getSimpleName(); + + public void showLog(View view) { + Log.d(TAG, "showLog: " + view); + } + public void showToast(View view,User user){ + Toast.makeText(view.getContext(), "hello,"+user.getFirstName(), Toast.LENGTH_SHORT).show(); + } +} +``` +`showLog(View view)`签名与`onClick(View view)`方法签名一致,采用方法引用 + +```xml + + + + + + +