소스 검색

整理完成

Jason 5 년 전
부모
커밋
cadf76fc1c
100개의 변경된 파일4041개의 추가작업 그리고 0개의 파일을 삭제
  1. BIN
      MyNFCDemon/libs/android-support-v4.jar
  2. 7 0
      VIEW双缓冲与SurfaceView比较/Asin/.classpath
  3. 33 0
      VIEW双缓冲与SurfaceView比较/Asin/.project
  4. 18 0
      VIEW双缓冲与SurfaceView比较/Asin/AndroidManifest.xml
  5. 11 0
      VIEW双缓冲与SurfaceView比较/Asin/default.properties
  6. BIN
      VIEW双缓冲与SurfaceView比较/Asin/res/drawable-hdpi/icon.png
  7. BIN
      VIEW双缓冲与SurfaceView比较/Asin/res/drawable-ldpi/icon.png
  8. BIN
      VIEW双缓冲与SurfaceView比较/Asin/res/drawable-mdpi/icon.png
  9. 13 0
      VIEW双缓冲与SurfaceView比较/Asin/res/layout/main.xml
  10. 5 0
      VIEW双缓冲与SurfaceView比较/Asin/res/values/strings.xml
  11. 26 0
      VIEW双缓冲与SurfaceView比较/Asin/src/com/bao/asin/AsinActivity.java
  12. 104 0
      VIEW双缓冲与SurfaceView比较/Asin/src/com/bao/asin/AsinSurfaceView.java
  13. 86 0
      VIEW双缓冲与SurfaceView比较/Asin/src/com/bao/asin/AsinView.java
  14. BIN
      jettison-1.3.7有序JSON.jar
  15. 27 0
      书本附源码/艺术探索/.gitignore
  16. 9 0
      书本附源码/艺术探索/Chapter_1/.classpath
  17. 33 0
      书本附源码/艺术探索/Chapter_1/.project
  18. 54 0
      书本附源码/艺术探索/Chapter_1/AndroidManifest.xml
  19. BIN
      书本附源码/艺术探索/Chapter_1/ic_launcher-web.png
  20. 20 0
      书本附源码/艺术探索/Chapter_1/proguard-project.txt
  21. 14 0
      书本附源码/艺术探索/Chapter_1/project.properties
  22. BIN
      书本附源码/艺术探索/Chapter_1/res/drawable-hdpi/ic_launcher.png
  23. BIN
      书本附源码/艺术探索/Chapter_1/res/drawable-mdpi/ic_launcher.png
  24. BIN
      书本附源码/艺术探索/Chapter_1/res/drawable-xhdpi/ic_launcher.png
  25. BIN
      书本附源码/艺术探索/Chapter_1/res/drawable-xxhdpi/ic_launcher.png
  26. 25 0
      书本附源码/艺术探索/Chapter_1/res/drawable/edit.xml
  27. 24 0
      书本附源码/艺术探索/Chapter_1/res/layout/activity_main.xml
  28. 20 0
      书本附源码/艺术探索/Chapter_1/res/layout/activity_second.xml
  29. 20 0
      书本附源码/艺术探索/Chapter_1/res/layout/activity_third.xml
  30. 6 0
      书本附源码/艺术探索/Chapter_1/res/values/colors.xml
  31. 7 0
      书本附源码/艺术探索/Chapter_1/res/values/strings.xml
  32. 38 0
      书本附源码/艺术探索/Chapter_1/res/values/styles.xml
  33. 97 0
      书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/MainActivity.java
  34. 56 0
      书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/SecondActivity.java
  35. 56 0
      书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/ThirdActivity.java
  36. 205 0
      书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/ui/RevealLayout.java
  37. 9 0
      书本附源码/艺术探索/Chapter_11/.classpath
  38. 33 0
      书本附源码/艺术探索/Chapter_11/.project
  39. 52 0
      书本附源码/艺术探索/Chapter_11/AndroidManifest.xml
  40. BIN
      书本附源码/艺术探索/Chapter_11/ic_launcher-web.png
  41. 20 0
      书本附源码/艺术探索/Chapter_11/proguard-project.txt
  42. 14 0
      书本附源码/艺术探索/Chapter_11/project.properties
  43. 14 0
      书本附源码/艺术探索/Chapter_11/res/anim/translate.xml
  44. BIN
      书本附源码/艺术探索/Chapter_11/res/drawable-hdpi/ic_launcher.png
  45. BIN
      书本附源码/艺术探索/Chapter_11/res/drawable-mdpi/ic_launcher.png
  46. BIN
      书本附源码/艺术探索/Chapter_11/res/drawable-xhdpi/ic_launcher.png
  47. BIN
      书本附源码/艺术探索/Chapter_11/res/drawable-xxhdpi/ic_launcher.png
  48. 25 0
      书本附源码/艺术探索/Chapter_11/res/drawable/edit.xml
  49. 9 0
      书本附源码/艺术探索/Chapter_11/res/layout/activity_main.xml
  50. 32 0
      书本附源码/艺术探索/Chapter_11/res/layout/activity_test.xml
  51. 25 0
      书本附源码/艺术探索/Chapter_11/res/layout/content_layout.xml
  52. 14 0
      书本附源码/艺术探索/Chapter_11/res/layout/content_list_item.xml
  53. 17 0
      书本附源码/艺术探索/Chapter_11/res/layout/demo_1.xml
  54. 14 0
      书本附源码/艺术探索/Chapter_11/res/layout/demo_2.xml
  55. 8 0
      书本附源码/艺术探索/Chapter_11/res/values/attrs.xml
  56. 7 0
      书本附源码/艺术探索/Chapter_11/res/values/colors.xml
  57. 9 0
      书本附源码/艺术探索/Chapter_11/res/values/strings.xml
  58. 40 0
      书本附源码/艺术探索/Chapter_11/res/values/styles.xml
  59. 30 0
      书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/LocalIntentService.java
  60. 106 0
      书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/MainActivity.java
  61. 204 0
      书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/ui/RevealLayout.java
  62. 15 0
      书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/utils/MyConstants.java
  63. 51 0
      书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/utils/MyUtils.java
  64. 9 0
      书本附源码/艺术探索/Chapter_12/.classpath
  65. 33 0
      书本附源码/艺术探索/Chapter_12/.project
  66. 33 0
      书本附源码/艺术探索/Chapter_12/AndroidManifest.xml
  67. BIN
      书本附源码/艺术探索/Chapter_12/ic_launcher-web.png
  68. 20 0
      书本附源码/艺术探索/Chapter_12/proguard-project.txt
  69. 14 0
      书本附源码/艺术探索/Chapter_12/project.properties
  70. 14 0
      书本附源码/艺术探索/Chapter_12/res/anim/translate.xml
  71. BIN
      书本附源码/艺术探索/Chapter_12/res/drawable-hdpi/ic_launcher_1.png
  72. BIN
      书本附源码/艺术探索/Chapter_12/res/drawable-mdpi/ic_launcher_1.png
  73. BIN
      书本附源码/艺术探索/Chapter_12/res/drawable-xhdpi/ic_launcher_1.png
  74. BIN
      书本附源码/艺术探索/Chapter_12/res/drawable-xhdpi/image_default.png
  75. BIN
      书本附源码/艺术探索/Chapter_12/res/drawable-xxhdpi/ic_launcher_1.png
  76. 25 0
      书本附源码/艺术探索/Chapter_12/res/drawable/edit.xml
  77. 20 0
      书本附源码/艺术探索/Chapter_12/res/layout/activity_main.xml
  78. 15 0
      书本附源码/艺术探索/Chapter_12/res/layout/image_list_item.xml
  79. 8 0
      书本附源码/艺术探索/Chapter_12/res/values/attrs.xml
  80. 7 0
      书本附源码/艺术探索/Chapter_12/res/values/colors.xml
  81. 6 0
      书本附源码/艺术探索/Chapter_12/res/values/ids.xml
  82. 8 0
      书本附源码/艺术探索/Chapter_12/res/values/strings.xml
  83. 40 0
      书本附源码/艺术探索/Chapter_12/res/values/styles.xml
  84. 191 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/MainActivity.java
  85. 953 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/loader/DiskLruCache.java
  86. 363 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/loader/ImageLoader.java
  87. 74 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/loader/ImageResizer.java
  88. 204 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/ui/RevealLayout.java
  89. 25 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/ui/SquareImageView.java
  90. 15 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/utils/MyConstants.java
  91. 71 0
      书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/utils/MyUtils.java
  92. 9 0
      书本附源码/艺术探索/Chapter_13/CrashTest/.classpath
  93. 33 0
      书本附源码/艺术探索/Chapter_13/CrashTest/.project
  94. 55 0
      书本附源码/艺术探索/Chapter_13/CrashTest/AndroidManifest.xml
  95. BIN
      书本附源码/艺术探索/Chapter_13/CrashTest/ic_launcher-web.png
  96. 20 0
      书本附源码/艺术探索/Chapter_13/CrashTest/proguard-project.txt
  97. 14 0
      书本附源码/艺术探索/Chapter_13/CrashTest/project.properties
  98. BIN
      书本附源码/艺术探索/Chapter_13/CrashTest/res/drawable-hdpi/ic_launcher.png
  99. BIN
      书本附源码/艺术探索/Chapter_13/CrashTest/res/drawable-mdpi/ic_launcher.png
  100. 0 0
      书本附源码/艺术探索/Chapter_13/CrashTest/res/drawable-xhdpi/ic_launcher.png

BIN
MyNFCDemon/libs/android-support-v4.jar


+ 7 - 0
VIEW双缓冲与SurfaceView比较/Asin/.classpath

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>

+ 33 - 0
VIEW双缓冲与SurfaceView比较/Asin/.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Asin</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 18 - 0
VIEW双缓冲与SurfaceView比较/Asin/AndroidManifest.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.bao.asin"
+      android:versionCode="1"
+      android:versionName="1.0">
+    <application android:icon="@drawable/icon" android:label="@string/app_name">
+        <activity android:name=".AsinActivity"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+    <uses-sdk android:minSdkVersion="8" />
+
+</manifest> 

+ 11 - 0
VIEW双缓冲与SurfaceView比较/Asin/default.properties

@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+# 
+# This file must be checked in Version Control Systems.
+# 
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-8

BIN
VIEW双缓冲与SurfaceView比较/Asin/res/drawable-hdpi/icon.png


BIN
VIEW双缓冲与SurfaceView比较/Asin/res/drawable-ldpi/icon.png


BIN
VIEW双缓冲与SurfaceView比较/Asin/res/drawable-mdpi/icon.png


+ 13 - 0
VIEW双缓冲与SurfaceView比较/Asin/res/layout/main.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="@string/hello"
+    />
+
+</LinearLayout>

+ 5 - 0
VIEW双缓冲与SurfaceView比较/Asin/res/values/strings.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello World, AsinActivity!</string>
+    <string name="app_name">Asin</string>
+</resources>

+ 26 - 0
VIEW双缓冲与SurfaceView比较/Asin/src/com/bao/asin/AsinActivity.java

@@ -0,0 +1,26 @@
+package com.bao.asin;
+
+import android.app.Activity;
+import android.graphics.drawable.GradientDrawable.Orientation;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+public class AsinActivity extends Activity {
+    /** Called when the activity is first created. */
+	
+	AsinSurfaceView asinSurfaceView;
+	AsinView assinView;
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+        asinSurfaceView = new AsinSurfaceView(this);
+        assinView = new AsinView(this);
+        
+        LinearLayout l = new LinearLayout(this);
+        l.setOrientation(LinearLayout.VERTICAL);
+        l.addView(asinSurfaceView);
+        l.addView(assinView);
+        setContentView(l);
+    }
+}

+ 104 - 0
VIEW双缓冲与SurfaceView比较/Asin/src/com/bao/asin/AsinSurfaceView.java

@@ -0,0 +1,104 @@
+package com.bao.asin;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder.Callback;
+
+public class AsinSurfaceView extends SurfaceView implements Callback {
+	ShowThread showThread;
+	int x = 0;
+	Bitmap bitmap;
+	int A = 50;
+	Paint paint;
+	class ShowThread extends Thread{
+		private AsinSurfaceView asinSurfaceView;
+		private SurfaceHolder surfaceHolder;
+		boolean flag = true;
+		int sleepTime = 200;
+		
+		public ShowThread(AsinSurfaceView asinSurfaceView, SurfaceHolder surfaceHolder){
+			this.asinSurfaceView = asinSurfaceView;
+			this.surfaceHolder = surfaceHolder;
+		}
+		@Override
+		public void run() {
+			// TODO Auto-generated method stub
+			super.run();
+			while(flag == true){
+				
+				
+				Canvas canvas = surfaceHolder.lockCanvas(null);
+			
+				onDraw(canvas);
+				surfaceHolder.unlockCanvasAndPost(canvas);
+				
+				asinSurfaceView.x += 5;
+				
+				try {
+					Thread.sleep(sleepTime);
+				} catch (InterruptedException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+	
+	public AsinSurfaceView(Context context) {
+		super(context);
+		// TODO Auto-generated constructor stub
+		showThread = new ShowThread(this,getHolder());
+		getHolder().addCallback(this);
+		paint = new Paint();
+		paint.setColor(Color.WHITE);
+	}
+
+	@Override
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+		// TODO Auto-generated method stub
+		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+		setMeasuredDimension(480, 160);
+		
+	}
+
+	
+	public void onDraw(Canvas canvas){
+		if(x >= 480 || x == 0){
+			x = 0;
+			canvas.drawColor(Color.BLACK);// Çå³ý»­²¼  
+
+		}
+		else{
+			if(x == 5){
+				canvas.drawColor(Color.BLACK);// Çå³ý»­²¼  
+
+			}
+			int y = (int)(A*Math.sin(x/180.0f*Math.PI));
+			System.out.println("==================y:"+y+"===x:"+x);
+			canvas.drawPoint(x, y+50, paint);
+		}
+	}
+	public void surfaceChanged(SurfaceHolder holder, int format, int width,
+			int height) {
+		// TODO Auto-generated method stub
+		
+	}
+
+	public void surfaceCreated(SurfaceHolder holder) {
+		// TODO Auto-generated method stub
+		showThread.start();
+		
+	}
+
+	public void surfaceDestroyed(SurfaceHolder holder) {
+		// TODO Auto-generated method stub
+		
+	}
+	
+}

+ 86 - 0
VIEW双缓冲与SurfaceView比较/Asin/src/com/bao/asin/AsinView.java

@@ -0,0 +1,86 @@
+package com.bao.asin;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Bitmap.Config;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.SurfaceHolder.Callback;
+import android.view.View;
+
+public class AsinView extends View{
+	ShowThread showThread;
+	int x = 0;
+	Bitmap bitmap;
+	int A = 50;
+	Paint paint;
+	
+	class ShowThread extends Thread{
+		private AsinView ssinView;
+		boolean flag = true;
+		int sleepTime = 200;
+		
+		public ShowThread(AsinView ssinView){
+			this.ssinView = ssinView;
+		}
+		@Override
+		public void run() {
+			// TODO Auto-generated method stub
+			super.run();
+			while(flag == true){
+				
+				ssinView.postInvalidate();
+		
+				try {
+					Thread.sleep(sleepTime);
+				} catch (InterruptedException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+	
+	public AsinView(Context context) {
+		super(context);
+		// TODO Auto-generated constructor stub
+		showThread = new ShowThread(this);
+		
+		bitmap = Bitmap.createBitmap(480, 104, Config.ARGB_8888);     
+		paint = new Paint();
+		paint.setColor(Color.WHITE);
+		showThread.start();
+	}
+
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+		// TODO Auto-generated method stub
+		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+		setMeasuredDimension(480, 160);
+		
+	}
+
+	public void onDraw(Canvas canvas){
+		
+		if(x >= 480){
+			x = 0;
+			canvas.drawColor(Color.BLACK); 
+			bitmap = Bitmap.createBitmap(480, 104, Config.ARGB_8888);     
+
+		}else{
+			Canvas c = new Canvas();
+			c.setBitmap(bitmap);
+			int y = (int)(A*Math.sin(x/180.0f*Math.PI));
+			System.out.println("==================y:"+y+"===x:"+x);
+			c.drawPoint(x, y+A, paint);
+			//canvas.save();
+			canvas.drawBitmap(bitmap, 0, 0,paint);
+			x+=5;
+		}
+	}
+	
+}

BIN
jettison-1.3.7有序JSON.jar


+ 27 - 0
书本附源码/艺术探索/.gitignore

@@ -0,0 +1,27 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+
+# Gradle files
+.gradle/
+build/
+/*/build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log

+ 9 - 0
书本附源码/艺术探索/Chapter_1/.classpath

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>

+ 33 - 0
书本附源码/艺术探索/Chapter_1/.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Chapter_1</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 54 - 0
书本附源码/艺术探索/Chapter_1/AndroidManifest.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.ryg.chapter_1"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="19" />
+
+    <!-- <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> -->
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="com.ryg.chapter_1.MainActivity"
+            android:configChanges="orientation|screenSize"
+            android:label="@string/app_name"
+            android:launchMode="standard" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="com.ryg.chapter_1.SecondActivity"
+            android:configChanges="screenLayout"
+            android:label="@string/app_name"
+            android:launchMode="standard"
+            android:taskAffinity="com.ryg.task1" />
+        <activity
+            android:name="com.ryg.chapter_1.ThirdActivity"
+            android:configChanges="screenLayout"
+            android:label="@string/app_name"
+            android:launchMode="singleTask"
+            android:taskAffinity="com.ryg.task1" >
+            <intent-filter>
+                <action android:name="com.ryg.charpter_1.c" />
+                <action android:name="com.ryg.charpter_1.d" />
+
+                <category android:name="com.ryg.category.c" />
+                <category android:name="com.ryg.category.d" />
+                <category android:name="android.intent.category.DEFAULT" />
+
+                <data android:mimeType="text/plain" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

BIN
书本附源码/艺术探索/Chapter_1/ic_launcher-web.png


+ 20 - 0
书本附源码/艺术探索/Chapter_1/proguard-project.txt

@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 14 - 0
书本附源码/艺术探索/Chapter_1/project.properties

@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19

BIN
书本附源码/艺术探索/Chapter_1/res/drawable-hdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_1/res/drawable-mdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_1/res/drawable-xhdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_1/res/drawable-xxhdpi/ic_launcher.png


+ 25 - 0
书本附源码/艺术探索/Chapter_1/res/drawable/edit.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item>
+        <shape android:shape="rectangle" >
+            <solid android:color="#0ac39e" />
+        </shape>
+    </item>
+
+    <item android:bottom="6dp">
+        <shape android:shape="rectangle" >
+            <solid android:color="#ffffff" />
+        </shape>
+    </item>
+
+    <item
+        android:bottom="1dp"
+        android:left="1dp"
+        android:right="1dp">
+        <shape android:shape="rectangle" >
+            <solid android:color="#ffffff" />
+        </shape>
+    </item>
+
+</layer-list>

+ 24 - 0
书本附源码/艺术探索/Chapter_1/res/layout/activity_main.xml

@@ -0,0 +1,24 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ffffff"
+    android:orientation="vertical" >
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="open activity B" />
+
+    <EditText
+        android:id="@+id/editText1"
+        android:layout_width="150dp"
+        android:layout_height="wrap_content"
+        android:text="A"
+        android:ems="10" >
+
+        <requestFocus />
+    </EditText>
+
+</LinearLayout>

+ 20 - 0
书本附源码/艺术探索/Chapter_1/res/layout/activity_second.xml

@@ -0,0 +1,20 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ffffff"
+    android:orientation="vertical" >
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="open activity C" />
+
+    <TextView
+        android:id="@+id/textView1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Activity B" />
+
+</LinearLayout>

+ 20 - 0
书本附源码/艺术探索/Chapter_1/res/layout/activity_third.xml

@@ -0,0 +1,20 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ffffff"
+    android:orientation="vertical" >
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="open activity A" />
+
+    <TextView
+        android:id="@+id/textView1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Activity C" />
+
+</LinearLayout>

+ 6 - 0
书本附源码/艺术探索/Chapter_1/res/values/colors.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <color name="reveal_color">#1b000000</color>
+
+</resources>

+ 7 - 0
书本附源码/艺术探索/Chapter_1/res/values/strings.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Chapter_1</string>
+    <string name="hello_world">Hello world!</string>
+
+</resources>

+ 38 - 0
书本附源码/艺术探索/Chapter_1/res/values/styles.xml

@@ -0,0 +1,38 @@
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.





+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <!-- Common button styles -->
+    <style name="AppTheme.Button">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">44dp</item>
+        <item name="android:textSize">18dp</item>
+    </style>
+
+    <style name="AppTheme.Button.Green">
+        <item name="android:background">#0ac39e</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+
+    <style name="AppTheme.Button.Highlight">
+        <item name="android:background">#4185f2</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+
+
+</resources>

+ 97 - 0
书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/MainActivity.java

@@ -0,0 +1,97 @@
+package com.ryg.chapter_1;
+
+import com.ryg.chapter_1.R;
+
+import android.R.integer;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class MainActivity extends Activity {
+
+    private static final String TAG = "MainActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        if (savedInstanceState != null) {
+            String test = savedInstanceState.getString("extra_test");
+            Log.d(TAG, "[onCreate]restore extra_test:" + test);
+        }
+        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent("com.ryg.charpter_1.c");
+                //intent.setClass(MainActivity.this, SecondActivity.class);
+                intent.putExtra("time", System.currentTimeMillis());
+                intent.addCategory("com.ryg.category.c");
+                intent.setDataAndType(Uri.parse("file://abc"), "text/plain");
+                startActivity(intent);
+            }
+        });
+    }
+    
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        Log.d(TAG, "onNewIntent, time=" + intent.getLongExtra("time", 0));
+    }
+
+    @Override
+    protected void onStart() {
+        Log.d(TAG, "onStart");
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        Log.d(TAG, "onResume");
+        super.onStart();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        Log.d(TAG, "onConfigurationChanged, newOrientation:" + newConfig.orientation);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        Log.d(TAG, "onSaveInstanceState");
+        outState.putString("extra_test", "test");
+    }
+    
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        //Log.d(TAG, "onRestoreInstanceState");
+        String test = savedInstanceState.getString("extra_test");
+        Log.d(TAG, "[onRestoreInstanceState]restore extra_test:" + test);
+    }
+
+    @Override
+    protected void onPause() {
+        Log.d(TAG, "onPause");
+        super.onPause();
+    }
+    
+    @Override
+    protected void onStop() {
+        Log.d(TAG, "onStop");
+        super.onStop();
+    }
+    
+    @Override
+    protected void onDestroy() {
+        Log.d(TAG, "onDestroy");
+        super.onDestroy();
+    }
+}

+ 56 - 0
书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/SecondActivity.java

@@ -0,0 +1,56 @@
+package com.ryg.chapter_1;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class SecondActivity extends Activity {
+    private static final String TAG = "SecondActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // TODO Auto-generated method stub
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_second);
+        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent();
+                intent.setClass(SecondActivity.this, ThirdActivity.class);
+                intent.putExtra("time", System.currentTimeMillis());
+                startActivity(intent);
+            }
+        });
+        Log.d(TAG, "onCreate");
+    }
+
+    @Override
+    protected void onStart() {
+        // TODO Auto-generated method stub
+        super.onStart();
+        Log.d(TAG, "onStart");
+    }
+
+    @Override
+    protected void onResume() {
+        // TODO Auto-generated method stub
+        super.onResume();
+        Log.d(TAG, "onResume");
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        Log.d(TAG, "onSaveInstanceState");
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        Log.d(TAG, "onRestoreInstanceState");
+    }
+}

+ 56 - 0
书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/ThirdActivity.java

@@ -0,0 +1,56 @@
+package com.ryg.chapter_1;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+public class ThirdActivity extends Activity {
+    private static final String TAG = "ThirdActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // TODO Auto-generated method stub
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_third);
+        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                Intent intent = new Intent();
+                intent.setClass(ThirdActivity.this, MainActivity.class);
+                intent.putExtra("time", System.currentTimeMillis());
+                startActivity(intent);
+            }
+        });
+        Log.d(TAG, "onCreate");
+    }
+
+    @Override
+    protected void onStart() {
+        // TODO Auto-generated method stub
+        super.onStart();
+        Log.d(TAG, "onStart");
+    }
+
+    @Override
+    protected void onResume() {
+        // TODO Auto-generated method stub
+        super.onResume();
+        Log.d(TAG, "onResume");
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        Log.d(TAG, "onSaveInstanceState");
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        Log.d(TAG, "onRestoreInstanceState");
+    }
+}

+ 205 - 0
书本附源码/艺术探索/Chapter_1/src/com/ryg/chapter_1/ui/RevealLayout.java

@@ -0,0 +1,205 @@
+package com.ryg.chapter_1.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+import com.ryg.chapter_1.R;
+
+/**
+ * 一个特殊的LinearLayout,任何放入内部的clickable元素都具有波纹效果,当它被点击的时候,
+ * 为了性能,尽量不要在内部放入复杂的元素
+ * note: long click listener is not supported current for fix compatible bug.
+ */
+public class RevealLayout extends LinearLayout implements Runnable {
+
+    private static final String TAG = "DxRevealLayout";
+    private static final boolean DEBUG = true;
+
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    private int mTargetWidth;
+    private int mTargetHeight;
+    private int mMinBetweenWidthAndHeight;
+    private int mMaxBetweenWidthAndHeight;
+    private int mMaxRevealRadius;
+    private int mRevealRadiusGap;
+    private int mRevealRadius = 0;
+    private float mCenterX;
+    private float mCenterY;
+    private int[] mLocationInScreen = new int[2];
+
+    private boolean mShouldDoAnimation = false;
+    private boolean mIsPressed = false;
+    private int INVALIDATE_DURATION = 40;
+
+    private View mTouchTarget;
+    private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable();
+
+    public RevealLayout(Context context) {
+        super(context);
+        init();
+    }
+
+    public RevealLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public RevealLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        setWillNotDraw(false);
+        mPaint.setColor(getResources().getColor(R.color.reveal_color));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        this.getLocationOnScreen(mLocationInScreen);
+    }
+
+    private void initParametersForChild(MotionEvent event, View view) {
+        mCenterX = event.getX() ;
+        mCenterY = event.getY() ;
+        mTargetWidth = view.getMeasuredWidth();
+        mTargetHeight = view.getMeasuredHeight();
+        mMinBetweenWidthAndHeight = Math.min(mTargetWidth, mTargetHeight);
+        mMaxBetweenWidthAndHeight = Math.max(mTargetWidth, mTargetHeight);
+        mRevealRadius = 0;
+        mShouldDoAnimation = true;
+        mIsPressed = true;
+        mRevealRadiusGap = mMinBetweenWidthAndHeight / 8;
+
+        int[] location = new int[2];
+        view.getLocationOnScreen(location);
+        int left = location[0] - mLocationInScreen[0];
+        int transformedCenterX = (int)mCenterX - left;
+        mMaxRevealRadius = Math.max(transformedCenterX, mTargetWidth - transformedCenterX);
+    }
+
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {
+            return;
+        }
+
+        if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {
+            mRevealRadius += mRevealRadiusGap * 4;
+        } else {
+            mRevealRadius += mRevealRadiusGap;
+        }
+        this.getLocationOnScreen(mLocationInScreen);
+        int[] location = new int[2];
+        mTouchTarget.getLocationOnScreen(location);
+        int left = location[0] - mLocationInScreen[0];
+        int top = location[1] - mLocationInScreen[1];
+        int right = left + mTouchTarget.getMeasuredWidth();
+        int bottom = top + mTouchTarget.getMeasuredHeight();
+
+        canvas.save();
+        canvas.clipRect(left, top, right, bottom);
+        canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);
+        canvas.restore();
+
+        if (mRevealRadius <= mMaxRevealRadius) {
+            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
+        } else if (!mIsPressed) {
+            mShouldDoAnimation = false;
+            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        int x = (int) event.getRawX();
+        int y = (int) event.getRawY();
+        int action = event.getAction();
+        if (action == MotionEvent.ACTION_DOWN) {
+            View touchTarget = getTouchTarget(this, x, y);
+            if (touchTarget != null && touchTarget.isClickable() && touchTarget.isEnabled()) {
+                mTouchTarget = touchTarget;
+                initParametersForChild(event, touchTarget);
+                postInvalidateDelayed(INVALIDATE_DURATION);
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            mIsPressed = false;
+            postInvalidateDelayed(INVALIDATE_DURATION);
+            mDispatchUpTouchEventRunnable.event = event;
+            postDelayed(mDispatchUpTouchEventRunnable, 40);
+            return true;
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            mIsPressed = false;
+            postInvalidateDelayed(INVALIDATE_DURATION);
+        }
+
+        return super.dispatchTouchEvent(event);
+    }
+
+    private View getTouchTarget(View view, int x, int y) {
+        View target = null;
+        ArrayList<View> TouchableViews = view.getTouchables();
+        for (View child : TouchableViews) {
+            if (isTouchPointInView(child, x, y)) {
+                target = child;
+                break;
+            }
+        }
+
+        return target;
+    }
+
+    private boolean isTouchPointInView(View view, int x, int y) {
+        int[] location = new int[2];
+        view.getLocationOnScreen(location);
+        int left = location[0];
+        int top = location[1];
+        int right = left + view.getMeasuredWidth();
+        int bottom = top + view.getMeasuredHeight();
+        if (view.isClickable() && y >= top && y <= bottom
+                && x >= left && x <= right) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean performClick() {
+        postDelayed(this, 400);
+        return true;
+    }
+
+    @Override
+    public void run() {
+        super.performClick();
+    }
+
+    private class DispatchUpTouchEventRunnable implements Runnable {
+        public MotionEvent event;
+
+        @Override
+        public void run() {
+            if (mTouchTarget == null || !mTouchTarget.isEnabled()) {
+                return;
+            }
+
+            if (isTouchPointInView(mTouchTarget, (int)event.getRawX(), (int)event.getRawY())) {
+                mTouchTarget.performClick();
+            }
+        }
+    };
+
+}

+ 9 - 0
书本附源码/艺术探索/Chapter_11/.classpath

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>

+ 33 - 0
书本附源码/艺术探索/Chapter_11/.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Chapter_11</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 52 - 0
书本附源码/艺术探索/Chapter_11/AndroidManifest.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.ryg.chapter_11"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="19" />
+
+    <uses-permission android:name="com.ryg.PROVIDER" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".TestActivity"
+            android:configChanges="orientation|screenSize"
+            android:label="@string/app_name"
+            android:launchMode="standard" />
+        <activity
+            android:name=".DemoActivity_1"
+            android:configChanges="screenLayout"
+            android:label="@string/title_scene_1"
+            android:launchMode="standard" />
+        <activity
+            android:name=".DemoActivity_2"
+            android:configChanges="screenLayout"
+            android:label="@string/app_name"
+            android:launchMode="standard" />
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <receiver android:name=".MyReceiver" >
+            <intent-filter>
+                <action android:name="com.ryg.receiver.LAUNCH" />
+            </intent-filter>
+        </receiver>
+
+        <service android:name=".LocalIntentService" />
+    </application>
+
+</manifest>

BIN
书本附源码/艺术探索/Chapter_11/ic_launcher-web.png


+ 20 - 0
书本附源码/艺术探索/Chapter_11/proguard-project.txt

@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 14 - 0
书本附源码/艺术探索/Chapter_11/project.properties

@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19

+ 14 - 0
书本附源码/艺术探索/Chapter_11/res/anim/translate.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fillAfter="true"
+    android:zAdjustment="normal" >
+
+    <translate
+        android:duration="100"
+        android:fromXDelta="0"
+        android:fromYDelta="0"
+        android:interpolator="@android:anim/linear_interpolator"
+        android:toXDelta="100"
+        android:toYDelta="100" />
+
+</set>

BIN
书本附源码/艺术探索/Chapter_11/res/drawable-hdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_11/res/drawable-mdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_11/res/drawable-xhdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_11/res/drawable-xxhdpi/ic_launcher.png


+ 25 - 0
书本附源码/艺术探索/Chapter_11/res/drawable/edit.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item>
+        <shape android:shape="rectangle" >
+            <solid android:color="#0ac39e" />
+        </shape>
+    </item>
+
+    <item android:bottom="6dp">
+        <shape android:shape="rectangle" >
+            <solid android:color="#ffffff" />
+        </shape>
+    </item>
+
+    <item
+        android:bottom="1dp"
+        android:left="1dp"
+        android:right="1dp">
+        <shape android:shape="rectangle" >
+            <solid android:color="#ffffff" />
+        </shape>
+    </item>
+
+</layer-list>

+ 9 - 0
书本附源码/艺术探索/Chapter_11/res/layout/activity_main.xml

@@ -0,0 +1,9 @@
+<com.ryg.chapter_11.ui.RevealLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="12dp"
+    tools:context="${relativePackage}.${activityClass}" >
+
+</com.ryg.chapter_11.ui.RevealLayout>

+ 32 - 0
书本附源码/艺术探索/Chapter_11/res/layout/activity_test.xml

@@ -0,0 +1,32 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="10px" >
+
+    <Button
+        android:id="@+id/button1"
+        android:layout_width="wrap_content"
+        android:layout_height="100px"
+        android:background="#ff0000"
+        android:text="MovingButton" />
+
+    <Button
+        android:id="@+id/button2"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#000000"
+        android:gravity="center"
+        android:textColor="@android:color/white"
+        android:text="我可以滑动" />
+
+    <EditText
+        android:id="@+id/editText1"
+        android:layout_width="150dp"
+        android:layout_height="wrap_content"
+        android:ems="10"
+        android:text="A" >
+    </EditText>
+
+</LinearLayout>

+ 25 - 0
书本附源码/艺术探索/Chapter_11/res/layout/content_layout.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="5dp"
+        android:layout_marginBottom="5dp"
+        android:text="TextView" />
+
+    <ListView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#fff4f7f9"
+        android:cacheColorHint="#00000000"
+        android:divider="#dddbdb"
+        android:dividerHeight="1.0px"
+        android:listSelector="@android:color/transparent" />
+
+</LinearLayout>

+ 14 - 0
书本附源码/艺术探索/Chapter_11/res/layout/content_list_item.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="50dp"
+    android:gravity="center_vertical"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="TextView" />
+
+</LinearLayout>

+ 17 - 0
书本附源码/艺术探索/Chapter_11/res/layout/demo_1.xml

@@ -0,0 +1,17 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ffffff"
+    android:orientation="vertical" >
+
+    <com.ryg.chapter_11.ui.CircleView
+        android:id="@+id/circleView1"
+        android:layout_width="wrap_content"
+        android:layout_height="100dp"
+        android:layout_margin="20dp"
+        android:background="#000000"
+        android:padding="20dp"
+        app:circle_color="@color/light_green" />
+
+</LinearLayout>

+ 14 - 0
书本附源码/艺术探索/Chapter_11/res/layout/demo_2.xml

@@ -0,0 +1,14 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ffffff"
+    android:orientation="vertical" >
+
+    <com.ryg.chapter_11.ui.HorizontalScrollViewEx
+        android:id="@+id/container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent" />
+
+
+</LinearLayout>

+ 8 - 0
书本附源码/艺术探索/Chapter_11/res/values/attrs.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <declare-styleable name="CircleView">
+        <attr name="circle_color" format="color" />
+    </declare-styleable>
+
+</resources>

+ 7 - 0
书本附源码/艺术探索/Chapter_11/res/values/colors.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <color name="reveal_color">#1b000000</color>
+    <color name="light_green">#0ac39e</color>
+    
+</resources>

+ 9 - 0
书本附源码/艺术探索/Chapter_11/res/values/strings.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Chapter_11</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="title_scene_1">Chapter_11:CircleView</string>
+    <string name="title_activity_main">MainActivity</string>
+
+</resources>

+ 40 - 0
书本附源码/艺术探索/Chapter_11/res/values/styles.xml

@@ -0,0 +1,40 @@
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.





+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <!-- Common button styles -->
+    <style name="AppTheme.Button">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">44dp</item>
+        <item name="android:textSize">18dp</item>
+    </style>
+
+    <style name="AppTheme.Button.Green">
+        <item name="android:background">#0ac39e</item>
+        <item name="android:textColor">#ffffff</item>
+        <item name="android:layout_marginTop">5dp</item>
+        <item name="android:layout_marginBottom">5dp</item>
+    </style>
+
+    <style name="AppTheme.Button.Highlight">
+        <item name="android:background">#4185f2</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+
+
+</resources>

+ 30 - 0
书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/LocalIntentService.java

@@ -0,0 +1,30 @@
+package com.ryg.chapter_11;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class LocalIntentService extends IntentService {
+    private static final String TAG = "LocalIntentService";
+
+    public LocalIntentService() {
+        super(TAG);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        String action = intent.getStringExtra("task_action");
+        Log.d(TAG, "receive task :" +  action);
+        SystemClock.sleep(3000);
+        if ("com.ryg.action.TASK1".equals(action)) {
+            Log.d(TAG, "handle task: " + action);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "service destroyed.");
+        super.onDestroy();
+    }
+}

+ 106 - 0
书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/MainActivity.java

@@ -0,0 +1,106 @@
+package com.ryg.chapter_11;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import com.ryg.chapter_11.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.View;
+
+public class MainActivity extends Activity {
+    private static final String TAG = "MainActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        scheduleThreads();
+
+    }
+
+    public void onButtonClick(View v) {
+
+    }
+
+    private void scheduleThreads() {
+        runAsyncTask();
+        runIntentService();
+        runThreadPool();
+    }
+
+    private void runThreadPool() {
+        Runnable command = new Runnable() {
+            @Override
+            public void run() {
+                SystemClock.sleep(2000);
+            }
+        };
+
+        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
+        fixedThreadPool.execute(command);
+        
+        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
+        cachedThreadPool.execute(command);
+        
+        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
+        // 2000ms后执行command
+        scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
+        // 延迟10ms后,每隔1000ms执行一次command
+        scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS);
+
+        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
+        singleThreadExecutor.execute(command);
+    }
+
+    private void runIntentService() {
+        Intent service = new Intent(this, LocalIntentService.class);
+        service.putExtra("task_action", "com.ryg.action.TASK1");
+        startService(service);
+        service.putExtra("task_action", "com.ryg.action.TASK2");
+        startService(service);
+        service.putExtra("task_action", "com.ryg.action.TASK3");
+        startService(service);
+    }
+
+    private void runAsyncTask() {
+        try {
+            new DownloadFilesTask().execute(new URL("http://www.baidu.com"),
+                    new URL("http://www.renyugang.cn"));
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
+        protected Long doInBackground(URL... urls) {
+            int count = urls.length;
+            long totalSize = 0;
+            for (int i = 0; i < count; i++) {
+                // totalSize += Downloader.downloadFile(urls[i]);
+                publishProgress((int) ((i / (float) count) * 100));
+                // Escape early if cancel() is called
+                if (isCancelled())
+                    break;
+            }
+            return totalSize;
+        }
+
+        protected void onProgressUpdate(Integer... progress) {
+            // setProgressPercent(progress[0]);
+        }
+
+        protected void onPostExecute(Long result) {
+            // showDialog("Downloaded " + result + " bytes");
+        }
+    }
+
+}

+ 204 - 0
书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/ui/RevealLayout.java

@@ -0,0 +1,204 @@
+package com.ryg.chapter_11.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+import com.ryg.chapter_11.R;
+
+/**
+ * 一个特殊的LinearLayout,任何放入内部的clickable元素都具有波纹效果,当它被点击的时候,
+ * 为了性能,尽量不要在内部放入复杂的元素
+ * note: long click listener is not supported current for fix compatible bug.
+ */
+public class RevealLayout extends LinearLayout implements Runnable {
+
+    private static final String TAG = "DxRevealLayout";
+    private static final boolean DEBUG = true;
+
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    private int mTargetWidth;
+    private int mTargetHeight;
+    private int mMinBetweenWidthAndHeight;
+    private int mMaxBetweenWidthAndHeight;
+    private int mMaxRevealRadius;
+    private int mRevealRadiusGap;
+    private int mRevealRadius = 0;
+    private float mCenterX;
+    private float mCenterY;
+    private int[] mLocationInScreen = new int[2];
+
+    private boolean mShouldDoAnimation = false;
+    private boolean mIsPressed = false;
+    private int INVALIDATE_DURATION = 40;
+
+    private View mTouchTarget;
+    private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable();
+
+    public RevealLayout(Context context) {
+        super(context);
+        init();
+    }
+
+    public RevealLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public RevealLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        setWillNotDraw(false);
+        mPaint.setColor(getResources().getColor(R.color.reveal_color));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        this.getLocationOnScreen(mLocationInScreen);
+    }
+
+    private void initParametersForChild(MotionEvent event, View view) {
+        mCenterX = event.getX() ;
+        mCenterY = event.getY() ;
+        mTargetWidth = view.getMeasuredWidth();
+        mTargetHeight = view.getMeasuredHeight();
+        mMinBetweenWidthAndHeight = Math.min(mTargetWidth, mTargetHeight);
+        mMaxBetweenWidthAndHeight = Math.max(mTargetWidth, mTargetHeight);
+        mRevealRadius = 0;
+        mShouldDoAnimation = true;
+        mIsPressed = true;
+        mRevealRadiusGap = mMinBetweenWidthAndHeight / 8;
+
+        int[] location = new int[2];
+        view.getLocationOnScreen(location);
+        int left = location[0] - mLocationInScreen[0];
+        int transformedCenterX = (int)mCenterX - left;
+        mMaxRevealRadius = Math.max(transformedCenterX, mTargetWidth - transformedCenterX);
+    }
+
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {
+            return;
+        }
+
+        if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {
+            mRevealRadius += mRevealRadiusGap * 4;
+        } else {
+            mRevealRadius += mRevealRadiusGap;
+        }
+        this.getLocationOnScreen(mLocationInScreen);
+        int[] location = new int[2];
+        mTouchTarget.getLocationOnScreen(location);
+        int left = location[0] - mLocationInScreen[0];
+        int top = location[1] - mLocationInScreen[1];
+        int right = left + mTouchTarget.getMeasuredWidth();
+        int bottom = top + mTouchTarget.getMeasuredHeight();
+
+        canvas.save();
+        canvas.clipRect(left, top, right, bottom);
+        canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);
+        canvas.restore();
+
+        if (mRevealRadius <= mMaxRevealRadius) {
+            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
+        } else if (!mIsPressed) {
+            mShouldDoAnimation = false;
+            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        int x = (int) event.getRawX();
+        int y = (int) event.getRawY();
+        int action = event.getAction();
+        if (action == MotionEvent.ACTION_DOWN) {
+            View touchTarget = getTouchTarget(this, x, y);
+            if (touchTarget != null && touchTarget.isClickable() && touchTarget.isEnabled()) {
+                mTouchTarget = touchTarget;
+                initParametersForChild(event, touchTarget);
+                postInvalidateDelayed(INVALIDATE_DURATION);
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            mIsPressed = false;
+            postInvalidateDelayed(INVALIDATE_DURATION);
+            mDispatchUpTouchEventRunnable.event = event;
+            postDelayed(mDispatchUpTouchEventRunnable, 200);
+            return true;
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            mIsPressed = false;
+            postInvalidateDelayed(INVALIDATE_DURATION);
+        }
+
+        return super.dispatchTouchEvent(event);
+    }
+
+    private View getTouchTarget(View view, int x, int y) {
+        View target = null;
+        ArrayList<View> TouchableViews = view.getTouchables();
+        for (View child : TouchableViews) {
+            if (isTouchPointInView(child, x, y)) {
+                target = child;
+                break;
+            }
+        }
+
+        return target;
+    }
+
+    private boolean isTouchPointInView(View view, int x, int y) {
+        int[] location = new int[2];
+        view.getLocationOnScreen(location);
+        int left = location[0];
+        int top = location[1];
+        int right = left + view.getMeasuredWidth();
+        int bottom = top + view.getMeasuredHeight();
+        if (view.isClickable() && y >= top && y <= bottom
+                && x >= left && x <= right) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean performClick() {
+        postDelayed(this, 400);
+        return true;
+    }
+
+    @Override
+    public void run() {
+        super.performClick();
+    }
+
+    private class DispatchUpTouchEventRunnable implements Runnable {
+        public MotionEvent event;
+
+        @Override
+        public void run() {
+            if (mTouchTarget == null || !mTouchTarget.isEnabled()) {
+                return;
+            }
+
+            if (isTouchPointInView(mTouchTarget, (int)event.getRawX(), (int)event.getRawY())) {
+                mTouchTarget.performClick();
+            }
+        }
+    };
+
+}

+ 15 - 0
书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/utils/MyConstants.java

@@ -0,0 +1,15 @@
+package com.ryg.chapter_11.utils;
+
+import android.os.Environment;
+
+public class MyConstants {
+    public static final String CHAPTER_2_PATH = Environment
+            .getExternalStorageDirectory().getPath()
+            + "/singwhatiwanna/chapter_2/";
+
+    public static final String CACHE_FILE_PATH = CHAPTER_2_PATH + "usercache";
+
+    public static final int MSG_FROM_CLIENT = 0;
+    public static final int MSG_FROM_SERVICE = 1;
+
+}

+ 51 - 0
书本附源码/艺术探索/Chapter_11/src/com/ryg/chapter_11/utils/MyUtils.java

@@ -0,0 +1,51 @@
+package com.ryg.chapter_11.utils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+public class MyUtils {
+
+    public static String getProcessName(Context cxt, int pid) {
+        ActivityManager am = (ActivityManager) cxt
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
+        if (runningApps == null) {
+            return null;
+        }
+        for (RunningAppProcessInfo procInfo : runningApps) {
+            if (procInfo.pid == pid) {
+                return procInfo.processName;
+            }
+        }
+        return null;
+    }
+
+    public static void close(Closeable closeable) {
+        try {
+            if (closeable != null) {
+                closeable.close();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static DisplayMetrics getScreenMetrics(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(dm);
+        return dm;
+    }
+
+    public static void executeInThread(Runnable runnable) {
+        new Thread(runnable).start();
+    }
+
+}

+ 9 - 0
书本附源码/艺术探索/Chapter_12/.classpath

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>

+ 33 - 0
书本附源码/艺术探索/Chapter_12/.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>Chapter_12</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 33 - 0
书本附源码/艺术探索/Chapter_12/AndroidManifest.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.ryg.chapter_12"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="19" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher_1"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:hardwareAccelerated="true"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>

BIN
书本附源码/艺术探索/Chapter_12/ic_launcher-web.png


+ 20 - 0
书本附源码/艺术探索/Chapter_12/proguard-project.txt

@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 14 - 0
书本附源码/艺术探索/Chapter_12/project.properties

@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19

+ 14 - 0
书本附源码/艺术探索/Chapter_12/res/anim/translate.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:fillAfter="true"
+    android:zAdjustment="normal" >
+
+    <translate
+        android:duration="100"
+        android:fromXDelta="0"
+        android:fromYDelta="0"
+        android:interpolator="@android:anim/linear_interpolator"
+        android:toXDelta="100"
+        android:toYDelta="100" />
+
+</set>

BIN
书本附源码/艺术探索/Chapter_12/res/drawable-hdpi/ic_launcher_1.png


BIN
书本附源码/艺术探索/Chapter_12/res/drawable-mdpi/ic_launcher_1.png


BIN
书本附源码/艺术探索/Chapter_12/res/drawable-xhdpi/ic_launcher_1.png


BIN
书本附源码/艺术探索/Chapter_12/res/drawable-xhdpi/image_default.png


BIN
书本附源码/艺术探索/Chapter_12/res/drawable-xxhdpi/ic_launcher_1.png


+ 25 - 0
书本附源码/艺术探索/Chapter_12/res/drawable/edit.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item>
+        <shape android:shape="rectangle" >
+            <solid android:color="#0ac39e" />
+        </shape>
+    </item>
+
+    <item android:bottom="6dp">
+        <shape android:shape="rectangle" >
+            <solid android:color="#ffffff" />
+        </shape>
+    </item>
+
+    <item
+        android:bottom="1dp"
+        android:left="1dp"
+        android:right="1dp">
+        <shape android:shape="rectangle" >
+            <solid android:color="#ffffff" />
+        </shape>
+    </item>
+
+</layer-list>

+ 20 - 0
书本附源码/艺术探索/Chapter_12/res/layout/activity_main.xml

@@ -0,0 +1,20 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="5dp" >
+
+    <GridView
+        android:id="@+id/gridView1"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:horizontalSpacing="5dp"
+        android:verticalSpacing="5dp"
+        android:listSelector="@android:color/transparent"
+        android:numColumns="3"
+        android:stretchMode="columnWidth" >
+    </GridView>
+
+</LinearLayout>

+ 15 - 0
书本附源码/艺术探索/Chapter_12/res/layout/image_list_item.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="vertical" >
+
+    <com.ryg.chapter_12.ui.SquareImageView
+        android:id="@+id/image"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:scaleType="centerCrop"
+        android:src="@drawable/image_default" />
+
+</LinearLayout>

+ 8 - 0
书本附源码/艺术探索/Chapter_12/res/values/attrs.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <declare-styleable name="CircleView">
+        <attr name="circle_color" format="color" />
+    </declare-styleable>
+
+</resources>

+ 7 - 0
书本附源码/艺术探索/Chapter_12/res/values/colors.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <color name="reveal_color">#1b000000</color>
+    <color name="light_green">#0ac39e</color>
+    
+</resources>

+ 6 - 0
书本附源码/艺术探索/Chapter_12/res/values/ids.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <item name="imageloader_uri" type="id"/>
+
+</resources>

+ 8 - 0
书本附源码/艺术探索/Chapter_12/res/values/strings.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Chapter_12</string>
+    <string name="hello_world">Hello world!</string>
+    <string name="title_activity_main">MainActivity</string>
+
+</resources>

+ 40 - 0
书本附源码/艺术探索/Chapter_12/res/values/styles.xml

@@ -0,0 +1,40 @@
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.





+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <!-- Common button styles -->
+    <style name="AppTheme.Button">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">44dp</item>
+        <item name="android:textSize">18dp</item>
+    </style>
+
+    <style name="AppTheme.Button.Green">
+        <item name="android:background">#0ac39e</item>
+        <item name="android:textColor">#ffffff</item>
+        <item name="android:layout_marginTop">5dp</item>
+        <item name="android:layout_marginBottom">5dp</item>
+    </style>
+
+    <style name="AppTheme.Button.Highlight">
+        <item name="android:background">#4185f2</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+
+
+</resources>

+ 191 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/MainActivity.java

@@ -0,0 +1,191 @@
+package com.ryg.chapter_12;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ryg.chapter_12.R;
+import com.ryg.chapter_12.loader.ImageLoader;
+import com.ryg.chapter_12.utils.MyUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+
+public class MainActivity extends Activity implements OnScrollListener {
+    private static final String TAG = "MainActivity";
+
+    private List<String> mUrList = new ArrayList<String>();
+    ImageLoader mImageLoader;
+    private GridView mImageGridView;
+    private BaseAdapter mImageAdapter;
+
+    private boolean mIsGridViewIdle = true;
+    private int mImageWidth = 0;
+    private boolean mIsWifi = false;
+    private boolean mCanGetBitmapFromNetWork = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        initData();
+        initView();
+        mImageLoader = ImageLoader.build(MainActivity.this);
+    }
+
+    private void initData() {
+        String[] imageUrls = {
+                "http://b.hiphotos.baidu.com/zhidao/pic/item/a6efce1b9d16fdfafee0cfb5b68f8c5495ee7bd8.jpg",
+                "http://pic47.nipic.com/20140830/7487939_180041822000_2.jpg",
+                "http://pic41.nipic.com/20140518/4135003_102912523000_2.jpg",
+                "http://img2.imgtn.bdimg.com/it/u=1133260524,1171054226&fm=21&gp=0.jpg",
+                "http://h.hiphotos.baidu.com/image/pic/item/3b87e950352ac65c0f1f6e9efff2b21192138ac0.jpg",
+                "http://pic42.nipic.com/20140618/9448607_210533564001_2.jpg",
+                "http://pic10.nipic.com/20101027/3578782_201643041706_2.jpg",
+                "http://picview01.baomihua.com/photos/20120805/m_14_634797817549375000_37810757.jpg",
+                "http://img2.3lian.com/2014/c7/51/d/26.jpg",
+                "http://img3.3lian.com/2013/c1/34/d/93.jpg",
+                "http://b.zol-img.com.cn/desk/bizhi/image/3/960x600/1375841395686.jpg",
+                "http://picview01.baomihua.com/photos/20120917/m_14_634834710114218750_41852580.jpg",
+                "http://cdn.duitang.com/uploads/item/201311/03/20131103171224_rr2aL.jpeg",
+                "http://imgrt.pconline.com.cn/images/upload/upc/tx/wallpaper/1210/17/c1/spcgroup/14468225_1350443478079_1680x1050.jpg",
+                "http://pic41.nipic.com/20140518/4135003_102025858000_2.jpg",
+                "http://www.1tong.com/uploads/wallpaper/landscapes/200-4-730x456.jpg",
+                "http://pic.58pic.com/58pic/13/00/22/32M58PICV6U.jpg",
+                "http://picview01.baomihua.com/photos/20120629/m_14_634765948339062500_11778706.jpg",
+                "http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=429e7b1b92ef76c6d087f32fa826d1cc/7acb0a46f21fbe09cc206a2e69600c338744ad8a.jpg",
+                "http://pica.nipic.com/2007-12-21/2007122115114908_2.jpg",
+                "http://cdn.duitang.com/uploads/item/201405/13/20140513212305_XcKLG.jpeg",
+                "http://photo.loveyd.com/uploads/allimg/080618/1110324.jpg",
+                "http://img4.duitang.com/uploads/item/201404/17/20140417105820_GuEHe.thumb.700_0.jpeg",
+                "http://cdn.duitang.com/uploads/item/201204/21/20120421155228_i52eX.thumb.600_0.jpeg",
+                "http://img4.duitang.com/uploads/item/201404/17/20140417105856_LTayu.thumb.700_0.jpeg",
+                "http://img04.tooopen.com/images/20130723/tooopen_20530699.jpg",
+                "http://www.qjis.com/uploads/allimg/120612/1131352Y2-16.jpg",
+                "http://pic.dbw.cn/0/01/33/59/1335968_847719.jpg",
+                "http://a.hiphotos.baidu.com/image/pic/item/a8773912b31bb051a862339c337adab44bede0c4.jpg",
+                "http://h.hiphotos.baidu.com/image/pic/item/f11f3a292df5e0feeea8a30f5e6034a85edf720f.jpg",
+                "http://img0.pconline.com.cn/pconline/bizi/desktop/1412/ER2.jpg",
+                "http://pic.58pic.com/58pic/11/25/04/91v58PIC6Xy.jpg",
+                "http://img3.3lian.com/2013/c2/32/d/101.jpg",
+                "http://pic25.nipic.com/20121210/7447430_172514301000_2.jpg",
+                "http://img02.tooopen.com/images/20140320/sy_57121781945.jpg",
+                "http://www.renyugang.cn/emlog/content/plugins/kl_album/upload/201004/852706aad6df6cd839f1211c358f2812201004120651068641.jpg"
+        };
+        for (String url : imageUrls) {
+            mUrList.add(url);
+        }
+        int screenWidth = MyUtils.getScreenMetrics(this).widthPixels;
+        int space = (int)MyUtils.dp2px(this, 20f);
+        mImageWidth = (screenWidth - space) / 3;
+        mIsWifi = MyUtils.isWifi(this);
+        if (mIsWifi) {
+            mCanGetBitmapFromNetWork = true;
+        }
+    }
+
+    private void initView() {
+        mImageGridView = (GridView) findViewById(R.id.gridView1);
+        mImageAdapter = new ImageAdapter(this);
+        mImageGridView.setAdapter(mImageAdapter);
+        mImageGridView.setOnScrollListener(this);
+
+        if (!mIsWifi) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setMessage("初次使用会从网络下载大概5MB的图片,确认要下载吗?");
+            builder.setTitle("注意");
+            builder.setPositiveButton("是", new OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    mCanGetBitmapFromNetWork = true;
+                    mImageAdapter.notifyDataSetChanged();
+                }
+            });
+            builder.setNegativeButton("否", null);
+            builder.show();
+        }
+    }
+
+    private class ImageAdapter extends BaseAdapter {
+        private LayoutInflater mInflater;
+        private Drawable mDefaultBitmapDrawable;
+
+        private ImageAdapter(Context context) {
+            mInflater = LayoutInflater.from(context);
+            mDefaultBitmapDrawable = context.getResources().getDrawable(R.drawable.image_default);
+        }
+
+        @Override
+        public int getCount() {
+            return mUrList.size();
+        }
+
+        @Override
+        public String getItem(int position) {
+            return mUrList.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            ViewHolder holder = null;
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.image_list_item,parent, false);
+                holder = new ViewHolder();
+                holder.imageView = (ImageView) convertView.findViewById(R.id.image);
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+            ImageView imageView = holder.imageView;
+            final String tag = (String)imageView.getTag();
+            final String uri = getItem(position);
+            if (!uri.equals(tag)) {
+                imageView.setImageDrawable(mDefaultBitmapDrawable);
+            }
+            if (mIsGridViewIdle && mCanGetBitmapFromNetWork) {
+                imageView.setTag(uri);
+                mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);
+            }
+            return convertView;
+        }
+
+    }
+
+    private static class ViewHolder {
+        public ImageView imageView;
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+        if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
+            mIsGridViewIdle = true;
+            mImageAdapter.notifyDataSetChanged();
+        } else {
+            mIsGridViewIdle = false;
+        }
+    }
+
+    @Override
+    public void onScroll(AbsListView view, int firstVisibleItem,
+            int visibleItemCount, int totalItemCount) {
+        // ignored
+        
+    }
+}

+ 953 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/loader/DiskLruCache.java

@@ -0,0 +1,953 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ryg.chapter_12.loader;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ ******************************************************************************
+ * Taken from the JB source code, can be found in:
+ * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
+ * or direct link:
+ * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
+ ******************************************************************************
+ *
+ * A cache that uses a bounded amount of space on a filesystem. Each cache
+ * entry has a string key and a fixed number of values. Values are byte
+ * sequences, accessible as streams or files. Each value must be between {@code
+ * 0} and {@code Integer.MAX_VALUE} bytes in length.
+ *
+ * <p>The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * <p>This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * <p>Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * <ul>
+ *     <li>When an entry is being <strong>created</strong> it is necessary to
+ *         supply a full set of values; the empty value should be used as a
+ *         placeholder if necessary.
+ *     <li>When an entry is being <strong>edited</strong>, it is not necessary
+ *         to supply data for every value; values default to their previous
+ *         value.
+ * </ul>
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * <p>This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+    static final String JOURNAL_FILE = "journal";
+    static final String JOURNAL_FILE_TMP = "journal.tmp";
+    static final String MAGIC = "libcore.io.DiskLruCache";
+    static final String VERSION_1 = "1";
+    static final long ANY_SEQUENCE_NUMBER = -1;
+    private static final String CLEAN = "CLEAN";
+    private static final String DIRTY = "DIRTY";
+    private static final String REMOVE = "REMOVE";
+    private static final String READ = "READ";
+
+    private static final Charset UTF_8 = Charset.forName("UTF-8");
+    private static final int IO_BUFFER_SIZE = 8 * 1024;
+
+    /*
+     * This cache uses a journal file named "journal". A typical journal file
+     * looks like this:
+     *     libcore.io.DiskLruCache
+     *     1
+     *     100
+     *     2
+     *
+     *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+     *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
+     *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+     *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
+     *     DIRTY 1ab96a171faeeee38496d8b330771a7a
+     *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+     *     READ 335c4c6028171cfddfbaae1a9c313c52
+     *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+     *
+     * The first five lines of the journal form its header. They are the
+     * constant string "libcore.io.DiskLruCache", the disk cache's version,
+     * the application's version, the value count, and a blank line.
+     *
+     * Each of the subsequent lines in the file is a record of the state of a
+     * cache entry. Each line contains space-separated values: a state, a key,
+     * and optional state-specific values.
+     *   o DIRTY lines track that an entry is actively being created or updated.
+     *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
+     *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+     *     temporary files may need to be deleted.
+     *   o CLEAN lines track a cache entry that has been successfully published
+     *     and may be read. A publish line is followed by the lengths of each of
+     *     its values.
+     *   o READ lines track accesses for LRU.
+     *   o REMOVE lines track entries that have been deleted.
+     *
+     * The journal file is appended to as cache operations occur. The journal may
+     * occasionally be compacted by dropping redundant lines. A temporary file named
+     * "journal.tmp" will be used during compaction; that file should be deleted if
+     * it exists when the cache is opened.
+     */
+
+    private final File directory;
+    private final File journalFile;
+    private final File journalFileTmp;
+    private final int appVersion;
+    private final long maxSize;
+    private final int valueCount;
+    private long size = 0;
+    private Writer journalWriter;
+    private final LinkedHashMap<String, Entry> lruEntries
+            = new LinkedHashMap<String, Entry>(0, 0.75f, true);
+    private int redundantOpCount;
+
+    /**
+     * To differentiate between old and current snapshots, each entry is given
+     * a sequence number each time an edit is committed. A snapshot is stale if
+     * its sequence number is not equal to its entry's sequence number.
+     */
+    private long nextSequenceNumber = 0;
+
+    /* From java.util.Arrays */
+    @SuppressWarnings("unchecked")
+    private static <T> T[] copyOfRange(T[] original, int start, int end) {
+        final int originalLength = original.length; // For exception priority compatibility.
+        if (start > end) {
+            throw new IllegalArgumentException();
+        }
+        if (start < 0 || start > originalLength) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        final int resultLength = end - start;
+        final int copyLength = Math.min(resultLength, originalLength - start);
+        final T[] result = (T[]) Array
+                .newInstance(original.getClass().getComponentType(), resultLength);
+        System.arraycopy(original, start, result, 0, copyLength);
+        return result;
+    }
+
+    /**
+     * Returns the remainder of 'reader' as a string, closing it when done.
+     */
+    public static String readFully(Reader reader) throws IOException {
+        try {
+            StringWriter writer = new StringWriter();
+            char[] buffer = new char[1024];
+            int count;
+            while ((count = reader.read(buffer)) != -1) {
+                writer.write(buffer, 0, count);
+            }
+            return writer.toString();
+        } finally {
+            reader.close();
+        }
+    }
+
+    /**
+     * Returns the ASCII characters up to but not including the next "\r\n", or
+     * "\n".
+     *
+     * @throws java.io.EOFException if the stream is exhausted before the next newline
+     *     character.
+     */
+    public static String readAsciiLine(InputStream in) throws IOException {
+        // TODO: support UTF-8 here instead
+
+        StringBuilder result = new StringBuilder(80);
+        while (true) {
+            int c = in.read();
+            if (c == -1) {
+                throw new EOFException();
+            } else if (c == '\n') {
+                break;
+            }
+
+            result.append((char) c);
+        }
+        int length = result.length();
+        if (length > 0 && result.charAt(length - 1) == '\r') {
+            result.setLength(length - 1);
+        }
+        return result.toString();
+    }
+
+    /**
+     * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
+     */
+    public static void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    /**
+     * Recursively delete everything in {@code dir}.
+     */
+    // TODO: this should specify paths as Strings rather than as Files
+    public static void deleteContents(File dir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files == null) {
+            throw new IllegalArgumentException("not a directory: " + dir);
+        }
+        for (File file : files) {
+            if (file.isDirectory()) {
+                deleteContents(file);
+            }
+            if (!file.delete()) {
+                throw new IOException("failed to delete file: " + file);
+            }
+        }
+    }
+
+    /** This cache uses a single background thread to evict entries. */
+    private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
+            60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+    private final Callable<Void> cleanupCallable = new Callable<Void>() {
+        @Override public Void call() throws Exception {
+            synchronized (DiskLruCache.this) {
+                if (journalWriter == null) {
+                    return null; // closed
+                }
+                trimToSize();
+                if (journalRebuildRequired()) {
+                    rebuildJournal();
+                    redundantOpCount = 0;
+                }
+            }
+            return null;
+        }
+    };
+
+    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
+        this.directory = directory;
+        this.appVersion = appVersion;
+        this.journalFile = new File(directory, JOURNAL_FILE);
+        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
+        this.valueCount = valueCount;
+        this.maxSize = maxSize;
+    }
+
+    /**
+     * Opens the cache in {@code directory}, creating a cache if none exists
+     * there.
+     *
+     * @param directory a writable directory
+     * @param appVersion
+     * @param valueCount the number of values per cache entry. Must be positive.
+     * @param maxSize the maximum number of bytes this cache should use to store
+     * @throws java.io.IOException if reading or writing the cache directory fails
+     */
+    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+            throws IOException {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+        if (valueCount <= 0) {
+            throw new IllegalArgumentException("valueCount <= 0");
+        }
+
+        // prefer to pick up where we left off
+        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+        if (cache.journalFile.exists()) {
+            try {
+                cache.readJournal();
+                cache.processJournal();
+                cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
+                        IO_BUFFER_SIZE);
+                return cache;
+            } catch (IOException journalIsCorrupt) {
+//                System.logW("DiskLruCache " + directory + " is corrupt: "
+//                        + journalIsCorrupt.getMessage() + ", removing");
+                cache.delete();
+            }
+        }
+
+        // create a new empty cache
+        directory.mkdirs();
+        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+        cache.rebuildJournal();
+        return cache;
+    }
+
+    private void readJournal() throws IOException {
+        InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
+        try {
+            String magic = readAsciiLine(in);
+            String version = readAsciiLine(in);
+            String appVersionString = readAsciiLine(in);
+            String valueCountString = readAsciiLine(in);
+            String blank = readAsciiLine(in);
+            if (!MAGIC.equals(magic)
+                    || !VERSION_1.equals(version)
+                    || !Integer.toString(appVersion).equals(appVersionString)
+                    || !Integer.toString(valueCount).equals(valueCountString)
+                    || !"".equals(blank)) {
+                throw new IOException("unexpected journal header: ["
+                        + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
+            }
+
+            while (true) {
+                try {
+                    readJournalLine(readAsciiLine(in));
+                } catch (EOFException endOfJournal) {
+                    break;
+                }
+            }
+        } finally {
+            closeQuietly(in);
+        }
+    }
+
+    private void readJournalLine(String line) throws IOException {
+        String[] parts = line.split(" ");
+        if (parts.length < 2) {
+            throw new IOException("unexpected journal line: " + line);
+        }
+
+        String key = parts[1];
+        if (parts[0].equals(REMOVE) && parts.length == 2) {
+            lruEntries.remove(key);
+            return;
+        }
+
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            entry = new Entry(key);
+            lruEntries.put(key, entry);
+        }
+
+        if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
+            entry.readable = true;
+            entry.currentEditor = null;
+            entry.setLengths(copyOfRange(parts, 2, parts.length));
+        } else if (parts[0].equals(DIRTY) && parts.length == 2) {
+            entry.currentEditor = new Editor(entry);
+        } else if (parts[0].equals(READ) && parts.length == 2) {
+            // this work was already done by calling lruEntries.get()
+        } else {
+            throw new IOException("unexpected journal line: " + line);
+        }
+    }
+
+    /**
+     * Computes the initial size and collects garbage as a part of opening the
+     * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+     */
+    private void processJournal() throws IOException {
+        deleteIfExists(journalFileTmp);
+        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
+            Entry entry = i.next();
+            if (entry.currentEditor == null) {
+                for (int t = 0; t < valueCount; t++) {
+                    size += entry.lengths[t];
+                }
+            } else {
+                entry.currentEditor = null;
+                for (int t = 0; t < valueCount; t++) {
+                    deleteIfExists(entry.getCleanFile(t));
+                    deleteIfExists(entry.getDirtyFile(t));
+                }
+                i.remove();
+            }
+        }
+    }
+
+    /**
+     * Creates a new journal that omits redundant information. This replaces the
+     * current journal if it exists.
+     */
+    private synchronized void rebuildJournal() throws IOException {
+        if (journalWriter != null) {
+            journalWriter.close();
+        }
+
+        Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
+        writer.write(MAGIC);
+        writer.write("\n");
+        writer.write(VERSION_1);
+        writer.write("\n");
+        writer.write(Integer.toString(appVersion));
+        writer.write("\n");
+        writer.write(Integer.toString(valueCount));
+        writer.write("\n");
+        writer.write("\n");
+
+        for (Entry entry : lruEntries.values()) {
+            if (entry.currentEditor != null) {
+                writer.write(DIRTY + ' ' + entry.key + '\n');
+            } else {
+                writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+            }
+        }
+
+        writer.close();
+        journalFileTmp.renameTo(journalFile);
+        journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
+    }
+
+    private static void deleteIfExists(File file) throws IOException {
+//        try {
+//            Libcore.os.remove(file.getPath());
+//        } catch (ErrnoException errnoException) {
+//            if (errnoException.errno != OsConstants.ENOENT) {
+//                throw errnoException.rethrowAsIOException();
+//            }
+//        }
+        if (file.exists() && !file.delete()) {
+            throw new IOException();
+        }
+    }
+
+    /**
+     * Returns a snapshot of the entry named {@code key}, or null if it doesn't
+     * exist is not currently readable. If a value is returned, it is moved to
+     * the head of the LRU queue.
+     */
+    public synchronized Snapshot get(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null) {
+            return null;
+        }
+
+        if (!entry.readable) {
+            return null;
+        }
+
+        /*
+         * Open all streams eagerly to guarantee that we see a single published
+         * snapshot. If we opened streams lazily then the streams could come
+         * from different edits.
+         */
+        InputStream[] ins = new InputStream[valueCount];
+        try {
+            for (int i = 0; i < valueCount; i++) {
+                ins[i] = new FileInputStream(entry.getCleanFile(i));
+            }
+        } catch (FileNotFoundException e) {
+            // a file must have been deleted manually!
+            return null;
+        }
+
+        redundantOpCount++;
+        journalWriter.append(READ + ' ' + key + '\n');
+        if (journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+
+        return new Snapshot(key, entry.sequenceNumber, ins);
+    }
+
+    /**
+     * Returns an editor for the entry named {@code key}, or null if another
+     * edit is in progress.
+     */
+    public Editor edit(String key) throws IOException {
+        return edit(key, ANY_SEQUENCE_NUMBER);
+    }
+
+    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
+                && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
+            return null; // snapshot is stale
+        }
+        if (entry == null) {
+            entry = new Entry(key);
+            lruEntries.put(key, entry);
+        } else if (entry.currentEditor != null) {
+            return null; // another edit is in progress
+        }
+
+        Editor editor = new Editor(entry);
+        entry.currentEditor = editor;
+
+        // flush the journal before creating files to prevent file leaks
+        journalWriter.write(DIRTY + ' ' + key + '\n');
+        journalWriter.flush();
+        return editor;
+    }
+
+    /**
+     * Returns the directory where this cache stores its data.
+     */
+    public File getDirectory() {
+        return directory;
+    }
+
+    /**
+     * Returns the maximum number of bytes that this cache should use to store
+     * its data.
+     */
+    public long maxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Returns the number of bytes currently being used to store the values in
+     * this cache. This may be greater than the max size if a background
+     * deletion is pending.
+     */
+    public synchronized long size() {
+        return size;
+    }
+
+    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
+        Entry entry = editor.entry;
+        if (entry.currentEditor != editor) {
+            throw new IllegalStateException();
+        }
+
+        // if this edit is creating the entry for the first time, every index must have a value
+        if (success && !entry.readable) {
+            for (int i = 0; i < valueCount; i++) {
+                if (!entry.getDirtyFile(i).exists()) {
+                    editor.abort();
+                    throw new IllegalStateException("edit didn't create file " + i);
+                }
+            }
+        }
+
+        for (int i = 0; i < valueCount; i++) {
+            File dirty = entry.getDirtyFile(i);
+            if (success) {
+                if (dirty.exists()) {
+                    File clean = entry.getCleanFile(i);
+                    dirty.renameTo(clean);
+                    long oldLength = entry.lengths[i];
+                    long newLength = clean.length();
+                    entry.lengths[i] = newLength;
+                    size = size - oldLength + newLength;
+                }
+            } else {
+                deleteIfExists(dirty);
+            }
+        }
+
+        redundantOpCount++;
+        entry.currentEditor = null;
+        if (entry.readable | success) {
+            entry.readable = true;
+            journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
+            if (success) {
+                entry.sequenceNumber = nextSequenceNumber++;
+            }
+        } else {
+            lruEntries.remove(entry.key);
+            journalWriter.write(REMOVE + ' ' + entry.key + '\n');
+        }
+
+        if (size > maxSize || journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+    }
+
+    /**
+     * We only rebuild the journal when it will halve the size of the journal
+     * and eliminate at least 2000 ops.
+     */
+    private boolean journalRebuildRequired() {
+        final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
+        return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
+                && redundantOpCount >= lruEntries.size();
+    }
+
+    /**
+     * Drops the entry for {@code key} if it exists and can be removed. Entries
+     * actively being edited cannot be removed.
+     *
+     * @return true if an entry was removed.
+     */
+    public synchronized boolean remove(String key) throws IOException {
+        checkNotClosed();
+        validateKey(key);
+        Entry entry = lruEntries.get(key);
+        if (entry == null || entry.currentEditor != null) {
+            return false;
+        }
+
+        for (int i = 0; i < valueCount; i++) {
+            File file = entry.getCleanFile(i);
+            if (!file.delete()) {
+                throw new IOException("failed to delete " + file);
+            }
+            size -= entry.lengths[i];
+            entry.lengths[i] = 0;
+        }
+
+        redundantOpCount++;
+        journalWriter.append(REMOVE + ' ' + key + '\n');
+        lruEntries.remove(key);
+
+        if (journalRebuildRequired()) {
+            executorService.submit(cleanupCallable);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if this cache has been closed.
+     */
+    public boolean isClosed() {
+        return journalWriter == null;
+    }
+
+    private void checkNotClosed() {
+        if (journalWriter == null) {
+            throw new IllegalStateException("cache is closed");
+        }
+    }
+
+    /**
+     * Force buffered operations to the filesystem.
+     */
+    public synchronized void flush() throws IOException {
+        checkNotClosed();
+        trimToSize();
+        journalWriter.flush();
+    }
+
+    /**
+     * Closes this cache. Stored values will remain on the filesystem.
+     */
+    public synchronized void close() throws IOException {
+        if (journalWriter == null) {
+            return; // already closed
+        }
+        for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
+            if (entry.currentEditor != null) {
+                entry.currentEditor.abort();
+            }
+        }
+        trimToSize();
+        journalWriter.close();
+        journalWriter = null;
+    }
+
+    private void trimToSize() throws IOException {
+        while (size > maxSize) {
+//            Map.Entry<String, Entry> toEvict = lruEntries.eldest();
+            final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
+            remove(toEvict.getKey());
+        }
+    }
+
+    /**
+     * Closes the cache and deletes all of its stored values. This will delete
+     * all files in the cache directory including files that weren't created by
+     * the cache.
+     */
+    public void delete() throws IOException {
+        close();
+        deleteContents(directory);
+    }
+
+    private void validateKey(String key) {
+        if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
+            throw new IllegalArgumentException(
+                    "keys must not contain spaces or newlines: \"" + key + "\"");
+        }
+    }
+
+    private static String inputStreamToString(InputStream in) throws IOException {
+        return readFully(new InputStreamReader(in, UTF_8));
+    }
+
+    /**
+     * A snapshot of the values for an entry.
+     */
+    public final class Snapshot implements Closeable {
+        private final String key;
+        private final long sequenceNumber;
+        private final InputStream[] ins;
+
+        private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
+            this.key = key;
+            this.sequenceNumber = sequenceNumber;
+            this.ins = ins;
+        }
+
+        /**
+         * Returns an editor for this snapshot's entry, or null if either the
+         * entry has changed since this snapshot was created or if another edit
+         * is in progress.
+         */
+        public Editor edit() throws IOException {
+            return DiskLruCache.this.edit(key, sequenceNumber);
+        }
+
+        /**
+         * Returns the unbuffered stream with the value for {@code index}.
+         */
+        public InputStream getInputStream(int index) {
+            return ins[index];
+        }
+
+        /**
+         * Returns the string value for {@code index}.
+         */
+        public String getString(int index) throws IOException {
+            return inputStreamToString(getInputStream(index));
+        }
+
+        @Override public void close() {
+            for (InputStream in : ins) {
+                closeQuietly(in);
+            }
+        }
+    }
+
+    /**
+     * Edits the values for an entry.
+     */
+    public final class Editor {
+        private final Entry entry;
+        private boolean hasErrors;
+
+        private Editor(Entry entry) {
+            this.entry = entry;
+        }
+
+        /**
+         * Returns an unbuffered input stream to read the last committed value,
+         * or null if no value has been committed.
+         */
+        public InputStream newInputStream(int index) throws IOException {
+            synchronized (DiskLruCache.this) {
+                if (entry.currentEditor != this) {
+                    throw new IllegalStateException();
+                }
+                if (!entry.readable) {
+                    return null;
+                }
+                return new FileInputStream(entry.getCleanFile(index));
+            }
+        }
+
+        /**
+         * Returns the last committed value as a string, or null if no value
+         * has been committed.
+         */
+        public String getString(int index) throws IOException {
+            InputStream in = newInputStream(index);
+            return in != null ? inputStreamToString(in) : null;
+        }
+
+        /**
+         * Returns a new unbuffered output stream to write the value at
+         * {@code index}. If the underlying output stream encounters errors
+         * when writing to the filesystem, this edit will be aborted when
+         * {@link #commit} is called. The returned output stream does not throw
+         * IOExceptions.
+         */
+        public OutputStream newOutputStream(int index) throws IOException {
+            synchronized (DiskLruCache.this) {
+                if (entry.currentEditor != this) {
+                    throw new IllegalStateException();
+                }
+                return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
+            }
+        }
+
+        /**
+         * Sets the value at {@code index} to {@code value}.
+         */
+        public void set(int index, String value) throws IOException {
+            Writer writer = null;
+            try {
+                writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
+                writer.write(value);
+            } finally {
+                closeQuietly(writer);
+            }
+        }
+
+        /**
+         * Commits this edit so it is visible to readers.  This releases the
+         * edit lock so another edit may be started on the same key.
+         */
+        public void commit() throws IOException {
+            if (hasErrors) {
+                completeEdit(this, false);
+                remove(entry.key); // the previous entry is stale
+            } else {
+                completeEdit(this, true);
+            }
+        }
+
+        /**
+         * Aborts this edit. This releases the edit lock so another edit may be
+         * started on the same key.
+         */
+        public void abort() throws IOException {
+            completeEdit(this, false);
+        }
+
+        private class FaultHidingOutputStream extends FilterOutputStream {
+            private FaultHidingOutputStream(OutputStream out) {
+                super(out);
+            }
+
+            @Override public void write(int oneByte) {
+                try {
+                    out.write(oneByte);
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override public void write(byte[] buffer, int offset, int length) {
+                try {
+                    out.write(buffer, offset, length);
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override public void close() {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+
+            @Override public void flush() {
+                try {
+                    out.flush();
+                } catch (IOException e) {
+                    hasErrors = true;
+                }
+            }
+        }
+    }
+
+    private final class Entry {
+        private final String key;
+
+        /** Lengths of this entry's files. */
+        private final long[] lengths;
+
+        /** True if this entry has ever been published */
+        private boolean readable;
+
+        /** The ongoing edit or null if this entry is not being edited. */
+        private Editor currentEditor;
+
+        /** The sequence number of the most recently committed edit to this entry. */
+        private long sequenceNumber;
+
+        private Entry(String key) {
+            this.key = key;
+            this.lengths = new long[valueCount];
+        }
+
+        public String getLengths() throws IOException {
+            StringBuilder result = new StringBuilder();
+            for (long size : lengths) {
+                result.append(' ').append(size);
+            }
+            return result.toString();
+        }
+
+        /**
+         * Set lengths using decimal numbers like "10123".
+         */
+        private void setLengths(String[] strings) throws IOException {
+            if (strings.length != valueCount) {
+                throw invalidLengths(strings);
+            }
+
+            try {
+                for (int i = 0; i < strings.length; i++) {
+                    lengths[i] = Long.parseLong(strings[i]);
+                }
+            } catch (NumberFormatException e) {
+                throw invalidLengths(strings);
+            }
+        }
+
+        private IOException invalidLengths(String[] strings) throws IOException {
+            throw new IOException("unexpected journal line: " + Arrays.toString(strings));
+        }
+
+        public File getCleanFile(int i) {
+            return new File(directory, key + "." + i);
+        }
+
+        public File getDirtyFile(int i) {
+            return new File(directory, key + "." + i + ".tmp");
+        }
+    }
+}

+ 363 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/loader/ImageLoader.java

@@ -0,0 +1,363 @@
+package com.ryg.chapter_12.loader;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.ryg.chapter_12.R;
+import com.ryg.chapter_12.utils.MyUtils;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.StatFs;
+import android.support.v4.util.LruCache;
+import android.util.Log;
+import android.widget.ImageView;
+
+public class ImageLoader {
+
+    private static final String TAG = "ImageLoader";
+
+    public static final int MESSAGE_POST_RESULT = 1;
+
+    private static final int CPU_COUNT = Runtime.getRuntime()
+            .availableProcessors();
+    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
+    private static final long KEEP_ALIVE = 10L;
+
+    private static final int TAG_KEY_URI = R.id.imageloader_uri;
+    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
+    private static final int IO_BUFFER_SIZE = 8 * 1024;
+    private static final int DISK_CACHE_INDEX = 0;
+    private boolean mIsDiskLruCacheCreated = false;
+
+    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+        private final AtomicInteger mCount = new AtomicInteger(1);
+
+        public Thread newThread(Runnable r) {
+            return new Thread(r, "ImageLoader#" + mCount.getAndIncrement());
+        }
+    };
+
+    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
+            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
+            KEEP_ALIVE, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<Runnable>(), sThreadFactory);
+    
+    private Handler mMainHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            LoaderResult result = (LoaderResult) msg.obj;
+            ImageView imageView = result.imageView;
+            String uri = (String) imageView.getTag(TAG_KEY_URI);
+            if (uri.equals(result.uri)) {
+                imageView.setImageBitmap(result.bitmap);
+            } else {
+                Log.w(TAG, "set image bitmap,but url has changed, ignored!");
+            }
+        };
+    };
+
+    private Context mContext;
+    private ImageResizer mImageResizer = new ImageResizer();
+    private LruCache<String, Bitmap> mMemoryCache;
+    private DiskLruCache mDiskLruCache;
+
+    private ImageLoader(Context context) {
+        mContext = context.getApplicationContext();
+        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+        int cacheSize = maxMemory / 8;
+        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
+            @Override
+            protected int sizeOf(String key, Bitmap bitmap) {
+                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
+            }
+        };
+        File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
+        if (!diskCacheDir.exists()) {
+            diskCacheDir.mkdirs();
+        }
+        if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
+            try {
+                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1,
+                        DISK_CACHE_SIZE);
+                mIsDiskLruCacheCreated = true;
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * build a new instance of ImageLoader
+     * @param context
+     * @return a new instance of ImageLoader
+     */
+    public static ImageLoader build(Context context) {
+        return new ImageLoader(context);
+    }
+
+    private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
+        if (getBitmapFromMemCache(key) == null) {
+            mMemoryCache.put(key, bitmap);
+        }
+    }
+
+    private Bitmap getBitmapFromMemCache(String key) {
+        return mMemoryCache.get(key);
+    }
+
+    /**
+     * load bitmap from memory cache or disk cache or network async, then bind imageView and bitmap.
+     * NOTE THAT: should run in UI Thread
+     * @param uri http url
+     * @param imageView bitmap's bind object
+     */
+    public void bindBitmap(final String uri, final ImageView imageView) {
+        bindBitmap(uri, imageView, 0, 0);
+    }
+
+    public void bindBitmap(final String uri, final ImageView imageView,
+            final int reqWidth, final int reqHeight) {
+        imageView.setTag(TAG_KEY_URI, uri);
+        Bitmap bitmap = loadBitmapFromMemCache(uri);
+        if (bitmap != null) {
+            imageView.setImageBitmap(bitmap);
+            return;
+        }
+
+        Runnable loadBitmapTask = new Runnable() {
+
+            @Override
+            public void run() {
+                Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight);
+                if (bitmap != null) {
+                    LoaderResult result = new LoaderResult(imageView, uri, bitmap);
+                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
+                }
+            }
+        };
+        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
+    }
+
+    /**
+     * load bitmap from memory cache or disk cache or network.
+     * @param uri http url
+     * @param reqWidth the width ImageView desired
+     * @param reqHeight the height ImageView desired
+     * @return bitmap, maybe null.
+     */
+    public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) {
+        Bitmap bitmap = loadBitmapFromMemCache(uri);
+        if (bitmap != null) {
+            Log.d(TAG, "loadBitmapFromMemCache,url:" + uri);
+            return bitmap;
+        }
+
+        try {
+            bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight);
+            if (bitmap != null) {
+                Log.d(TAG, "loadBitmapFromDisk,url:" + uri);
+                return bitmap;
+            }
+            bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight);
+            Log.d(TAG, "loadBitmapFromHttp,url:" + uri);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        if (bitmap == null && !mIsDiskLruCacheCreated) {
+            Log.w(TAG, "encounter error, DiskLruCache is not created.");
+            bitmap = downloadBitmapFromUrl(uri);
+        }
+
+        return bitmap;
+    }
+
+    private Bitmap loadBitmapFromMemCache(String url) {
+        final String key = hashKeyFormUrl(url);
+        Bitmap bitmap = getBitmapFromMemCache(key);
+        return bitmap;
+    }
+
+    private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight)
+            throws IOException {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new RuntimeException("can not visit network from UI Thread.");
+        }
+        if (mDiskLruCache == null) {
+            return null;
+        }
+        
+        String key = hashKeyFormUrl(url);
+        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
+        if (editor != null) {
+            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
+            if (downloadUrlToStream(url, outputStream)) {
+                editor.commit();
+            } else {
+                editor.abort();
+            }
+            mDiskLruCache.flush();
+        }
+        return loadBitmapFromDiskCache(url, reqWidth, reqHeight);
+    }
+
+    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth,
+            int reqHeight) throws IOException {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            Log.w(TAG, "load bitmap from UI Thread, it's not recommended!");
+        }
+        if (mDiskLruCache == null) {
+            return null;
+        }
+
+        Bitmap bitmap = null;
+        String key = hashKeyFormUrl(url);
+        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
+        if (snapShot != null) {
+            FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
+            FileDescriptor fileDescriptor = fileInputStream.getFD();
+            bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
+                    reqWidth, reqHeight);
+            if (bitmap != null) {
+                addBitmapToMemoryCache(key, bitmap);
+            }
+        }
+
+        return bitmap;
+    }
+
+    public boolean downloadUrlToStream(String urlString,
+            OutputStream outputStream) {
+        HttpURLConnection urlConnection = null;
+        BufferedOutputStream out = null;
+        BufferedInputStream in = null;
+
+        try {
+            final URL url = new URL(urlString);
+            urlConnection = (HttpURLConnection) url.openConnection();
+            in = new BufferedInputStream(urlConnection.getInputStream(),
+                    IO_BUFFER_SIZE);
+            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
+
+            int b;
+            while ((b = in.read()) != -1) {
+                out.write(b);
+            }
+            return true;
+        } catch (IOException e) {
+            Log.e(TAG, "downloadBitmap failed." + e);
+        } finally {
+            if (urlConnection != null) {
+                urlConnection.disconnect();
+            }
+            MyUtils.close(out);
+            MyUtils.close(in);
+        }
+        return false;
+    }
+
+    private Bitmap downloadBitmapFromUrl(String urlString) {
+        Bitmap bitmap = null;
+        HttpURLConnection urlConnection = null;
+        BufferedInputStream in = null;
+
+        try {
+            final URL url = new URL(urlString);
+            urlConnection = (HttpURLConnection) url.openConnection();
+            in = new BufferedInputStream(urlConnection.getInputStream(),
+                    IO_BUFFER_SIZE);
+            bitmap = BitmapFactory.decodeStream(in);
+        } catch (final IOException e) {
+            Log.e(TAG, "Error in downloadBitmap: " + e);
+        } finally {
+            if (urlConnection != null) {
+                urlConnection.disconnect();
+            }
+            MyUtils.close(in);
+        }
+        return bitmap;
+    }
+
+    private String hashKeyFormUrl(String url) {
+        String cacheKey;
+        try {
+            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
+            mDigest.update(url.getBytes());
+            cacheKey = bytesToHexString(mDigest.digest());
+        } catch (NoSuchAlgorithmException e) {
+            cacheKey = String.valueOf(url.hashCode());
+        }
+        return cacheKey;
+    }
+
+    private String bytesToHexString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < bytes.length; i++) {
+            String hex = Integer.toHexString(0xFF & bytes[i]);
+            if (hex.length() == 1) {
+                sb.append('0');
+            }
+            sb.append(hex);
+        }
+        return sb.toString();
+    }
+
+    public File getDiskCacheDir(Context context, String uniqueName) {
+        boolean externalStorageAvailable = Environment
+                .getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+        final String cachePath;
+        if (externalStorageAvailable) {
+            cachePath = context.getExternalCacheDir().getPath();
+        } else {
+            cachePath = context.getCacheDir().getPath();
+        }
+
+        return new File(cachePath + File.separator + uniqueName);
+    }
+
+    @TargetApi(VERSION_CODES.GINGERBREAD)
+    private long getUsableSpace(File path) {
+        if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
+            return path.getUsableSpace();
+        }
+        final StatFs stats = new StatFs(path.getPath());
+        return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
+    }
+
+    private static class LoaderResult {
+        public ImageView imageView;
+        public String uri;
+        public Bitmap bitmap;
+
+        public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) {
+            this.imageView = imageView;
+            this.uri = uri;
+            this.bitmap = bitmap;
+        }
+    }
+}

+ 74 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/loader/ImageResizer.java

@@ -0,0 +1,74 @@
+package com.ryg.chapter_12.loader;
+
+import java.io.FileDescriptor;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+
+public class ImageResizer {
+    private static final String TAG = "ImageResizer";
+
+    public ImageResizer() {
+    }
+
+    public Bitmap decodeSampledBitmapFromResource(Resources res,
+            int resId, int reqWidth, int reqHeight) {
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeResource(res, resId, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth,
+                reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeResource(res, resId, options);
+    }
+
+    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
+        // First decode with inJustDecodeBounds=true to check dimensions
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFileDescriptor(fd, null, options);
+
+        // Calculate inSampleSize
+        options.inSampleSize = calculateInSampleSize(options, reqWidth,
+                reqHeight);
+
+        // Decode bitmap with inSampleSize set
+        options.inJustDecodeBounds = false;
+        return BitmapFactory.decodeFileDescriptor(fd, null, options);
+    }
+
+    public int calculateInSampleSize(BitmapFactory.Options options,
+            int reqWidth, int reqHeight) {
+        if (reqWidth == 0 || reqHeight == 0) {
+            return 1;
+        }
+
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        Log.d(TAG, "origin, w= " + width + " h=" + height);
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+            final int halfHeight = height / 2;
+            final int halfWidth = width / 2;
+
+            // Calculate the largest inSampleSize value that is a power of 2 and
+            // keeps both
+            // height and width larger than the requested height and width.
+            while ((halfHeight / inSampleSize) >= reqHeight
+                    && (halfWidth / inSampleSize) >= reqWidth) {
+                inSampleSize *= 2;
+            }
+        }
+
+        Log.d(TAG, "sampleSize:" + inSampleSize);
+        return inSampleSize;
+    }
+}

+ 204 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/ui/RevealLayout.java

@@ -0,0 +1,204 @@
+package com.ryg.chapter_12.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+import com.ryg.chapter_12.R;
+
+/**
+ * 一个特殊的LinearLayout,任何放入内部的clickable元素都具有波纹效果,当它被点击的时候,
+ * 为了性能,尽量不要在内部放入复杂的元素
+ * note: long click listener is not supported current for fix compatible bug.
+ */
+public class RevealLayout extends LinearLayout implements Runnable {
+
+    private static final String TAG = "DxRevealLayout";
+    private static final boolean DEBUG = true;
+
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    private int mTargetWidth;
+    private int mTargetHeight;
+    private int mMinBetweenWidthAndHeight;
+    private int mMaxBetweenWidthAndHeight;
+    private int mMaxRevealRadius;
+    private int mRevealRadiusGap;
+    private int mRevealRadius = 0;
+    private float mCenterX;
+    private float mCenterY;
+    private int[] mLocationInScreen = new int[2];
+
+    private boolean mShouldDoAnimation = false;
+    private boolean mIsPressed = false;
+    private int INVALIDATE_DURATION = 40;
+
+    private View mTouchTarget;
+    private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable();
+
+    public RevealLayout(Context context) {
+        super(context);
+        init();
+    }
+
+    public RevealLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public RevealLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        setWillNotDraw(false);
+        mPaint.setColor(getResources().getColor(R.color.reveal_color));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        this.getLocationOnScreen(mLocationInScreen);
+    }
+
+    private void initParametersForChild(MotionEvent event, View view) {
+        mCenterX = event.getX() ;
+        mCenterY = event.getY() ;
+        mTargetWidth = view.getMeasuredWidth();
+        mTargetHeight = view.getMeasuredHeight();
+        mMinBetweenWidthAndHeight = Math.min(mTargetWidth, mTargetHeight);
+        mMaxBetweenWidthAndHeight = Math.max(mTargetWidth, mTargetHeight);
+        mRevealRadius = 0;
+        mShouldDoAnimation = true;
+        mIsPressed = true;
+        mRevealRadiusGap = mMinBetweenWidthAndHeight / 8;
+
+        int[] location = new int[2];
+        view.getLocationOnScreen(location);
+        int left = location[0] - mLocationInScreen[0];
+        int transformedCenterX = (int)mCenterX - left;
+        mMaxRevealRadius = Math.max(transformedCenterX, mTargetWidth - transformedCenterX);
+    }
+
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {
+            return;
+        }
+
+        if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {
+            mRevealRadius += mRevealRadiusGap * 4;
+        } else {
+            mRevealRadius += mRevealRadiusGap;
+        }
+        this.getLocationOnScreen(mLocationInScreen);
+        int[] location = new int[2];
+        mTouchTarget.getLocationOnScreen(location);
+        int left = location[0] - mLocationInScreen[0];
+        int top = location[1] - mLocationInScreen[1];
+        int right = left + mTouchTarget.getMeasuredWidth();
+        int bottom = top + mTouchTarget.getMeasuredHeight();
+
+        canvas.save();
+        canvas.clipRect(left, top, right, bottom);
+        canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);
+        canvas.restore();
+
+        if (mRevealRadius <= mMaxRevealRadius) {
+            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
+        } else if (!mIsPressed) {
+            mShouldDoAnimation = false;
+            postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        int x = (int) event.getRawX();
+        int y = (int) event.getRawY();
+        int action = event.getAction();
+        if (action == MotionEvent.ACTION_DOWN) {
+            View touchTarget = getTouchTarget(this, x, y);
+            if (touchTarget != null && touchTarget.isClickable() && touchTarget.isEnabled()) {
+                mTouchTarget = touchTarget;
+                initParametersForChild(event, touchTarget);
+                postInvalidateDelayed(INVALIDATE_DURATION);
+            }
+        } else if (action == MotionEvent.ACTION_UP) {
+            mIsPressed = false;
+            postInvalidateDelayed(INVALIDATE_DURATION);
+            mDispatchUpTouchEventRunnable.event = event;
+            postDelayed(mDispatchUpTouchEventRunnable, 200);
+            return true;
+        } else if (action == MotionEvent.ACTION_CANCEL) {
+            mIsPressed = false;
+            postInvalidateDelayed(INVALIDATE_DURATION);
+        }
+
+        return super.dispatchTouchEvent(event);
+    }
+
+    private View getTouchTarget(View view, int x, int y) {
+        View target = null;
+        ArrayList<View> TouchableViews = view.getTouchables();
+        for (View child : TouchableViews) {
+            if (isTouchPointInView(child, x, y)) {
+                target = child;
+                break;
+            }
+        }
+
+        return target;
+    }
+
+    private boolean isTouchPointInView(View view, int x, int y) {
+        int[] location = new int[2];
+        view.getLocationOnScreen(location);
+        int left = location[0];
+        int top = location[1];
+        int right = left + view.getMeasuredWidth();
+        int bottom = top + view.getMeasuredHeight();
+        if (view.isClickable() && y >= top && y <= bottom
+                && x >= left && x <= right) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean performClick() {
+        postDelayed(this, 400);
+        return true;
+    }
+
+    @Override
+    public void run() {
+        super.performClick();
+    }
+
+    private class DispatchUpTouchEventRunnable implements Runnable {
+        public MotionEvent event;
+
+        @Override
+        public void run() {
+            if (mTouchTarget == null || !mTouchTarget.isEnabled()) {
+                return;
+            }
+
+            if (isTouchPointInView(mTouchTarget, (int)event.getRawX(), (int)event.getRawY())) {
+                mTouchTarget.performClick();
+            }
+        }
+    };
+
+}

+ 25 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/ui/SquareImageView.java

@@ -0,0 +1,25 @@
+package com.ryg.chapter_12.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class SquareImageView extends ImageView {
+
+    public SquareImageView(Context context) {
+        super(context);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+    }
+}

+ 15 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/utils/MyConstants.java

@@ -0,0 +1,15 @@
+package com.ryg.chapter_12.utils;
+
+import android.os.Environment;
+
+public class MyConstants {
+    public static final String CHAPTER_2_PATH = Environment
+            .getExternalStorageDirectory().getPath()
+            + "/singwhatiwanna/chapter_2/";
+
+    public static final String CACHE_FILE_PATH = CHAPTER_2_PATH + "usercache";
+
+    public static final int MSG_FROM_CLIENT = 0;
+    public static final int MSG_FROM_SERVICE = 1;
+
+}

+ 71 - 0
书本附源码/艺术探索/Chapter_12/src/com/ryg/chapter_12/utils/MyUtils.java

@@ -0,0 +1,71 @@
+package com.ryg.chapter_12.utils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.WindowManager;
+
+public class MyUtils {
+
+    public static String getProcessName(Context cxt, int pid) {
+        ActivityManager am = (ActivityManager) cxt
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
+        if (runningApps == null) {
+            return null;
+        }
+        for (RunningAppProcessInfo procInfo : runningApps) {
+            if (procInfo.pid == pid) {
+                return procInfo.processName;
+            }
+        }
+        return null;
+    }
+
+    public static void close(Closeable closeable) {
+        try {
+            if (closeable != null) {
+                closeable.close();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static DisplayMetrics getScreenMetrics(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(dm);
+        return dm;
+    }
+
+    public static float dp2px(Context context, float dp) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
+                context.getResources().getDisplayMetrics());
+    }
+
+    public static boolean isWifi(Context context) {
+        ConnectivityManager connectivityManager = (ConnectivityManager) context
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
+        if (activeNetInfo != null
+                && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+            return true;
+        }
+        return false;
+    }
+
+    public static void executeInThread(Runnable runnable) {
+        new Thread(runnable).start();
+    }
+
+}

+ 9 - 0
书本附源码/艺术探索/Chapter_13/CrashTest/.classpath

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>

+ 33 - 0
书本附源码/艺术探索/Chapter_13/CrashTest/.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>CrashTest</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 55 - 0
书本附源码/艺术探索/Chapter_13/CrashTest/AndroidManifest.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.ryg.crashtest"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="19" />
+
+    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER" />
+    <uses-permission android:name="android.permission.BATTERY_STATS" />
+    <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
+    <uses-permission android:name="android.permission.GET_TASKS" />
+    <uses-permission android:name="android.permission.RESTART_PACKAGES" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_SMS" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
+    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
+
+    <application
+        android:name="com.ryg.crashtest.TestApp"
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+
+        <activity
+            android:name="com.ryg.crashtest.CrashActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>

BIN
书本附源码/艺术探索/Chapter_13/CrashTest/ic_launcher-web.png


+ 20 - 0
书本附源码/艺术探索/Chapter_13/CrashTest/proguard-project.txt

@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 14 - 0
书本附源码/艺术探索/Chapter_13/CrashTest/project.properties

@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19

BIN
书本附源码/艺术探索/Chapter_13/CrashTest/res/drawable-hdpi/ic_launcher.png


BIN
书本附源码/艺术探索/Chapter_13/CrashTest/res/drawable-mdpi/ic_launcher.png


+ 0 - 0
书本附源码/艺术探索/Chapter_13/CrashTest/res/drawable-xhdpi/ic_launcher.png


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.