├── Algorithm-learning └── Sort │ ├── Algorithm_impl_java │ ├── .classpath │ ├── .project │ ├── .settings │ │ └── org.eclipse.jdt.core.prefs │ ├── bin │ │ └── sort │ │ │ ├── BubbleSort.class │ │ │ ├── HeapSort.class │ │ │ ├── InsertionSort.class │ │ │ ├── MergeSort.class │ │ │ ├── QuickSort.class │ │ │ ├── RadixSort.class │ │ │ ├── SelectSort.class │ │ │ ├── ShellSort.class │ │ │ └── SortTest.class │ └── src │ │ └── sort │ │ ├── BubbleSort.java │ │ ├── HeapSort.java │ │ ├── InsertionSort.java │ │ ├── MergeSort.java │ │ ├── QuickSort.java │ │ ├── RadixSort.java │ │ ├── SelectSort.java │ │ ├── ShellSort.java │ │ └── SortTest.java │ ├── 冒泡排序.md │ ├── 基数排序.md │ ├── 堆排序.md │ ├── 希尔排序.md │ ├── 归并排序.md │ ├── 快速排序.md │ ├── 直接插入排序.md │ └── 选择排序.md ├── Android ├── Activity详细解析.md ├── Android内存泄漏总结.md ├── Android录音实现(AudioRecord).md ├── Android录音实现(MediaRecord).md ├── BroadcastReceiver 详细解析.md ├── Lambda表达式在Android中的使用.md ├── Service详细解析.md ├── adb使用介绍.md └── picture │ └── 20170707215115.png ├── DesignPattern ├── Builder模式.md ├── Intent原型模式实现.png ├── 单例模式.md ├── 原型模式.md ├── 原型模式类图.jpg └── 观察者模式.md ├── Java └── 浅谈Java 浅拷贝&深拷贝.md └── README.md /Algorithm-learning/Sort/Algorithm_impl_java/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Algorithm-learning 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/BubbleSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/BubbleSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/HeapSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/HeapSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/InsertionSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/InsertionSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/MergeSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/MergeSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/QuickSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/QuickSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/RadixSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/RadixSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/SelectSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/SelectSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/ShellSort.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/ShellSort.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/SortTest.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Algorithm-learning/Sort/Algorithm_impl_java/bin/sort/SortTest.class -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/BubbleSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | public class BubbleSort { 4 | 5 | public static void main(String[] strs) { 6 | // TODO Auto-generated method stub 7 | int[] args = {8, 2, 1, 4,6, 3, 5, 9, 6,11,19,13,55,67,32}; 8 | bubbleSort(args); 9 | for(int i = 0; i < args.length; i++) { 10 | System.out.print(args[i] + ", "); 11 | } 12 | 13 | } 14 | 15 | public static void bubbleSort(int[] args) { 16 | //第一层循环从数组的最后往前遍历 17 | for (int i = args.length - 1; i > 0 ; --i) { 18 | //这里循环的上界是 i - 1,在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除” 19 | for (int j = 0; j < i; j++) { 20 | //保证在相邻的两个数中比较选出最大的并且进行交换(冒泡过程) 21 | if (args[j] > args[j+1]) { 22 | int temp = args[j]; 23 | args[j] = args[j+1]; 24 | args[j+1] = temp; 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/HeapSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | public class HeapSort { 4 | 5 | public static void main(String[] args) { 6 | int[] arr = {50, 10, 90, 30, 70, 40, 80, 60, 20}; 7 | System.out.println("排序之前:"); 8 | for (int i = 0; i < arr.length; i++) { 9 | System.out.print(arr[i] + " "); 10 | } 11 | 12 | // 堆排序 13 | heapSort(arr); 14 | 15 | System.out.println(); 16 | System.out.println("排序之后:"); 17 | for (int i = 0; i < arr.length; i++) { 18 | System.out.print(arr[i] + " "); 19 | } 20 | } 21 | 22 | /** 23 | * 堆排序 24 | */ 25 | public static void heapSort(int[] arr) { 26 | // 将待排序的序列构建成一个大顶堆 27 | for (int i = arr.length / 2; i >= 0; i--){ 28 | heapAdjust(arr, i, arr.length); 29 | } 30 | 31 | // 逐步将每个最大值的根节点与末尾元素交换,并且再调整二叉树,使其成为大顶堆 32 | for (int i = arr.length - 1; i > 0; i--) { 33 | swap(arr, 0, i); // 将堆顶记录和当前未经排序子序列的最后一个记录交换 34 | heapAdjust(arr, 0, i); // 交换之后,需要重新检查堆是否符合大顶堆,不符合则要调整 35 | } 36 | } 37 | 38 | /** 39 | * 构建堆的过程 40 | * @param arr 需要排序的数组 41 | * @param i 需要构建堆的根节点的序号 42 | * @param n 数组的长度 43 | */ 44 | private static void heapAdjust(int[] arr, int i, int n) { 45 | int child; 46 | int father; 47 | for (father = arr[i]; leftChild(i) < n; i = child) { 48 | child = leftChild(i); 49 | 50 | // 如果左子树小于右子树,则需要比较右子树和父节点 51 | if (child != n - 1 && arr[child] < arr[child + 1]) { 52 | child++; // 序号增1,指向右子树 53 | } 54 | 55 | // 如果父节点小于孩子结点,则需要交换 56 | if (father < arr[child]) { 57 | arr[i] = arr[child]; 58 | } else { 59 | break; // 大顶堆结构未被破坏,不需要调整 60 | } 61 | } 62 | arr[i] = father; 63 | } 64 | 65 | // 获取到左孩子结点 66 | private static int leftChild(int i) { 67 | return 2 * i + 1; 68 | } 69 | 70 | // 交换元素位置 71 | private static void swap(int[] arr, int index1, int index2) { 72 | int tmp = arr[index1]; 73 | arr[index1] = arr[index2]; 74 | arr[index2] = tmp; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/InsertionSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | public class InsertionSort { 4 | 5 | public static void main(String[] strs) { 6 | // TODO Auto-generated method stub 7 | int[] args = {8, 2, 1, 4,6, 3, 5, 9, 6,11,19,13,55,67,32}; 8 | insertionSort(args); 9 | for(int i = 0; i < args.length; i++) { 10 | System.out.print(args[i] + ", "); 11 | } 12 | 13 | } 14 | 15 | public static void insertionSort(int[] arr) { 16 | for( int i=0; i0; j-- ) { 18 | if( arr[j-1] <= arr[j] ) 19 | break; 20 | int temp = arr[j]; 21 | arr[j] = arr[j-1]; 22 | arr[j-1] = temp; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/MergeSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | import java.util.Arrays; 3 | 4 | public class MergeSort { 5 | 6 | /** 7 | * 归并排序 8 | * 简介:将两个(或两个以上)有序表合并成一个新的有序表 即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列 9 | * 时间复杂度为O(nlogn) 10 | * 稳定排序方式 11 | * @param nums 待排序数组 12 | * @return 输出有序数组 13 | */ 14 | public static int[] mergeSort(int[] nums, int low, int high) { 15 | int mid = (low + high) / 2; 16 | if (low < high) { 17 | // 左边 18 | mergeSort(nums, low, mid); 19 | // 右边 20 | mergeSort(nums, mid + 1, high); 21 | // 左右归并 22 | merge(nums, low, mid, high); 23 | } 24 | return nums; 25 | } 26 | 27 | public static void merge(int[] nums, int low, int mid, int high) { 28 | int[] temp = new int[high - low + 1]; 29 | int i = low;// 左指针 30 | int j = mid + 1;// 右指针 31 | int k = 0; 32 | 33 | // 把较小的数先移到新数组中 34 | while (i <= mid && j <= high) { 35 | if (nums[i] < nums[j]) { 36 | temp[k++] = nums[i++]; 37 | } else { 38 | temp[k++] = nums[j++]; 39 | } 40 | } 41 | 42 | // 把左边剩余的数移入数组 43 | while (i <= mid) { 44 | temp[k++] = nums[i++]; 45 | } 46 | 47 | // 把右边边剩余的数移入数组 48 | while (j <= high) { 49 | temp[k++] = nums[j++]; 50 | } 51 | 52 | // 把新数组中的数覆盖nums数组 53 | for (int k2 = 0; k2 < temp.length; k2++) { 54 | nums[k2 + low] = temp[k2]; 55 | } 56 | } 57 | 58 | 59 | // 归并排序的实现 60 | public static void main(String[] args) { 61 | 62 | int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 }; 63 | 64 | MergeSort.mergeSort(nums, 0, nums.length-1); 65 | System.out.println(Arrays.toString(nums)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/QuickSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | public class QuickSort { 4 | 5 | public static void main(String[] strs) { 6 | // TODO Auto-generated method stub 7 | int[] args = {8, 2, 1, 4,6, 3, 5, 9, 6,11,19,13,55,67,32}; 8 | quickSort(args, 0, args.length-1); 9 | for(int i = 0; i < args.length; i++) { 10 | System.out.print(args[i] + ", "); 11 | } 12 | 13 | } 14 | 15 | /** 16 | * 快速排序 17 | * 18 | * @param args 待排序的目标数组 19 | * @param start 数组的起始索引 20 | * @param end 数组的结束索引 21 | */ 22 | public static void quickSort(int[] args, int start, int end) { 23 | //当分治的元素大于1个的时候,才有意义 24 | if ( end - start > 1) { 25 | int mid = 0; 26 | mid = dividerAndChange(args, start, end); 27 | // 对左部分排序 28 | quickSort(args, start, mid); 29 | // 对右部分排序 30 | quickSort(args, mid + 1, end); 31 | } 32 | } 33 | 34 | /** 35 | * 拆分数组 36 | * 37 | * @param args 要拆分的数组 38 | * @param start 数组拆分的起始索引 (从0开始) 39 | * @param end 数组拆分的结束索引 40 | */ 41 | public static int dividerAndChange(int[] args, int start, int end) { 42 | //标准值 43 | int pivot = args[start]; 44 | while (start < end) { 45 | // 从右向左寻找,一直找到比参照值还小的数值,进行替换 46 | // 这里要注意,循环条件必须是 当后面的数 小于 参照值的时候 47 | // 我们才跳出这一层循环 48 | while (start < end && args[end] >= pivot) 49 | end--; 50 | 51 | if (start < end) { 52 | swap(args, start, end); 53 | start++; 54 | } 55 | 56 | // 从左向右寻找,一直找到比参照值还大的数组,进行替换 57 | while (start < end && args[start] < pivot) 58 | start++; 59 | 60 | if (start < end) { 61 | swap(args, end, start); 62 | end--; 63 | } 64 | } 65 | 66 | args[start] = pivot; 67 | return start; 68 | } 69 | 70 | /** 71 | * 交换数组中指定位置的两个元素 72 | * 73 | * @param args 74 | * @param fromIndex 75 | * @param toIndex 76 | */ 77 | private static void swap(int[] args, int fromIndex, int toIndex) { 78 | args[fromIndex] = args[toIndex]; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/RadixSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | import java.util.ArrayList; 3 | import java.util.List; 4 | 5 | public class RadixSort { 6 | 7 | public static void main(String[] strs) { 8 | // TODO Auto-generated method stub 9 | int[] args = {8, 2, 1, 4,6, 3, 5, 9, 6,11,19,13,55,67,32}; 10 | radixSortByAsc(args); 11 | for(int i = 0; i < args.length; i++) { 12 | System.out.print(args[i] + ", "); 13 | } 14 | 15 | } 16 | 17 | /** 18 | * 基数排序法 19 | * 升序排列 20 | * @param data 21 | */ 22 | public static void radixSortByAsc(int[] data) { 23 | /** step1:确定排序的趟数*/ 24 | int max=data[0]; 25 | for(int i=1;imax) { 27 | max=data[i]; 28 | } 29 | } 30 | /** step2:判断位数*/ 31 | int digit = 0; 32 | while(max > 0) { 33 | max/=10; 34 | digit++; 35 | } 36 | /**初始化一个二维数组,相当于二维数组,可以把重复的存进去*/ 37 | List> temp = new ArrayList<>(); 38 | for(int i = 0;i < 10;i++) { 39 | temp.add(new ArrayList()); 40 | } 41 | /**开始合并收集*/ 42 | for(int i = 0; i < digit; i++) { 43 | /** 对每一位进行排序 */ 44 | for(int j = 0; j < data.length; j++) { 45 | /**求每一个数的第i位的数,然后存到相对应的数组中*/ 46 | int digitInx = data[j]%(int)Math.pow(10, i + 1)/(int)Math.pow(10, i); 47 | ArrayList tempInside = temp.get(digitInx); 48 | tempInside.add(data[j]); 49 | temp.set(digitInx,tempInside ); 50 | } 51 | /**收集数组元素*/ 52 | int count = 0; 53 | for(int k = 0;k < 10;k++) { 54 | for(;temp.get(k).size()>0;count++) { 55 | ArrayList temp2 = temp.get(k); 56 | data[count] = temp2.get(0); 57 | temp2.remove(0); 58 | } 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * 基数排序法 65 | * 降序排列 66 | * @param data 67 | */ 68 | public static void radixSortByDesc(int[] data) { 69 | 70 | /** step1:确定排序的趟数*/ 71 | int max=data[0]; 72 | for(int i=1;imax) { 74 | max=data[i]; 75 | } 76 | 77 | } 78 | /** step2:判断位数*/ 79 | int digit = 0; 80 | while(max > 0) { 81 | max/=10; 82 | digit++; 83 | } 84 | /**初始化一个二维数组,相当于二维数组,可以把重复的存进去*/ 85 | List> temp = new ArrayList<>(); 86 | for(int i = 0;i < 10;i++) { 87 | temp.add(new ArrayList()); 88 | } 89 | /**开始合并收集*/ 90 | for(int i = 0; i < digit; i++) { 91 | /** 对每一位进行排序 */ 92 | for(int j = 0; j < data.length; j++) { 93 | /**求每一个数的第i位的数,然后存到相对应的数组中*/ 94 | int digitInx = data[j]%(int)Math.pow(10, i + 1)/(int)Math.pow(10, i); 95 | ArrayList tempInside = temp.get(digitInx); 96 | tempInside.add(data[j]); 97 | temp.set(digitInx,tempInside ); 98 | } 99 | int count = data.length - 1; 100 | for(int k = 0;k < 10;k++) { 101 | for(;temp.get(k).size()>0;count--) { 102 | ArrayList temp2 = temp.get(k); 103 | data[count] = temp2.get(0); 104 | temp2.remove(0); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/SelectSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | public class SelectSort { 4 | 5 | public static void main(String[] strs) { 6 | // TODO Auto-generated method stub 7 | int[] args = {8, 2, 1, 4,6, 3, 5, 9, 6,11,19,13,55,67,32}; 8 | selectSort(args); 9 | for(int i = 0; i < args.length; i++) { 10 | System.out.print(args[i] + ", "); 11 | } 12 | 13 | } 14 | 15 | public static void selectSort(int[] args) { 16 | int len = args.length; 17 | for (int i = 0,k = 0; i < len; i++,k = i) { 18 | // 在这一层循环中找最小 19 | for (int j = i + 1; j < len; j++) { 20 | // 如果后面的元素比前面的小,那么就交换下标,每一趟都会选择出来一个最小值的下标 21 | if (args[k] > args[j]) k = j; 22 | } 23 | 24 | if (i != k) { 25 | int tmp = args[i]; 26 | args[i] = args[k]; 27 | args[k] = tmp; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/ShellSort.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | public class ShellSort { 4 | 5 | public static void main(String[] strs) { 6 | // TODO Auto-generated method stub 7 | int[] args = {8, 2, 1, 4,6, 3, 5, 9, 6,11,19,13,55,67,32}; 8 | shellSort(args); 9 | for(int i = 0; i < args.length; i++) { 10 | System.out.print(args[i] + ", "); 11 | } 12 | 13 | } 14 | 15 | public static void shellSort(int[] arr) { 16 | int gap = 1, i, j, len = arr.length; 17 | int temp; 18 | while (gap < len / 3) 19 | gap = gap * 3 + 1; // : 1, 4, 13, 40, 121, ... 20 | for (; gap > 0; gap /= 3) { 21 | for (i = gap; i < len; i++) { 22 | temp = arr[i]; 23 | for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap) 24 | arr[j + gap] = arr[j]; 25 | arr[j + gap] = temp; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/Algorithm_impl_java/src/sort/SortTest.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.Random; 4 | import java.util.Scanner; 5 | 6 | public class SortTest { 7 | 8 | public static final int BUBBLE_SORT = 0; 9 | public static final int HEAP_SORT = 1; 10 | public static final int INSERTION_SORT = 2; 11 | public static final int MERGE_SORT = 3; 12 | public static final int QUICK_SORT = 4; 13 | public static final int RADIX_SORT = 5; 14 | public static final int SELECT_SORT = 6; 15 | public static final int SHELL_SORT = 7; 16 | 17 | private static String[] sortNameArr = {"冒泡排序(BubbleSort)", "堆排序(HeapSort)" 18 | , "插入排序(InsertionSort)", "归并排序(MergeSort)" 19 | , "快速排序(QuickSort)", "基数排序(RadixSort)" 20 | , "选择排序(SelectSort)", "希尔排序(ShellSort)"}; 21 | 22 | public static void main(String[] args) { 23 | Scanner sc = new Scanner(System.in); 24 | System.out.println("请输入测试数组长度-LENGTH:"); 25 | int length = sc.nextInt(); 26 | System.out.println("请输入测试数据中最大值-MAXNUM:"); 27 | int maxnum = sc.nextInt(); 28 | 29 | // 随机生成待排序的数据集合 30 | int[] arr = new int[length]; 31 | Random random = new Random(); 32 | for (int i = 0; i < length; i++) { 33 | arr[i] = random.nextInt(maxnum); 34 | } 35 | 36 | for(int i=0; i<8; i++) { 37 | // 算法开始的时间 38 | long beginTime = System.currentTimeMillis(); 39 | // 执行算法 40 | sortTest(arr, length, maxnum, i); 41 | // 算法运行的时间 = 算法结束的时间 - beginTime 42 | long deffTime = System.currentTimeMillis() - beginTime; 43 | System.out.println(sortNameArr[i] + " : " + deffTime); 44 | } 45 | 46 | } 47 | 48 | private static void sortTest(int[] arr, int length, int maxnum, int sortType) { 49 | // TODO Auto-generated method stub 50 | switch (sortType) { 51 | case BUBBLE_SORT: 52 | BubbleSort.bubbleSort(arr); 53 | break; 54 | case HEAP_SORT: 55 | HeapSort.heapSort(arr); 56 | break; 57 | case INSERTION_SORT: 58 | InsertionSort.insertionSort(arr); 59 | break; 60 | case MERGE_SORT: 61 | MergeSort.mergeSort(arr, 0, arr.length-1); 62 | break; 63 | case QUICK_SORT: 64 | QuickSort.quickSort(arr, 0, arr.length-1); 65 | break; 66 | case RADIX_SORT: 67 | RadixSort.radixSortByAsc(arr); 68 | break; 69 | case SELECT_SORT: 70 | SelectSort.selectSort(arr); 71 | break; 72 | case SHELL_SORT: 73 | ShellSort.shellSort(arr); 74 | break; 75 | default: 76 | break; 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/冒泡排序.md: -------------------------------------------------------------------------------- 1 | # 冒泡排序: 2 | --- 3 | * 背景介绍: 是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F) 4 | * 算法规则: 由于算法每次都将一个最大的元素往上冒,我们可以将待排序集合(0...n)看成两部分,一部分为(k..n)的待排序unsorted集合,另一部分为(0...k)的已排序sorted集合,每一次都在unsorted集合从前往后遍历,选出一个数,如果这个数比其后面的数大,则进行交换。完成一轮之后,就肯定能将这一轮unsorted集合中最大的数移动到集合的最后,并且将这个数从unsorted中删除,移入sorted中。 5 | 6 | * 代码实现(Java版本) 7 | ``` 8 | public void bubbleSort(int[] args) { 9 | //第一层循环从数组的最后往前遍历 10 | for (int i = args.length - 1; i > 0 ; --i) { 11 | //这里循环的上界是 i - 1,在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除” 12 | for (int j = 0; j < i; j++) { 13 | //保证在相邻的两个数中比较选出最大的并且进行交换(冒泡过程) 14 | if (args[j] > args[j+1]) { 15 | int temp = args[j]; 16 | args[j] = args[j+1]; 17 | args[j+1] = temp; 18 | } 19 | } 20 | } 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/基数排序.md: -------------------------------------------------------------------------------- 1 | # 基数排序: 2 | --- 3 | * 背景介绍: 是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。 4 | 5 | * 算法规则: 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。基数排序可以采用两种方式分别是LSD(Least Significant Digital)和 MSD(Most Significant Digital);我们以LSD方式为例,从数组R[1..n]中每个元素的最低位开始处理,假设基数为radix,如果是十进制,则radix=10。基本过程如下所示:
1.计算R中最大的元素,求得位数最大的元素,最大位数记为distance;
2.对每一位round<=distance,计算R[i] % radix即可得到;
3.将上面计算得到的余数作为bucket编号,每个bucket中可能存放多个数组R的元素;
4.按照bucket编号的顺序,收集bucket中元素,就地替换数组R中元素;
5.重复2~4,最终数组R中的元素为有序。 6 | 7 | * 代码实现(Java版本) 8 | 1. 升序排序法(LSD): 9 | 10 | /** 11 | * 基数排序法 12 | * 升序排列 13 | * @param data 14 | */ 15 | public void sortByAsc(int[] data) { 16 | /** step1:确定排序的趟数*/ 17 | int max=data[0]; 18 | for(int i=1;imax){ 20 | max=data[i]; 21 | } 22 | } 23 | /** step2:判断位数*/ 24 | int digit = 0; 25 | while(max > 0){ 26 | max/=10; 27 | digit++; 28 | } 29 | /**初始化一个二维数组,相当于二维数组,可以把重复的存进去*/ 30 | List> temp = new ArrayList<>(); 31 | for(int i = 0;i < 10;i++){ 32 | temp.add(new ArrayList()); 33 | } 34 | /**开始合并收集*/ 35 | for(int i = 0; i < digit; i++){ 36 | /** 对每一位进行排序 */ 37 | for(int j = 0; j < data.length; j++){ 38 | /**求每一个数的第i位的数,然后存到相对应的数组中*/ 39 | int digitInx = data[j]%(int)Math.pow(10, i + 1)/(int)Math.pow(10, i); 40 | ArrayList tempInside = temp.get(digitInx); 41 | tempInside.add(data[j]); 42 | temp.set(digitInx,tempInside ); 43 | } 44 | /**收集数组元素*/ 45 | int count = 0; 46 | for(int k = 0;k < 10;k++){ 47 | for(;temp.get(k).size()>0;count++){ 48 | ArrayList temp2 = temp.get(k); 49 | data[count] = temp2.get(0); 50 | temp2.remove(0); 51 | } 52 | } 53 | } 54 | } 55 | 56 | 57 | 58 | 2. 升序排序法(MSD): 59 | 60 | /** 61 | * 基数排序法 62 | * 降序排列 63 | * @param data 64 | */ 65 | public void sortByDesc(int[] data) { 66 | 67 | /** step1:确定排序的趟数*/ 68 | int max=data[0]; 69 | for(int i=1;imax) { 71 | max=data[i]; 72 | } 73 | 74 | } 75 | /** step2:判断位数*/ 76 | int digit = 0; 77 | while(max > 0) { 78 | max/=10; 79 | digit++; 80 | } 81 | /**初始化一个二维数组,相当于二维数组,可以把重复的存进去*/ 82 | List> temp = new ArrayList<>(); 83 | for(int i = 0;i < 10;i++) { 84 | temp.add(new ArrayList()); 85 | } 86 | /**开始合并收集*/ 87 | for(int i = 0; i < digit; i++) { 88 | /** 对每一位进行排序 */ 89 | for(int j = 0; j < data.length; j++) { 90 | /**求每一个数的第i位的数,然后存到相对应的数组中*/ 91 | int digitInx = data[j]%(int)Math.pow(10, i + 1)/(int)Math.pow(10, i); 92 | ArrayList tempInside = temp.get(digitInx); 93 | tempInside.add(data[j]); 94 | temp.set(digitInx,tempInside ); 95 | } 96 | int count = data.length - 1; 97 | for(int k = 0;k < 10;k++) { 98 | for(;temp.get(k).size()>0;count--) { 99 | ArrayList temp2 = temp.get(k); 100 | data[count] = temp2.get(0); 101 | temp2.remove(0); 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /Algorithm-learning/Sort/堆排序.md: -------------------------------------------------------------------------------- 1 | # 堆排序 2 | --- 3 | * 背景介绍: 是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 4 | 5 | * 算法规则: 堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。 6 | 7 | * 代码实现(Java版本) 8 | 9 | /** 10 | * 堆排序 11 | */ 12 | private static void heapSort(int[] arr) { 13 | // 将待排序的序列构建成一个大顶堆 14 | for (int i = arr.length / 2; i >= 0; i--){ 15 | heapAdjust(arr, i, arr.length); 16 | } 17 | 18 | // 逐步将每个最大值的根节点与末尾元素交换,并且再调整二叉树,使其成为大顶堆 19 | for (int i = arr.length - 1; i > 0; i--) { 20 | swap(arr, 0, i); // 将堆顶记录和当前未经排序子序列的最后一个记录交换 21 | heapAdjust(arr, 0, i); // 交换之后,需要重新检查堆是否符合大顶堆,不符合则要调整 22 | } 23 | } 24 | 25 | /** 26 | * 构建堆的过程 27 | * @param arr 需要排序的数组 28 | * @param i 需要构建堆的根节点的序号 29 | * @param n 数组的长度 30 | */ 31 | private static void heapAdjust(int[] arr, int i, int n) { 32 | int child; 33 | int father; 34 | for (father = arr[i]; leftChild(i) < n; i = child) { 35 | child = leftChild(i); 36 | 37 | // 如果左子树小于右子树,则需要比较右子树和父节点 38 | if (child != n - 1 && arr[child] < arr[child + 1]) { 39 | child++; // 序号增1,指向右子树 40 | } 41 | 42 | // 如果父节点小于孩子结点,则需要交换 43 | if (father < arr[child]) { 44 | arr[i] = arr[child]; 45 | } else { 46 | break; // 大顶堆结构未被破坏,不需要调整 47 | } 48 | } 49 | arr[i] = father; 50 | } 51 | 52 | // 获取到左孩子结点 53 | private static int leftChild(int i) { 54 | return 2 * i + 1; 55 | } 56 | 57 | // 交换元素位置 58 | private static void swap(int[] arr, int index1, int index2) { 59 | int tmp = arr[index1]; 60 | arr[index1] = arr[index2]; 61 | arr[index2] = tmp; 62 | } -------------------------------------------------------------------------------- /Algorithm-learning/Sort/希尔排序.md: -------------------------------------------------------------------------------- 1 | # 希尔排序: 2 | --- 3 | * 背景介绍: 也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
2.但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F) 4 | 5 | * 算法规则: 希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。 6 | 7 | * 代码实现(Java版本) 8 | 9 | public static void shellSort(int[] arr) { 10 | int gap = 1, i, j, len = arr.length; 11 | int temp; 12 | while (gap < len / 3) 13 | gap = gap * 3 + 1; // : 1, 4, 13, 40, 121, ... 14 | for (; gap > 0; gap /= 3) { 15 | for (i = gap; i < len; i++) { 16 | temp = arr[i]; 17 | for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap) 18 | arr[j + gap] = arr[j]; 19 | arr[j + gap] = temp; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Algorithm-learning/Sort/归并排序.md: -------------------------------------------------------------------------------- 1 | # 归并排序: 2 | --- 3 | * 背景介绍: 是创建在归并操作上的一种有效的排序算法,效率为O(n log n)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F) 4 | * 算法规则: 像快速排序一样,由于归并排序也是分治算法,因此可使用分治思想:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4.重复步骤3直到某一指针到达序列尾
5.将另一序列剩下的所有元素直接复制到合并序列尾 5 | 6 | * 代码实现(Java版本) 7 | 8 | 9 | 10 | public static int[] mergeSort(int[] nums, int low, int high) { 11 | int mid = (low + high) / 2; 12 | if (low < high) { 13 | // 左边 14 | mergeSort(nums, low, mid); 15 | // 右边 16 | mergeSort(nums, mid + 1, high); 17 | // 左右归并 18 | merge(nums, low, mid, high); 19 | } 20 | return nums; 21 | } 22 | 23 | public static void merge(int[] nums, int low, int mid, int high) { 24 | int[] temp = new int[high - low + 1]; 25 | int i = low;// 左指针 26 | int j = mid + 1;// 右指针 27 | int k = 0; 28 | 29 | // 把较小的数先移到新数组中 30 | while (i <= mid && j <= high) { 31 | if (nums[i] < nums[j]) { 32 | temp[k++] = nums[i++]; 33 | } else { 34 | temp[k++] = nums[j++]; 35 | } 36 | } 37 | 38 | // 把左边剩余的数移入数组 39 | while (i <= mid) { 40 | temp[k++] = nums[i++]; 41 | } 42 | 43 | // 把右边边剩余的数移入数组 44 | while (j <= high) { 45 | temp[k++] = nums[j++]; 46 | } 47 | 48 | // 把新数组中的数覆盖nums数组 49 | for (int k2 = 0; k2 < temp.length; k2++) { 50 | nums[k2 + low] = temp[k2]; 51 | } 52 | } -------------------------------------------------------------------------------- /Algorithm-learning/Sort/快速排序.md: -------------------------------------------------------------------------------- 1 | # 快速排序: 2 | --- 3 | * 背景介绍: 又称划分交换排序(partition-exchange sort),一种排序算法,最早由东尼·霍尔提出。在平均状况下,排序n个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F) ** 4 | * 算法规则: 本质来说,快速排序的过程就是不断地将无序元素集递归分割,一直到所有的分区只包含一个元素为止。
由于快速排序是一种分治算法,我们可以用分治思想将快排分为三个步骤:
1.分:设定一个分割值,并根据它将数据分为两部分
2.治:分别在两部分用递归的方式,继续使用快速排序法
3.合:对分割的部分排序直到完成 5 | 6 | * 代码实现(Java版本) 7 | ``` 8 | public int dividerAndChange(int[] args, int start, int end) 9 | { 10 | //标准值 11 | int pivot = args[start]; 12 | while (start < end) { 13 | // 从右向左寻找,一直找到比参照值还小的数值,进行替换 14 | // 这里要注意,循环条件必须是 当后面的数 小于 参照值的时候 15 | // 我们才跳出这一层循环 16 | while (start < end && args[end] >= pivot) 17 | end--; 18 | 19 | if (start < end) { 20 | swap(args, start, end); 21 | start++; 22 | } 23 | 24 | // 从左向右寻找,一直找到比参照值还大的数组,进行替换 25 | while (start < end && args[start] < pivot) 26 | start++; 27 | 28 | if (start < end) { 29 | swap(args, end, start); 30 | end--; 31 | } 32 | } 33 | 34 | args[start] = pivot; 35 | return start; 36 | } 37 | 38 | public void quickSort(int[] args, int start, int end) 39 | { 40 | //当分治的元素大于1个的时候,才有意义 41 | if ( end - start > 1) { 42 | int mid = 0; 43 | mid = dividerAndChange(args, start, end); 44 | // 对左部分排序 45 | sort(args, start, mid); 46 | // 对右部分排序 47 | sort(args, mid + 1, end); 48 | } 49 | } 50 | 51 | private void swap(int[] args, int fromIndex, int toIndex) 52 | { 53 | args[fromIndex] = args[toIndex]; 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /Algorithm-learning/Sort/直接插入排序.md: -------------------------------------------------------------------------------- 1 | # 直接插入排序 2 | --- 3 | * 背景介绍:是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F) 4 | 5 | * 算法规则:一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
1.从第一个元素开始,该元素可以认为已经被排序
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置将
5. 新元素插入到该位置后
6.重复步骤2~5 6 | 7 | * 代码实现(Java版本) 8 | 9 | public int insertionSort( int[] arr ) { 10 | for( int i=0; i0; j-- ) { 12 | if( arr[j-1] <= arr[j] ) 13 | break; 14 | int temp = arr[j]; 15 | arr[j] = arr[j-1]; 16 | arr[j-1] = temp; 17 | } 18 | } -------------------------------------------------------------------------------- /Algorithm-learning/Sort/选择排序.md: -------------------------------------------------------------------------------- 1 | # 选择排序: 2 | --- 3 | * 背景介绍: 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 ----- 来自 [wikipedia](https://zh.wikipedia.org/wiki/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F) 4 | * 算法规则: 将待排序集合(0...n)看成两部分,在起始状态中,一部分为(k..n)的待排序unsorted集合,另一部分为(0...k)的已排序sorted集合,在待排序集合中挑选出最小元素并且记录下标i,若该下标不等于k,那么 unsorted[i] 与 sorted[k]交换 ,一直重复这个过程,直到unsorted集合中元素为空为止。 5 | 6 | * 代码实现(Java版本) 7 | ``` 8 | public void selectSort(int[] args) 9 | { 10 | int len = args.length; 11 | for (int i = 0,k = 0; i < len; i++,k = i) { 12 | // 在这一层循环中找最小 13 | for (int j = i + 1; j < len; j++) { 14 | // 如果后面的元素比前面的小,那么就交换下标,每一趟都会选择出来一个最小值的下标 15 | if (args[k] > args[j]) k = j; 16 | } 17 | 18 | if (i != k) { 19 | int tmp = args[i]; 20 | args[i] = args[k]; 21 | args[k] = tmp; 22 | } 23 | } 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /Android/Activity详细解析.md: -------------------------------------------------------------------------------- 1 | # Activity 详细解析 2 | --- 3 | 4 | ## Activity的生命周期 5 | 6 | ![Activity 生命周期图.png](http://upload-images.jianshu.io/upload_images/4037756-5574a4d135a8a7fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 7 | 8 | Android 中的 Activity 全都归属于 Task 管理 。Task 是多个 Activity 的集合,这些 activity 按照启动顺序排队存入一个栈(即“back stack”)。 Android 默认会为每个 app 维持一个 Task 来存放该 app 的所有 Activity,Task 的默认 name 为该 app 的 packagename 。 9 | 10 | 首先打开一个新的 Activity 实例的时候,系统会依次调用 **onCreate() -> onStart() -> onResume()** 然后开始 running, 在 running 的时候被覆盖了(从它打开了新的 Activity 或是被锁屏,但是它**依然在前台**运行, lost focus but is still visible),系统调用onPause(),该方法执行 Activity 暂停,通常用于提交未保存的更改到持久化数据,停止动画和其他的东西。但这个 Activity 还是完全活着(它保持所有的状态和成员信息,并保持连接到窗口管理器) 11 | 12 | **接下来这个 Activity 有三种不一样的人生:** 13 | 14 | 1. 用户返回到该 Activity 就调用 onResume() 方法重新 running 15 | 16 | 17 | 2. 用户回到桌面或是打开其他 Activity,就会调用 onStop() 进入停止状态(保留所有的状态和成员信息,**对用户不可见**) 18 | 19 | 20 | 3. 系统内存不足,拥有更高限权的应用需要内存,那么该 Activity 的进程就可能会被系统回收(回收onRause()和onStop()状态的 Activity 进程)要想重新打开就必须重新创建一遍。 21 | 22 | 如果用户返回到 onStop() 状态的 Activity(又显示在前台了),系统会调用 onRestart() -> onStart() -> onResume() 然后重新 running 在 Activity 结束(调用finish () )或是被系统杀死之前会调用 onDestroy() 方法释放所有占用的资源。 23 | 24 | **Activity 生命周期中三个嵌套的循环:** 25 | 26 | * Activity 的完整生存期会在 onCreate() 调用和 onDestroy() 调用之间发生。 27 | 28 | 29 | * Activity 的可见生存期会在 onStart() 调用和 onStop() 调用之间发生。系统会在 Activity 的整个生存期内多次调用 onStart() 和 onStop(), 因为 Activity 可能会在显示和隐藏之间不断地来回切换。  30 | 31 | 32 | * Activity 的前后台切换会在 onResume() 调用和 onPause() 之间发生。 因为这个状态可能会经常发生转换,为了避免切换迟缓引起的用户等待,**这两个方法中的代码应该相当地轻量化**。 33 | 34 | ## Activity 的启动模式 35 | 36 | ### 启动模式什么? 37 | 38 | 简单的说就是定义 Activity 实例与 Task 的关联方式。 39 | 40 | ### 为什么要定义启动模式? 41 | 42 | 为了实现一些默认启动(standard)模式之外的需求: 43 | 44 | * 让某个 Activity 启动一个新的 Task (而不是被放入当前 Task ) 45 | 46 | * 让 Activity 启动时只是调出已有的某个实例(而不是在 back stack 顶创建一个新的实例) 47 | 48 | * 或者,你想在用户离开 Task 时只保留根 Activity,而 back stack 中的其它 Activity 都要清空 49 | 50 | ### 怎样定义启动模式? 51 | 52 | 定义启动模式的方法有两种: 53 | 54 | #### 使用 manifest 文件 55 | 56 | 在 manifest 文件中 Activity 声明时,利用 Activity 元素的 **launchMode** 属性来设定 Activity 与 Task 的关系。 57 | 58 | > 注意: 你用 launchMode 属性为 Activity 设置的模式可以被启动 Activity 的 intent 标志所覆盖。 59 | 60 | ##### 有哪些启动模式? 61 | 62 | * **"standard"(默认模式)** 63 | 64 | 当通过这种模式来启动 Activity 时, Android 总会为目标 Activity 创建一个新的实例,并将该 Activity 添加到当前Task栈中。这种方式不会启动新的 Task,只是将新的 Activity 添加到原有的 Task 中。 65 | 66 | * **"singleTop"** 67 | 68 | 该模式和standard模式基本一致,但有一点不同:当将要被启动的 Activity 已经位于 Task 栈顶时,系统不会重新创建目标 Activity 实例,而是直接复用 Task 栈顶的 Activity。 69 | 70 | * **"singleTask"** 71 | 72 | Activity 在同一个 Task 内只有一个实例。如果将要启动的 Activity 不存在,那么系统将会创建该实例,并将其加入 Task 栈顶。 73 | 74 | 如果将要启动的 Activity 已存在,且存在栈顶,直接复用 Task 栈顶的 Activity。 75 | 76 | 如果 Activity 存在但是没有位于栈顶,那么此时系统会把位于该 Activity 上面的所有其他Activity全部移出 Task,从而使得该目标 Activity 位于栈顶。 77 | 78 | * **"singleInstance"** 79 | 80 | 无论从哪个 Task 中启动目标 Activity,只会创建一个目标 Activity 实例且会用一个全新的 Task 栈来装载该 Activity 实例(全局单例) 81 | 82 | 如果将要启动的 Activity 不存在,那么系统将会先创建一个全新的 Task ,再创建目标 Activity 实例并将该 Activity 实例放入此全新的 Task 中。 83 | 84 | 如果将要启动的Activity已存在,那么无论它位于哪个应用程序,哪个 Task 中;系统都会把该 Activity 所在的 Task 转到前台,从而使该 Activity 显示出来。 85 | 86 | #### 使用 Intent 标志 87 | 88 | 在要启动 activity 时,你可以在传给 startActivity() 的 intent 中包含相应标志,以修改 activity 与 task 的默认关系。 89 | 90 | like this 91 | > Intent i = new Intent(this,NewActivity.class); 92 | > **i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);** 93 | > startActivity(i); 94 | 95 | ##### 可以通过标志修改的默认模式有哪些? 96 | 97 | * FLAG_ACTIVITY_NEW_TASK 98 | 99 | 与 "singleTask" 模式相同,在新的 task 中启动 Activity。如果要启动的 Activity 已经运行于某 Task 中,则那个 task 将调入前台。 100 | 101 | * FLAG_ACTIVITY_SINGLE_TOP 102 | 103 | 与 "singleTop" 模式相同,如果要启动的 Activity位于back stack 顶,系统不会重新创建目标 Activity 实例,而是直接复用 Task 栈顶的 Activity。 104 | 105 | * FLAG_ACTIVITY_CLEAR_TOP 106 | 107 | 此种模式在 launchMode 中没有对应的属性值。 108 | 109 | 如果要启动的 Activity 已经在当前 Task 中运行,则不再启动一个新的实例,且所有在其上面的 Activity 将被销毁。 110 | 111 | > 一般不要改变 Activity 和 Task 默认的工作方式。 如果你确定有必要修改默认方式,请保持谨慎,并确保 Activity 在启动和从其它 Activity 返回时的可用性,多做测试和安全方面的工作。 112 | 113 | ## Activity被回收之后状态的保存和恢复 114 | 115 | ``` 116 | public class MainActivity extends Activity { 117 | 118 | @Override 119 | protected void onCreate(Bundle savedInstanceState) { 120 | if(savedInstanceState!=null){ //判断是否有以前的保存状态信息 121 | savedInstanceState.get("Key"); 122 | } 123 | super.onCreate(savedInstanceState); 124 | setContentView(R.layout.activity_main); 125 | } 126 | 127 | @Override 128 | protected void onSaveInstanceState(Bundle outState) { 129 | //可能被回收内存前保存状态和信息, 130 | Bundle data = new Bundle(); 131 | data.putString("key", "last words before be kill"); 132 | outState.putAll(data); 133 | super.onSaveInstanceState(outState); 134 | } 135 | 136 | @Override 137 | protected void onRestoreInstanceState(Bundle savedInstanceState) { 138 | if(savedInstanceState!=null){ //判断是否有以前的保存状态信息 139 | savedInstanceState.get("Key"); 140 | } 141 | super.onRestoreInstanceState(savedInstanceState); 142 | } 143 | } 144 | ``` 145 | 146 | ### onSaveInstanceState 方法 147 | 148 | 在 Activity 可能被回收之前调用,用来保存自己的状态和信息,以便回收后重建时恢复数据(在 onCreate() 或 onRestoreInstanceState() 中恢复)。旋转屏幕重建 Activity 会调用该方法,但其他情况在 onRause() 和 onStop() 状态的 Activity 不一定会调用,下面是该方法的文档说明。 149 | 150 | > One example of when onPause and onStop is called and not this method is when a user navigates back from activity B to activity A: there is no need to call onSaveInstanceState on B because that particular instance will never be restored, so the system avoids calling it. An example when onPause is called and not onSaveInstanceState is when activity B is launched in front of activity A: the system may avoid calling onSaveInstanceState on activity A if it isn't killed during the lifetime of B since the state of the user interface of A will stay intact. 151 | 152 | 也就是说,系统灵活的来决定调不调用该方法,**但是如果要调用就一定发生在 onStop 方法之前,但并不保证发生在 onPause 的前面还是后面。** 153 | 154 | ### onRestoreInstanceState 方法 155 | 156 | 这个方法在 onStart 和 onPostCreate 之间调用,在 onCreate 中也可以状态恢复,但有时候需要所有布局初始化完成后再恢复状态。 157 | 158 | onPostCreate:一般不实现这个方法,当程序的代码开始运行时,它调用系统做最后的初始化工作。 159 | 160 | ## Intent Filter 161 | 162 | Android 的 3 个核心组件—— Activity、Services、广播接收器——是通过 intent 传递消息的。 intent 消息用于在运行时绑定不同的组件。   在 Android 的 AndroidManifest.xml 配置文件中可以通过 intent-filter 节点为一个 Activity 指定其 Intent Filter,以便告诉系统该 Activity 可以响应什么类型的 Intent。 163 | 164 | ### intent-filter 的三大属性 165 | 166 | * **Action** 167 | 168 | 一个 Intent Filter 可以包含多个 Action,Action 列表用于标示 Activity 所能接受的“动作”,它是一个用户自定义的字符串。 169 | 170 | ``` 171 | 172 | 173 | 174 | …… 175 | 176 | 177 | ``` 178 | 179 | 在代码中使用以下语句便可以启动该Intent 对象: 180 | 181 | > Intent i=new Intent(); 182 | > i.setAction("com.scu.amazing7Action"); 183 | 184 | Action 列表中包含了“com.scu.amazing7Action”的 Activity 都将会匹配成功 185 | 186 | * **URL** 187 | 188 | 在 intent-filter 节点中,通过 data 节点匹配外部数据,也就是通过 URI 携带外部数据给目标组件。 189 | 190 | ``` 191 | 196 | ``` 197 | 198 | > 注意:只有data的所有的属性都匹配成功时 URI 数据匹配才会成功 199 | 200 | * **Category** 201 | 202 | 指定 Action 范围,这个选项指定了将要执行的这个 Action 的其他一些额外的约束.有时通过 Action,配合 Data 或 Type ,很多时候可以准确的表达出一个完整的意图了,但也会需要加一些约束在里面才能够更精准。 203 | 204 | ``` 205 | 206 | 207 | 208 | 209 | ``` 210 | 211 | ### Activity 中 Intent Filter 的匹配过程 212 | 213 | 1. 加载所有的 Intent Filter列表 214 | 215 | 2. 去掉 Action 匹配失败的 Intent Filter 216 | 217 | 3. 去掉 url 匹配失败的 Intent Filter 218 | 219 | 4. 去掉 Category 匹配失败的 Intent Filter 220 | 221 | 5. 判断剩下的 Intent Filter 数目是否为 0 。如果为 0 查找失败返回异常;如果大于 0 ,就按优先级排序,返回最高优先级的 Intent Filter 222 | 223 | 一般设置Activity为非公开的 224 | ``` 225 | 228 | ``` 229 | > 注意:非公开的 Activity 不能设置 Intent-filter,以免被其他 Activity 唤醒(如果拥有相同的 Intent-filter )。 230 | 231 | * 不要指定 Activity 的 taskAffinity 属性 232 | 233 | * 不要设置 Activity 的 LaunchMode(保持默认) 234 | 235 | > 注意 Activity 的 intent 最好也不要设定为 FLAG_ACTIVITY_NEW_TASK 236 | 237 | * 在匿名内部类中使用 this 时加上 Activity 类名(类名.this,不一定是当前 Activity ) 238 | -------------------------------------------------------------------------------- /Android/Android内存泄漏总结.md: -------------------------------------------------------------------------------- 1 | # Android 内存泄漏总结 2 | --- 3 | 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。最近自己阅读了大量相关的文档资料,打算做个 总结 沉淀下来跟大家一起分享和学习,也给自己一个警示,以后 coding 时怎么避免这些情况,提高应用的体验和质量。 4 | 5 | 我会从 java 内存泄漏的基础知识开始,并通过具体例子来说明 Android 引起内存泄漏的各种原因,以及如何利用工具来分析应用内存泄漏,最后再做总结。 6 | 7 | 8 | ## Java 内存分配策略 9 | 10 | Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。 11 | 12 | * 静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。 13 | 14 | * 栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 15 | 16 | * 堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。 17 | 18 | ## 栈与堆的区别: 19 | 20 | 在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。 21 | 22 | 堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。 23 | 24 | 举个例子: 25 | 26 | ``` 27 | public class Sample { 28 | int s1 = 0; 29 | Sample mSample1 = new Sample(); 30 | 31 | public void method() { 32 | int s2 = 1; 33 | Sample mSample2 = new Sample(); 34 | } 35 | } 36 | 37 | Sample mSample3 = new Sample(); 38 | ``` 39 | 40 | Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。 41 | mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,而它自己存在于栈中。 42 | 43 | 结论: 44 | 45 | 局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。 46 | 47 | 成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。 48 | 49 | 了解了 Java 的内存分配之后,我们再来看看 Java 是怎么管理内存的。 50 | 51 | ## Java是如何管理内存 52 | 53 | Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。 54 | 55 | 监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。 56 | 57 | 为了更好理解 GC 的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被 GC 回收。 58 | 以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。 59 | 60 | ![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/1.gif) 61 | 62 | Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。 63 | 64 | ## 什么是Java中的内存泄露 65 | 66 | 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。 67 | 68 | 在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。 69 | 70 | 通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。 71 | 72 | ![](http://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/2.gif) 73 | 74 | 因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。 75 | 76 | 对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。 77 | 78 | 同样给出一个 Java 内存泄漏的典型例子, 79 | 80 | ``` 81 | Vector v = new Vector(10); 82 | for (int i = 1; i < 100; i++) { 83 | Object o = new Object(); 84 | v.add(o); 85 | o = null; 86 | } 87 | ``` 88 | 89 | 在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。 90 | 91 | 92 | **详细Java中的内存泄漏** 93 | 94 | 1.Java内存回收机制 95 | 96 | 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。 97 | 98 | 2.Java内存泄漏引起的原因 99 | 100 | 内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。j 101 | 102 | Java内存泄漏的根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。具体主要有如下几大类: 103 | 104 | 1、静态集合类引起内存泄漏: 105 | 106 | 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。 107 | 108 | 例如 109 | 110 | ``` 111 | Static Vector v = new Vector(10); 112 | for (int i = 1; i<100; i++) 113 | { 114 | Object o = new Object(); 115 | v.add(o); 116 | o = null; 117 | } 118 | ``` 119 | 120 | 在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。 121 | 122 | 2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。 123 | 124 | 例如: 125 | 126 | ``` 127 | public static void main(String[] args) 128 | { 129 | Set set = new HashSet(); 130 | Person p1 = new Person("唐僧","pwd1",25); 131 | Person p2 = new Person("孙悟空","pwd2",26); 132 | Person p3 = new Person("猪八戒","pwd3",27); 133 | set.add(p1); 134 | set.add(p2); 135 | set.add(p3); 136 | System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! 137 | p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 138 | 139 | set.remove(p3); //此时remove不掉,造成内存泄漏 140 | 141 | set.add(p3); //重新添加,居然添加成功 142 | System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! 143 | for (Person person : set) 144 | { 145 | System.out.println(person); 146 | } 147 | } 148 | ``` 149 | 150 | 3、监听器 151 | 152 | 在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。 153 | 154 | 4、各种连接 155 | 156 | 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。 157 | 158 | 5、内部类和外部模块的引用 159 | 160 | 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: 161 | public void registerMsg(Object b); 162 | 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。 163 | 164 | 6、单例模式 165 | 166 | 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏,考虑下面的例子: 167 | 168 | ``` 169 | class A{ 170 | public A(){ 171 | B.getInstance().setA(this); 172 | } 173 | .... 174 | } 175 | //B类采用单例模式 176 | class B{ 177 | private A a; 178 | private static B instance=new B(); 179 | public B(){} 180 | public static B getInstance(){ 181 | return instance; 182 | } 183 | public void setA(A a){ 184 | this.a=a; 185 | } 186 | //getter... 187 | } 188 | ``` 189 | 190 | 191 | 显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况 192 | 193 | ## Android中常见的内存泄漏汇总 194 | --- 195 | 196 | ### 集合类泄漏 197 | 198 | 集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。比如上面的典型例子就是其中一种情况,当然实际上我们在项目中肯定不会写这么 2B 的代码,但稍不注意还是很容易出现这种情况,比如我们都喜欢通过 HashMap 做一些缓存之类的事,这种情况就要多留一些心眼。 199 | 200 | ### 单例造成的内存泄漏 201 | 202 | 由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如下面一个典型的例子, 203 | 204 | ``` 205 | public class AppManager { 206 | private static AppManager instance; 207 | private Context context; 208 | private AppManager(Context context) { 209 | this.context = context; 210 | } 211 | public static AppManager getInstance(Context context) { 212 | if (instance == null) { 213 | instance = new AppManager(context); 214 | } 215 | return instance; 216 | } 217 | } 218 | ``` 219 | 220 | 这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要: 221 | 222 | 1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。 223 | 224 | 2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。 225 | 226 | 正确的方式应该改为下面这种方式: 227 | 228 | ``` 229 | public class AppManager { 230 | private static AppManager instance; 231 | private Context context; 232 | private AppManager(Context context) { 233 | this.context = context.getApplicationContext();// 使用Application 的context 234 | } 235 | public static AppManager getInstance(Context context) { 236 | if (instance == null) { 237 | instance = new AppManager(context); 238 | } 239 | return instance; 240 | } 241 | } 242 | ``` 243 | 244 | 或者这样写,连 Context 都不用传进来了: 245 | 246 | ``` 247 | 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context, 248 | 249 | ... 250 | 251 | context = getApplicationContext(); 252 | 253 | ... 254 | /** 255 | * 获取全局的context 256 | * @return 返回全局context对象 257 | */ 258 | public static Context getContext(){ 259 | return context; 260 | } 261 | 262 | public class AppManager { 263 | private static AppManager instance; 264 | private Context context; 265 | private AppManager() { 266 | this.context = MyApplication.getContext();// 使用Application 的context 267 | } 268 | public static AppManager getInstance() { 269 | if (instance == null) { 270 | instance = new AppManager(); 271 | } 272 | return instance; 273 | } 274 | } 275 | ``` 276 | 277 | ### 匿名内部类/非静态内部类和异步线程 278 | 279 | 非静态内部类创建静态实例造成的内存泄漏 280 | 281 | 有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法: 282 | 283 | ``` 284 | public class MainActivity extends AppCompatActivity { 285 | private static TestResource mResource = null; 286 | @Override 287 | protected void onCreate(Bundle savedInstanceState) { 288 | super.onCreate(savedInstanceState); 289 | setContentView(R.layout.activity_main); 290 | if(mManager == null){ 291 | mManager = new TestResource(); 292 | } 293 | //... 294 | } 295 | class TestResource { 296 | //... 297 | } 298 | } 299 | ``` 300 | 这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为: 301 | 302 | 将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下: 303 | 304 | ![](http://img.blog.csdn.net/20151123144226349?spm=5176.100239.blogcont.9.CtU1c4) 305 | 306 | 其中: NO1表示 Application 和 Service 可以启动一个 Activity,不过需要创建一个新的 task 任务队列。而对于 Dialog 而言,只有在 Activity 中才能创建 307 | 308 | ### 匿名内部类 309 | 310 | android开发经常会继承实现Activity/Fragment/View,此时如果你使用了匿名类,并被异步线程持有了,那要小心了,如果没有任何措施这样一定会导致泄露 311 | 312 | ``` 313 | public class MainActivity extends Activity { 314 | ... 315 | Runnable ref1 = new MyRunable(); 316 | Runnable ref2 = new Runnable() { 317 | @Override 318 | public void run() { 319 | 320 | } 321 | }; 322 | ... 323 | } 324 | ``` 325 | 326 | ref1和ref2的区别是,ref2使用了匿名内部类。我们来看看运行时这两个引用的内存: 327 | 328 | ![](http://img2.tbcdn.cn/L1/461/1/fb05ff6d2e68f309b94dd84352c81acfe0ae839e?spm=5176.100239.blogcont.10.CtU1c4) 329 | 330 | 可以看到,ref1没什么特别的。 331 | 332 | 但ref2这个匿名类的实现对象里面多了一个引用: 333 | 334 | this$0这个引用指向MainActivity.this,也就是说当前的MainActivity实例会被ref2持有,如果将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就造成了Activity的泄露。 335 | 336 | ### Handler 造成的内存泄漏 337 | 338 | Handler 的使用造成的内存泄漏问题应该说是最为常见了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。 339 | 340 | 由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。 341 | 342 | 举个例子: 343 | 344 | ``` 345 | public class SampleActivity extends Activity { 346 | 347 | private final Handler mLeakyHandler = new Handler() { 348 | @Override 349 | public void handleMessage(Message msg) { 350 | // ... 351 | } 352 | } 353 | 354 | @Override 355 | protected void onCreate(Bundle savedInstanceState) { 356 | super.onCreate(savedInstanceState); 357 | 358 | // Post a message and delay its execution for 10 minutes. 359 | mLeakyHandler.postDelayed(new Runnable() { 360 | @Override 361 | public void run() { /* ... */ } 362 | }, 1000 * 60 * 10); 363 | 364 | // Go back to the previous Activity. 365 | finish(); 366 | } 367 | } 368 | ``` 369 | 370 | 在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。 371 | 372 | 修复方法:在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码: 373 | 374 | ``` 375 | public class SampleActivity extends Activity { 376 | 377 | /** 378 | * Instances of static inner classes do not hold an implicit 379 | * reference to their outer class. 380 | */ 381 | private static class MyHandler extends Handler { 382 | private final WeakReference mActivity; 383 | 384 | public MyHandler(SampleActivity activity) { 385 | mActivity = new WeakReference(activity); 386 | } 387 | 388 | @Override 389 | public void handleMessage(Message msg) { 390 | SampleActivity activity = mActivity.get(); 391 | if (activity != null) { 392 | // ... 393 | } 394 | } 395 | } 396 | 397 | private final MyHandler mHandler = new MyHandler(this); 398 | 399 | /** 400 | * Instances of anonymous classes do not hold an implicit 401 | * reference to their outer class when they are "static". 402 | */ 403 | private static final Runnable sRunnable = new Runnable() { 404 | @Override 405 | public void run() { /* ... */ } 406 | }; 407 | 408 | @Override 409 | protected void onCreate(Bundle savedInstanceState) { 410 | super.onCreate(savedInstanceState); 411 | 412 | // Post a message and delay its execution for 10 minutes. 413 | mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 414 | 415 | // Go back to the previous Activity. 416 | finish(); 417 | } 418 | } 419 | ``` 420 | 421 | 综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。 422 | 423 | 前面提到了 WeakReference,所以这里就简单的说一下 Java 对象的几种引用类型。 424 | 425 | Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种。 426 | 427 | ![](https://gw.alicdn.com/tps/TB1U6TNLVXXXXchXFXXXXXXXXXX-644-546.jpg) 428 | 429 | 在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。 430 | 431 | 软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。 432 | 433 | 假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形: 434 | 435 | 首先定义一个HashMap,保存软引用对象。 436 | 437 | ``` 438 | private Map > imageCache = new HashMap > (); 439 | ``` 440 | 441 | 再来定义一个方法,保存Bitmap的软引用到HashMap。 442 | 443 | ![](https://gw.alicdn.com/tps/TB1oW_FLVXXXXXuaXXXXXXXXXXX-679-717.jpg) 444 | 445 | 使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。 446 | 447 | 如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。 448 | 449 | 另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。 450 | 451 | ok,继续回到主题。前面所说的,创建一个静态Handler内部类,然后对 Handler 持有的对象使用弱引用,这样在回收时也可以回收 Handler 持有的对象,但是这样做虽然避免了 Activity 泄漏,不过 Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。 452 | 453 | 下面几个方法都可以移除 Message: 454 | 455 | ``` 456 | public final void removeCallbacks(Runnable r); 457 | 458 | public final void removeCallbacks(Runnable r, Object token); 459 | 460 | public final void removeCallbacksAndMessages(Object token); 461 | 462 | public final void removeMessages(int what); 463 | 464 | public final void removeMessages(int what, Object object); 465 | ``` 466 | 467 | ### 尽量避免使用 static 成员变量 468 | 469 | 如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。 470 | 471 | 这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,yi'wei如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。 472 | 473 | 这里修复的方法是: 474 | 475 | 不要在类初始时初始化静态成员。可以考虑lazy初始化。 476 | 架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。 477 | 478 | ### 避免 override finalize() 479 | 480 | 1、finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是: 481 | 虚拟机调用GC的时间不确定 482 | Finalize daemon线程被调度到的时间不确定 483 | 484 | 2、finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是: 485 | 486 | 含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。 487 | 488 | 3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。 489 | 490 | 491 | ### 资源未关闭造成的内存泄漏 492 | 493 | 对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。 494 | 495 | ### 一些不良代码造成的内存压力 496 | 497 | 有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。 498 | 499 | 比如: 500 | Bitmap 没调用 recycle()方法,对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存,然后才它设置为 null. 因为加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C 的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。 501 | 构造 Adapter 时,没有使用缓存的 convertView ,每次都在创建新的 converView。这里推荐使用 ViewHolder。 502 | 503 | ## 总结 504 | 505 | 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。 506 | 507 | 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。 508 | 509 | 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏: 510 | 511 | 将内部类改为静态内部类 512 | 静态内部类中使用弱引用来引用外部类的成员变量 513 | 514 | Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable. 515 | 516 | 在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。 517 | 518 | 正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。 519 | 520 | 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。 -------------------------------------------------------------------------------- /Android/Android录音实现(AudioRecord).md: -------------------------------------------------------------------------------- 1 | # Android录音实现(AudioRecord) 2 | --- 3 | 上一篇文章介绍了使用 MediaRecorder 实现录音功能 [Android录音实现(MediaRecorder)](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Android录音实现(MediaRecorder).md) ,下面我们继续看看使用 AudioRecord 实现录音功能。 4 | 5 | ### AudioRecord 6 | 7 | 首先看看Android帮助文档中对该类的简单概述: AndioRecord 类的主要功能是让各种 Java 应用能够管理音频资源,以便它们通过此类能够录制平台的声音输入硬件所收集的声音。此功能的实现就是通过 "pulling 同步"(reading读取)AudioRecord 对象的声音数据来完成的。在录音过程中,应用所需要做的就是通过后面三个类方法中的一个去及时地获取 AudioRecord 对象的录音数据。 AudioRecord 类提供的三个获取声音数据的方法分别是 read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int)。无论选择使用那一个方法都必须事先设定方便用户的声音数据的存储格式。 8 | 9 | 开始录音的时候,一个 AudioRecord 需要初始化一个相关联的声音buffer,这个 buffer 主要是用来保存新的声音数据。这个 buffer 的大小,我们可以在对象构造期间去指定。它表明一个 AudioRecord 对象还没有被读取(同步)声音数据前能录多长的音(即一次可以录制的声音容量)。声音数据从音频硬件中被读出,数据大小不超过整个录音数据的大小(可以分多次读出),即每次读取初始化 buffer 容量的数据。 10 | 11 | 采集工作很简单,我们只需要构造一个AudioRecord对象,然后传入各种不同配置的参数即可。一般情况下录音实现的简单流程如下: 12 | 13 | 1. 音频源:我们可以使用麦克风作为采集音频的数据源。 14 | 15 | 2. 采样率:一秒钟对声音数据的采样次数,采样率越高,音质越好。 16 | 17 | 3. 音频通道:单声道,双声道等, 18 | 19 | 4. 音频格式:一般选用PCM格式,即原始的音频样本。 20 | 21 | 5. 缓冲区大小:音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。(将音频采集到缓冲区中然后再从缓冲区中读取)。 22 | 23 | 代码实现如下: 24 | 25 | public class AudioRecorder { 26 | private static AudioRecorder audioRecorder; 27 | // 音频源:音频输入-麦克风 28 | private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC; 29 | // 采样率 30 | // 44100是目前的标准,但是某些设备仍然支持22050,16000,11025 31 | // 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级 32 | private final static int AUDIO_SAMPLE_RATE = 16000; 33 | // 音频通道 单声道 34 | private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO; 35 | // 音频格式:PCM编码 36 | private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT; 37 | // 缓冲区大小:缓冲区字节大小 38 | private int bufferSizeInBytes = 0; 39 | // 录音对象 40 | private AudioRecord audioRecord; 41 | // 录音状态 42 | private Status status = Status.STATUS_NO_READY; 43 | // 文件名 44 | private String fileName; 45 | // 录音文件集合 46 | private List filesName = new ArrayList<>(); 47 | 48 | private AudioRecorder() { 49 | } 50 | 51 | //单例模式 52 | public static AudioRecorder getInstance() { 53 | if (audioRecorder == null) { 54 | audioRecorder = new AudioRecorder(); 55 | } 56 | return audioRecorder; 57 | } 58 | 59 | /** 60 | * 创建录音对象 61 | */ 62 | public void createAudio(String fileName, int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) { 63 | // 获得缓冲区字节大小 64 | bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, 65 | channelConfig, audioFormat); 66 | audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); 67 | this.fileName = fileName; 68 | } 69 | 70 | /** 71 | * 创建默认的录音对象 72 | * @param fileName 文件名 73 | */ 74 | public void createDefaultAudio(String fileName) { 75 | mContext = ctx; 76 | mHandler = handler; 77 | // 获得缓冲区字节大小 78 | bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, 79 | AUDIO_CHANNEL, AUDIO_ENCODING); 80 | audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes); 81 | this.fileName = fileName; 82 | status = Status.STATUS_READY; 83 | } 84 | 85 | /** 86 | * 开始录音 87 | * @param listener 音频流的监听 88 | */ 89 | public void startRecord(final RecordStreamListener listener) { 90 | 91 | if (status == Status.STATUS_NO_READY || TextUtils.isEmpty(fileName)) { 92 | throw new IllegalStateException("录音尚未初始化,请检查是否禁止了录音权限~"); 93 | } 94 | if (status == Status.STATUS_START) { 95 | throw new IllegalStateException("正在录音"); 96 | } 97 | Log.d("AudioRecorder","===startRecord==="+audioRecord.getState()); 98 | audioRecord.startRecording(); 99 | 100 | new Thread(new Runnable() { 101 | @Override 102 | public void run() { 103 | writeDataTOFile(listener); 104 | } 105 | }).start(); 106 | } 107 | 108 | /** 109 | * 停止录音 110 | */ 111 | public void stopRecord() { 112 | Log.d("AudioRecorder","===stopRecord==="); 113 | if (status == Status.STATUS_NO_READY || status == Status.STATUS_READY) { 114 | throw new IllegalStateException("录音尚未开始"); 115 | } else { 116 | audioRecord.stop(); 117 | status = Status.STATUS_STOP; 118 | release(); 119 | } 120 | } 121 | 122 | /** 123 | * 取消录音 124 | */ 125 | public void canel() { 126 | filesName.clear(); 127 | fileName = null; 128 | if (audioRecord != null) { 129 | audioRecord.release(); 130 | audioRecord = null; 131 | } 132 | status = Status.STATUS_NO_READY; 133 | } 134 | 135 | /** 136 | * 释放资源 137 | */ 138 | public void release() { 139 | Log.d("AudioRecorder","===release==="); 140 | //假如有暂停录音 141 | try { 142 | if (filesName.size() > 0) { 143 | List filePaths = new ArrayList<>(); 144 | for (String fileName : filesName) { 145 | filePaths.add(FileUtils.getPcmFileAbsolutePath(fileName)); 146 | } 147 | //清除 148 | filesName.clear(); 149 | //将多个pcm文件转化为wav文件 150 | mergePCMFilesToWAVFile(filePaths); 151 | 152 | } else { 153 | //这里由于只要录音过filesName.size都会大于0,没录音时fileName为null 154 | //会报空指针 NullPointerException 155 | // 将单个pcm文件转化为wav文件 156 | //Log.d("AudioRecorder", "=====makePCMFileToWAVFile======"); 157 | //makePCMFileToWAVFile(); 158 | } 159 | } catch (IllegalStateException e) { 160 | throw new IllegalStateException(e.getMessage()); 161 | } 162 | 163 | if (audioRecord != null) { 164 | audioRecord.release(); 165 | audioRecord = null; 166 | } 167 | status = Status.STATUS_NO_READY; 168 | } 169 | 170 | /** 171 | * 将音频信息写入文件 172 | * @param listener 音频流的监听 173 | */ 174 | private void writeDataTOFile(RecordStreamListener listener) { 175 | // new一个byte数组用来存一些字节数据,大小为缓冲区大小 176 | byte[] audiodata = new byte[bufferSizeInBytes]; 177 | 178 | FileOutputStream fos = null; 179 | int readsize = 0; 180 | try { 181 | String currentFileName = fileName; 182 | if (status == Status.STATUS_PAUSE) { 183 | //假如是暂停录音 将文件名后面加个数字,防止重名文件内容被覆盖 184 | currentFileName += filesName.size(); 185 | } 186 | filesName.add(currentFileName); 187 | File file = new File(FileUtils.getPcmFileAbsolutePath(currentFileName)); 188 | if (file.exists()) { 189 | file.delete(); 190 | } 191 | fos = new FileOutputStream(file);// 建立一个可存取字节的文件 192 | } catch (IllegalStateException e) { 193 | Log.e("AudioRecorder", e.getMessage()); 194 | throw new IllegalStateException(e.getMessage()); 195 | } catch (FileNotFoundException e) { 196 | Log.e("AudioRecorder", e.getMessage()); 197 | } 198 | //将录音状态设置成正在录音状态 199 | status = Status.STATUS_START; 200 | while (status == Status.STATUS_START) { 201 | readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes); 202 | if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) { 203 | try { 204 | fos.write(audiodata); 205 | if (listener != null) { 206 | //用于拓展业务 207 | listener.recordOfByte(audiodata, 0, audiodata.length); 208 | } 209 | } catch (IOException e) { 210 | Log.e("AudioRecorder", e.getMessage()); 211 | } 212 | } 213 | } 214 | try { 215 | if (fos != null) { 216 | fos.close();// 关闭写入流 217 | } 218 | } catch (IOException e) { 219 | Log.e("AudioRecorder", e.getMessage()); 220 | } 221 | } 222 | } 223 | 224 | AudioRecorder 录音声音数据从音频硬件中被读出,编码格式为 PCM格式,但 PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩。下面实现 PCM 语音数据转为 WAV 文件。 225 | 226 | /** 227 | * 将一个pcm文件转化为wav文件 228 | * @param pcmPath pcm文件路径 229 | * @param destinationPath 目标文件路径(wav) 230 | * @param deletePcmFile 是否删除源文件 231 | * @return 232 | */ 233 | public static boolean makePCMFileToWAVFile(String pcmPath, String destinationPath, boolean deletePcmFile) { 234 | byte buffer[] = null; 235 | int TOTAL_SIZE = 0; 236 | File file = new File(pcmPath); 237 | if (!file.exists()) { 238 | return false; 239 | } 240 | TOTAL_SIZE = (int) file.length(); 241 | // 填入参数,比特率等等。这里用的是16位单声道 8000 hz 242 | WaveHeader header = new WaveHeader(); 243 | // 长度字段 = 内容的大小(TOTAL_SIZE) + 244 | // 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节) 245 | header.fileLength = TOTAL_SIZE + (44 - 8); 246 | header.FmtHdrLeth = 16; 247 | header.BitsPerSample = 16; 248 | header.Channels = 2; 249 | header.FormatTag = 0x0001; 250 | header.SamplesPerSec = 8000; 251 | header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8); 252 | header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec; 253 | header.DataHdrLeth = TOTAL_SIZE; 254 | 255 | byte[] h = null; 256 | try { 257 | h = header.getHeader(); 258 | } catch (IOException e1) { 259 | Log.e("PcmToWav", e1.getMessage()); 260 | return false; 261 | } 262 | 263 | if (h.length != 44) // WAV标准,头部应该是44字节,如果不是44个字节则不进行转换文件 264 | return false; 265 | 266 | // 先删除目标文件 267 | File destfile = new File(destinationPath); 268 | if (destfile.exists()) 269 | destfile.delete(); 270 | 271 | // 合成的pcm文件的数据,写到目标文件 272 | try { 273 | buffer = new byte[1024 * 4]; // Length of All Files, Total Size 274 | InputStream inStream = null; 275 | OutputStream ouStream = null; 276 | 277 | ouStream = new BufferedOutputStream(new FileOutputStream( 278 | destinationPath)); 279 | ouStream.write(h, 0, h.length); 280 | inStream = new BufferedInputStream(new FileInputStream(file)); 281 | int size = inStream.read(buffer); 282 | while (size != -1) { 283 | ouStream.write(buffer); 284 | size = inStream.read(buffer); 285 | } 286 | inStream.close(); 287 | ouStream.close(); 288 | } catch (FileNotFoundException e) { 289 | Log.e("PcmToWav", e.getMessage()); 290 | return false; 291 | } catch (IOException ioe) { 292 | Log.e("PcmToWav", ioe.getMessage()); 293 | return false; 294 | } 295 | if (deletePcmFile) { 296 | file.delete(); 297 | } 298 | Log.i("PcmToWav", "makePCMFileToWAVFile success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date())); 299 | return true; 300 | } 301 | 302 | 303 | 总结:AudioRecorder 录音相比较 MediaRecorder 使用起来会麻烦一些,但优点也是显而易见的,AudioRecorder 录音时直接操纵硬件获取音频流数据,该过程是实时处理,可以用代码实现各种音频的封装,同时也可实现暂停功能,关于实现暂停录音功能今天在这里就不赘述了,推荐大家阅读 imhxl 博主的分享 http://blog.csdn.net/imhxl/article/details/52190451 。 -------------------------------------------------------------------------------- /Android/Android录音实现(MediaRecord).md: -------------------------------------------------------------------------------- 1 | # Android录音实现(MediaRecorder) 2 | --- 3 | 最近在项目中实现录音功能,并在逻辑中还有对录音文件的特殊要求,前前后后看了很多资料,学习了很多,今天在这里分享记录一下,以便后期回看。 4 | 5 | Android提供了两个API用于录音的实现:MediaRecorder 和AudioRecord。 6 | 7 | 1. MediaRecorder:录制的音频文件是经过压缩后的,需要设置编码器。并且录制的音频文件可以用系统自带的Music播放器播放。MediaRecorder已经集成了录音、编码、压缩等,并支持少量的录音音频格式,但是这也是他的缺点,支持的格式过少并且无法实时处理音频数据。 8 | 9 | 2. AudioRecord:主要实现对音频实时处理以及边录边播功能,相对MediaRecorder比较专业,输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩。 10 | 11 | ### MediaRecorder 12 | 13 | MediaRecorder因为已经集成了录音、编码、压缩等功能,所以使用起来相对比较简单。 14 | 15 | #### 开始录音 16 | 17 | MediaRecorder 使用起来相对简单,音频编码可以根据自己实际需要自己设定,文件名防止重复,使用了日期_时分秒的结构,audioSaveDir 是文件存储目录,可自行设定。下面贴出示例代码: 18 | 19 | public void startRecord() { 20 | // 开始录音 21 | /* ①Initial:实例化MediaRecorder对象 */ 22 | if (mMediaRecorder == null) 23 | mMediaRecorder = new MediaRecorder(); 24 | try { 25 | /* ②setAudioSource/setVedioSource */ 26 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风 27 | /* 28 | * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式 29 | * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB) 30 | */ 31 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 32 | /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */ 33 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 34 | fileName = DateFormat.format("yyyyMMdd_HHmmss", Calendar.getInstance(Locale.CHINA)) + ".m4a"; 35 | if (!FileUtils.isFolderExist(FileUtils.getFolderName(audioSaveDir))) { 36 | FileUtils.makeFolders(audioSaveDir); 37 | } 38 | filePath = audioSaveDir + fileName; 39 | /* ③准备 */ 40 | mMediaRecorder.setOutputFile(filePath); 41 | mMediaRecorder.prepare(); 42 | /* ④开始 */ 43 | mMediaRecorder.start(); 44 | } catch (IllegalStateException e) { 45 | LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage()); 46 | } catch (IOException e) { 47 | LogUtil.i("call startAmr(File mRecAudioFile) failed!" + e.getMessage()); 48 | } 49 | } 50 | 51 | 上面代码只是基本使用方式,具体使用还需结合项目具体需求制定具体逻辑,但是MediaRecorder使用时需实例化,所以在不用时一定要记得即时释放,以免造成内存泄漏。 52 | 53 | #### 结束录音 54 | 55 | public void stopRecord() { 56 | try { 57 | mMediaRecorder.stop(); 58 | mMediaRecorder.release(); 59 | mMediaRecorder = null; 60 | filePath = ""; 61 | } catch (RuntimeException e) { 62 | LogUtil.e(e.toString()); 63 | mMediaRecorder.reset(); 64 | mMediaRecorder.release(); 65 | mMediaRecorder = null; 66 | 67 | File file = new File(filePath); 68 | if (file.exists()) 69 | file.delete(); 70 | 71 | filePath = ""; 72 | } 73 | } 74 | 75 | 总结:MediaRecorder 实现录音还是比较简单的,代码量相对较少,较为简明,但也有不足之处,例如输出文件格式选择较少,录音过程不能暂停等。 76 | 77 | 78 | -------------------------------------------------------------------------------- /Android/BroadcastReceiver 详细解析.md: -------------------------------------------------------------------------------- 1 | # BroadcastReceiver 详细解析 2 | --- 3 | 4 | 广播是一种广泛运用的在应用程序之间传输信息的机制,主要用来监听系统或者应用发出的广播信息,然后根据广播信息作为相应的逻辑处理,也可以用来传输**少量、频率低**的数据。 5 | 6 | 在实现开机启动服务和网络状态改变、电量变化、短信和来电时通过接收系统的广播让应用程序作出相应的处理。 7 | 8 | BroadcastReceiver 自身并不实现图形用户界面,但是当它收到某个通知后, BroadcastReceiver 可以通过启动 Service 、启动 Activity 或是 NotificationMananger 提醒用户。 9 | 10 | ## 使用广播的注意事项 11 | 12 | 当系统或应用发出广播时,将会扫描系统中的所有广播接收者,通过 action 匹配将广播发送给相应的接收者,接收者收到广播后将会产生一个广播接收者的实例,执行其中的 onReceiver() 这个方法;特别需要注意的是这个实例的生命周期只有**10**秒,如果**10**秒内没执行结束 onReceiver() ,系统将会报错。在 onReceiver() 执行完毕之后,该实例将会被销毁,所以**不要在 onReceiver() 中执行耗时操作,也不要在里面创建子线程处理业务**(因为可能子线程没处理完,接收者就被回收了,那么子线程也会跟着被回收掉);正确的处理方法就是通过 intent 调用 Activity 或者 Service 处理业务。 13 | 14 | ## BroadcastReceiver的注册 15 | 16 | BroadcastReceiver 的注册方式有且只有两种,一种是**静态注册**(推荐使用),另外一种是**动态注册**,广播接收者在注册后就开始监听系统或者应用之间发送的广播消息。 17 | 18 | 下面通过例子看一下两种注册方式,先定义一个接收短信的 BroadcastReceiver 类: 19 | 20 | ``` 21 | public class MyBroadcastReceiver extends BroadcastReceiver { 22 | // action 名称 23 | String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED" ; 24 | public void onReceive(Context context, Intent intent) { 25 | if (intent.getAction().equals( SMS_RECEIVED )) { 26 | // 一个receiver可以接收多个action的,即可以有 27 | // 多个intent-filter,需要在 onReceive 28 | // 里面对 intent.getAction(action name) 进行判断。 29 | ... 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | * **静态注册** 36 | 37 | 在 AndroidManifest.xml 的 application 里面定义 receiver 并设置要接收的action。 38 | 39 | ``` 40 | < receiver android:name = ".MyBroadcastReceiver" > 41 | < intent-filter android:priority = "777" > 42 | 43 | 44 | 45 | ``` 46 | 47 | 这里的 priority 取值是 -1000 到 1000 ,值越大优先级越高,同时注意加上系统接收短信的限权。 48 | 49 | 静态注册的广播接收者是一个常驻在系统中的全局监听器,当你在应用中配置了一个静态的 BroadcastReceiver ,安装了应用后而无论应用是否处于运行状态,广播接收者都是已经常驻在系统中了。同时应用里的所有 receiver 都在清单文件里面,方便查看。要销毁掉静态注册的广播接收者,可以通过调用 PackageManager 将 Receiver 禁用。 50 | 51 | * **动态注册** 52 | 53 | 在 Activity 中声明 BroadcastReceiver 的扩展对象,在 onResume 中注册,onPause 中卸载. 54 | 55 | ``` 56 | public class MainActivity extends Activity { 57 | MyBroadcastReceiver receiver; 58 | 59 | @Override 60 | protected void onResume() { 61 | // 动态注册广播 (代码执行到这才会开始监听广播消息,并对广播消息作为相应的处理) 62 | receiver = new MyBroadcastReceiver(); 63 | IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED" ); 64 | registerReceiver( receiver , intentFilter); 65 | super.onResume(); 66 | } 67 | 68 | @Override 69 | protected void onPause() { 70 | // 撤销注册 (撤销注册后广播接收者将不会再监听系统的广播消息) 71 | unregisterReceiver(receiver); 72 | super.onPause(); 73 | } 74 | } 75 | ``` 76 | 77 | * **静态注册和动态注册的区别** 78 | 79 | 1. 静态注册的广播接收者一经安装就常驻在系统之中,不需要重新启动唤醒接收者;动态注册的广播接收者随着应用的生命周期,由 registerReceiver 开始监听,由 unregisterReceiver 撤销监听,如果应用退出后,没有撤销已经注册的接收者应用应用将会报错。 80 | 81 | 2. 当广播接收者通过 intent 启动一个 activity 或者 service 时,如果 intent 中无法匹配到相应的组件。动态注册的广播接收者将会导致应用报错,而静态注册的广播接收者将不会有任何报错,因为自从应用安装完成后,广播接收者跟应用已经脱离了关系。 82 | 83 | ## 发送广播主要类型 84 | 85 | * **普通广播** 86 | 87 | 普通广播是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,所有满足条件的 BroadcastReceiver 都会随机地执行其 onReceive() 方法。 88 | 89 | 同级别接收是先后是随机的;级别低的收到广播;消息传递的效率比较高,并且无法中断广播的传播。 90 | 91 | ``` 92 | Intent intent = new Intent("android.provider.Telephony.SMS_RECEIVED"); 93 | //通过intent传递少量数据 94 | intent.putExtra("data", "finch"); 95 | // 发送普通广播 96 | sendBroadcast(Intent); 97 | ``` 98 | 99 | * **有序广播** 100 | 101 | 有序广播通过 Context.sendOrderedBroadcast() 来发送,所有的广播接收器优先级依次执行,广播接收器的优先级通过 receiver 的 intent-filter 中的 android:priority 属性来设置,数值越大优先级越高。 102 | 103 | 当广播接收器接收到广播后,可以使用 setResult() 函数来结果传给下一个广播接收器接收,然后通过 getResult() 函数来取得上个广播接收器接收返回的结果。 104 | 105 | 当广播接收器接收到广播后,也可以用 abortBroadcast() 函数来让系统拦截下来该广播,并将该广播丢弃,使该广播不再传送到别的广播接收器接收。 106 | 107 | * **本地广播** 108 | 109 | 在 API21 的 Support v4 包中新增本地广播,也就是 LocalBroadcastManager 。由于之前的广播都是全局的,所有应用程序都可以接收到,这样就会带来安全隐患,所以我们使用 LocalBroadcastManager 只发送给自己应用内的信息广播,限制在进程内使用。 110 | 111 | 它的用法很简单,只需要把调用 context 的 sendBroadcast、registerReceiver、unregisterReceiver 的地方换为 LocalBroadcastManager.getInstance(Context context)中对应的函数即可。 112 | 113 | 这里创建广播的过程和普通广播是一样的过程,这里就不过多介绍了 114 | 115 | * **系统广播** 116 | 117 | 当然系统中也会有很多自带的广播,当符合一定条件时,系统会发送一些定义好的广播,比如:重启、充电、来电电话等等。我们可以通过action属性来监听我们的系统广播,创建广播的过程和普通广播是一样的过程,这里就不过多介绍了。 118 | 119 | 常用的广播action属性有: 120 | 121 | * 屏幕被关闭之后的广播:Intent.ACTION_SCREEN_OFF 122 | 123 | * 屏幕被打开之后的广播:Intent.ACTION_SCREEN_ON 124 | 125 | * 充电状态,或者电池的电量发生变化:Intent.ACTION_BATTERY_CHANGED 126 | 127 | * 关闭或打开飞行模式时的广播:Intent.ACTION_AIRPLANE_MODE_CHANGED 128 | 129 | * 表示电池电量低:Intent.ACTION_BATTERY_LOW 130 | 131 | * 表示电池电量充足,即电池电量饱满时会发出广播:Intent.ACTION_BATTERY_OKAY 132 | 133 | * 按下照相时的拍照按键(硬件按键)时发出的广播:Intent.ACTION_CAMERA_BUTTON 134 | 135 | > 值得注意的是,随着系统版本的提升,很多系统广播已经不再被支持,如果你需要使用哪个系统广播,最好看看最新版本的支持情况。 -------------------------------------------------------------------------------- /Android/Lambda表达式在Android中的使用.md: -------------------------------------------------------------------------------- 1 | # Lambda表达式在Android中的使用 2 | --- 3 | 4 | Lambda ,希腊字母 “λ” 的英文名称。没错,就是你高中数学老师口中的那个“兰布达”。在编程世界中,它是匿名函数的别名, Java 从 Java 8 开始引入 lambda 表达式。而 Android 开发者的世界里,直到 Android Studio 2.4 Preview 4 及其之后的版本里,lambda 表达式才得到完全的支持(在此之前需要使用 Jack 编译器或 retrolambda 等插件,详见: 5 | 6 | > [使用 Java 8 语言功能](https://developer.android.google.cn/guide/platform/j8-jack.html?hl=zh-cn#configuration) 7 | 8 | 新版本 Android Studio 使用向导详见: 9 | 10 | > [Use Java 8 language features](https://developer.android.google.cn/studio/preview/features/java8-support.html) 11 | 12 | Oracle 官方推出的 lambda 教程开篇第一句就表扬了其对匿名内部类笨拙繁琐的代码的简化,然而,在各大 RxJava 教程下的评论中,最受吐槽的就是作者提供的示例代码用了 lambda 表达式,给阅读造成了很大的障碍。 13 | 14 | 所以,在这篇文章中,我会先讲解 lambda 表达式的作用和三种形式,之后提供一个在 Android Studio 便捷使用 lambda 的小技巧,然后说一说 lambda 表达式中比较重要的变量捕获概念,最后再讲一些使用 lambda 表达式前后的差异。 15 | 16 | ## 作用 17 | 18 | 前面提到,lambda 是匿名函数的别名。简单来说,lambda 表达式是对匿名内部类的进一步简化。使用 lambda 表达式的前提是编译器可以准确的判断出你需要哪一个匿名内部类的哪一个方法。 19 | 20 | 我们最经常接触使用匿名内部类的行为是为 view 设置 OnClickListener ,这时你的代码是这样的: 21 | 22 | button.setOnClickListener(new View.OnClickListener(){ 23 | @Override 24 | public void onClick(View v) { 25 | doSomeWork(); 26 | } 27 | }); 28 | 29 | 使用匿名内部类,实现了对象名的隐匿;而匿名函数,则是对方法名的隐匿。所以当使用 lambda 表达式实现上述代码时,是这样的: 30 | 31 | button.setOnClickListener( 32 | (View v) -> { 33 | doSomeWork(); 34 | } 35 | ); 36 | 37 | 看不懂?没关系,在这两个示例中,你只要理解,lambda 表达式不仅对对象名进行隐匿,更完成了方法名的隐匿,展示了一个接口抽象方法最有价值的两点:参数列表和具体实现。下面我会对 lambda 的各种形式进行列举。 38 | 39 | ## 形式 40 | 41 | 在 Java 中,lambda 表达式共有三种形式:函数式接口、方法引用和构造器引用。其中,函数式接口形式是最基本的 lambda 形式,其余两种形式都是基于此形式进行拓展。 42 | 43 | PS:为了更好的展示使用 lambda 表达式前后的代码区别,本文将使用 lambda 表达式给引用赋值的形式作为实例展示,而不是常用的直接将 lambda 表达式传入方法之中。同时,举例也不一定具有实际意义。 44 | 45 | ### 函数式接口 46 | 47 | 函数式接口是指有且只有一个抽象方法的接口,比如各种 Listener 接口和 Runnable 接口。lambda 表达式就是对这类接口的匿名内部类进行简化。基本形式如下: 48 | 49 | > ( 参数列表... ) -> { 语句块... } 50 | 51 | 下面以 Java 提供的 Comparator 接口来展示一个实例,该接口常用于排序比较: 52 | 53 | interface Comparator {int compare(T var1, T var2);} 54 | Comparator comparator = new Comparator() { 55 | @Override 56 | public int compare(String s1, String s2) { 57 | doSomeWork(); 58 | return result; 59 | } 60 | }; 61 | 62 | 当编译器可以推导出具体的参数类型时,我们可以从参数列表中忽略参数类型,那么上面的代码就变成了: 63 | 64 | Comparator comparator = (s1, s2) -> { 65 | doSomeWork(); 66 | return result; 67 | }; 68 | 69 | 当参数只有一个时,参数列表两侧的圆括号也可省略,比如 OnClickListener 接口可写成 : 70 | 71 | interface OnClickListener { 72 | void onClick(View v); 73 | } 74 | 75 | OnClickLisenter listener = v -> {语句块...}; 76 | 77 | 然而,当方法没有传入参数的时候,则记得提供一对空括号假装自己是参数列表(雾),比如 Runnable 接口: 78 | 79 | interface Runnable { 80 | void void run(); 81 | } 82 | 83 | Runnable runnable = v -> {语句块...}; 84 | 85 | 当语句块内的处理逻辑只有一句表达式时,其两侧的花括号也可省略,特别注意这句处理逻辑表达式后面也不带分号。比如这个关闭 activity 的点击方法: 86 | 87 | button.setOnClickListener( v -> activity.finish() ); 88 | 89 | 同时,当只有一句去除花括号的表达式且接口方法需要返回值时,这个表达式不用(也不能)在表达式前加 return ,就可以当作返回语句。下面用 Java 的 Function 接口作为示例,这是一个用于转换类型的接口,在这里我们获取一个 User 对象的姓名字符串并返回: 90 | 91 | interface Function { R apply(T t); } 92 | Function function = new Function() { 93 | @Override 94 | public String apply(User user) { 95 | return user.getName(); 96 | } 97 | }; 98 | Function function = user -> user.getName(); 99 | 100 | ### 方法引用 101 | 102 | 在介绍第一种形式的之前,我曾写道:函数式接口形式是最基本的 lambda 表达式形式,其余形式都是由其拓展而来。那么,现在来介绍第二种形式:方法引用形式。 103 | 104 | 当我们使用第一种 lambda 表达式的时候,进行逻辑实现的时候我们既可以自己实现一系列处理,也可以直接调用已经存在的方法,下面以 Java 的 Predicate 接口作为示例,此接口用来实现判断功能,我们来对字符串进行全面的判空操作: 105 | 106 | interface Predicate { boolean test(T t); } 107 | Predicate predicate = 108 | s -> { 109 | // 用基本代码组合进行判断 110 | return s== null || s.length() == 0; 111 | }; 112 | 113 | 我们知道,TextUtils 的 isEmpty() 方法实现了上述功能,所以我们可以写作: 114 | 115 | Predicate predicate = s -> TextUtils.isEmpty(s); 116 | 117 | 这时我们调用了已存在的方法来进行逻辑判断,我们就可以使用方法引用的形式继续简化这一段 lambda 表达式: 118 | 119 | Predicate predicate = TextUtils::isEmpty(s); 120 | 121 | 惊不惊喜?意不意外? 122 | 123 | 方法引用形式就是当逻辑实现只有一句且调用了已存在的方法进行处理( this 和 super 的方法也可包括在内)时,对函数式接口形式的 lambda 表达式进行进一步的简化。传入引用方法的参数就是原接口方法的参数。 124 | 125 | 接下来总结一下方法引用形式的三种格式: 126 | 127 | * object :: instanceMethod 128 | 129 | 直接调用任意对象的实例方法,如 obj::equals 代表调用 obj 的 equals 方法与接口方法参数比较是否相等,效果等同 obj.equals(t);当前类的方法可用this::method进行调用,父类方法同理。 130 | 131 | * ClassName :: staticMethod 132 | 133 | 直接调用某类的静态方法,并将接口方法参数传入,如上述 TextUtils::isEmpty,效果等同 TextUtils.isEmpty(s); 134 | 135 | * ClassName :: instanceMethod 136 | 137 | 较为特殊,将接口方法参数列表的第一个参数作为方法调用者,其余参数作为方法参数。由于此类接口较少,故选择 Java 提供的 BiFunction 接口作为示例,该接口方法接收一个 T1 类对象和一个 T2 类对象,通过处理后返回 R 类对象: 138 | 139 | interface BiFunction { 140 | R apply(T1 t1, T2 t2); 141 | } 142 | BiFunction biFunction =new BiFunction() { 143 | @Override 144 | public Boolean apply(String s1, String s2) { 145 | return s1.equals(s2); 146 | } 147 | }; 148 | // ClassName 为接口方法的第一个参数的类名,同时利用接口方法的第一个参数作为方法调用者, 149 | // 其余参数作为方法参数,实现 s1.equals(s2); 150 | BiFunction biFunction = String::equals; 151 | 152 | ### 构造器引用 153 | 154 | Lambda 表达式的第三种形式,其实和方法引用十分相似,只不过方法名替换为 new 。其格式为 ClassName :: new。这时编译器会通过上下文判断传入的参数的类型、顺序、数量等,来调用适合的构造器,返回对象。 155 | 156 | ## 使用技巧 157 | 158 | Android Studio 会在可以转化为 lambda 表达式的代码上进行如图的灰色标识,这时将光标移至灰色区域,按下 Alt + Enter ,选择第一项(方法引用和构造器引用在第二项),IDE 就会自动进行转换。 159 | 160 | ![](https://github.com/dreamfish797/StudyNotes/tree/master/Android/picture/20170707215115.png) 161 | 162 | ### 变量捕获 163 | 164 | 在使用匿名内部类时,若要在内部类中使用外部变量,则需要将此变量定义为 final 变量。因为我们并不知道所实现的接口方法何时会被调用,所以通过设立 final 来确保安全。在 lambda 表达式中,仍然需要遵守这个标准。 165 | 166 | 不过在 Java 8 中,新增了一个 effective final 功能,只要一个变量没有被修改过引用(基本变量则不能更改变量值),即为实质上的 final 变量,那么不用再在声明变量时加上 final 修饰符。接下来还是通过一个示例解释,示例中共有三句被注释掉的赋值语句,去除任意一句的注释,都会报错:Variable used in lambda expression should be final or effectively final。 167 | 168 | int effectiveFinalInt = 666; // 外部变量 169 | // 1.effectiveFinalInt = 233; 170 | button.setOnClickListener( v -> { 171 | Toast.makeText( effectiveFinalInt + "").show(); 172 | // 2.effectiveFinalInt = 233; 173 | }); 174 | // 3.effectiveFinalInt = 233; 175 | 176 | 可以看到,我们可以不做任何声明上的改变即可在 lambda 中使用外部变量,前提是我们以 final 的规则对待这个变量。 177 | 178 | ### 一点玄学 179 | 180 | 1. **this 关键字** 181 | 182 | 在匿名内部类中,this 关键字指向的是匿名类本身的对象,而在 lambda 中,this 指向的是 lambda 表达式的外部类。 183 | 184 | 2. **方法数量差异** 185 | 186 | 当前 Android Studio 对 Java 8 新特性编译时采用脱糖(desugar)处理,lambda 表达式经过编译器编译后,每一个 lambda 表达式都会增加 1~2 个方法数。而 Android 应用的方法数不能超过 65536 个。虽然一般应用较难触发,但仍需注意。 -------------------------------------------------------------------------------- /Android/Service详细解析.md: -------------------------------------------------------------------------------- 1 | # Service详细解析 2 | --- 3 | 4 | Service 是 Android 中实现程序后台运行的解决方案,它非常适用于去执行那些不需要和用户交互而且还要求长期运行的任务。Service 默认并不会运行在子线程中,它也不运行在一个独立的进程中,它同样执行在 **UI 线程**中,因此,不要在 Service 中执行耗时的操作,除非你在 Service 中创建了子线程来完成耗时操作 5 | 6 | Service 的运行不依赖于任何用户界面,即使程序被切换到后台或者用户打开另一个应用程序,Service 仍然能够保持正常运行,这也正是 Service 的使用场景。当某个应用程序进程被杀掉时,所有依赖于该进程的 Service 也会停止运行。 7 | 8 | ## Service的生命周期 9 | 10 | ![Service lifecycle](http://upload-images.jianshu.io/upload_images/4037756-092f726077efe728.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | 12 | 通过这个图可以看到,两种启动 Service 的方式以及他们的生命周期, bindService 的不同之处在于当绑定的组件销毁后,对应的 Service 也就被 Kill 了。 13 | 14 | * **被启动的服务的生命周期** 15 | 16 | 一个 Service 被使用 startService 方法启动,不管是否调用了 bindService(绑定服务)或 unbindService (解除绑定服务)到该 Service ,该 Service 都会在后台运行并不受影响。 17 | 18 | 一个 Service 被使用 startService 方法启动多少次,onCreate 方法只会调用一次,onStartCommand 方法将会被调用多次(与startService的次数一致),且系统只会创建一个 Service 实例(结束该 Service 也只需要调用一次 stopService ),该 Service 会一直在后台运行,直至调用 stopService 或调用自身的 stopSelf 方法。 19 | 20 | > **注:在系统资源不足的情况下,服务有可能被系统结束(Kill);** 21 | 22 | * **被绑定的服务的生命周期** 23 | 24 | 如果一个 Service 在某个 Activity 中被调用 bindService 方法启动,不论 bindService 被调用几次, Service 的 onCreate 方法只会执行一次,同时 onStartCommand 方法始终不会调用。 25 |    26 | 当建立连接后,Service会一直运行,除非调用 unbindService 来接触绑定、断开连接或调用该 Service 的 Context 不存在了(如 Activity 被 Finish ——即通过 bindService 启动的 Service 的生命周期依附于启动它的 Context ),系统在这时会自动停止该 27 | 28 | * **被启动又被绑定的服务的生命周期** 29 | 30 | 当一个 Service 在被启动 (startService) 的同时又被绑定 (bindService) ,该 Service 将会一直在后台运行,并且不管调用几次, onCreate 方法始终只会调用一次, onStartCommand 的调用次数与 startService 调用的次数一致(使用 bindService 方法不会调用 onStartCommand )。同时,调用 unBindService 将不会停止 Service ,必须调用 stopService 或 Service 自身的 stopSelf 来停止服务。 31 | 32 | * **当服务被停止时** 33 | 34 | 当一个服务被终止(stopService、stopSelf、unbindService)时,onDestory 方法将会被调用——所以我们需要在该方法中清除一些工作(依附该 Service 生命周期的,如:停止在 Service 中创建并运行的线程)。 35 | 36 | > 特别注意: 37 | > 1. 在使用 startService 方法启动服务后,一定要调用 stopService 方法来停止该服务(同上,可以在 Activity 的**onDestory** 中来停止服务); 38 | > 2. 在某处调用 bindService 绑定 Service 的时候,要在对应的某处调用 unbindService 来解除绑定(如在 Activity 中绑定了 Service ,可以在 **onDestory** 中来解除绑定——虽然绑定的 Service 会在 Activity 结束时自动解除、停止); 39 | > 3. 如果同时使用 startService 与 bindService 方法启动 Service ,需要终止该 Service 时,要调用 stopService 和 unbindService 方法( unbindService 依附于启动它的 Context ,startServicec 并不依附于启动它的 Context 。如果先调用 unbindService ,这时服务并不会被终止,当调用 stopService 后,服务才会被终止;如果先调用 stopService ,服务也不会被终止,当调用 unbindService 或者之前调用 bindService 的 Context 不存在了(如 Activity 被 finish 掉了)服务才会自动停止); 40 | > 4. 当手机屏幕发生旋转时,如果 Activity 设置的是自动旋转的话,在旋转的过程中, Activity 会重新创建,那么之前通过 bindService 建立的连接便会断开(之前绑定该服务的 Context 不存在了),服务也会被自动停止。 41 | 42 | ## Service 使用 43 | 44 | 在新建一个 Service 后,记得在 AndroidManifest.xml 中注册 Service ,在 application 内添加需要注册的 Service 信息: 45 | 46 | ``` 47 | 51 | ``` 52 | 53 | AndroidManifest.xml中Service元素常见属性: 54 | 55 | * **andorid:name** 56 | 57 | 服务类名。可以是完整的包名 + 类名。也可使用.代替包名。 58 | 59 | * **adroid:exported** 60 | 61 | 其他应用能否访问该服务,如果不能,则只有本应用或有相同用户 ID 的应用能访问。默认为 false 。 62 | 63 | * **android:enabled** 64 | 65 | 标识服务是否可以被系统实例化。 true --系统默认启动, false --不启动。(默认值为 true ) 66 | 67 | * **android:label** 68 | 69 | 显示给用户的服务名称。如果没有进行服务名称的设置,默认显示服务的类名。 70 | 71 | * **android:process** 72 | 73 | 服务所运行的进程名。默认是在当前进程下运行,与包名一致。如果进行了设置,将会在包名后加上设置的集成名。 74 | 如果名称设置为冒号 :开头,一个对应用程序私有的新进程会在需要时和运行到这个进程时建立。如果名称为小写字母开头,服务会在一个相同名字的全局进程运行,如果有权限这样的话。这允许不同应用程序的组件可以分享一个进程,减少了资源的使用。 75 | 76 | * **android:icon** 77 | 78 | 服务的图标。 79 | 80 | * **android:permission** 81 | 82 | 申请使用该服务的权限,如果没有配置下相关权限,服务将不执行,使用 startService() 、 bindService() 方法将都得不到执行。 83 | 84 | ## Service 种类 85 | 86 | 服务是一个应用程序组件,可以在后台执行长时间运行的操作,不提供用户界面。一个应用程序组件可以启动一个服务,它将继续在后台运行,即使用户切换到另一个应用程序。此外,一个组件可以绑定到一个服务与它交互,甚至执行进程间通信 (IPC) 。例如,一个服务可能处理网络通信、播放音乐、计时操作或与一个内容提供者交互,都在后台执行。总共可分为**后台服务**、**前台服务**、**IntentService**、**跨进程服务**(远程服务)、**无障碍服务**、**系统服务 **。 87 | 88 | 1. **后台服务** 89 | 90 | 后台服务可交互性主要是体现在不同的启动服务方式, startService() 和 bindService() 。 bindService() 可以返回一个代理对象,可调用 Service 中的方法和获取返回结果等操作,而 startService() 不行。 91 | 92 | * 不可交互的后台服务 93 | 94 | 不可交互的后台服务即是普通的 Service , Service 的生命周期很简单,分别为 onCreate、onStartCommand、onDestroy 这三个。当我们 startService() 的时候,首次创建 Service 会回调 onCreate() 方法,然后回调 onStartCommand() 方法,再次 startService() 的时候,就只会执行一次 onStartCommand() 。服务一旦开启后,我们就需要通过 stopService() 方法或者 stopSelf() 方法,就能把服务关闭,这时就会回调 onDestroy() 。 95 | 96 | * 可交互的后台服务 97 | 98 | 可交互的后台服务是指前台页面可以调用后台服务的方法,可交互的后台服务实现步骤是和不可交互的后台服务实现步骤是一样的,区别在于启动的方式和获得 Service 的代理对象。区别在于多了一个 ServiceConnection 对象,该对象是用户绑定后台服务后,可获取后台服务代理对象的回调,我们可以通过该回调,拿到后台服务的代理对象,并调用后台服务定义的方法,也就实现了后台服务和前台的交互。 99 | 100 | 2. **前台服务** 101 | 102 | 由于后台服务优先级相对比较低,当系统出现内存不足的情况下,它就有可能会被回收掉,所以前台服务就是来弥补这个缺点的,它可以一直保持运行状态而不被系统回收。例如:墨迹天气在状态栏中的天气预报。 103 | 104 | 前台服务创建很简单,其实就在 Service 的基础上创建一个 Notification ,然后使用 Service 的 startForeground() 方法即可启动为前台服务 105 | > startService(new Intent(this, ForegroundService.class)); 106 | 107 | 3. **IntentService** 108 | 109 | IntentService 是专门用来解决 Service 中不能执行耗时操作这一问题的,创建一个 IntentService 也很简单,只要继承 IntentService 并覆写 onHandlerIntent 函数,在该函数中就可以执行耗时操作了。 110 | 111 | ``` 112 | public class MyIntentService extends IntentService { 113 | public MyIntentService(String name) { 114 | super(name); 115 | } 116 | 117 | @Override 118 | protected void onHandleIntent(Intent intent) { 119 | // 在这里执行耗时操作 120 | } 121 | } 122 | ``` 123 | 在 IntentService 内有一个工作线程来处理耗时操作,当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,执行完自动结束。 124 | 125 | 4. **跨进程服务**(远程服务) 126 | 127 | 远程服务为独立的进程,对应进程名格式为所在包名加上你指定的 android:process 字符串。由于是独立的进程,因此在 Activity 所在进程被 Kill 的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。由于是独立的进程,会占用一定资源,并且使用 AIDL 进行 IPC 稍微麻烦一点,这种 Service 是**常驻**的。 128 | 129 | > 关于 AIDL 推荐大家看看[Android使用AIDL实现跨进程通讯(IPC)](http://blog.csdn.net/ydxlt/article/details/50812559) 130 | 131 | 5. **AccessibilityService无障碍服务** 132 | 133 | 无障碍服务旨在帮助身心有障碍的用户使用 Android 设备和应用。无障碍服务在后台运行,当无障碍事件被激活时系统会执行 AccessibilityService 的 onAccessibilityEvent(AccessibilityEvent event) 方法。这些事件表示在用户界面中的一些状态的改变,例如:焦点的改变、按钮被点击等。这类服务可以有选择性地请求查询活动窗口的内容。无障碍服务的开发需要继承 AccessibilityService 和实现它的抽象方法。 134 | 跟详细的介绍推荐阅读[ Android 无障碍辅助功能AccessibilityService(1)](http://blog.csdn.net/pjingying/article/details/52670162) 135 | 136 | 6. **系统服务** 137 | 138 | 系统服务提供了很多便捷服务,可以查询 Wifi 、网络状态、查询电量、查询音量、查询包名、查询 Application 信息等等等相关多的服务,具体大家可以自信查询文档,这里举例几个常见的服务: 139 | 140 | * 判断Wifi是否开启 141 | 142 | ``` 143 | WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE); 144 | boolean enabled = wm.isWifiEnabled(); 145 | ``` 146 | 147 | * 获取系统最大音量 148 | 149 | ``` 150 | AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); 151 | int max = am.getStreamMaxVolume(AudioManager.STREAM_SYSTEM); 152 | ``` 153 | 154 | * 获取当前音量 155 | 156 | ``` 157 | AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); 158 | int current = am.getStreamMaxVolume(AudioManager.STREAM_RING); 159 | ``` 160 | 161 | * 判断网络是否有连接 162 | 163 | ``` 164 | ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 165 | NetworkInfo info = cm.getActiveNetworkInfo(); 166 | boolean isAvailable = info.isAvailable(); 167 | ``` 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /Android/adb使用介绍.md: -------------------------------------------------------------------------------- 1 | # Android 工具:ADB 使用总结 2 | 3 | ADB全称Android Debug Bridge,Android 开发中, 我们在开发过程中经常使用这个工具来操作Android系统,是 Android 开发者必须掌握的。 4 | 5 | ### 功能介绍 6 | 7 | ADB主要功能有: 8 | 9 | 1. 在Android设备上运行Shell(命令行) 10 | 11 | 2. 管理模拟器或设备的端口映射 12 | 13 | 3. 在计算机和设备之间上传/下载文件 14 | 15 | 4. 将电脑上的本地APK软件安装至Android模拟器或设备上 16 | 17 | ### 使用配置 18 | 19 | 一个这么常用工具, Google 早就在 Android SDK 中帮我们集成了,就放在\android-sdk-windows\platform-tools这个目录下面,我们只需要配置一下环境变量就可以使用了。作为开发者,配置环境变量这种小儿科我就不赘述了...... 20 | 21 | ### ADB 用法 22 | 23 | #### 查询模拟器或手机状态 24 | 25 | 查看 adb 服务端连接的模拟器或手机可以帮助更好的使用 adb 命令,这可以通过 devices 命令来列举当前连接的设备: 26 | 27 | > adb devices 28 | 29 | 执行结果是 adb 为每一个设备输出以下状态信息: 30 | 31 | > 序列号(serialNumber):由 adb 创建用于唯一标识设备的字符串,格式是 <设备类型>-<端口号>,例如: emulator-5554 32 | 33 | > 连接状态(state),其值是可能是下面的任意一种: 34 | 35 | > offline — 未连接或未响应 36 | 37 | > device — 表示设备已经连接到服务端。但需要注意的是,这个状态并不表示 Android 系统已经完全启动起来并且可操作,因为系统在启动的过程中就已经连接 adb ,但这个状态是正常的可操作状态。 38 | 39 | > no device – 表示没有任何设备连接(楼主测试过程中没有碰到过 no device 的情况,没连接设备就没任何输出) 40 | 41 | #### 操作指定模拟器或手机 42 | 43 | 如果有多个模拟器或手机正在运行,当使用 adb 命令的时候就需要指定目标设备,这可以通过使用 -s 选项参数实现,用法如下: 44 | 45 | > adb -s < serialNumber > < command > 46 | 47 | > // 例如在 emulator-5556 设备上安装应用: 48 | 49 | > adb -s emulator-5556 install xxxx.apk 50 | 51 | 这里补充一点, Google 官方给出在多设备的情况下,不用 -s 参数指定目标设备的快捷方式。 52 | 53 | > adb -e install xxxx.apk 54 | 55 | > //同理,如果有多个设备,但只有一个真机,可以使用如下命令快速发送命令 56 | 57 | > adb -d install xxxx.apk 58 | 59 | #### 安装应用 60 | 61 | 使用 adb install 命令可以从开发用电脑中复制应用程序并且安装到模拟器或手机上, adb install 命令必须指定待安装的.apk文件的路径。 62 | 63 | > adb install [-lrtsdg] < path_to_apk > 64 | 65 | > (-l : 锁定该程序) 66 | > 67 | > (-r : 重新安装该程序,保留应用数据) 68 | > 69 | > (-t : allow test packages) 70 | > 71 | > (-s : 将应用安装到 SD卡,不过现在手机好像都没有 SD卡 了吧) 72 | > 73 | > (-d : 允许降版本号安装,当然只有 debug 包才能使用) 74 | > 75 | > (-g : 安装完默认授予所有运行时权限) 76 | 77 | #### 卸载应用 78 | 79 | 上面介绍了安装应用命令,既然有安装应用的命令,那当然有卸载应用的命令。卸载应用命令的格式如下: 80 | 81 | > // < package > 表示要卸载应用的包名 82 | 83 | > adb uninstall [-k] < package > (-k:不删除程序运行所产生的数据和缓存目录) 84 | 85 | #### 与模拟器或手机传输文件 86 | 87 | 使用 adb 命令 pull 和 push 能从 Android 设备拷贝或复制文件到 Android 设备。跟 install 命令不同,pull 和 push 命令允许拷贝和复制文件到任何位置。 88 | 89 | * pull 90 | 91 | > adb pull [-a] < remote_path > < local_path > (-a:保留文件时间戳及属性) 92 | 93 | 举个栗子,我想把应用中的数据库文件复制到本地目录下: 94 | > adb pull sdcard/contacts_app.db 95 | 96 | 97 | * push 98 | 99 | > adb push < local_path > < remote_path > 100 | 101 | 举个栗子,我想把桌面的 log.txt 复制到手机的 dev 目录下: 102 | > adb push .../log.txt /dev 103 | 104 | #### 无线调试 105 | 106 | 平时我们都是使用 USB调试,但是有时候设备老化或者数据线连接不稳定, USB 调试就不好使了。这时我们就想能不能抛开这根数据线呢?当然可以,adb 也是支持通过 WIFI 进行调试了,使用方式如下: 107 | 108 | * 首先,你要将 Android 设备和 装有 adb 的电脑连接到同一 Wi-Fi 网络。其次,你需要配置好防火墙,否则很有可能导致 Wi-Fi 调试不能使用。 109 | * 使用 USB数据线 将手机连接到电脑。 110 | * 设置目标设备监听 5555端口 的 TCP/IP 连接。 111 | 112 | > adb tcpip 5555 113 | * 断开手机与电脑的 USB 连接。 114 | * 查看手机的 IP地址 。 115 | * 通过 IP 连接手机 116 | 117 | > adb connect < device_ip_address > 118 | 119 | 这时就可以使用 adb devices 确认手机是否连接到电脑上了。 120 | 121 | 通过以上步骤,就可以开心的享用 WiFi 调试了。如果没有正常连接,可以按照下面的步骤检查: 122 | 123 | 1. 检查电脑和手机是否还在同一个 WiFi 网络下 124 | 2. 重新执行一次 adb connect 命令 125 | 3. 重启 adb 服务,然后重头再来 126 | 4. 检查是否是防火墙的设置问题 127 | 128 | #### 查看设备的 log 129 | 130 | 在日常开发中,我们经常要查看日志进行调试我们的app,adb 提供了强大的日志查看命令。 adb logcat 命令格式是这样的: 131 | 132 | > adb logcat [选项] [过滤项] 133 | > 134 | > (-s : 设置输出日志的标签, 只显示该标签的日志) 135 | > 136 | > (-f : 将日志输出到文件, 默认输出到标准输出流中, -f 参数执行不成功) 137 | > 138 | > (-r : 按照每千字节输出日志, 需要 -f 参数, 不过这个命令没有执行成功) 139 | > 140 | > (-n : 设置日志输出的最大数目, 需要 -r 参数, 这个执行 感觉 跟 adb logcat 效果一样) 141 | > 142 | > (-v : 设置日志的输出格式, 注意只能设置一项) 143 | > 144 | > (-c : 清空所有的日志缓存信息) 145 | > 146 | > (-d : 将缓存的日志输出到屏幕上, 并且不会阻塞) 147 | > 148 | > (-t : 输出最近的几行日志, 输出完退出, 不阻塞) 149 | > 150 | > (-g : 查看日志缓冲区信息) 151 | > 152 | > (-b : 加载一个日志缓冲区, 默认是 main, 下面详解) 153 | > 154 | > (-B : 以二进制形式输出日志) 155 | 156 | 举几个常用的栗子,场景如下: 157 | 158 | * 应用调试过程中,我想看全部日志,并想看日志输出时间。 159 | > adb logcat -v time 160 | 161 | * 日志输出后发现在终端控制台里查看不太方面,想输出到文件中查看。 162 | > adb logcat -v time >xxx.txt 163 | 164 | * 应用崩溃了,我现在只关心崩溃日志。 165 | > adb logcat -v time *:e 166 | 167 | * 崩溃解决了,心安了,现在想看某一个逻辑完整的执行过程(日志TAG:######)。 168 | > adb logcat -s ###### 169 | 170 | 这里就介绍这些基本且常用的命令的使用,如果你想深入了解一下,可以到 ![Android调试桥](https://developer.android.com/studio/command-line/adb.html?hl=zh-cn) 具体学习。 171 | 172 | #### 重启手机 173 | 174 | 有时候,手动关机太麻烦,那就来个命令行吧~ 175 | 176 | > adb reboot 177 | 178 | #### 以 root 权限开启 adb 守护进程 179 | 180 | 很多时候我们需要 root 手机以获得高权限操作手机,此时就需要下面的命令了: 181 | 182 | > // 此命令会重启守护进程 183 | > 184 | > adb root 185 | > 186 | > // root 成功后需要重新挂在磁盘 187 | > adb remount 188 | 189 | #### 开启或关闭 adb 服务 190 | 191 | 在某些情况下需要重启 adb 服务来解决问题,比如 adb 无响应。这时你可以通过 adb kill-server 来实现这一操作。 192 | 193 | > adb kill-server 194 | 195 | kill 之后,通过 adb start-server 或者任意 adb 命令来重启 adb 服务。 196 | 197 | > adb start-server 198 | 199 | ### 总结 200 | 201 | 以上就是 adb 命令的常见用法,都是本人日常开发中使用频率高的。有些不常用的 adb 命令没有介绍,更多 adb 用法请见:Adb Command Summary。文中如有纰漏,欢迎大家留言指出。 -------------------------------------------------------------------------------- /Android/picture/20170707215115.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/Android/picture/20170707215115.png -------------------------------------------------------------------------------- /DesignPattern/Builder模式.md: -------------------------------------------------------------------------------- 1 | #Builder模式 2 | --- 3 | 最近工作之余一直在断断续续的研究**媒体选择库**,在 GitHub 上搜了好多库对比看了看,在学习研究过程中发现其中都运用了 Builder模式,今天就一起学习一下 Builder模式,顺便看看它在 Android 源码中的应用。 4 | 5 | 我们在实际开发中,必然会遇到一些需求需要构建一个十分复杂的对象,譬如本人最近开发的项目中就需要构建一个媒体库选择器,类似微信和众多app中都有的图片、视屏资源选择器。这个选择器(Selector)是相对比较复杂的,它需要很多属性,比如: 6 | > * 媒体资源类型: 图片/视屏 7 | > * 选择模式: 单选/多选 8 | > * 多选上限 9 | > * 是否支持预览 10 | > * 是否支持裁剪 11 | > * 选择器UI风格样式 12 | > * ...... 13 | 14 | 通常我们可以通过构造函数的参数形式去写一个实现类 15 | 16 | Selector(int type) 17 | 18 | Selector(int type, int model) 19 | 20 | Selector(int type, int model, int maxSize) 21 | 22 | Selector(int type, int model, int maxsize, boolean isPreview) 23 | 24 | ...... 25 | 26 | 再或者可以使用 getter 和 setter 方法去设置各个属性 27 | 28 | public class Selector { 29 | private int type; // 媒体资源类型: 图片/视屏 30 | private int model; // 选择模式: 单选/多选 31 | private int maxSize; // 多选上限 32 | private boolean isPreview; // 是否支持预览 33 | 34 | private ...... // 更多属性就不一一举例了,大家脑部一下 35 | 36 | public int getType() { 37 | return type; 38 | } 39 | 40 | public void setType(int type) { 41 | this.type = type; 42 | } 43 | 44 | public int getModel() { 45 | return model; 46 | } 47 | 48 | public void setModel(int model) { 49 | this.model = model; 50 | } 51 | 52 | public int getMaxSize() { 53 | return maxSize; 54 | } 55 | 56 | public void setMaxSize(int maxSize) { 57 | this.maxSize = maxSize; 58 | } 59 | 60 | public boolean isPreview() { 61 | return isPreview; 62 | } 63 | 64 | public void setPreview(boolean isPreview) { 65 | this.isPreview = isPreview; 66 | } 67 | 68 | ....... 69 | } 70 | 71 | 先分析一下这两种构建对象的方式: 72 | 73 | 第一种方式通过重载构造方法实现,在参数不多的情况下,是比较方便快捷的,一旦参数多了,就会产生大量的构造方法,代码可读性大大降低,并且难以维护,对调用者来说也是一种灾难; 74 | 75 | 第二种方法可读性不错,也易于维护,但是这样子做对象会产生不确定性,当你构造 Selector 时想要传入全部参数的时候,那你就必需将所有的 setter 方法调用完成之后才算创建完成。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实 Selector 对象并没有创建完成,另外,这个 Selector 对象也是可变的,不可变类的所有好处也将随之消散。 76 | 77 | 写到这里其实大家已经想到了,肯定有更好的办法去解决这个问题,是的,你猜对了, 今天我们的主角 Builder模式 就是为解决这类问题而生的。下面我们一起看看 Builder模式 是如何优雅的处理这类尴尬的。 78 | 79 | ### 模式的定义 80 | 81 | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 82 | 83 | ### 模式的使用场景 84 | 85 | 1. 相同的方法,不同的执行顺序,产生不同的事件结果时; 86 | 2. 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时; 87 | 3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适; 88 | 89 | ### 化解上述尴尬的过程 90 | 91 | Builder模式 属于创建型,一步一步将一个复杂对象创建出来,允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。还是上面同样的需求,使用 Builder模式 实现如下: 92 | 93 | public class Selector { 94 | private final int type; // 媒体资源类型: 图片/视屏 95 | private final int model; // 选择模式: 单选/多选 96 | private final int maxSize; // 多选上限 97 | private final boolean isPreview; // 是否支持预览 98 | 99 | private final ...... // 更多属性就不一一举例了,大家脑部一下 100 | 101 | // 私有构造方法 102 | private Selector(SelectorBuilder selectorBuilder){ 103 | this.type = selectorBuilder.type; 104 | this.model = selectorBuilder.model; 105 | this.maxSize = selectorBuilder.maxSize; 106 | this.isPreview = selectorBuilder.isPreview; 107 | ...... 108 | 109 | /** 由于所有属性都是 final 修饰的,所以只提供 getter 方法 **/ 110 | 111 | public int getType() { 112 | return type; 113 | } 114 | 115 | public int getModel() { 116 | return model; 117 | } 118 | 119 | public int getMaxSize() { 120 | return maxSize; 121 | } 122 | 123 | public boolean isPreview() { 124 | return isPreview; 125 | } 126 | 127 | ....... 128 | 129 | // Builder 方法 130 | public static class SelectorBuilder{ 131 | private int type; // 媒体资源类型: 图片/视屏 132 | private int model; // 选择模式: 单选/多选 133 | private int maxSize; // 多选上限 134 | private boolean isPreview; // 是否支持预览 135 | 136 | private ...... // 更多属性就不一一举例了,大家脑部一下 137 | public SelectorBuilder() { 138 | // 设置各个属性的默认值 139 | this.type = 1; 140 | this.model = 1; 141 | this.maxSize = 9; 142 | this.isPreview = true; 143 | ...... 144 | } 145 | 146 | public SelectorBuilder setType(int type){ 147 | this.type = type; 148 | return this; 149 | } 150 | 151 | public SelectorBuilder setModel(int model) { 152 | this.model = model; 153 | return this; 154 | } 155 | 156 | public SelectorBuilder setMaxSize(int maxSize) { 157 | this.maxSize = maxSize; 158 | return this; 159 | } 160 | 161 | ...... // 全部的setter方法就省略了 162 | 163 | public Selector build() { 164 | return new Selector(this); 165 | } 166 | } 167 | } 168 | 169 | 值得注意的是 Selector 的构造方法是私有的,并且所有属性都是 final 修饰的,是不可变属性,对调用者也只提供 getter 方法,SelectorBuilder 内部类可以根据调用者的具体需求随意接收任意多个参数,应为我们再 SelectorBuilder 的构造方法中为每一个参数都设置了默认值,即使调用者调用时漏传某个参数,也不会影响整个创建过程。当我们将我们需用的所有参数传入后,随后调用 build() 构造 Selector 对象,代码如下: 170 | 171 | new Selector.SelectorBuilder() 172 | .setType(2) 173 | .setModel(2) 174 | .setMaxSize(3) 175 | . ...... 176 | .build(); 177 | 178 | 是不是很简洁?意不意外?惊不惊喜?你没看错,就是这么厉害! 179 | 180 | 181 | ### Android源码中的模式实现 182 | 183 | 在Android源码中,我们最常用到的Builder模式就是AlertDialog.Builder, 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 : 184 | 185 | // 显示基本的AlertDialog 186 | private void showDialog(Context context) { 187 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 188 | builder.setIcon(R.drawable.icon); 189 | builder.setTitle("Title"); 190 | builder.setMessage("Message"); 191 | builder.setPositiveButton("Button1", 192 | new DialogInterface.OnClickListener() { 193 | public void onClick(DialogInterface dialog, int whichButton) { 194 | setTitle("点击了对话框上的Button1"); 195 | } 196 | }); 197 | builder.setNeutralButton("Button2", 198 | new DialogInterface.OnClickListener() { 199 | public void onClick(DialogInterface dialog, int whichButton) { 200 | setTitle("点击了对话框上的Button2"); 201 | } 202 | }); 203 | builder.setNegativeButton("Button3", 204 | new DialogInterface.OnClickListener() { 205 | public void onClick(DialogInterface dialog, int whichButton) { 206 | setTitle("点击了对话框上的Button3"); 207 | } 208 | }); 209 | builder.create().show(); // 构建AlertDialog, 并且显示 210 | } 211 | 212 | 213 | ## 优缺点 214 | 215 | 当然在代码世界,永远没有绝对的完美,我们只是在走向完美的道路上尽力去填补一个个坑而已。 Builder模式 有它的好处,给我们带来了方便,但同时也会牺牲一些美好,这是不可避免的。 216 | 217 | ### 优点 218 | 219 | * 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节; 220 | * 建造者独立,容易扩展; 221 | * 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。 222 | 223 | ### 缺点 224 | 225 | * 会产生多余的Builder对象,消耗内存; 226 | * 对象的构建过程暴露。 -------------------------------------------------------------------------------- /DesignPattern/Intent原型模式实现.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/DesignPattern/Intent原型模式实现.png -------------------------------------------------------------------------------- /DesignPattern/单例模式.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | --- 3 | 4 | ### 定义 5 | >保证一个类仅有一个实例,并提供一个访问它的全局访问点。 6 | 7 | >Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。 8 | 9 | ### 功能 10 | 单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。 11 | 12 | ### 饿汉法 13 | 顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。代码如下: 14 | 15 | public class Singleton { 16 | private static Singleton = new Singleton(); 17 | private Singleton() {} 18 | public static getSignleton(){ 19 | return singleton; 20 | } 21 | } 22 | 23 | 饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。但是无法做到延迟创建对象,从而减小负载。 24 | 25 | ### 懒汉式 26 | 27 | > 单线程写法 28 | 29 | public class Singleton { 30 | private static Singleton singleton = null; 31 | private Singleton(){} 32 | public static Singleton getSingleton() { 33 | if(singleton == null) singleton = new Singleton(); 34 | return singleton; 35 | } 36 | } 37 | 38 | 这种不加同步的懒汉式写法虽然简单但同时它是线程不安全的,这种写法由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象。这种方法可以实现延时加载,但是有一个致命弱点:线程不安全。 39 | 40 | 41 | > 线程安全的写法 42 | 43 | 如何实现懒汉式的线程安全?加上synchronized? 44 | 45 | ``` 46 | public static synchronized Singleton getInstance(){} 47 | ``` 48 | 49 | 但这样会降低整个访问的速度,而且每次都要判断。现在比较流行的是用双重检查加锁。 50 | 51 | 双重加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。这是第二重检查。 52 | 53 | 双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。 54 | 55 | /** 56 | * 双重检查加锁的单例模式 57 | * @author dream 58 | * 59 | */ 60 | public class Singleton { 61 | 62 | /** 63 | * 对保存实例的变量添加volitile的修饰 64 | */ 65 | private volatile static Singleton instance = null; 66 | private Singleton(){ 67 | 68 | } 69 | 70 | public static Singleton getInstance(){ 71 | //先检查实例是否存在,如果不存在才进入下面的同步块 72 | if(instance == null){ 73 | //同步块,线程安全的创建实例 74 | synchronized (Singleton.class) { 75 | //再次检查实例是否存在,如果不存在才真正的创建实例 76 | instance = new Singleton(); 77 | } 78 | } 79 | return instance; 80 | } 81 | 82 | } 83 | 但是双重加锁机制,一句话说是“成也volatile,败也volatile”,因为volatile关键字在JDK1.5之前无法保证线程安全,所有双重加锁机制也不是完美的,也是有瑕疵的。 84 | 85 | > 一种更好的单例实现方式 86 | 87 | 静态内部类法,它可以延时加载,并且能保证线程安全,我们把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。 88 | 89 | public class Singleton { 90 | 91 | /** 92 | * 类级的内部类,也就是静态类的成员式内部类,该内部类的实例与外部类的实例 93 | * 没有绑定关系,而且只有被调用时才会装载,从而实现了延迟加载 94 | * @author dream 95 | * 96 | */ 97 | private static class SingletonHolder{ 98 | /** 99 | * 静态初始化器,由JVM来保证线程安全 100 | */ 101 | private static Singleton instance = new Singleton(); 102 | } 103 | 104 | /** 105 | * 私有化构造方法 106 | */ 107 | private Singleton(){ 108 | 109 | } 110 | 111 | public static Singleton getInstance(){ 112 | return SingletonHolder.instance; 113 | } 114 | } 115 | 116 | 117 | ### 优缺点 118 | 119 | * 懒汉式是典型的时间换空间 120 | * 饿汉式是典型的空间换时间 121 | 122 | 123 | ### 何时选用单例模式 124 | 当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。代码没有一劳永逸的写法,只有在特定条件下最合适的写法。在不同的平台、不同的开发环境(尤其是jdk版本)下,自然有不同的最优解(或者说较优解),比如双重检查锁法,不能在jdk1.5之前使用,但是本人是Android开发者,而Android基本都是基于JDK1.6以上开发的,所以我个人偏爱比较双重检查锁法。 125 | 当然,还有一种更加优雅的写法:**枚举写法**,但为什么我没有介绍呢,应为它在Android平台上却是不被推荐的。所以感兴趣的同学可以自行去了解一下。 -------------------------------------------------------------------------------- /DesignPattern/原型模式.md: -------------------------------------------------------------------------------- 1 | # 原型模式 2 | --- 3 | 4 | ## 模式介绍 5 | 6 | ### 模式的定义 7 | 8 | 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。 9 | 10 | ### 模式的内图 11 | 12 | ![Alt text](http://github.com/dreamfish797/StudyNotes/tree/master/DesignPattern/原型模式类图.jpg) 13 | 14 | ### 模式实现 15 | 16 | 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件: 17 | 18 | * 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。 19 | * 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。 20 | 21 | 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗;通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。 22 | 23 | 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。 24 | 25 | ### 实现代码 26 | 27 | class Prototype implements Cloneable { 28 | public Prototype clone(){ 29 | Prototype prototype = null; 30 | try{ 31 | prototype = (Prototype)super.clone(); 32 | }catch(CloneNotSupportedException e){ 33 | e.printStackTrace(); 34 | } 35 | return prototype; 36 | } 37 | } 38 | 39 | class ConcretePrototype extends Prototype{ 40 | public void show(){ 41 | System.out.println("原型模式实现类"); 42 | } 43 | } 44 | 45 | public class Client { 46 | public static void main(String[] args){ 47 | ConcretePrototype cp = new ConcretePrototype(); 48 | for(int i=0; i< 10; i++){ 49 | ConcretePrototype clonecp = (ConcretePrototype)cp.clone(); 50 | clonecp.show(); 51 | } 52 | } 53 | } 54 | 55 | ### 原型模式的优点及适用场景 56 | 使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。 57 | 58 | 使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。 59 | 60 | 因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。 61 | 62 | ### 原型模式的注意事项 63 | * 使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。 64 | * 深拷贝与浅拷贝。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。例如: 65 | 66 | public class Prototype implements Cloneable { 67 | private ArrayList list = new ArrayList(); 68 | public Prototype clone(){ 69 | Prototype prototype = null; 70 | try{ 71 | prototype = (Prototype)super.clone(); 72 | prototype.list = (ArrayList) this.list.clone(); 73 | }catch(CloneNotSupportedException e){ 74 | e.printStackTrace(); 75 | } 76 | return prototype; 77 | } 78 | } 79 | 80 | 81 | > 由于ArrayList不是基本类型,所以成员变量list,不会被拷贝,需要 我们自己实现深拷贝,幸运的是Java提供的大部分的容器类都实现了Cloneable接口。所以实现深拷贝并不是特别困难。 82 | > 83 | PS:深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝。 84 | 85 | 86 | ## Android源码中的模式实现 87 | 88 | 在Android源码中,我们以熟悉的Intent来分析源码中的原型模式。下面这个例子是我在Internet上看到的最多的一个,那我也用这个吧......,简单示例如下 : 89 | 90 | Uri uri = Uri.parse("smsto:0800000123"); 91 | Intent shareIntent = new Intent(Intent.ACTION_SENDTO, uri); 92 | shareIntent.putExtra("sms_body", "The SMS text"); 93 | 94 | Intent intent = (Intent)shareIntent.clone() ; 95 | startActivity(intent); 96 | 97 | 这段代码是通过shareIntent.clone方法拷贝了一个对象intent, 然后执行startActivity(intent), 随即就进入了短信页面,号码为0800000123,文本内容为The SMS text,即这些内容都与shareIntent一致。 98 | 99 | 我们可以跟进Android源码看看Intent的clone的实现: 100 | 101 | ![Alt text](http://github.com/dreamfish797/StudyNotes/tree/master/DesignPattern/Intent原型模式实现.png) -------------------------------------------------------------------------------- /DesignPattern/原型模式类图.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinchen797/StudyNotes/b6aa143bf4a2e82031e35fc3bce2f156283b444e/DesignPattern/原型模式类图.jpg -------------------------------------------------------------------------------- /DesignPattern/观察者模式.md: -------------------------------------------------------------------------------- 1 | # 观察者模式 2 | --- 3 | ### 定义 4 | >定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。 5 | 6 | ### 模式分析 7 | 观察者模式使用三个类 **Subject**、**Observer** 和 **Client**。**Subject** 对象带有绑定观察者到 **Client** 对象和从 **Client** 对象解绑观察者的方法。我们创建 **Subject** 类、**Observer** 抽象类和扩展了抽象类 **Observer** 的实体类。 8 | 9 | >**Subject** 10 | 11 | 主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做被观察者角色。 12 | 13 | public class Subject { 14 | /** 15 | * 用来保存注册的观察者对象 16 | */ 17 | private List list = new ArrayList(); 18 | 19 | /** 20 | * 用来保存被观察的参数 21 | */ 22 | private int state; 23 | 24 | /** 25 | * 注册观察者对象 26 | * @param observer 观察者对象 27 | */ 28 | public void attach(Observer observer) { 29 | list.add(observer); 30 | System.out.println("Attached an observer"); 31 | } 32 | 33 | /** 34 | * 删除观察者对象 35 | * @param observer 观察者对象 36 | */ 37 | public void detach(Observer observer) { 38 | list.remove(observer); 39 | } 40 | 41 | public int getState() { 42 | return state; 43 | } 44 | 45 | /** 46 | * 改变状态 47 | * @param state 新状态 48 | */ 49 | public void change(int state) { 50 | this.state = state; 51 | notifyAllObservers(); 52 | } 53 | 54 | /** 55 | * 通知所有注册的观察者对象 56 | */ 57 | public void nodifyObservers() { 58 | for(Observer observer : list) { 59 | observer.update(state); 60 | } 61 | } 62 | } 63 | 64 | 65 | >**Observer** 66 | 67 | 抽象观察者(Observer)角色为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。 68 | 69 | public interface Observer { 70 | /** 71 | * 更新接口 72 | * @param state 更新的状态 73 | */ 74 | public void update(String state); 75 | } 76 | 77 | >**ConcreteObserver** 78 | 79 | 具体观察者(ConcreteObserver)角色存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。 80 | 81 | public class ConcreteObserver implements Observer { 82 | //观察者的状态 83 | private String observerState; 84 | 85 | @Override 86 | public void update(String state) { 87 | /** 88 | * 更新观察者的状态,使其与目标的状态保持一致 89 | */ 90 | observerState = state; 91 | System.out.println("状态为:"+observerState); 92 | } 93 | } 94 | 95 | >**Client** 96 | 97 | 客户端类只需要创建具体主题对象和具体观察者对象,然后将观察者对象登记到主题对象上就完成了观察者模式建立。 98 | 99 | public class Client { 100 | 101 | public static void main(String[] args) { 102 | //创建主题对象 103 | ConcreteSubject subject = new Subject(); 104 | //创建观察者对象 105 | Observer observer = new ConcreteObserver(); 106 | //将观察者对象登记到主题对象上 107 | subject.attach(observer); 108 | //改变主题对象的状态 109 | subject.change("new state"); 110 | } 111 | 112 | } 113 | 114 | ### 应用实例 115 | 在Android中,我们往ListView添加数据后,都会调用Adapter的notifyDataChanged()方法,其中使用了观察者模式。 116 | 117 | 当ListView的数据发生变化时,调用Adapter的notifyDataSetChanged函数,这个函数又会调用DataSetObservable的notifyChanged函数,这个函数会调用所有观察者(AdapterDataSetObserver)的onChanged方法,在onChanged函数中又会调用ListView重新布局的函数使得ListView刷新界面。 118 | 119 | ### 使用时注意事项 120 | * Java中已经有了对观察者模式的支持类。 121 | 122 | >在Java语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。 123 | 124 | * 避免循环引用。 125 | 126 | >如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 127 | 128 | * 一般采用异步方式。 129 | 130 | >如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。另外,如果顺序执行,某一观察者错误会导致系统卡壳。 -------------------------------------------------------------------------------- /Java/浅谈Java 浅拷贝&深拷贝.md: -------------------------------------------------------------------------------- 1 | # 浅谈Java 浅拷贝&深拷贝 2 | --- 3 | 4 | 上一篇文章 设计模式:原型模式 讲的是拷贝对象,文中提到了深拷贝和浅拷贝的概念,我自己在学习Java的时候也没注意,虽然Java中对象回收工作由GC帮我们做了,但在码代码时如果不注意也会埋下隐藏的BUG,今天我们深入探究一下深拷贝和浅拷贝。 5 | 6 | 我们在写代码时经常会需要将一个对象传递给另一个对象,Java语言中对于基本型变量采用的是值传递,而对于非基本类型对象传递时采用的引用传递也就是地址传递,而很多时候对于非基本类型对象传递我们也希望能够象值传递一样,使得传递之前和之后有不同的内存地址。在两种情况就是我们今天要讨论的 **浅拷贝** 和 **深拷贝** 。 7 | 8 | 有Java基础的同学可能发现上面论述有些不严谨,String 类型在传递时其实也是值传递,因为 String 类型是不可变对象。 9 | 10 | ### 浅拷贝 11 | 12 | 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 13 | 14 | 下面我们看一个例子: 15 | 16 | public class Book implements Cloneable { 17 | String bookName; 18 | double price; 19 | Person author; 20 | 21 | public Book(String bn, double price, Person author) { 22 | bookName = bn; 23 | this.price = price; 24 | this.author = author; 25 | } 26 | 27 | public Object clone() { 28 | Book b = null; 29 | try { 30 | b = (Book) super.clone(); 31 | } catch (CloneNotSupportedException e) { 32 | e.printStackTrace(); 33 | } 34 | return b; 35 | } 36 | 37 | public static void main(String args[]) { 38 | Person p = new Person("Dream", 34); 39 | Book book1 = new Book("Java开发", 30.00, p); 40 | Book book2 = (Book) b1.clone(); 41 | book2.price = 44.00; 42 | book2.author.setAge(45); 43 | book2.author.setName("Fish"); 44 | book2.bookName = "Android开发"; 45 | System.out.print("age = " + book1.author.getAge() + " name = " 46 | + book1.bookName + " price = " + book1.price); 47 | System.out.println(); 48 | System.out.print("age = " + book2.author.getAge() + " name = " 49 | + book2.bookName + " price = " + book2.price); 50 | } 51 | } 52 | 53 | 54 | > 结果: 55 | 56 | > age = 45 name = Java开发 price = 30.0 57 | > 58 | > age = 45 name = Android开发 price = 44.0 59 | 60 | 从结果中发现在改变 book2 对象的 name 和 price 属性时 book1 的属性并不会跟随改变,当改变 book2 对象的 author 属性时 book1 的 author 对象的属性也改变了,说明 author 是浅拷贝,和 book1 的 author 是使用同一引用。这时我们就需要使用深拷贝了。 61 | 62 | ### 深拷贝 63 | 64 | 被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。 65 | 66 | 为了解决上面 Person 对象未完全拷贝问题,我们需要用到深拷贝,其实很简单在拷贝book对象的时候加入如下语句: 67 | 68 | b.author =(Person)author.clone(); //将Person对象进行拷贝,Person对象需进行了拷贝 69 | 70 | > 结果 71 | 72 | > age = 34 name = Java开发 price = 30.0 73 | > 74 | > age = 45 name = Android开发 price = 44.0 75 | 76 | 77 | 78 | 上面是用 clone() 方法实现深拷贝,传统重载clone()方法,但当类中有很多引用时,比较麻烦。 当然我们还有一种深拷贝方法,就是将对象 **序列化** 。 79 | 80 | 把对象写到流里的过程是序列化(Serilization)过程;而把对象从流中读出来的反序列化(Deserialization)过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。 81 | 82 | 在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。 83 | 84 | 还是上面的例子,我们重写 clone() 方法: 85 | 86 | public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException { 87 | // 将对象写到流里 88 | OutputStream bo = new ByteArrayOutputStream(); 89 | //OutputStream op = new ObjectOutputStream(); 90 | ObjectOutputStream oo = new ObjectOutputStream(bo); 91 | oo.writeObject(this); 92 | 93 | // 从流里读出来 94 | InputStream bi = new ByteArrayInputStream(((ByteArrayOutputStream) bo).toByteArray()); 95 | ObjectInputStream oi = new ObjectInputStream(bi); 96 | return (oi.readObject()); 97 | } 98 | 99 | 100 | 然后在拷贝对象时调用重写的 deepClone() 方法 101 | 102 | Book book2 = (Book) b1.deepClone(); 103 | 104 | > 结果 105 | 106 | > age = 34 name = Java开发 price = 30.0 107 | > 108 | > age = 45 name = Android开发 price = 44.0 109 | 110 | 111 | PS:这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient(自行了解) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StudyNotes 2 | 3 | 学习笔记 4 | 5 | ## 第一部分 6 | 7 | > **Java** 8 | 9 | * [浅谈Java 浅拷贝&深拷贝](https://github.com/dreamfish797/StudyNotes/tree/master/Java/浅谈Java浅拷贝&深拷贝.md) 10 | 11 | ## 第二部分 12 | 13 | > **Algorithm(算法)** 14 | 15 | * [排序](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort) 16 | * [快速排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/快速排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/QuickSort.java) 17 | * [冒泡排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/冒泡排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/BubbleSort.java) 18 | * [选择排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/选择排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/SelectSort.java) 19 | * [归并排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/归并排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/MergeSort.java) 20 | * [直接插入排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/直接插入排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/InsertionSort.java) 21 | * [希尔排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/希尔排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/ShellSort.java) 22 | * [基数排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/基数排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/src/sort/RadixSort.java) 23 | * [堆排序------](https://github.com/dreamfish797/StudyNotes/tree/master/Algorithm-learning/Sort/堆排序.md) [java实现](https://github.com/dreamfish797/StudyNotes/blob/master/Algorithm-learning/Sort/Algorithm_impl_java/srcsort//RadixSort.java) 24 | 25 | ## 第三部分 26 | 27 | > **DesignPattern(设计模式)** 28 | 29 | * [单例模式](https://github.com/dreamfish797/StudyNotes/tree/master/DesignPattern/单例模式.md) 30 | * [观察者模式](https://github.com/dreamfish797/StudyNotes/tree/master/DesignPattern/观察者模式.md) 31 | * [原型模式](https://github.com/dreamfish797/StudyNotes/tree/master/DesignPattern/原型模式.md) 32 | 33 | ## 第四部分 34 | > **Android(安卓)** 35 | 36 | * [Android 内存泄漏总结](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Android内存泄漏总结.md) 37 | * [Android录音实现(MediaRecord)](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Android录音实现(MediaRecord).md) 38 | * [Android录音实现(AudioRecord)](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Android录音实现(AudioRecord).md) 39 | * [Lambda表达式在Android中的使用](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Lambda表达式在Android中的使用.md) 40 | * [adb使用介绍](https://github.com/dreamfish797/StudyNotes/tree/master/Android/adb使用介绍.md) 41 | * [Activity 详细解析](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Activity详细解析.md) 42 | * [Service 详细解析](https://github.com/dreamfish797/StudyNotes/tree/master/Android/Service详细解析.md) 43 | * [BroadcastReceiver 详细解析](https://github.com/dreamfish797/StudyNotes/tree/master/Android/BroadcastReceiver 详细解析.md) 44 | --------------------------------------------------------------------------------