├── .gitignore ├── AndroidApk签名.md ├── AndroidAycnTask源码解析.md ├── AndroidHeroes读书笔记.md ├── Android摄像头Camera.md ├── Android数据存储的五种种方式.md ├── Android消息处理机制.md ├── Android系统服务.md ├── Bitmap的加载和缓存.md ├── Effective Java .md ├── Fragment设计哲学.md ├── Gradle全解.md ├── GreenDao3.0学习笔记.md ├── Gson使用详细介绍.md ├── Intent的标准用法.md ├── JNI和NDK编程.md ├── Java笔记 ├── IO输入流.docx ├── JSP.docx ├── JavaWeb.docx ├── Java中的反射.docx ├── Java注解.md ├── XML.docx ├── ~$IO输入流.docx ├── ~$东Java笔记(五)集合框架.docx ├── 毕向东Java笔记(七)网络编程.docx ├── 毕向东Java笔记(五)集合框架.docx ├── 毕向东Java笔记(四)String类,包装类.docx ├── 毕向东Java笔记(二)异常.docx ├── 毕向东java笔记(一).docx └── 毕向东java笔记(三)多线程.docx ├── Java编程思想.md ├── Java面试复习.md ├── ListView解析.md ├── README.md ├── Realm学习笔记.md ├── RxJava使用场景小结.md ├── android studio 使用技巧.md ├── android-tips.md ├── dagger2.md ├── imgs └── preview.gif ├── res资源目录.md ├── 个人使用的AndroidStudio插件.md ├── 个人使用的开发软件.md ├── 你应该了解的CrashHandler用法.md ├── 值得阅读的Android优秀文章.md ├── 工具类 ├── ACache.java ├── Acache.md ├── AppUtils.java ├── ColorUtil.java ├── DateUtils.java ├── DensityUtils.java ├── DeviceUtils.java ├── FlowLayout.java ├── KeyBoardUtil.java ├── NetUtils.java ├── SDCardUtils.java ├── SPUtils.java ├── ShareUtils.java ├── ThreadManager.java ├── ToastUtils.java └── colors.xml ├── 开发艺术探索第三章View事件体系.md ├── 开发艺术探索第六章Drawable.md ├── 收集Android开源项目.md ├── 网络请求框架.md ├── 跳过Flash界面的正确姿势.md └── 问题总结.md /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /AndroidApk签名.md: -------------------------------------------------------------------------------- 1 | ## Android APK的数字签名的作用和意义? ## 2 | 3 | 1. 什么是数字签名? 4 | 5 | 数字签名就是为你的程序打上一种标记,来作为你自己的标识,当别人看到签名的时候会知道它是与你相关的 6 | 7 | 2. 为什么要数字签名? 8 | 9 | 最简单直接的回答: 系统要求的。 10 | 11 | Android系统要求每一个Android应用程序必须要经过数字签名才能够安装到系统中,也就是说如果一个Android应用程序没有经过数字签名,是没有办法安装到系统中的! 12 | Android通过数字签名来标识应用程序的作者和在应用程序之间建立信任关系,不是用来决定最终用户可以安装哪些应用程序。 13 | 这个数字签名由应用程序的作者完成,并不需要权威的数字证书签名机构认证,它只是用来让应用程序包自我认证的。 14 | 15 | 3. 数字证书的机制? 16 | 17 | Android使用Java的数字证书相关的机制来给apk加盖数字证书,要理解android的数字证书,需要先了解以下数字证书的概念和java的数字证书机制。 18 | 19 | 20 | 4. 程序使用相同的数字证书的好处 21 | 22 | (1)有利于程序升级 23 | 24 | 当新版程序和旧版程序的数字证书相同时,Android系统才会认为这两个程序是同一个程序的不同版本。如果新版程序和旧版程序的数字证书不相同,则Android系统认为他们是不同的程序,并产生冲突,会要求新程序更改包名。 25 | 26 | (2)有利于程序的模块化设计和开发。 27 | 28 | Android系统允许拥有同一个数字签名的程序运行在一个进程中,Android程序会将他们视为同一个程序。所以开发者可以将自己的程序分模块开发,而用户只需要在需要的时候下载适当的模块。 29 | 30 | (3)可以通过权限(permission)的方式在多个程序间共享数据和代码。 31 | 32 | Android提供了基于数字证书的权限赋予机制,应用程序可以和其他的程序共享概功能或者数据给那那些与自己拥有相同数字证书的程序。如果某个权限(permission)的protectionLevel是signature,则这个权限就只 33 | 34 | 能授予那些跟该权限所在的包拥有同一个数字证书的程序。 35 | 36 | 5. 在签名时,需要考虑数字证书的有效期: 37 | (1)数字证书的有效期要包含程序的预计生命周期,一旦数字证书失效,持有改数字证书的程序将不能正常升级。 38 | (2)如果多个程序使用同一个数字证书,则该数字证书的有效期要包含所有程序的预计生命周期。 39 | (3)Android Market强制要求所有应用程序数字证书的有效期要持续到2033年10月22日以后。 40 | 41 | 6. 数字证书的要点: 42 | Android数字证书包含以下几个要点: 43 | 44 | (1)所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序 45 | (2)Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证 46 | (3)如果要正式发布一个Android ,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用adt插件或者ant工具生成的调试证书来发布。 47 | (4)数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。 48 | (5)Android使用标准的java工具 Keytool and Jarsigner 来生成数字证书,并给应用程序包签名。 49 | 6)使用zipalign优化程序。 50 | 51 | 52 | 数字签名的两种模式 53 | 54 | 我们都知道Android系统不会安装运行任何一款未经数字签名的apk程序,无论是在模拟器上还是在实际的物理设备上。所以我们会有一个疑问,为何在日常开发过程中我没有进行任何签名的操作,程序都会在模拟器和真机上运行?下面我们来讲讲 55 | 56 | APK程序的两种模式: 调试模式(debug mode)和发布模式(release mode) 57 | 58 | 59 | 1. 调试模式(debug mode) : 在调试模式下, ADT会自动的使用debug密钥为应用程序签名,因此我们可以直接运行程序。 60 | 61 | debug密钥: 一个名为debug.keystore的文件 62 | 63 | 存放位置 : C:\Users\Xiaopeng\.android\debug.keystore Xiaopeng对应替换为自己操作系统的用户名 64 | 65 | 两个风险: 66 | debug签名的应用程序有这样两个风险: 67 | 68 | 1)debug签名的应用程序不能在Android Market上架销售,它会强制你使用自己的签名; 69 | 70 | 2)debug.keystore在不同的机器上所生成的可能都不一样,就意味着如果你换了机器进行apk版本升级,那么将会出现上面那种程序不能覆盖安装的问题。 71 | 不要小视这个问题,如果你开发的程序只有你自己使用,当然无所谓,卸载再安装就可以了。但要是你的软件有很多使用客户,这就是大问题了,就相当于软件不具备升级功能! 72 | 73 | 所以一定要有自己的数字证书来签名; 74 | 75 | 2. 发布模式(release mode) : 当要发布程序时,开发者就需要使用自己的数字证书给apk包签名 76 | 77 | 使用自己的数字证书给APK签名的两种方法: 78 | 79 | (1)通过DOS命令来对APK签名。 80 | (2)使用ADT Export Wizard进行签名 -------------------------------------------------------------------------------- /AndroidAycnTask源码解析.md: -------------------------------------------------------------------------------- 1 | 首先分析execate()方法,execate()方法又会调用executeOnExecutor()方法。它们的源码如下。 2 | 3 | ```java 4 | @MainThread 5 | public final AsyncTask execute(Params... params) { 6 | return executeOnExecutor(sDefaultExecutor, params); 7 | } 8 | 9 | @MainThread 10 | public final AsyncTask executeOnExecutor(Executor exec, 11 | Params... params) { 12 | if (mStatus != Status.PENDING) { 13 | switch (mStatus) { 14 | case RUNNING: 15 | throw new IllegalStateException("Cannot execute task:" 16 | + " the task is already running."); 17 | case FINISHED: 18 | throw new IllegalStateException("Cannot execute task:" 19 | + " the task has already been executed " 20 | + "(a task can be executed only once)"); 21 | } 22 | } 23 | mStatus = Status.RUNNING; 24 | //onPreExecute()方法最新执行! 25 | onPreExecute(); 26 | //mWorker记录传递参数。 27 | mWorker.mParams = params; 28 | //串行的线程池开始执行。参数mFuture充当一个Runnable,它内部有mWorker。 29 | //即mFuture可以理解为,封装了params的Runnble对象。 30 | exec.execute(mFuture); 31 | return this; 32 | } 33 | ``` 34 | 35 | execute()传递给executeOnExecutor()一个sDefaultExecutor参数,它其实是一个串行的线程池,一个进程中的所有的AsyncTask全部在这个串行的线程中排队执行。 36 | 37 | 下面分析这个串行的线程池的执行过程,它在AsyncTask中的具体实现如下。 38 | 39 | ```Java 40 | //串行的线程池。 41 | public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); 42 | private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; 43 | 44 | private static class SerialExecutor implements Executor { 45 | final ArrayDeque mTasks = new ArrayDeque(); 46 | Runnable mActive; 47 | public synchronized void execute(final Runnable r) { 48 | mTasks.offer(new Runnable() { 49 | public void run() { 50 | try { 51 | //mFuture的run()方法执行,它会调用mWork的call方法, 52 | //call方法最终会在线程池中执行。 53 | r.run(); 54 | } finally { 55 | //开始执行AsyncTask的任务。 56 | scheduleNext(); 57 | } 58 | } 59 | }); 60 | //如果这个时候没有正在活动的AsyncTask任务,那么scheduleNext会执行下一个任务。 61 | //直到所有任务执行完成。 62 | if (mActive == null) { 63 | scheduleNext(); 64 | } 65 | } 66 | protected synchronized void scheduleNext() { 67 | if ((mActive = mTasks.poll()) != null) { 68 | //执行任务的线程池。 69 | THREAD_POOL_EXECUTOR.execute(mActive); 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | mWork是AsyckTask中继承了Callable的抽象类WorkerRunnable的实例,内部用数据mParams数组记录了传递进来的参数。mFuture是系统为我们封装的带有Params的Runnable。上面说道,mFuture的run()方法执行,它会调用mWork的call方法,那么它最终会在线程池中执行。接下来看以下mWork的call方法,他在AsyncTask的构造方法中初始化。 76 | 77 | ```java 78 | private final WorkerRunnable mWorker; 79 | private final FutureTask mFuture; 80 | /** 81 | * Creates a new asynchronous task. This constructor must be invoked on the UI thread. 82 | */ 83 | public AsyncTask() { 84 | mWorker = new WorkerRunnable() { 85 | public Result call() throws Exception { 86 | // mTaskInvoked设置为(true),表示当前的任务已经执行。 87 | mTaskInvoked.set(true); 88 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 89 | //noinspection unchecked 90 | //call方法执行,doInBackground方法就会被调用。 91 | Result result = doInBackground(mParams); 92 | Binder.flushPendingCommands(); 93 | //将doInbackground的返回结构传递给postResult(); 94 | return postResult(result); 95 | } 96 | }; 97 | mFuture = new FutureTask(mWorker) { 98 | @Override 99 | protected void done() { 100 | try { 101 | postResultIfNotInvoked(get()); 102 | } catch (InterruptedException e) { 103 | android.util.Log.w(LOG_TAG, e); 104 | } catch (ExecutionException e) { 105 | throw new RuntimeException("An error occurred while executing doInBackground()", 106 | e.getCause()); 107 | } catch (CancellationException e) { 108 | postResultIfNotInvoked(null); 109 | } 110 | } 111 | }; 112 | } 113 | ``` 114 | 115 | 处理doInBackground返回的值postResult方法实现。 116 | 117 | ```java 118 | private Result postResult(Result result) { 119 | @SuppressWarnings("unchecked") 120 | Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, 121 | new AsyncTaskResult(this, result)); 122 | message.sendToTarget(); 123 | return result; 124 | } 125 | ``` 126 | 127 | 这里处理就是通过sHandler发送一个MESSAGE_POST_RESULT的消息。sHandler的定义如下所示。 128 | 129 | ```java 130 | private static class InternalHandler extends Handler { 131 | 132 | public InternalHandler() { 133 | super(Looper.getMainLooper()); 134 | } 135 | @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) 136 | @Override 137 | public void handleMessage(Message msg) { 138 | AsyncTaskResult result = (AsyncTaskResult) msg.obj; 139 | switch (msg.what) { 140 | case MESSAGE_POST_RESULT: 141 | // There is only one result 142 | result.mTask.finish(result.mData[0]); 143 | break; 144 | case MESSAGE_POST_PROGRESS: 145 | result.mTask.onProgressUpdate(result.mData); 146 | break; 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | sHandler是一个静态的对象,所以必须在主线程中初始化,而在ActivityThread的main方法中已经做了系统已经为我们做了初始化。当sHandler接受到MESSAGE_POST_RESULT就会调用finish方法。 153 | 154 | ```java 155 | private void finish(Result result) { 156 | if (isCancelled()) { 157 | onCancelled(result); 158 | } else { 159 | onPostExecute(result); 160 | } 161 | mStatus = Status.FINISHED; 162 | } 163 | ``` 164 | 165 | finish方法中,如果AsyncTask取消了,那么就会回调onCancelled()方法,否则就会调用onPostExecute()方法,也就是这两个方法只会回调一个。 166 | 167 | --- 168 | 169 | 以上分析,AsyncTask的确是串行运行的(3.0以下为并行),为了能在3.0以上使用并行,我们可以调用executeOnExecutor()。如下所示,让其在AsyncTask.THREAD_POOL_EXECUTOR线程运行。 170 | 171 | ```java 172 | new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,""); 173 | new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,""); 174 | new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,""); 175 | new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,""); 176 | ``` 177 | 178 | -------------------------------------------------------------------------------- /Android摄像头Camera.md: -------------------------------------------------------------------------------- 1 | ## Android调用摄像头 2 | 3 | #### 启动相机: 4 | 5 | - 隐示intent:注册该action的IntentFliter都是响应该intent。 6 | 7 | ```java 8 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 9 | //第二参数,为保存的路径 10 | intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(new File("sdcard/1 .jpg"))); 11 | startActivityForResult(intent, 10); 12 | ``` 13 | 14 | 启动相机获取拍摄图片: 15 | 16 | - `startActivityForResult()`在返回的方法中,返回压缩后的照片。 17 | 18 | - 将拍摄照片保存到本地缓存。然后读取文件进行压缩。 19 | 20 | ```Java 21 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 22 | File file = new File( 23 | getExternalCacheDir().getAbsolutePath()+"/1.jpg"); 24 | Uri uri = Uri.fromFile(file); 25 | //指定图片保存路径的Uri 26 | intent.putExtra(MediaStore.EXTRA_OUTPUT,uri); 27 | startActivity(intent); 28 | ``` 29 | 30 | #### 自定义相机 31 | 32 | - Camera:camera包下,5.0之后被废弃 33 | - CameraDevice:camera2包下,google提供更为全面的相机api。 -------------------------------------------------------------------------------- /Android数据存储的五种种方式.md: -------------------------------------------------------------------------------- 1 | ## Android数据存储的五种种方式 2 | 3 | > ### 1. Shared Preferences 4 | 5 | * `getSharedPreferences()` 第一个参数传入文件名 6 | * `getPreferences()`在Activity中获取,文件名为xxxActivicity.xml 7 | 8 | > ### 2. 内部存储 files目录下的文件 9 | 10 | #### Context提供的打开流的两个方法: 11 | 12 | - `openFileOutput()`和 `openFileInput()` 13 | - 写入方法`write()`,读取方法`read()`。操作完成别忘记关闭流`close()` 14 | - 实例: 15 | 16 | ```Java 17 | String FILENAME = "hello_file"; 18 | String string = "hello world!"; 19 | 20 | FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE); 21 | fos.write(string.getBytes()); 22 | fos.close(); 23 | ``` 24 | 25 | #### 手动的创建流读取,Context同样提供了获取目录的方法 26 | 27 | - `getFilesDir()` 28 | - `getCacheDir()` 29 | 30 | #### 其它有用的方法 31 | 32 | - `getDir()`在/data/data/<包名>/目录下,创建或打开一个目录。 33 | - `deleteFile()` 删除`files`目录下一个文件,不能删除目录。 34 | - `fileList`返回`files`文件下的文件数组。 35 | - `openRawResource()`打开R.raw.xxx下的资源文件。 36 | 37 | > ### 3. 外部存储(File文件存储) 38 | 39 | - `Environment.getExternalStorageState()` 获取SD卡状态 40 | 41 | 42 | - `Environment.getExternalStorageDirectory()`获取SD卡根目录 43 | 44 | > ### 4. SQLite 45 | 46 | SQLite的数据类型: integer,real浮点型,text 文本类型,blob 二进制类型 47 | 48 | - SQLiteOpenHelper:数据库的创建个更新。 49 | 50 | ```Java 51 | public MyOpenHelper( 52 | Context context, 53 | String name, 54 | CursorFactory factory, 55 | int version){ 56 | //构造方法 57 | } 58 | //数据库被创建时调用,如果数据库已经存在就不再调用该方法。通常完成建表操作 59 | public void onCreate(SQLiteDatabase db) { 60 | db.execSQL("create table person ( 61 | _id integer primary key autoincrement, 62 | name char(10), 63 | phone char(20), 64 | money integer(20))"); 65 | } 66 | //数据库升级时调用:onUpgrade方法。 67 | //如果创建的表已经存在,就会报错。需要重新创建。 68 | public void onUpgrade(SQLiteDatabase db, 69 | int oldVersion, int newVersion) { 70 | db.execSQL("drop table if exists Book"); 71 | db.execSQL("drop table if exists Category"); 72 | onCreate(db); 73 | } 74 | 75 | ``` 76 | 77 | - SQLiteDataBase 78 | 79 | ```Java 80 | MyOpenHelper oh = new MyOpenHelper(getContext(), "person.db", null, 1); 81 | SQLiteDatabase db = oh.getWritableDatabase(); 82 | getWritableDatabase()//打开或创建可读写的数据库 83 | getReadableDatabase()//打开或创建,在磁盘空间不足时打开只读数据库,否则打开可读写数据库。而getWritableDatabase()方法会出现异常。 84 | 85 | ``` 86 | 87 | - 增删改查 88 | 89 | ```Java 90 | //执行SQL语句实现增删改查 91 | //插入 92 | db.execSQL("insert into person (name, phone, money) 93 | values (?, ?, ?);", new Object[]{"张三", 15987461, 75000}); 94 | //查询 95 | Cursor cs = db.rawQuery("select _id, name, money from person where 96 | name = ?;", new String[]{"张三"}); 97 | //使用api实现增删改查 98 | //插入 99 | ContentValues cv = new ContentValues(); 100 | cv.put("name", "刘能"); 101 | cv.put("phone", 1651646); 102 | cv.put("money", 3500); 103 | //返回值是该行的主键,如果出错返回-1 104 | long i = db.insert("person", null, cv); 105 | //删除,返回值是删除的行数 106 | int i = db.delete("person", "_id = ? and name = ?", 107 | new String[]{"1", "张三"}); 108 | //修改 109 | ContentValues cv = new ContentValues(); 110 | cv.put("money", 25000); 111 | int i = db.update("person", cv, "name = ?", new String[]{"赵四"}); 112 | //查询 113 | //arg1:要查询的字段,arg2:查询条件,arg3:填充查询条件的占位符 114 | Cursor cs = db.query("person", new String[]{"name", "money"}, 115 | "name = ?", new String[]{"张三"}, null, null, null); 116 | while(cs.moveToNext()){ 117 | } 118 | //事务api 119 | try { 120 | //开启事务 121 | db.beginTransaction(); 122 | ........... 123 | //设置事务执行成功 124 | db.setTransactionSuccessful(); 125 | } finally{ 126 | //关闭事务 127 | //如果此时已经设置事务执行成功,则sql语句生效,否则不生效 128 | db.endTransaction(); 129 | } 130 | 131 | ``` 132 | 133 | ## 跨程序间的数据共享ContentProvider 134 | 135 | ContentProvider是Android不同程序间标准的数据共享方式。 136 | 137 | Sharedpreference的共享方式在android4.2中已经被废弃。(安全性和隐私) 138 | 139 | Android系统的ContentProvider提供把私有数据共享给其他应用。 140 | 141 | #### 使用步骤: 142 | 143 | ```Java 144 | 1. 自定义内容提供者,继承ContentProvider抽象类,重写增删改查方法。 145 | public Uri insert(Uri uri, ContentValues values) { 146 | db.insert("person", null, values); 147 | return uri; 148 | } 149 | … … 150 | 2. 在AndroidManifest文件中定义内容提供者的标签,注意必须要有authorities属性,这是内容提供者的主机名,功能类似地址。还可以设置访该 提供者需要的权限 151 | 155 | /provider> 156 | 3. 其他应用访问该ContentProvider 157 | ContentResolver cr = getContentResolver(); 158 | ContentValues cv = new ContentValues(); 159 | … … 160 | Uri是你要访问的协议名+主机名。内容提供者的协议名是content:// 161 | cr.insert(Uri.parse("content://com.koter.myprovider"),cv); 162 | 163 | ``` 164 | 165 | #### UriMatcher (Uri匹配器) 166 | 167 | ```Java 168 | 1. 在内容提供者中指定要匹配的Uri 169 | UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH); 170 | { //在代码块中初始化匹配项。 arg0:主机名 arg1:访问路径 age3:返回码 171 | um.addURI("com.itheima.person", "person", PERSON_CODE); 172 | um.addURI("com.itheima.person", "company", COMPANY_CODE); 173 | //#号可以代表任意数字。可以参考访问系统指定的联系人 174 | um.addURI("com.itheima.person", "person/#", 175 | QUERY_ONE_PERSON_CODE); 176 | } 177 | 2. 通过传递的Uri进行不同的操作 178 | @Override 179 | public Uri insert(Uri uri, ContentValues values) { 180 | if(um.match(uri) == PERSON_CODE){ 181 | db.insert("person", null, values); 182 | } 183 | else if(um.match(uri) == COMPANY_CODE){ 184 | db.insert("company", null, values); 185 | } 186 | else{ 187 | throw new IllegalArgumentException(); 188 | } 189 | return uri; 190 | } 191 | //如果Uri携带数据,取出携带的数字,并进行相应的操作。例如查询指定联系人,传入联系人位置。 192 | long id = ContentUris.parseId(uri); 193 | ``` 194 | 195 | #### 获取系统的联系人 196 | 197 | - 联系人数据库contacts2.db 198 | 199 | ```Java 200 | raw_contacts表: 201 | contact_id字段:联系人id。 202 | data表:联系人信息表,一个联系人信息对应多行。 203 | 根据raw_contacts表查询的contact_id字段查询该表。对应该表的raw_contact_id字段。 204 | data1字段 :包含了联系人的具体信息 205 | mimetype_id字段:描述信息是属于什么类型 Email name 等。是外键 206 | mimetypes表: 207 | mimetype字段 :定义了联系人信息类型 208 | 209 | ``` 210 | 211 | - 获取联系人表信息之后,查询联系人信息 212 | 213 | ```Java 214 | 1) 查询raw_contacts表,拿到contacts_id。 215 | ContentResolver cr = getContentResolver(); 216 | Cursor cursor = cr.query(Uri.parse ("content://com.android.contacts/raw_contacts") 217 | , new String[]{"contact_id"}, null, null, null); 218 | 2) 根据contact_id查询data表的data1,mimetype(/data 自动查询)字段,获取联系人信息。 219 | while(cursor.moveToNext()){ 220 | //获取查询到的联系人id,作为查询条件 221 | String contactId = cursor.getString(0); 222 | //查询data表,返回mimetype,data1字段信息。根据mimetype解析data信息类型 223 | //data表中没有mimetype,实际上查的是data_view视图。 224 | Cursor cursorData =cr.query(Uri.parse 225 | ("content://com.android.contacts/data"), 226 | new String[]{"data1", "mimetype"}, "raw_contact_id = ?", 227 | new String[]{contactId}, null); 228 | //将联系人信息封装到JavaBean对象。 229 | Contact contact = new Contact(); 230 | while(cursorData.moveToNext()){ 231 | String data1 = cursorData.getString(0); 232 | String mimetype = cursorData.getString(1); 233 | if("vnd.android.cursor.item/email_v2".equals(mimetype)){ 234 | contact.setEmail(data1); 235 | } 236 | else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){ 237 | contact.setPhone(data1); 238 | } 239 | else if("vnd.android.cursor.item/name".equals(mimetype)){ 240 | contact.setName(data1); 241 | } 242 | } 243 | ``` 244 | 245 | #### 获取系统短信 246 | 247 | 只需要关注sms表的4个字段 248 | 249 | - body:短信内容 250 | - address:短信的发件人或收件人号码(跟你聊天那哥们的号码) 251 | - date:短信时间 252 | - type:1为收到,2为发送 253 | 254 | ```Java 255 | ContentResolver resolver = getContentResolver(); 256 | //1. 查询系统短信表。获取address,data日期,type1、2,body字段信息。 257 | Cursor cursor = resolver.query(Uri.parse("content://sms"), new String[]{"address", 258 | "date", "type", "body"}, null, null, null); 259 | while(cursor.moveToNext()){ 260 | String address = cursor.getString(0); 261 | long date = cursor.getLong(1); 262 | int type = cursor.getInt(2); 263 | String body = cursor.getString(3); 264 | System.out.println(address + ";" + date + ";" + type + ";" + body); 265 | } 266 | ``` 267 | 268 | -------------------------------------------------------------------------------- /Android消息处理机制.md: -------------------------------------------------------------------------------- 1 | #### 思考:为了Android设计只能通过UI线程更新UI? 2 | 3 | 最根本的原因时为了解决多线程并发。假设多个线程更新UI,很容易造成界面混乱,Android的UI控件不是线程安全的,如果对更新界面的操作进行加锁,又会造成性能下降,UI访问过于复杂。 4 | 5 | 如果在子线程中更新UI那么就会抛出异常,这点可以在ViewRootImpl中的chechThread()得道验证。 6 | 7 | ```Java 8 | void checkThread(){ 9 | if(mThread != Thread.currentThread()){ 10 | throw new CalledFromWrongThreadException(Only the original ....); 11 | } 12 | } 13 | ``` 14 | 15 | #### 主线程和子线程创建Handler对象的方式。 16 | 17 | 主线程相关的Handler 18 | 19 | ```Java 20 | Handler mHandler = new Handler(){ 21 | handMessage(){} 22 | }; 23 | ``` 24 | 25 | 子线程相关的Handler 26 | 27 | ```Java 28 | new Thread(new Runnable() { 29 | @Override 30 | public void run() { 31 | Looper.prepare(); 32 | mHandler = new Handler(){ 33 | }; 34 | Looper.loop(); 35 | } 36 | }).start(); 37 | ``` 38 | 39 | #### 原理解析,为什么子线程和主线程创建Handler对象的方式不一样呢? 40 | 41 | 首先是Handler无参的构造方法 42 | 43 | ```Java 44 | public Handler() { 45 | if (FIND_POTENTIAL_LEAKS) { 46 | final Class klass = getClass(); 47 | if ((klass.isAnonymousClass() || klass.isMemberClass() || 48 | klass.isLocalClass()) && 49 | (klass.getModifiers() & Modifier.STATIC) == 0) { 50 | Log.w(TAG, "The following Handler class should be static 51 | or leaks might occur: " + 52 | klass.getCanonicalName()); 53 | } 54 | } 55 | //通过Looper.myLooper()方法取出Looper对象 56 | mLooper = Looper.myLooper(); 57 | if (mLooper == null) { 58 | throw new RuntimeException( 59 | "Can't create handler inside thread that has not called Looper.prepare()"); 60 | } 61 | mQueue = mLooper.mQueue; 62 | mCallback = null; 63 | } 64 | ``` 65 | 66 | 在创建Handler对象就会执行Looper.myLooper()方法取出mLooper对象。如果取出mLooper对象为空就会抛出“不能在线程中创建handler而没有调用Looper.prepare()方法”异常。这也印证了为什么不能在没有Looper对象的情况下创建Handler? 67 | 68 | 还是先来看一下Looper.myLooper()和Looper.prepare()的实现和作用。 69 | 70 | Looper.myLooper(),调用ThreadLocal的get()方法,回去当前线程的Looper对象。 71 | 72 | ```Java 73 | public static final Looper myLooper() { 74 | return (Looper)sThreadLocal.get(); 75 | } 76 | ``` 77 | 78 | Looper.prepare()方法,创建looper对象,并设置给ThreadLocal。 79 | 80 | ```Java 81 | public static final void prepare() { 82 | //如果已经存在Looper对象,抛出异常 83 | if (sThreadLocal.get() != null) { 84 | throw new RuntimeException("Only one Looper may be created per thread"); 85 | } 86 | //设置looper对象。 87 | sThreadLocal.set(new Looper()); 88 | } 89 | ``` 90 | 91 | 结论:由此可以看出,创建Handler对象之前,必须先创建Looper对象。 92 | 93 | #### 那么主线程为什么可以直接创建Handelr对象? 94 | 95 | 这是因为在ActivityThread的main方法中,系统已经自动帮我们调用了Looper.prepare()方法。 96 | 97 | ```Java 98 | public static void main(String[] args) { 99 | SamplingProfilerIntegration.start(); 100 | CloseGuard.setEnabled(false); 101 | Environment.initForCurrentUser(); 102 | EventLogger.setReporter(new EventLoggingReporter()); 103 | Process.setArgV0(""); 104 | //该放又会去调用Looper.prepare()方法。 105 | Looper.prepareMainLooper(); 106 | ActivityThread thread = new ActivityThread(); 107 | thread.attach(false); 108 | if (sMainThreadHandler == null) { 109 | sMainThreadHandler = thread.getHandler(); 110 | } 111 | AsyncTask.init(); 112 | if (false) { 113 | Looper.myLooper().setMessageLogging(new 114 | LogPrinter(Log.DEBUG, "ActivityThread")); 115 | } 116 | Looper.loop(); 117 | throw new RuntimeException( 118 | "Main thread loop unexpectedly exited"); 119 | } 120 | ``` 121 | 122 | #### Handler对象发送消息(send和post(Runnable)) 123 | 124 | Handler提供了很多发送消息的方法,除了`sendMessageAtFrontOfQueue()`方法之外,其它发送消息的方法都会辗转调用到`sendMessageAtTime()`方法。 125 | 126 | ```Java 127 | public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 128 | boolean sent = false; 129 | //获取消息队列 130 | MessageQueue queue = mQueue; 131 | if (queue != null) { 132 | //记录handler 133 | msg.target = this; 134 | //将Message消息对象将MessageQueue消息队列 135 | sent = queue.enqueueMessage(msg, uptimeMillis); 136 | } 137 | else { 138 | RuntimeException e = new RuntimeException( 139 | this + " sendMessageAtTime() called with no mQueue"); 140 | Log.w("Looper", e.getMessage(), e); 141 | } 142 | return sent; 143 | } 144 | ``` 145 | 146 | MessageQueue即消息队列,这个类是在Looper的构造方法创建的,因此一个Looper也就对应一个MessageQueue对象,它的作用是将Message以单链表的形式排列,这里的`queue.enqueueMessage()`方法就是将消息加入消息队列。这里`msg.target = this`将handler对象赋给Message的target变量。 147 | 148 | --- 149 | 150 | 这里我们也看一下MessageQueue的enqueueMessage()方法的源码 151 | 152 | ```Java 153 | final boolean enqueueMessage(Message msg, long when) { 154 | ... 155 | synchronized (this) { 156 | ... 157 | //enpueueMessage其实就向单链表中插入操作。 158 | msg.when = when; 159 | Message p = mMessages; 160 | if (p == null || when == 0 || when < p.when) { 161 | msg.next = p; 162 | mMessages = msg; 163 | this.notify(); 164 | } else { 165 | Message prev = null; 166 | while (p != null && p.when <= when) { 167 | prev = p; 168 | p = p.next; 169 | } 170 | msg.next = prev.next; 171 | prev.next = msg; 172 | this.notify(); 173 | } 174 | } 175 | return true; 176 | } 177 | ``` 178 | 179 | Message对象加入MessageQueue后,就是Loop发挥作用的时候,loop方式是一个死循环,而MessageQueue的next()是一个阻塞式方法,没有消息就等待。 180 | 181 | --- 182 | 183 | Looper.loop():轮询MessageQueue,也是Looper中最重要的一个方法了。它会调用MessageQueue的next方法,没有就阻塞。 184 | 185 | ```Java 186 | public static final void loop() { 187 | Looper me = myLooper(); 188 | MessageQueue queue = me.mQueue; 189 | while (true) { 190 | //取出消息,没有阻塞等待。 191 | Message msg = queue.next(); // might block 192 | if (msg != null) { 193 | if (msg.target == null) { 194 | return; 195 | } 196 | if (me.mLogging!= null) me.mLogging.println( 197 | ">>>>> Dispatching to " + msg.target + " " 198 | + msg.callback + ": " + msg.what 199 | ); 200 | //调用handler自己的dispatchMessage()方法 201 | msg.target.dispatchMessage(msg); 202 | if (me.mLogging!= null) me.mLogging.println( 203 | "<<<<< Finished to " + msg.target + " " 204 | + msg.callback); 205 | msg.recycle(); 206 | } 207 | } 208 | } 209 | ``` 210 | 211 | 如果loop轮询到消息对象,就会执行msg.target.dispatchMessage(msg)这行代码。msg.target也就是发送消息的Handle对象,然后调用Handelr的dispatchMessage方法,那么hander就会处理这条消息啦。但是这里需要注意的是,Handler的dispatchMessage方法是在创建Handler是所使用的Looper中执行的,这样就成功的将代码逻辑切换到指定的线程中了。 212 | 213 | ```Java 214 | public void dispatchMessage(Message msg) { 215 | if (msg.callback != null) { 216 | handleCallback(msg); 217 | } else { 218 | if (mCallback != null) { 219 | if (mCallback.handleMessage(msg)) { 220 | return; 221 | } 222 | } 223 | handleMessage(msg); 224 | } 225 | } 226 | ``` 227 | dispatchMessage就是Handler来处理这个消息了。 228 | 229 | 首先,它会判断msg的callback是否为空,如果不为空就会交给handleCallback处理。msg的Callback对象就是Handler的poat方法所传递出来的Runnable。handleCallback实现也很简单,如下所示。 230 | 231 | ```java 232 | private static void handleCallback(Message msg){ 233 | message.callback.run(); 234 | } 235 | ``` 236 | 237 | 其次,检查mCallback是否为空,不为空就调用mCallback的handerMessage方法来处理。Callback接口定义如下所示。 238 | 239 | ```java 240 | public interface Callback{ 241 | public boolean handleMessage(Message msg); 242 | } 243 | ``` 244 | 245 | Callback接口的好处就是我们可以用来穿件一个handler而不用去派生它的子类。 246 | 247 | 最后,才会去hander自己的handleMessage方法。 248 | 249 | --- 250 | 251 | 以上就是Android消息处理机制的全过程啦。 252 | 253 | -------------------------------------------------------------------------------- /Android系统服务.md: -------------------------------------------------------------------------------- 1 | ###Context类提供的getSystemService() 2 | 3 | ### 获取电话相关。Telephoney ### 4 | 5 | TelephonyManager tm = (TelephonyManager) getSystemService 6 | (TELEPHONY_SERVICE); 7 | //需要read phone stage权限 8 | // 获取sim卡序列号 9 | String simSerialNumber = tm.getSimSerialNumber(); 10 | 11 | //监听来电。系统会启动一个服务 去电系统才发广播(ip拨号器) 12 | tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); 13 | //注册监听器。继承PhoneStateListener 14 | class MyListener extends PhoneStateListener 15 | 16 | ### LBS,基于位置的服务 LocationManager ### 17 | 18 | //粗糙和精准的网络定位。联网最好加上Internet权限 19 | android.permission.ACCESS_FINE_LOCATION 20 | android.permission.ACCESS_COARSE_LOCATION 21 | LocationManager locationManager = (LocationManager) 22 | getSystemService(Context.LOCATION_SERVICE); 23 | //设置定位标准/方式 24 | Criteria criteria = new Criteria(); 25 | criteria.setCostAllowed(true); //设置是否允许产生费用 26 | criteria.setSpeedRequired(true);//设置是否对速度敏感 27 | criteria.setAltitudeRequired(true);//设置是否海拔敏感 28 | criteria.setAccuracy(Criteria.ACCURACY_FINE); //设置准确的定位 29 | //获取可用的定位。 30 | String provider = locationManager.getBestProvider(criteria, true); 31 | 32 | ### 手机震动Vibrator ### 33 | 34 | 需要权限 android.permission.VIBRATE 35 | 36 | Vibrator vibrator = (Vibrator) getSystemService (VIBRATOR_SERVICE); 37 | // vibrator.vibrate(2000);震动两秒 38 | 39 | //开始震动,等待1秒->震动2秒->等待1秒->震动3秒。 -1表示循环一次 40 | //参数2大于0 表示从第几个位置开始循环 41 | vibrator.vibrate(new long[] { 1000, 2000, 1000, 3000 }, -1); 42 | 43 | ### 获得超级管理员 ### 44 | 45 | DevicePolicyManager manager = (DevicePolicyManager) context. 46 | getSystemService(Context.DEVICE_POLICY_SERVICE); 47 | manager.wipeData(0); 48 | 49 | 50 | ###获取系统窗体服务 ###。 51 | 52 | - Window层的添加,删除view。 53 | - 在其他App上显示自己的浮窗。 54 | - 并且可以相应点击事件 55 | - WindowManager.LayoutParams 56 | 57 | ### 运用Toast的原理: 58 | 1. 获取windows的管理者,并设置 WindowManager.LayoutParams的参数。 59 | 2. 需要一个View 。直接new 或者通过布局加载器,加载一个布局到view。 60 | 3. 添加,更新,移除View 。 61 | 4. 62 | 63 | WindowManager windowManager = (WindowManager) getSystemService 64 | (WINDOW_SERVICE); 65 | 66 | 67 | // 将view添加在屏幕上(Window) 68 | windowManager.addView(view, params); 69 | // 从window中移除view 70 | windowManager.removeView(view); 71 | ### 进程管理器(任务管理器)ActivityManager ### 72 | 73 | am = (ActivityManager) context.getSystemService 74 | (Context.ACTIVITY_SERVICE); 75 | //获取正在运行进程 76 | getRunningAppprocesses(); 77 | //获取系统Ram. c思想 78 | getMemoryInfo(memory); 79 | //获取正在运行的服务,数量是100个 80 | am.getRunningServices(100) 81 | //清理后台进程。权限 82 | killBackgroundProcesses() 83 | 84 | ### 包管理器(程序管理器) ### 85 | 86 | context.getPackageManager(); 87 | //获取所有已安装的包信息 88 | packageManager.getInstalledPackages(0) 89 | 90 | ### 获取屏幕的宽高 ### 91 | 92 | getWindowManager().getDefaultDisplay().getWidth(); 93 | 94 | ### 警报管理器 ### 95 | 具有唤醒Cpu的功能,而Timer在Cpu休眠时不能唤醒Cpu执行定时任务。 96 | AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 97 | 98 | ### 音频管理器 ### 99 | 100 | AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 101 | 102 | ### 剪切板管理器 ### 103 | 104 | ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); 105 | ClipData clipData = ClipData.newPlainText("msg", info); 106 | manager.setPrimaryClip(clipData); 107 | ### 连接管理器 ### 108 | 109 | 110 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 111 | 112 | ### 输入法管理器 ### 113 | 114 | InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 115 | ### 键盘管理器 ### 116 | 117 | KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); 118 | ### 通知管理器 ### 119 | 120 | NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 121 | ### wifi管理器 ### 122 | 123 | 124 | WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 125 | ### 传感器 ### 126 | 127 | SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /Bitmap的加载和缓存.md: -------------------------------------------------------------------------------- 1 | ### 加载图片实现目标 2 | 3 | ##### 1、尽可能避免内存溢出 4 | 5 | - 根据图片实际显示的大小压缩图片 6 | - 使用LruCache对我们的图片进行缓存 7 | 8 | ##### 2、用户操作的必须充分流畅 9 | 10 | - getView方法禁止做耗时操作(异步加载+回调显示) 11 | 12 | ##### 3、用户预期显示的图片尽可能的快 13 | 14 | - 图片加载策略的选择`FIFO`,`LIFO` 15 | 16 | 17 | ### 网络请求图片 18 | 19 | - 加载图片 20 | 1. 优先使用内存中,速度最快。 21 | 2. 使用本地SD卡, 速度快。 22 | 3. 网络加载,速度慢,浪费流量。 23 | - 将Bitmap进行缓存。 24 | 1. 使用LruCache 25 | - 内部使用LinkedHashMap 26 | - least recentlly use 最少最近使用算法 27 | - 会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定 28 | 2. 在加载图片时将图片压缩 29 | - 即使使用了LruChche。当大量请求图片时,在请求过程中也容易照成oom。 30 | - 所以在请求图片时,将图片压缩。 31 | - BitmapFactory.Options 设置解析参数。 32 | - BitmapFactory.decodeStream(is,options) 33 | - java中的引用(Android 中使用LruCache) 34 | - 强引用 垃圾回收器不会回收, java默认引用都是强引用 35 | - 软引用 SoftReference 在内存不够时,垃圾回收器会考虑回收 36 | - 弱引用 WeakReference 在内存不够时,垃圾回收器会优先回收 37 | - 虚引用 PhantomReference 在内存不够时,垃圾回收器最优先回收 38 | 39 | 注意: Android2.3+, 系统会优先将SoftReference的对象提前回收掉, 即使内存够用 40 | 41 | ### BaseAdapter显示图片乱序问题 42 | 43 | ``` 44 | - 由于ListView的缓存机制。 45 | - 不同的item复用了convertView的同一个ImageView对象。 46 | - 当item没有请求完成,就被滚出屏幕。那么他将很快背复用起来。 47 | - 而当复用的时候,该imageView又去请求图片,这是复用的item的正好有了响应,就会将图片设置给imageView。 48 | - 新的ImageView请求成功,重新设置。就造成了ImageView的多次设置。 49 | - 解决办法,给ImageView个Url绑定,在设置图片的时候取出tag,一样才去设置图片。 50 | ``` 51 | ### 请求框架picasso的介绍 52 | 53 | 大部分网路请求框架已经帮我们解决了大部分问题,例如: 54 | 55 | - 在adapter中回收和取消当前下载。 56 | - 使用最小的内存完成图片的转换。 57 | - 自动的内存缓存和硬盘缓存。 58 | - 图片的转换,大小、旋转。 -------------------------------------------------------------------------------- /Effective Java .md: -------------------------------------------------------------------------------- 1 | ## 第一章 引言 2 | 3 | ### 几条基本的原则 4 | 5 | - 清晰性和简洁性最为重要:模块的用户永远也不应该被模块的行为所迷惑(那样就不清晰了)。 6 | - 模块尽可能的小,但又不能太小(Module是指任何可重用的软件组件,从单个方法,到包含多个包的复杂系统都可以统称为一个模块) 7 | - 代码应该被重用,而不是被拷贝。 8 | - 模块之间的依赖性应该尽可能的降到最少。 9 | - 错误应该今早的别检验出来,最好是在编译的时刻。 10 | 11 | 规则虽然体现了大多数的最佳程序实践,但是不应该盲目的遵循这些规则,在偶尔的情况下,有了充分的理由我们打破这些规则。学习编程首先要学会基本规则,但是在去学习什么时候可以打破这些规则。 12 | 13 | 我们要做的是先编写出清晰、正确、可用、健壮、灵活、可维护性的程序来,如果能做到这些,想要获取更佳的性能就相对简单啦。 14 | 15 | ## 第二章 创建和销毁对象 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Fragment设计哲学.md: -------------------------------------------------------------------------------- 1 | ### Fragment你需要知道的知识点 2 | 3 | > #### [官网介绍](https://developer.android.com/guide/components/fragments.html#Transactions) 4 | 5 | Fragment译为“碎片”,是一个可重用的重要组件,一般对应一个布局文件,并且在`onCreateView()`方法中将布局文件`inflate`转为成view对象,因此Fragment也可以理解一个View,可以灵活的添加和移除。 6 | 7 | #### 静态添加Fragment。 8 | 9 | ```Java 10 | 11 | 18 | ``` 19 | 20 | > 静态加载会执行onInflate()方法,不过这个方法已经废弃。 21 | 22 | #### 动态添加Fragment 23 | 24 | Fragment在Acticity的添加和移除遵循数据库的事务特性。 25 | 26 | ```Java 27 | Fragment fragment = new TestFragment(); 28 | getFragmentManager().beginTransaction() 29 | .replace(R.id.add_fragment,fragment) 30 | .addToBackStack(null) 31 | .commit(); 32 | ``` 33 | 34 | #### 与Activity通信 35 | 36 | ```Java 37 | //获取Fragment对象 38 | getFragmentManager().findFragmentById(R.id.rightfragment); 39 | //这个tag需要在添加的时候指定。 40 | getFragmentManager().findFragmentByTag(tag); 41 | //获取Acticity对象 42 | MainActivity activity = (MainActivity) getActivity(); 43 | View listView = activity.findViewById(R.id.list_view); 44 | ``` 45 | 46 | #### 生命周期 47 | 48 | > ##### Acticity的返回栈(任务栈)由系统管理,而Fragment的返回栈有Activity管理 49 | 50 | - 第一次被添加到Acticity的声明周期 51 | 52 | `onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume();` 53 | 54 | ```Java 55 | MainActivity.onCreate / ☐→ 56 | MainActivity.onContentChanged / ☐→ 57 | TestFragment.onAttach / ☐→ 58 | MainActivity.onAttachFragment / ☐→ 59 | TestFragment.onCreate / ☐→ 60 | TestFragment.onCreateView / ☐→ 61 | TestFragment.onViewCreated / ☐→ 62 | TestFragment.onActivityCreated / ☐→ 63 | TestFragment.onViewStateRestored / ☐→ 64 | MainActivity.onStart / ☐→ 65 | TestFragment.onStart / ☐→ 66 | MainActivity.onPostCreate / ☐→ 67 | MainActivity.onResume / ☐→ 68 | TestFragment.onResume / ☐→ 69 | MainActivity.onPostResume / ☐→ 70 | MainActivity.onAttachedToWindow / ☐→ 71 | MainActivity.onWindowFocusChanged / ☐→ 72 | ``` 73 | 74 | - 被remove或replace,并且执行了addToBackStack()方法。 75 | 76 | ```Java 77 | MainActivity.onUserInteraction / →☐ 78 | TestFragment.onPause / ☐→ 79 | TestFragment.onStop / ☐→ 80 | TestFragment.onDestroyView / ☐→ 81 | //点击back键返回。 82 | TestFragment.onCreateView / ☐→ 83 | TestFragment.onActivityCreated / ☐→ 84 | TestFragment.onResume / ☐→ 85 | ``` 86 | 87 | - 被remove或replace,没有执行addToBackStack()方法。 88 | 89 | ```Java 90 | MainActivity.onUserInteraction / →☐ 91 | TestFragment.onPause / ☐→ 92 | TestFragment.onStop / ☐→ 93 | TestFragment.onDestroyView / ☐→ 94 | TestFragment.onDestroy / ☐→ 95 | TestFragment.onDetach / ☐→ 96 | ``` 97 | 98 | - Fragment搭配ViewPager使用。 99 | 100 | 由于ViewPager的预加载特性,ViewPager左右的Fragment会预加载,并且生命周期从onAttach - > onResume 偷偷跑一遍。当某一个Fragment不在当前显示的ViewPager的左面或者右面的时候,这个Fragment就会进入onDestroyView状态,当该Fragment重新进入ViewPager预加载页面的时候,又会从新执行onCreateView - > onResume方法,以上就是Fragment和ViewPager基本的生命周期方法。 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Gradle全解.md: -------------------------------------------------------------------------------- 1 | ## Gradle学习整理。 2 | 3 | Gradle是Android Studio默认编辑工具,它在项目的依赖管理、库管理渠道管理有着非常强大的功能。Gradle是基于Groovy(Groovy语言基于JVM)脚本语言进行构建的通过DSL(领域特定语言)语言进行描述和控制构建逻辑。 4 | 5 | 使用Android Studio创建工程会默认帮我们创建Gradle相关文件,并做基本的配置。在Project层级的文件结构目录层级下会有如下四个Gradel文件: 6 | 7 | - build.gradle : 全局的Gradle配置。 8 | - gradle.properties : 一般用来配置build.gradle脚本中的动态参数,例如signingConfigs信息。 9 | - local.properties : 主要配置SDK和NDK的路径,例如`sdk.dir =D\:\\Android\\sdk\\ndk-bundle` 10 | - settings.gradle : 引入module,配置module工程目录。 11 | 12 | 在每个module中会有一个关于我们module的gradle配置文件: 13 | 14 | - build.gradle : 15 | 16 | module中gradle文件配置信息相对其他较复杂,我们的项目最核心的配置信息也就是在这里,前面提到gradle使用的是DSL语言进行控制构建逻辑,那么我们分析项目module的向gradle文件也从每个领域进行分析即可。这其中主要包括一下领域:**apply plugin领域**、**android领域**、**dependencies领域** 。以上领域分别描述了gradle所引入的插件、androidModule在构建时所用到的参数、androidModule在构建时所依赖的库。更多下详细的可查看[Android Gradle DSL 的文档](http://google.github.io/android-gradle-dsl/current/index.html) 。 17 | 18 | Gradle编辑过程图解。 19 | 20 |
21 | 22 |
23 | 24 | 上图展示了一个App编译的过程,主要包括一下几步: 25 | 26 | 1. 编译源代码转化成dex文件,编译项目中的资源文件。 27 | 2. APK Packager 整dex文件和资源文件,并对apk进行签名。 28 | 3. 在最终生成Apk文件之前,APK Packager 会使用zipalign工具优化整合App,以便App在使用过程中更加的节省内存。 29 | 30 | ### Gradle进阶 31 | 32 | ##### 1、更改项目的目录结构 33 | 34 | Android Studio默认对项目的目录是有一定约束的,其默认结构是`src/main` ,我们可以在android领域配置以下信息来让gradle识别我们的目录。 35 | 36 | ```Java 37 | sourceSets { 38 | main { 39 | jniLibs.srcDirs = ['libs'] //配置jniLibs的默认文件夹 40 | res.srcDirs = ['src/main/res', 'src/main/res/layout/activity', 41 | 'src/main/res/layout/fragment'] 42 | } 43 | } 44 | ``` 45 | 46 | ##### 2、构建全局的配置信息 47 | 48 | 对于全局多处使用的变量,我们可以提取出来,例如:`compileSdkVersion` 、`buildToolsVersion` 。通过ext领域可以指定全局的配置信息,通过ext指定全局信息有多种方式我们可以根目录的build.gradle中,通过`rootProject.ext.变量名` 来引用全局的信息 。也可以直接在`allprojects`领域配置ext领域变量信息,这样就可以直接在module中引用这个变量了,不过这种写法Gradle的版本更新通知检查机制就无效了,但是还是利大于弊的。其实,个人觉得最喜欢下面这种配置方式,也详细介绍这种方式。我们可以将全局的配置配置信息写在一个单独的config.gradle文件中,这个文件配置信息如下所示: 49 | 50 | ```Java 51 | ext { 52 | //android领域相关配置信息 53 | android = [compileSdkVersion: 24, 54 | buildToolsVersion: "24.0.2", 55 | applicationId : "me.research", 56 | minSdkVersion : 14, 57 | targetSdkVersion : 24, 58 | versionCode : 1, 59 | versionName : "1.0"] 60 | //dependencies领域相关配置信息 61 | dependencies = ["appcompat-v7" : "com.android.support:appcompat-v7:23.3.0", 62 | "design" : "com.android.support:design:23.3.0", 63 | "recyclerview-v7" : "com.android.support:recyclerview-v7:23.3.0", 64 | "butterknife" : "com.jakewharton:butterknife:7.0.1", 65 | "otto" : "com.squareup:otto:1.3.8", 66 | "glide" : "com.github.bumptech.glide:glide:3.6.1"] 67 | } 68 | ``` 69 | 70 | 配置完我们需要的全局信息,我们还需要将这个config.gradle文件引入到根目录的build.gradle文件中`apply from 'config.gradle'` ,这样我们就可以在所有的module中使用这些参数了。使用方法如下所示: 71 | 72 | ```Java 73 | android { 74 | 75 | compileSdkVersion rootProject.ext.android.compileSdkVersion 76 | buildToolsVersion rootProject.ext.android.buildToolsVersion 77 | 78 | defaultConfig { 79 | applicationId rootProject.ext.android.applicationId 80 | minSdkVersion rootProject.ext.android.minSdkVersion 81 | targetSdkVersion rootProject.ext.android.targetSdkVersion 82 | versionCode rootProject.ext.android.versionCode 83 | versionName rootProject.ext.android.versionName 84 | } 85 | ... 86 | } 87 | 88 | dependencies { 89 | compile rootProject.ext.dependencies["design"] 90 | compile rootProject.ext.dependencies["appcompat-v7"] 91 | compile rootProject.ext.dependencies["recyclerview-v7"] 92 | compile rootProject.ext.dependencies["glide"] 93 | } 94 | ``` 95 | 96 | ##### 3、Build Types - 编译类型 97 | 98 | 一般包括release和debug两种不同的构建类型,我们也可以根据这两种类型定义出更多的类型,从而生成不同类型的Apk文件。下面的kw就是自定义的构建类型。 99 | 100 | ```java 101 | buildTypes { 102 | release { 103 | minifyEnabled false 104 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 105 | //配置签名信息 106 | signingConfig signingConfigs.kw 107 | } 108 | debug{ 109 | applicationIdSuffix ".debug" //app-debug.apk 110 | } 111 | 112 | //kw继承了debug构建类型,同时还添加了自定义的配置。 113 | kw.initWith(buildTypes.debug) 114 | kw{ 115 | applicationIdSuffix ".kwdebug" 116 | jniDebuggable true //开启jnidebug 117 | } 118 | } 119 | ``` 120 | 121 | 构建类型的参数配置信息还有很多,我们可以使用android studio工具在Project Structure中的Build Types中进行快捷配置,其中各个参数在release和debug版本中有着不同的默认值。 122 | 123 | ##### 4、构建signingConfigs 124 | 125 | ```java 126 | signingConfigs { 127 | kw { 128 | keyAlias 'koterwong' 129 | keyPassword '123456' 130 | storeFile file('mKeystore.jks') 131 | storePassword '123456' 132 | } 133 | } 134 | ``` 135 | 136 | 以上就我们配置的签名信息,很简单,使用这个签名信息的话需要在我们的buildType中添加`signingConfig signingConfigs.kw` 引入我们签名即可。需要注意的是一般不会将该信息直接卸载build.gradle中,一方面是程序的通用性考虑,另一方面是安全行考虑,这个时候就需要我们构建动态的设置参数了,我们通常会在gradle.propertiesz中配置这些参数. 137 | 138 | - System.properties方式获取参数。 139 | 140 | ```java 141 | //在gradle.properties加入如下配置,将参数配置到systemProp中。 142 | systemProp.keyStorePassword=123456 143 | systemProp.keyAlias=koterwong 144 | systemProp.keyAliasPassword=123456 145 | systemProp.keyStore=koterwong.jks 146 | 147 | //引入配置参数。通过System.properties[KEY]的方式获取。 148 | signingConfigs { 149 | app { 150 | storeFile file(System.properties['keyStore']) 151 | storePassword System.properties['keyStorePassword'] 152 | keyAlias System.properties['keyAlias'] 153 | keyPassword System.properties['keyAliasPassword'] 154 | } 155 | } 156 | ``` 157 | 158 | ##### 5、ProductFlavors - 多渠道打包 159 | 160 | 在manifest文件application节点写添加如下代码 161 | 162 | ```java 163 | 167 | ``` 168 | 169 | 在build.gradle脚本中添加productFlavors领域,并自定义渠道名。代码如下: 170 | 171 | ```java 172 | productFlavors{ 173 | product1{ 174 | //替换起到占位符。 175 | manifestPlaceholders = [CHANNEL_VALUE:"product1"] 176 | } 177 | product2{ 178 | manifestPlaceholders = [CHANNEL_VALUE:"product2"] 179 | } 180 | } 181 | ``` 182 | 183 | 通过以上的步骤,我们在打包apk的时候就会生成不同渠道的apk。不过我们还可以对productFlavors进行优化增加`productFlavors.all` 领域对每一个productFlavors进行遍历,并使用其name作为渠道名。 184 | 185 | ``` 186 | productFlavors.all{ flavor-> 187 | flavor.manifestPlaceholders =[CHANNEL_VALUE:name] 188 | } 189 | ``` 190 | 191 | ##### 6、生成重命名包名 192 | 193 | ```java 194 | applicationVariants.all { variant -> 195 | variant.outputs.each { output -> 196 | def outputFile = output.outputFile 197 | if (outputFile != null && outputFile.name.endsWith('.apk')) { 198 | output.outputFile = new File( 199 | outputFile.getParent(), 200 | "kw_${variant.flavorName}_version${variant.versionName}.apk") 201 | } 202 | } 203 | } 204 | ``` 205 | 206 | ##### 7、gradle加速 207 | 208 | 在gradle.properties文件增加如下代码: 209 | 210 | ```java 211 | org.gradle.daemon = true //开启多个线程编译 212 | org.gradle.parallel = true //并行构建 213 | org.gradle.configureondemand =true //按需配置,目前在孵化阶段。 214 | android.enableBuildCache=true //android studio2.2新引入功能,目前还在改进阶段 215 | ``` 216 | 217 | 在Module的gradle文件添加 218 | 219 | ```java 220 | dexOptions{ 221 | incremental = true 222 | javaMaxHeapSize = "4g" //增加编译的内存到4G 223 | } 224 | ``` 225 | 226 | ##### 8、让x86的模拟器能安装仅支持armCPU的App。 227 | 228 | 这个是在stackoverflow看到的,当时在维护公司的老代码,而那份代码只引入了arm的so包,我又需要在x86的模拟器上测试,就找到了这个。需要注意的是,虽然能运行,但是涉及到so包的东西还是会崩溃的。 229 | 230 | ```java 231 | splits { 232 | abi { 233 | enable true 234 | reset() 235 | include 'x86', 'armeabi-v7a' 236 | universalApk true 237 | } 238 | } 239 | ``` -------------------------------------------------------------------------------- /GreenDao3.0学习笔记.md: -------------------------------------------------------------------------------- 1 | ### GreenDao数据库Orm框架。 2 | 3 | 在Android开发中,GreenDao这款ORM框架的数据存取性能已经得道了广大认可,GreenDao官网也列出了它相比其他Orm框架的优势。它的出现避免我们在使用数据库时,编写Sql语句等需要繁琐的工作。 4 | 5 | Orm:对象映射关系(Object Relational Mapping),它的思想就是将关系数据库中的表映射成对象,以对象的形式展示数据,让我们以面向对象的思想实现数据库的操作。 6 | 7 | GreenDao的优点:轻量级、快速、内存开销小、简单一用的api、性能高度优化、操作简单、支持get\update\delete等操作。 8 | 9 | --- 10 | 11 | GreenDao最新版本为3.0,相比2.X在使用上有了很大的变化,当然既然学习的话也就从最新版本开始咯。 12 | 13 | ### GreenDao的使用 14 | 15 | GreenDao3.0官网详细教程。[官网教程](http://greenrobot.org/greendao/documentation/updating-to-greendao-3-and-annotations/) 16 | 17 | ##### 1、GreenDao3.0的Gradle配置。 18 | 19 | ``` 20 | apply plugin: 'org.greenrobot.greendao' //GreenDao插件 21 | 22 | buildscript { 23 | repositories { 24 | mavenCentral() 25 | } 26 | dependencies { 27 | classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0' 28 | } 29 | } 30 | 31 | //配置GreenDao代码生成路径 32 | greendao { 33 | //数据库schema版本号,迁移等操作会用到 34 | schemaVersion 1 35 | //通过gradle插件生成的数据库相关文件的包名,默认为你的entity所在的包名 36 | daoPackage 'me.koterwong.greendaodemo.dao' 37 | //这就是我们上面说到的自定义生成数据库文件的目录了,可以将生成的文件放到我们的java目录中,而不是build中,这样就不用额外的设置资源目录了 38 | targetGenDir 'src/main/java' 39 | } 40 | 41 | dependencies { 42 | //配置GreenDao 43 | compile 'org.greenrobot:greendao:3.0.1' //库 44 | compile 'org.greenrobot:greendao-generator:3.0.0' //代码生成器 45 | } 46 | ``` 47 | 48 | ##### 2、定义entity实体类 49 | 50 | ```java 51 | @Entity 52 | public class User { 53 | @Id 54 | private Long id; //id 55 | private String name; 56 | @Transient 57 | private int tempUsageCount; // not persisted 58 | } 59 | ``` 60 | 61 | ##### 3‘、生成高效代码 62 | 63 | 相比2.x,3.0在生成带上更加的简单,只需要下载gradle相关插件即可。接下来使用就和2.x的使用差别不大了。 64 | 65 | ##### 4、简单的封装 66 | 67 | ```java 68 | public class DaoManager { 69 | 70 | public static final String TAG = DaoManager.class.getSimpleName(); 71 | 72 | /** 73 | * 数据库名 74 | */ 75 | private static final String DB_NAME = "my_db.sqlite"; 76 | 77 | private static DaoManager manager; 78 | private static DaoMaster daoMaster; 79 | private static DaoMaster.DevOpenHelper helper; 80 | private static DaoSession daoSession; 81 | private Context context; 82 | 83 | private DaoManager(Context context) { 84 | this.context = context; 85 | } 86 | 87 | public static DaoManager getInstance(Context context) { 88 | if (manager == null) { 89 | synchronized (DaoManager.class) { 90 | if (manager == null) { 91 | manager = new DaoManager(context); 92 | } 93 | } 94 | } 95 | return manager; 96 | } 97 | 98 | /** 99 | * 判断数据库是否存在,如果没有就创建。 100 | * 101 | * @return 一个DaoMaster就代表着一个数据库的连接 102 | */ 103 | public DaoMaster getDaoMaster() { 104 | 105 | if (daoMaster == null) { 106 | DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, DB_NAME, null); 107 | daoMaster = new DaoMaster(helper.getWritableDatabase()); 108 | } 109 | return daoMaster; 110 | } 111 | 112 | /** 113 | * 完成数据的添加、删除、修改、查询的操作 114 | * 115 | * @return DaoSession完成对Entity的基本操作和Dao操作类 116 | */ 117 | public DaoSession getDaoSession() { 118 | if (daoSession == null) { 119 | if (daoMaster == null) { 120 | daoMaster = getDaoMaster(); 121 | } 122 | daoSession = daoMaster.newSession(); 123 | } 124 | return daoSession; 125 | } 126 | 127 | /** 128 | * 设置Debug开启,默认关闭 129 | */ 130 | public void setDebug() { 131 | QueryBuilder.LOG_SQL = true; 132 | QueryBuilder.LOG_VALUES = true; 133 | } 134 | 135 | /** 136 | * 关闭数据库连接,数据库开启的时候,使用完毕了必须要关闭 137 | */ 138 | public void closeConnection() { 139 | closeHelper(); 140 | closeDaoSession(); 141 | } 142 | 143 | public void closeHelper() { 144 | if (helper != null) { 145 | helper.close(); 146 | helper = null; 147 | } 148 | } 149 | 150 | public void closeDaoSession() { 151 | if (daoSession != null) { 152 | daoSession.clear(); 153 | daoSession = null; 154 | } 155 | } 156 | } 157 | 158 | ``` 159 | 160 | 封装数据的操作。 161 | 162 | ```java 163 | public class DaoUtils { 164 | private static final String TAG = "DaoUtils"; 165 | private DaoManager manager; 166 | public DaoUtils(Context context) { 167 | manager = DaoManager.getInstance(context); 168 | } 169 | 170 | /** 171 | * 插入操作 172 | * 173 | * @param entity entity 174 | * @return boolean 175 | */ 176 | public boolean insertEntity(Entity entity) { 177 | boolean flag = false; 178 | flag = manager.getDaoSession().insert(entity) != -1; 179 | return flag; 180 | } 181 | 182 | /** 183 | * 插入多条数据 184 | * 185 | * @param entities entities 186 | * @return boolean 187 | */ 188 | public boolean insertMulitEntity(final List entities) { 189 | boolean flag = false; 190 | try { 191 | manager.getDaoSession().runInTx(new Runnable() { 192 | @Override public void run() { 193 | for (Entity e : entities) { 194 | manager.getDaoSession().insertOrReplace(e); 195 | } 196 | } 197 | }); 198 | flag = true; 199 | } catch (Exception e) { 200 | e.printStackTrace(); 201 | } 202 | return flag; 203 | } 204 | 205 | /** 206 | * 更新操作 207 | * 208 | * @param entity entity 209 | * @return boolean 210 | */ 211 | public boolean updateEntity(Entity entity) { 212 | boolean flag = false; 213 | try { 214 | manager.getDaoSession().update(entity); 215 | flag = true; 216 | } catch (Exception e) { 217 | e.printStackTrace(); 218 | } 219 | return flag; 220 | } 221 | 222 | /** 223 | * 删除操作 224 | * 225 | * @param entity entity 226 | * @return boolean 227 | */ 228 | public boolean deleteEntity(Entity entity) { 229 | boolean flag = false; 230 | try { 231 | manager.getDaoSession().delete(entity); 232 | flag = true; 233 | } catch (Exception e) { 234 | e.printStackTrace(); 235 | } 236 | return flag; 237 | } 238 | 239 | /** 240 | * 删除所有记录 241 | * 242 | * @param entity entityClass 243 | * @return boolean 244 | */ 245 | public boolean deleteAll(Entity entity) { 246 | boolean flag = false; 247 | try { 248 | manager.getDaoSession().deleteAll(entity.getClass()); 249 | flag = true; 250 | } catch (Exception e) { 251 | e.printStackTrace(); 252 | } 253 | return flag; 254 | } 255 | 256 | /** 257 | * 查询所有记录 258 | * 259 | * @return List 260 | */ 261 | @SuppressWarnings("unchecked") 262 | public List listAll(Entity entity) { 263 | return (List) manager.getDaoSession().loadAll(entity.getClass()); 264 | } 265 | 266 | /** 267 | * 查询主键 268 | * 269 | * @param key key 270 | * @return Entity 271 | */ 272 | public Entity quetyOneByKey(Entity entity, Long key) { 273 | return (Entity) manager.getDaoSession().load(entity.getClass(), key); 274 | } 275 | } 276 | 277 | ``` -------------------------------------------------------------------------------- /Gson使用详细介绍.md: -------------------------------------------------------------------------------- 1 | #GSON 2 | ---------------------------------------------- 3 | * [JSON 介绍](#json_intro) 4 | 5 | * [Gson 下载](#gson_download) 6 | 7 | * [Gson 解析 和 格式化](#gson) 8 | 9 | * [Gson 格式化](#gson_format) 10 | 11 | * [Gson 解析](#gson_parse) 12 | 13 | 14 | 15 | ## Json 介绍 16 | 17 | 18 | Json 全称 JavaScript Object Natation ,用来描述数据结构,它是基于纯文本的数据格式,是一种轻量级的数据交换格式。广泛应用于 服务端 与 客户端 的数据交互。 19 | ​ 20 | * __格式__ 21 | 22 | Json 以 key-value的形式存储数据; 23 | 24 | Key的取值 为 `String` 类型; 25 | 26 | Value的取值 为 `String`,`boolean`,`Number`,`数组`,`Object`,`null`; 27 | 28 | Json 串 以 `{` 开始, 以 `}` 结尾; 29 | 30 | Json 串中 `数组` 是 以 `[` 开始, 以 `]` 结尾; 31 | 32 | Json 串中 `Object` 是 以 `{` 开始, 以 `}` 结尾; 33 | 34 | * __案例__ 35 | 36 | __基本类型__ : 37 | 38 | ``` 39 | { 40 | "name": "张三", 41 | "age": 18, 42 | "sex": true 43 | } 44 | ``` 45 | 46 | __数组类型__ : 47 | 48 | [ 49 | { 50 | "name": "张三", 51 | "age": 18, 52 | "sex": true 53 | }, 54 | { 55 | "name": "李四", 56 | "age": 19, 57 | "sex": false 58 | } 59 | ] 60 | 61 | __对象嵌套__ : 62 | 63 | { 64 | "name": "teacher", 65 | "computer": { 66 | "CPU": "intel7", 67 | "disk": "512G" 68 | }, 69 | "students": [ 70 | { 71 | "name": "张三", 72 | "age": 18, 73 | "sex": true 74 | }, 75 | { 76 | "name": "李四", 77 | "age": 19, 78 | "sex": false 79 | } 80 | ] 81 | } 82 | 83 | 84 | * __树状结构__ 85 | 86 | json 字符串相当于一个倒挂的树, key 相当于 树的节点. 87 | 88 | ![json 树结构](./json_tree.png) 89 | 90 | 91 | ## Gson 下载 92 | 93 | * 网址 94 | * 选择版本,进入下载jar. 95 | 96 | 97 | 98 | ## Gson 格式化 和 解析 99 | 100 | ### Gson 格式化 101 | ----------------------------------------------- 102 | __将 java 对象 格式化为 Json 字符串.__ 103 | 104 | * 实现步骤 : 105 | 106 | 1. 获得需要的对象: 107 | 108 | Student stu = new Student(); 109 | stu.setName("张三"); 110 | stu.setAge(18); 111 | stu.setSex(true); 112 | 113 | 2. 格式化 114 | 115 | Gson gson = new Gson(); 116 | //将 对象 转化成 json 字符串 117 | String json = gson.toJson(stu) 118 | 119 | 120 | 121 | 122 | ### Gson 解析 123 | ----------------------------------------------- 124 | __将 Json 字符串 解析 成 java 对象.__ 125 | 126 | * __Gson 的 节点对象:__ 127 | 128 | `JsonElement` : 所有的节点 都是 JsonElement 对象. 129 | 130 | `JsonPrimitive` : 基本的 数据类型的 节点 对象, JsonElement 的子类. 131 | 132 | `JsonNull` : 代表 空节点 对象,即 有 key,value 为空,JsonElement 的子类. 133 | 134 | `JsonObject` : 对象 数据类型的 节点 对象, JsonElement 的 子类. 135 | 136 | `JsonArray` : 数组 数据类型的 节点 对象, JsonElement 的 子类. 137 | 138 | * __JsonElement的取值__: 139 | 140 | 1. `JsonPrimitive` : value 的 取值对应 java 141 | 142 | ``` 143 | int,double,float,long,short,boolean,char,byte,String,BigDecimal,BigInteger,Number 144 | ``` 145 | 2. `JsonObject` : value 的 取值对应 java 的 Object 对象. 146 | 147 | 3. `JsonArray` : value 的 取值对应 java 的 List 及其子类对象. 148 | 149 | * __Json的解析成 java 对象__ 150 | 151 | json: 152 | 153 | {'name':'张三','age':18,'sex':true} 154 | 155 | 代码: 156 | 157 | Gson gson = new Gson(); 158 | // 将json 转化成 java 对象 159 | Student stu = gson.fromJson(json, Student.class); 160 | 161 | * __Json 解析 成 List__ 162 | 163 | json: 164 | ​ 165 | [{'name':'小1','age':18,'sex':true},{'name':'小2','age':19,'sex':false},{'name':'小3','age':20,'sex':true},{'name':'小4','age':21,'sex':false},{'name':'小5','age':22,'sex':true}] 166 | 167 | 代码: 168 | 169 | Gson gson = new Gson(); 170 | // 将 json 转化 成 List泛型 171 | List stus = gson.fromJson(json, new TypeToken>() {}.getType()); 172 | 173 | 174 | * __Json 解析 成 map__ 175 | 176 | json: 177 | ​ 178 | {'小3':{'name':'小3','age':20,'sex':true},'小4':{'name':'小4','age':21,'sex':false},'小5':{'name':'小5','age':22,'sex':true},'小1':{'name':'小1','age':18,'sex':true},'小2':{'name':'小2','age':19,'sex':false}} 179 | 180 | 代码: 181 | 182 | Gson gson = new Gson(); 183 | // 将 json 转化 成 Map泛型 184 | Map map = gson.fromJson(json, new TypeToken>() {}.getType()); 185 | 186 | 187 | * __Json 节点 的解析__ 188 | 189 | json: 190 | 191 | {'flag':true,'data':{'name':'张三','age':18,'sex':true}} 192 | 193 | 步骤 : 194 | 195 | 1. 获得 解析者 196 | 197 | JsonParser parser = new JsonParser(); 198 | 199 | 2. 获得 根节点元素 200 | 201 | JsonElement element = parser.parse(json);​ 202 | 203 | 3. 根据 文档判断根节点属于 什么类型的 Gson节点对象 204 | 205 | 206 | // 假如文档 显示 根节点 为对象类型 207 | // 获得 根节点 的实际 节点类型 208 | JsonObject root = element.getAsJsonObject(); 209 | 210 | 211 | 1. 取得 节点 下 的某个节点的 value 212 | 213 | // 获得 flag 节点的值, flag 节点为基本数据节点 214 | JsonPrimitive flagJson = root.getAsJsonPrimitive("flag"); 215 | // 基本节点取值 216 | boolean flag = flagJson.getAsBoolean(); 217 | 218 | // 获得 data 节点的值,data 节点为Object数据节点 219 | JsonObject dataJson = root.getAsJsonObject("data"); 220 | // 将节点上的数据转换为对象 221 | Student stu = new Gson().fromJson(dataJson,Student.class); 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /Intent的标准用法.md: -------------------------------------------------------------------------------- 1 | ## Android系统的Intent 2 | 3 | - 启动浏览器 4 | 5 | ```Java 6 | Intent intent =new Intent("android.intent.action.VIEW"); 7 | intent.setData(Uri.parse("http://www.baidu.com")); 8 | startActivity(intent); 9 | ``` 10 | - 启动拨号器 11 | 12 | ```Java 13 | Intent intent =new Intent(Intent.ACTION_DIAL); 14 | intent.setData(Uri.parse("tel://10086"); 15 | startActivity(intent); 16 | ``` 17 | 18 | - 显示联系人列表 19 | 20 | ```Java 21 | Intent intent = new Intent(Intent.ACTION_VIEW); 22 | intent.setData(Uri.parse("content://contacts/people")); 23 | startActivity(intent); 24 | ``` 25 | - 选择联系人 26 | 27 | ```Java 28 | Intent intent = new Intent(Intent.ACTION_PICK, 29 | ContactsContract.Contacts.CONTENT_URI); 30 | startActivityForResult(intent,1); 31 | 32 | intent.getData(); 33 | ``` 34 | 35 | - 打电话,直接拨号 36 | 37 | ```Java 38 | Intent intent = new Intent(Intent.ACTION_CALL); 39 | intent.setData(Uri.parse("tel:10086")); 40 | startActivity(intent); 41 | ``` 42 | 43 | - 启动短信界面 44 | 45 | ```Java 46 | Intent intent = new Intent(Intent.ACTION_SENDTO); 47 | intent.setData(Uri.parse("smsto:")); 48 | startActivity(intent); 49 | ``` 50 | 51 | - 启动选择照片的Intent。 52 | 53 | ```java 54 | Intent intent = new Intent(Intent.ACTION_PICK); 55 | //获取所有的image类型。 56 | intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 57 | "image/*"); 58 | startActivityForResult(intent,1); 59 | //获取图片,返回Uri 60 | intent.getData(); 61 | ``` 62 | 63 | - 启动拍照的Intent 64 | 65 | ```Java 66 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 67 | //第二参数,为保存的路径 68 | intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(new File("sdcard/1 .jpg"))); 69 | startActivityForResult(intent, 10); 70 | ``` 71 | - 启动录像的Intent 72 | 73 | ```Java 74 | intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); 75 | intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File 76 | ("sdcard/2.3gp"))); 77 | intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); 78 | startActivityForResult(intent, 20); 79 | ``` 80 | 81 | - 启动安装程序的Intent /卸载程序的Intent 82 | 83 | ```Java 84 | Intent intent = new Intent(Intent.ACTION_VIEW); 85 | intent.setDataAndType(Uri.fromFile(new File(downloadFile)), 86 | "application/vnd.android.package-archive"); 87 | startActivityForResult(intent, REQUEST_INSTALL); 88 | ``` 89 | 90 | - 卸载程序的Intent 91 | 92 | ```Java 93 | Intent uninstallIntent = new Intent(); 94 | uninstallIntent.setAction(Intent.ACTION_DELETE); uninstallIntent.setData(Uri.parse("package:"+.packageName)); 95 | startActivity(uninstallIntent); 96 | ``` 97 | 98 | - 启动应用的Intent 99 | 100 | ```Java 101 | Intent startIntent = getPackageManager().getLaunchIntentForPackage(packName); 102 | startActivity(startIntent); 103 | ``` 104 | 105 | - 打开应用程序详细信息的Intent 106 | 107 | ```Java 108 | Intent settingIntent = new Intent(); settingIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 109 | settingIntent.setData(Uri.parse("package:" +packageName)); 110 | startActivity(settingIntent); 111 | ``` 112 | 113 | ## Acticity启动相关Flag ## 114 | 115 | 1. FLAG_ACTIVITY_NEW_TASK 116 | 117 | 2. FLAG_ACTIVITY_CLEAR_TOP 118 | 119 | 3. FLAG_ACTIVITY_SINGLE_TOP 120 | 121 | 4. FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 122 | -------------------------------------------------------------------------------- /JNI和NDK编程.md: -------------------------------------------------------------------------------- 1 | # C语言入门 2 | 3 | 两个常用的库`#include` `#include` 4 | 5 | #### 1、C基本数据类型 6 | 7 | - 数值型:`short`2字节、`int`4字节、`long`4字节、`float`4字节、`double`8字节。 8 | - 字符型:`char`1字节 9 | - 构造类型:数组、结构体`struct`、共用体(联合体)`union`、枚举`enum` 10 | - 指针类型:类似Java中表示对象,`int* p` 、`void*`通用指针。 11 | - 常量修饰符:`const` 12 | #### 2、基本输入输出函数 13 | - 输入函数 14 | ```c 15 | int count; char name[4]; 16 | //取出count ,name的内存地址,把用户输入的数据保存到内存地址上。 17 | //&表示取出地址 18 | scanf(“数量%d名称%s”,&count,&name); 19 | ``` 20 | - 输出函数 21 | ``` 22 | //输出函数 23 | int i = 1; 24 | printf(“%d\n%#\d”,i,&i); 25 | ``` 26 | - 占位符 27 | ```c 28 | %d int 29 | %ld long int 30 | %lld long long int 31 | %hd 断整形short int 32 | %c char 33 | %f fload 34 | %lf double 35 | %u 无符位 36 | %x 16进制 37 | %o 八进制 38 | %s 输出字符串 39 | ``` 40 | #### 3、数组 41 | 在C语言中,数组内存空间连续,数组的地址就是首个元素的地址。 42 | ```c 43 | int c []= {1, 2 ,3 ,4 }; 44 | int*p = &c; //int*p = &c[0]; 45 | printf("第0个元素:%d\n",*(p+0));//*(p+1表示指针位移一个单位。由指针类型决定。 46 | printf("第1个元素:%d\n",*(p+1)); 47 | printf("第2个元素:%d\n",*(p+2)); 48 | printf("第3个元素:%d\n",*(p+3)); 49 | ``` 50 | > C语言没有字符串。可以定义字符数组,但是必须指定长度。可以越界!!! 51 | ```c 52 | char arr [9] = “中文2个字节”; //自动转换 53 | ``` 54 | #### 4、指针 55 | >指针的长度都是4个字节。64位编译器,指针长度为8个字节。即一个内存地址。 56 | >32为系统,内存总线为32位,只能表示2^32个不同内存地址,即4GB。 57 | 58 | 指针变量的值指向了其它变量的地址。 59 | ```c 60 | int i = 1; 61 | int* p = &i; //一级指针 ,将i的地址的值取出,赋给p 62 | int** q = &p; //二级指针,地址对应的值还是地址 63 | printf("%#x\n",p); 64 | printf("%d\n",*p); //*p 就等同与i 65 | printf("%d\n",**q); 66 | ``` 67 | - 函数调用 68 | 69 | - 值传递:不改变变量的值 70 | 71 | - 指针传递:(传递地址,相对于Java传递对象),直接改变变量的地址 72 | 73 | ```C 74 | void function(int** p){ 75 | int i = 3; 76 | *p = &i ; //将i的地址,*p 。*p表示mianp的地址 77 | } 78 | main() { 79 | int* mainp; 80 | function(&mainp); //将二级指针传递过去 int** p = &mainp; 81 | printf("%d",*mainp); 82 | } 83 | ``` 84 | 85 | 86 | - 多级指针 87 | 88 | ```c 89 | int**** p; 90 | //*p的值是一个地址。**p的值是一个地址。***p的值是一个地址。****p才是数据。 91 | ``` 92 | 93 | #### 5、结构体(C语言的Java类) 94 | 95 | ```c 96 | void function(){ 97 | printf("function"); 98 | } 99 | //定义结构体 100 | struct student{ 101 | int age; 102 | int height; 103 | char sex; 104 | //函数指针 105 | void (*functionp)(); 106 | }; 107 | main() { 108 | //创建结构体类型,需要初始化结构体的变量和函数。 109 | struct student stu ={20,180,'m',function}; 110 | //输出长度为了运算方便,结构体长度自动补齐 111 | printf("%d\n",sizeof(stu)); 112 | //引用结构体变量的值 113 | printf("%d\n",stu.age); 114 | //调用结构体的指针函数 115 | stu.functionp(); 116 | //定义结构体指针,指向结构体变量 117 | struct student* stup = &stu; 118 | (*stup).functionp(); 119 | //->一级指针指向结构体的属性 120 | stup->functionp(); 121 | printf("%d\n",stup->age); 122 | } 123 | ``` 124 | 125 | #### 6、联合体 126 | 127 | ```c 128 | //联合体,能表示这几种变量。是一个新的类型。 129 | //只能为其中一个值。新值会覆盖旧值。 130 | //长度取决于最长的变量 131 | union{int i; short s ; char c} un; 132 | un.i = 20; 133 | un.s = 10; 134 | un.c = 'a'; 135 | printf("%d",un.s); //值为97 看输出格式 136 | ``` 137 | 138 | #### 7、自定义类型`typedef`,C语言数据类型de别名表示 139 | 140 | ```c 141 | //Java八大基本数据类型,在C语言中的表示 142 | typedef unsigned char jboolean; /* unsigned 8 bits */ 143 | typedef signed char jbyte; /* signed 8 bits */ 144 | typedef unsigned short jchar; /* unsigned 16 bits */ 145 | typedef short jshort; /* signed 16 bits */ 146 | typedef int jint; /* signed 32 bits */ 147 | typedef long long jlong; /* signed 64 bits */ 148 | typedef float jfloat; /* 32-bit IEEE 754 */ 149 | typedef double jdouble; /* 64-bit IEEE 754 */ 150 | 151 | //Java对象,在C语言中的表示 152 | typedef void* jobject; 153 | typedef jobject jclass; 154 | typedef jobject jstring; 155 | typedef jobject jarray; 156 | typedef jarray jobjectArray; 157 | typedef jarray jbooleanArray; 158 | typedef jarray jbyteArray; 159 | typedef jarray jcharArray; 160 | typedef jarray jshortArray; 161 | typedef jarray jintArray; 162 | typedef jarray jlongArray; 163 | typedef jarray jfloatArray; 164 | typedef jarray jdoubleArray; 165 | typedef jobject jthrowable; 166 | typedef jobject jweak; 167 | ``` 168 | #### 8、枚举 169 | 170 | ```c 171 | //规定了取值范围 172 | enum DAY { 173 | MON= 1, TUE, WED, THU, FRI, SAT, SUN 174 | }; 175 | main(){ 176 | enum DAY day = TUE; 177 | printf("%d\n",day); //输出2;定义了从1开始 178 | } 179 | ``` 180 | 181 | #### 9、内存堆栈 182 | 183 | - 栈:静态内存 184 | - 内存空间是连续的,后进先出 185 | - 自动分配的,大小固定 186 | - 由系统自动分配,效率更高 187 | - 系统自动释放内存 188 | - 堆:动态内存 189 | - 需要程序狗手动申请,大小取决于系统虚拟内存`java new`、`C malloc` 190 | - 程序狗手动释放`java 自动释放`、`C free函数` 191 | - 内存空间是单链表接口(查询效率低,增删效率高) 192 | - 空间不是连续的。 193 | ```c 194 | //C语言申请堆内存的API 195 | int* p = malloc(3*sizeof(int)); //申请12个字节的堆内存 196 | int i ; 197 | for(i=1;i<=3;i++){ 198 | scanf("%d\n",(p+i-1)); 199 | } 200 | p = realloc(p,(3+5)*sizeof(int)); //重新申请新的堆内存32字节,并将p原来地址数据,保存。 201 | for(i=1;i<=5;i++){ 202 | scanf("%d\n",(p+3+i-1)); 203 | } 204 | for(i=1;i<=8;i++){ 205 | printf("%d\n",*(p+i-1)); 206 | } 207 | ``` 208 | # JNI入门 209 | 210 | JNI的本意是Java Native Interface,它是为了方便Java调用C,C++等本地代码所封装的一层接口。 211 | 212 | 为什么要使用jni? 213 | 214 | - 1、JNI扩展了java 虚拟机的能力, 驱动开发  (wifi-hotspot) 215 | - 2、Native code效率高,数学运算,实时渲染的游戏上,音视频处理 (极品飞车,openGL,ffmpeg) 216 | - 3、复用代码 (文件压缩,人脸识别…) 217 | - 4、特殊的业务场景,物联网,智能家具。 218 | - 5、安全策略,反编译。 219 | 什么是交叉编译? 220 | 221 | 在一个平台编译出另一个平台可以执行的 二进制程序。NDK本身提供了这种功能。 222 | 223 | - CPU平台:x86  arm 。不同CPU处理的指令集不同。 224 | - 系统平台: Windows 、 linux 、Max OS` 225 | 编译工具: 226 | - NDK: native development kits 。 227 | - cygwin:一个模拟器,可以在Windows平台下执行Linux命令。 228 | 229 | 230 | ## 编写JNI步骤 231 | 232 | -------------------------------------------------------------------------------- /Java笔记/IO输入流.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/IO输入流.docx -------------------------------------------------------------------------------- /Java笔记/JSP.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/JSP.docx -------------------------------------------------------------------------------- /Java笔记/JavaWeb.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/JavaWeb.docx -------------------------------------------------------------------------------- /Java笔记/Java中的反射.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/Java中的反射.docx -------------------------------------------------------------------------------- /Java笔记/Java注解.md: -------------------------------------------------------------------------------- 1 | ## 全面解析Java注解(Annotation) 2 | 3 | ##### 概念 4 | 5 | Java提供了一种原程序中的元素, 关联任何信息和任何数据的途径和方法。 6 | 7 | 为什么要学习注解,学习注解的好处,学习注解能做什么? 8 | 9 | - 1、能够读懂别人写的代码,特别是框架相关的代码 10 | - 2、让编程更加简洁,代码更加清晰 11 | - 3、 让别人高看一眼。 12 | - 4、自定义注解 13 | 14 | ##### java中的常见注解 15 | 16 | - `Override:`复写了父类的方法 17 | - `Deprecated:`表示该方法已经过时 18 | - `@SuppressWarnings("deprecation")`忽略了“deprecation”警告 19 | 20 | ##### 第三方注解 21 | 22 | Spring注解,Mybatis注解 23 | 24 | ##### 注解分类 25 | 26 | 按照运行机制分 27 | 28 | - 源码注解:注解只在源码中存在,编译成.class文件就不存在了。 29 | - 编译时注解:注解在源码和class文件都存在。 30 | - 运行时注解:在运行阶段还起作用,甚至会影响运行逻辑的注解; 31 | 32 | 按照来源分:jdk自带注解,第三方注解,自定义注解。 33 | 34 | ##### 自定义注解 35 | 36 | ```Java 37 | /* 38 | * 声明注解。 39 | * 关键字说明: 40 | * 1、@Target:作用域( 41 | * constructor(构造方法声明) 42 | * field(字段声明) 43 | * local_variable(局部变量声明), 44 | * method(方法声明) 45 | * package(包声明) 46 | * parameter(参数声明) 47 | * type(类,接口声明) 48 | * 2、@Retention:生命周期 49 | * source:只在源码显示,编译时会丢弃. 50 | * class:编译时会记录到class中,运行时忽略。 51 | * runtime:运行时存在,可以通过反射读取 52 | * 3、Inherited ---继承注解 53 | * 只能继承类,不能继承接口,也不能继承父类方法的注解。 54 | * 4、Documented---生成Javadoc时,包含次注解。 55 | */ 56 | 57 | @Target({ ElementType.METHOD, ElementType.TYPE }) 58 | @Retention(RetentionPolicy.SOURCE) 59 | @Inherited 60 | @Documented 61 | public @interface Description { 62 | 63 | //成员类型基本数据类型,String,annotation,Enumeration 64 | //成员名只有一个时,必须是value(),在使用时可以忽略成员名和= 65 | //注解可以没有成员,没有成员的注解是表示注解 66 | String name(); 67 | String age(); 68 | } 69 | ``` 70 | 71 | ##### 获取定义的注解。 72 | 73 | 概念:通过反射获取类,函数或成员上的**运行时**注解信息,从而实现动态控制程序运行的逻辑。 74 | 75 | ```Java 76 | public static void main(String[] args) { 77 | //1、使用类加载器加载类 78 | try { 79 | Class clazz = Class.forName("annotation.PersonImpl01"); 80 | //2、判断是否存在注解 81 | boolean isExits = clazz.isAnnotationPresent(Description.class); 82 | if (isExits) { 83 | //3、解析注解 84 | Description description = (Description) 85 | clazz.getAnnotation(Description.class); 86 | System.out.println( 87 | "name="+description.name()+"" + 88 | ",age="+description.age()); 89 | } 90 | //a、获取方法上的注解 91 | Method[] methods = clazz.getMethods(); 92 | for (Method method : methods) { 93 | boolean idExits = method. 94 | isAnnotationPresent(Description.class); 95 | if (idExits) { 96 | Description description = method. 97 | getAnnotation(Description.class); 98 | System.out.println( 99 | "name="+description.name()+ 100 | ",age="+description.age()); 101 | } 102 | } 103 | 104 | } catch (ClassNotFoundException e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | ``` 109 | 110 | -------------------------------------------------------------------------------- /Java笔记/XML.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/XML.docx -------------------------------------------------------------------------------- /Java笔记/~$IO输入流.docx: -------------------------------------------------------------------------------- 1 | Koterwong Koterwong Microsoft\APA.XSL Couri -------------------------------------------------------------------------------- /Java笔记/~$东Java笔记(五)集合框架.docx: -------------------------------------------------------------------------------- 1 | Koterwong Koterwong Microsoft\APA.XSL Couri -------------------------------------------------------------------------------- /Java笔记/毕向东Java笔记(七)网络编程.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/毕向东Java笔记(七)网络编程.docx -------------------------------------------------------------------------------- /Java笔记/毕向东Java笔记(五)集合框架.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/毕向东Java笔记(五)集合框架.docx -------------------------------------------------------------------------------- /Java笔记/毕向东Java笔记(四)String类,包装类.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/毕向东Java笔记(四)String类,包装类.docx -------------------------------------------------------------------------------- /Java笔记/毕向东Java笔记(二)异常.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/毕向东Java笔记(二)异常.docx -------------------------------------------------------------------------------- /Java笔记/毕向东java笔记(一).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/毕向东java笔记(一).docx -------------------------------------------------------------------------------- /Java笔记/毕向东java笔记(三)多线程.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/Java笔记/毕向东java笔记(三)多线程.docx -------------------------------------------------------------------------------- /Java编程思想.md: -------------------------------------------------------------------------------- 1 | ## 第一章:对象导论 2 | 3 | ### 封装 4 | 5 | 被隐藏(也即封装)的部分通常代表对象内部脆弱的部分,它们很容易被程序员所毁坏,因此将实现隐藏起来可以减少程序的bug。  6 | 7 | 隐藏是通过访问控制修饰符(public、protected、包访问、private)来实现的。 8 | 9 | 访问控制的第一个存在原因就是让调用者无法直接触及他们不应该触及的部分,但从另一方面来看,其实这不失为一项服务,因为他们可以很容易地看出哪些东西对他们来说很重要,而哪些东西可能不关心;访问控制的第二个存在原因就是允许库设计者可以改变类的内部的工作方式而不用担心会影响到调用者。 10 | 11 | ### 继承 12 | 13 | 代码复用:复用是面向对象程序设计所提供最了不起的优点之一。 14 | 15 | 最简单的代码复用就是直接调用类的方法,此外,我们还可以将该类置于某个新类中,使它成为新类的属性成员。新的类也可由任意数量、任意类型的其他对象以任意可以实现新的类中想要功能的方式所组成,这种使用现有的类合成新的类方式称为组合复用。 16 | 17 | 组合复用带来了极大的灵活性,使得它能在运行时动态的修改其实现行为,但继承并不具备这样的灵活性,因为继承是在编译时期就已经确定其行为,在运行时期是不能修改的。 18 | 19 | 继承两种实现方式,第一种方式非常直接:直接在子类中添加新的方法,即扩展父类接口。第二种方式就是子类覆写父类方法,但不新增父类没有接口。 20 | 21 | “is-a是一个”与“is-like-a像是一个”。继承时,我们使用第一种还是第二种方式呢?这可能引起争论:继承应该只覆盖基类的方法,而并不添加在基类中没有的新方法吗?如果这样做,就意味着子类与基类是完全相同的类型,因为它们具有完全相同的接口,结果可以用一个子类对象来完全替代一个基类对象,这可被认为是纯粹替代,通常称之为替代原则,这也是一种理想的方式,我们经常称这种情况下的子类与基类的关系是“is-a是一个”;有时必须在子类型中添加新的接口,这样也就扩展了接口,这个新的类型仍可以替代基类,但是这种替代并不完美,因为基类无法访问新添加的方法,这种情况下我们可以描述为“is-like-a像是一个”关系。 22 | 23 | ### 多态 24 | 25 | 一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,而向对象程序设计语言使用了后期绑定的概念。 26 | 27 | 方法的调用就是编译器将产生对一个具体函数名字的调用,前期绑定是在运行时将这个调用解析到将要被执行的代码的绝对地址。然而在OOP中,程序直到运行时才能够确定代码的地址,为了执行后期绑定,Java编译器使用了一小段特殊代码来替代绝对地址调用,这段代码使用对象中存储的信息来计算方法体的地址,根据这段特殊代码,每个对象都可以具有不同的行为表现。 28 | 29 | 在某些语言中,必须明确地声明某个方法具备后期绑定属性所带来的灵活性,如C++使用virtual关键字来实现,在默认情况下,C++不是动态绑定的,而在Java中,动态绑定是默认行为,不需要添加额外的关键字来实现多态。 30 | 31 | ### Java语言支持四种类型 32 | 33 | 接口(interface)、类(class)、数组(array)和基本类型(primitive)。前三种类型通常被称为引用类型(reference type),类实例和数组是对象(object),而基本类型的值则不是对象。类的成员(member)由它的域(field)、方法(method)、成员类(member class)和成员接口(member interface)组成。方法签名(signature)由它的名称和所有参数类型组成;签名不包括它的返回类型。 34 | 35 | ### 类与类之间的关系 36 | 37 | 类和类、类和接口、接口和接口之间有如下几种关系:泛化关系、实现关系、关联关系(聚合、合成)、依赖关系。 38 | 39 | - 泛化:表示类与类之间的继承关系,使用extends关键字来表示。在图形上使用虚线三角形箭头表示。 40 | 41 | 42 | - 实现:表示类与接口之间的实现关系,使用implements关键字来表示。在图形上使用实线三角形箭头表示。 43 | - 关联:类与类之间的联接。关联可以是双向的,也可以是单向的,双向的关联可以有两个箭头或都没有箭头。单向的关联有一个箭头,表示关联的方向。在Java里,关联关系是使用实例变量实现的。在每一个关联的端点,还可以有一个基数,表时这一端的类可以有几个实例。常见的基数有:0..1(零个或者一个实例)、0..*或者*(没限制,可以是零)、1(只有一个实例)、1..*(至少有一个实例)。一个关联关系可以进一步确定为聚合与合成关系。在图形上使用实线的箭头表示。 44 | - 聚合:是关联关系的一种,是强的关联关系,聚合是整体和个体之间的关系。关联与聚合仅仅从Java语法是上是分辨不出的,需要考察所涉及的类之间的逻辑关系。如果不确定是聚合关系,可以将之设置为关联关系。图形是在实线的箭头的尾部多一个空心菱形。 45 | - 合成:是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。整体消亡,则部分与消亡。图形是在实线的箭头的尾部多一个黑心菱形。 46 | - 依赖:类与类之间的连接,依赖总是单向的。表示一个类依赖于另一个类的定义。一般而言,依赖关系在Java里体现为局部变量、方法的参数、以及对静态方法的调用。但如果对方出现在实例变量中,那关系就超过了依赖关系,而成了关联关系了。在图形上使用虚线的箭头表示。 47 | 48 | 49 | ## 第二章:一切都是对象 50 | 51 | ### 对象存放位置与生命周期 52 | 53 | C++创建的对象可以存放在栈、静态存储区与堆(heap)中,放在栈中的对象用完后不需手动释放,会自动销毁,但放在堆中的对象需手动释放,栈中的对象所需空间与生命周期都是确定的,堆中的对象内存分配是动态的,在运行时才知道需要多少内存以及生命周期,如果说在堆上创建对象,编译器就会对它的生命周期一无所知,C++就需要以编程的方式来确定何时销毁对象,这可能因不正确处理而导致内存泄漏,而Java则提供了自动垃圾回收机制。 54 | 55 | Java对象的创建采用了动态内存分配策略,即创建的对象都是放在堆中的。 56 | 57 | ### 数据内存分配 58 | 59 | 寄存器——位于处理器内部,这是最快的存储区,大小极其有限,一般不能直接控制,但C和C++允许你向编译器建议寄存器分配方式。 60 | 61 | 堆栈——位于通用RAM(随机访问存储器)中,堆栈指针向下移动,则分配新的内存;若向上移动,则释放内存。这是一种快速有效的分配方法,速度仅次于寄存器。创建程序时,Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些Java数据存储于堆栈中(如对象引用),但是Java对象并不存储于其中。 62 | 63 | 堆——一种通用的内存池(也位于RAM区),用于存放所有的Java对象,堆不同于堆栈的好处是,编译器不需要知道存储的数据在堆里存活多长时间。因此,在堆栈分配存储有很大的灵活性。当然,这种灵活性导致了分配需要更多的时间,时间效率上不如堆栈。 64 | 65 | 常量存储:常量值通常直接存放到程序代码内部,这样做是安全的,因为它们永远不会被改变。 66 | 67 | 非RAM存储:数据可完全存活于程序之外,在没有运行机制时也可以存在,如持久化对象的存放。 68 | 69 | JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。 70 | 71 | Java字节码的执行有两种方式: 72 | 73 | 1. 即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。 74 | 2. 解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。 75 | 76 | 通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。 77 | 78 | ### 基本类型 79 | 80 | void属于基本类型,但只能用来修饰方法,不能用来修饰变量。 81 | 82 | Java提供了两个用于高精度计算类:BigInteger和BigDecimal,大体属于“包装器类”范畴,但都没有对应的基本类型。 83 | 84 | BigInteger支持任意精度的整数,可表示任何大小的整数值。 85 | 86 | BigDecimal支持任意精度的定点数,例如,可以用它进行精确的货币计算。 87 | 88 | ### 引用与对象生存周期 89 | 90 | ``` 91 | { 92 | String s = new String("a string"); 93 | } 94 | ``` 95 | 96 | 引用s在作用域终点就消失了,然而,s指向的String对象继续占据内存,最后由垃圾回收器回收。 97 | 98 | ### 方法签名 99 | 100 | 方法名和参数列表(合起来被称为“方法签名”)唯一地标识出某个方法。 101 | 102 | ### static修饰字段与方法的区别 103 | 104 | 一个static字段对每个对象来说都只有一份空间,而非static字段则是对每个对象都有一个存储空间,但是如果static作用于某个方法时,差别却没有那么大,static方法的一个重要的用法就是在不创建任何对象的前提下就可以调用它,这一点对定义main()方法很重要,该方法是运行时程序的一个入口。 105 | 106 | -------------------------------------------------------------------------------- /Java面试复习.md: -------------------------------------------------------------------------------- 1 | ## Java面试题 2 | 3 | ##### 类与对象 4 | 5 | [Java之美[从菜鸟到高手演变\]之类与对象(一)](http://blog.csdn.net/zhangerqing/article/details/8294039) 6 | [Java之美[从菜鸟到高手演变\]之类与对象(二)](http://blog.csdn.net/zhangerqing/article/details/8298603) 7 | [Java之美[从菜鸟到高手演变\]之类与对象(三)](http://blog.csdn.net/zhangerqing/article/details/8301934)  8 | 9 | 博文的相关的知识点: 10 | 1.对象的创建过程,结合以下代码说明。 11 | 12 | ```Java 13 | public class Person { 14 | public Person(int id) { 15 | System.out.println("person(" + id + ")"); 16 | } 17 | } 18 | 19 | class Build { 20 | /*静态块*/ 21 | static { 22 | System.out.println("this is static block!"); 23 | } 24 | /*非静态块*/ { 25 | System.out.println("this is non-static block!"); 26 | } 27 | Person p1 = new Person(1); 28 | public Build() { 29 | System.out.println("this is build's block!"); 30 | Person p2 = new Person(2); 31 | } 32 | Person p3 = new Person(3); 33 | public static void main(String[] args) { 34 | Build b = new Build(); 35 | } 36 | } 37 | ``` 38 | 39 | 输出结果:this is static block->this is not-static block->person(1)->person(3)->this is build block!->person(2) 40 | 创建对象的执行过程:1、先装载.class文件,创建Class对象,对静态数据进行初始化,并且值初始化一次。2、new Build()在堆内存上分配控件。3、执行非静态块。4、初始化变量。5、执行构造器。 41 | 总体执行顺序:静态块,静态属性->非静态块,属性->构造器。 42 | ##### java字符串的处理 43 | [Java之美[从菜鸟到高手演变\]之字符串的处理](http://blog.csdn.net/zhangerqing/article/details/8093919) 44 | ##### 垃圾回收 45 | [Java之美[从菜鸟到高手演变\]之JVM内存管理及垃圾回收](http://blog.csdn.net/zhangerqing/article/details/8214365) 46 | ##### 设计模式 47 | [Java之美[从菜鸟到高手演变\]之设计模式](http://blog.csdn.net/zhangerqing/article/details/8194653) 48 | [Java之美[从菜鸟到高手演变\]之设计模式二](http://blog.csdn.net/zhangerqing/article/details/8239539)                          49 | [Java之美[从菜鸟到高手演变\]之设计模式三](http://blog.csdn.net/zhangerqing/article/details/8243942) 50 | [Java之美[从菜鸟到高手演变\]之设计模式四](http://blog.csdn.net/zhangerqing/article/details/8245537) 51 | ##### 多线程     52 | [Java之美[从菜鸟到高手演变\]之多线程简介](http://blog.csdn.net/zhangerqing/article/details/8271105)            53 | [Java之美[从菜鸟到高手演变\]之线程同步](http://blog.csdn.net/zhangerqing/article/details/8284609) 54 | ##### Java IO 55 | [Java之美[从菜鸟到高手演变\]之JavaIO](http://blog.csdn.net/zhangerqing/article/details/8466532) 56 | ##### 智力题 57 | [Java之美[从菜鸟到高手演变\]之智力题【史上最全】](http://blog.csdn.net/zhangerqing/article/details/8138296)      58 | ##### 数据结构 59 | [Java之美[从菜鸟到高手演变\]之数据结构基础、线性表、栈和队列、数组和字符串](http://blog.csdn.net/zhangerqing/article/details/8796518) 60 | ##### String、StringBuilder、StringBuffer、CharSequence 61 | 1) CharSequence接口:是一个字符序列.String StringBuilder 和 StringBuffer都实现了它. 62 | 2) String类:对象具有不变性,每次对字符串操作都是生成新的对象,然后引用指向新的对象。 63 | 3) StringBuilder类;只可以在单线程的情况下进行修改(线程不安全). 64 | 4) StringBuffer类:可以在多线程的情况下进行改变(线程安全,内部加锁机制). 65 | 5)Stringbuilder比StringBuffer效率高,应该尽量使用StringBuilder.、 66 | 67 | ##### 抽象类与接口的区别? 68 | 69 | - 抽象类 70 | 抽象类体现了数据抽象的思想,它定义了一组抽象的方法,这些抽象方法由继承该类的子类去实现。它的出发点就是为了继承,否则它没有存在的任何意义。例如在Android中定义的BaseActivity、BaseToolBarActicity、BasePresenter(attachView(),deathView())等。 71 | 语法:1.使用abstract关键词修饰的类称之为抽象类,同时抽象方法中的定义也需要abstract修饰。2.抽象类中也可以没有抽象方法,比如HttpServlet方法。3.抽象类中可以有已经实现的方法,可以定义成员变量。 72 | - 接口 73 | 接口是用来建立类与类之间的协议,它所提供的只是一种形式,而没有具体的实现。同时实现该接口的实现类必须要实现该接口的所有方法,通过使用implements关键字。 接口是抽象类的延伸,java为了保证数据安全是不能多重继承的,也就是说继承只能存在一个父类,但是接口不同,一个类可以同时实现多个接口,不管这些接口之间有没有关系,所以接口弥补了抽象类不能多重继承的缺陷。 74 | 75 | 语法:1.由interface关键词修饰的称之为接口;2.接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。3.接口中没有已经实现的方法,全部是抽象方法。4.一个类实现某一接口,必须实现接口中定义的所有方法。5.一个类可以实现多个接口。 76 | 77 | ##### Java中反射的作用是什么?什么时候会用到 78 | JAVA反射机制是在#运行时#,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java反射机制主要提供了以下功能: 79 | a)在运行时判断任意一个对象所属的类; 80 | b)在运行时构造任意一个类的对象; 81 | c)在运行时判断任意一个类所具有的成员变量和方法; 82 | **d)在运行时调用任意一个对象的方法;生成动态代理。** 83 | 84 | ##### 写出一个死锁的程序 85 | 86 | 一个死锁程序,同步中嵌套了同步,当两个线程互相索要对方锁的时候就会造成死锁。 87 | 88 | ```Java 89 | class Test implements Runnable { 90 | private boolean flag; 91 | Test(boolean flag) { 92 | this.flag = flag; 93 | } 94 | public void run() { 95 | if (flag) { 96 | while (true) { 97 | synchronized (MyLock.locka) { 98 | synchronized (MyLock.lockb) { 99 | } 100 | } 101 | } 102 | } else { 103 | while (true) { 104 | synchronized (MyLock.lockb) { 105 | synchronized (MyLock.locka) { 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | class MyLock { 113 | static final Object locka = new Object(); 114 | static final Object lockb = new Object(); 115 | } 116 | ``` 117 | 118 | ##### 生产者消费者问题 119 | 120 | ```JAVA 121 | /** 122 | * Java1.5新特性 lock对象替换synchronized。 123 | * condition对象替换object定义的控制器方法wait(),notify(),notifyAll();支持唤醒对象的操作。 124 | */ 125 | public class 生产者消费者2 { 126 | public static void main(String[] args) { 127 | Resouse_ r = new Resouse_(); 128 | Producer pro = new Producer(r); 129 | Consumer con = new Consumer(r); 130 | Thread t1 = new Thread(pro); 131 | Thread t2 = new Thread(pro); 132 | Thread t3 = new Thread(con); 133 | Thread t4 = new Thread(con); 134 | t1.start(); 135 | t2.start(); 136 | t3.start(); 137 | t4.start(); 138 | } 139 | } 140 | // 操作的共享数据 141 | class Resouse_ { 142 | private String name; 143 | private int id; 144 | private boolean flag = false; //标志位,是否生成或消费状态。 145 | private Lock lock = new ReentrantLock(); 146 | private Condition conditionPro = lock.newCondition(); 147 | private Condition conditionCus = lock.newCondition(); 148 | 149 | public void set(String name) throws InterruptedException { 150 | lock.lock(); 151 | try { 152 | // 使用while循环(不能使用if),每次线程被唤醒都去判断这个标志。 153 | while (flag) { 154 | conditionPro.await();// t1,t2 155 | } 156 | this.name = name + "--" + id++; 157 | System.out.println(Thread.currentThread().getName() + "-生产者--"+ this.name); 158 | flag = true; 159 | conditionCus.signalAll(); 160 | } finally { 161 | lock.unlock();// 释放锁的动作一定要执行。 162 | } 163 | } 164 | 165 | // t3 t4 166 | public void out() throws InterruptedException { 167 | lock.lock(); 168 | try { 169 | while (!flag) { 170 | conditionCus.await(); 171 | } 172 | System.out.println(Thread.currentThread().getName() + "-消费者----" + this.name); 173 | flag = false; 174 | conditionPro.signalAll(); 175 | } finally { 176 | lock.unlock(); 177 | } 178 | } 179 | } 180 | 181 | class Producer implements Runnable { 182 | private Resouse_ res; 183 | Producer(Resouse_ res) { 184 | this.res = res; 185 | } 186 | public void run() { 187 | while (true) { 188 | try { 189 | res.set("商品"); 190 | } catch (InterruptedException e) { 191 | } 192 | } 193 | } 194 | } 195 | 196 | class Consumer implements Runnable { 197 | private Resouse_ res; 198 | Consumer(Resouse_ res) { 199 | this.res = res; 200 | } 201 | public void run() { 202 | while (true) { 203 | try { 204 | res.out(); 205 | } catch (InterruptedException e) { 206 | } 207 | } 208 | } 209 | } 210 | ``` -------------------------------------------------------------------------------- /ListView解析.md: -------------------------------------------------------------------------------- 1 | > 问题思考? 2 | 3 | 1、ListView的继承关系? 4 | 5 | 2、Adapter在ListView中的作用和设计思想? 6 | 7 | 3、ListView的RecycleBin机制? 8 | 9 | ### ListView原理解析 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Realm学习笔记.md: -------------------------------------------------------------------------------- 1 | ### Realm使用入门教程 2 | 3 | [官网详细教程](https://realm.io/docs/java/latest/#getting-started),这里只做简单记录。使用Realm版本为1.1 4 | 5 | ##### 1、配置Grandle 6 | 7 | 在工程的build.grandle中添加如下代码 8 | 9 | ```java 10 | buildscript { 11 | repositories { 12 | jcenter() 13 | } 14 | dependencies { 15 | classpath "io.realm:realm-gradle-plugin:1.1.0" 16 | } 17 | } 18 | ``` 19 | 20 | 在具体的modle的build.grandle中配置 21 | 22 | ``` 23 | apply plugin: 'realm-android' 24 | ``` 25 | 26 | 配置完成以后,就可以在项目中使用Realm了。 27 | 28 | ##### 2、创建Realm类对象,实现基本的CRUD 29 | 30 | Realm类是实现对象持久化存储和事物管理的关键类,所有的RealmObject都强依赖与Realm类。创建Realm对象的方式有两种: 31 | 32 | ```java 33 | Realm realm = Realm.getDefaultInstance(); //通过默认的方式获取Realm对象。 34 | ``` 35 | 36 | ```Java 37 | RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this) 38 | .name("my.realm") 39 | .build(); 40 | Realm realm = Realm.getInstance(realmConfig); 41 | ``` 42 | 43 | 第一种使用默认的方式创建Realm实例,第二种方式可以自定义自己的Realm相关设置,同时在Realm类的头注释中也有官方的推荐方式。 44 | 45 | > 这里需要注意的是,Realm的实例不能在不同的线程间操作。确切的说,必须在每个要使用的线程上打开一个Realm实例。每个线程都会引用计算来自动缓存Realm实例,只要计数不为零,调用Realm.getInstance()方法将会返回缓存的Realm实例,这应该是一个轻量级的操作。   46 | 47 | 基本CRUD 48 | 49 | ```Java 50 | realm.executeTransaction(new Realm.Transaction() { 51 | @Override public void execute(Realm realm) { 52 | User user = realm.createObject(User.class); 53 | user.setAge(23); 54 | user.setName("wyk"); 55 | } 56 | }); 57 | 58 | final User user = realm.where(User.class).findFirst(); 59 | showStatus("insert ----->" + user.getName() + ":" + user.getAge()); 60 | 61 | //改 62 | realm.executeTransaction(new Realm.Transaction() { 63 | @Override public void execute(Realm realm) { 64 | user.setName("koterwong"); 65 | user.setAge(24); 66 | showStatus("------>" + user.getName() + ":" + user.getAge()); 67 | } 68 | }); 69 | 70 | realm.executeTransaction(new Realm.Transaction() { 71 | @Override public void execute(Realm realm) { 72 | realm.delete(User.class); 73 | } 74 | }); 75 | final User user_02 = realm.where(User.class).findFirst(); 76 | showStatus("execute delete ----->" + user_02.getName() + ":" + user_02.getAge()); 77 | ``` 78 | 79 | 以上执行增删改查的方式使用事物块的方式,内部也是调用 realm.beginTransaction()开启事物,调用realm.commitTransaction()提交事物,并在发生错误时执行realm.cancelTransaction()方法,实现起来更加简洁。另外,以上代码执行在主线程,开启事物会相互阻塞其所在的线程,通过异步事物,可以将Realm会在后台线程,并在事物完成后将结果回传回调用线程。 80 | 81 | 异步事物的使用,需要注意的是,OnSuccess和OnError并不是必须的参数,看需要而定。 82 | 83 | ```java 84 | realm.executeTransactionAsync(new Realm.Transaction() { 85 | @Override public void execute(Realm realm) { 86 | //do some operator 87 | } 88 | }, new Realm.Transaction.OnSuccess() { 89 | @Override public void onSuccess() { 90 | //存储成功回调 91 | } 92 | }, new Realm.Transaction.OnError() { 93 | @Override public void onError(Throwable error) { 94 | //存储失败回调 95 | } 96 | }); 97 | ``` 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /RxJava使用场景小结.md: -------------------------------------------------------------------------------- 1 | ##RxJava使用场景 2 | 3 | 1、线程并发和线程调度 4 | 5 | ```Java 6 | Observable.just(true) 7 | .map(new Func1() { 8 | @Override public Boolean call(Boolean aBoolean) { 9 | //do some long operation 10 | return false; 11 | } 12 | }) 13 | .subscribeOn(Schedulers.io()) 14 | .observeOn(AndroidSchedulers.mainThread()) 15 | .subscribe(getSubscribe()); 16 | 17 | private Action1 getSubscribe() { 18 | return new Action1() { 19 | @Override public void call(Boolean aBoolean) { 20 | //只关心onNext()事件 21 | } 22 | }; 23 | } 24 | ``` 25 | 26 | 2、buffer变换操作符,将事件缓冲,然后再发射Observable序列。(例如Button点击次数) 27 | 28 | - 指定Observable发射多少次,达到指定次数之后再发射给Subscriber。 29 | - 指定时间内在发射一次Obserable序列。 30 | 31 | ```Java 32 | RxView.clickEvents(btn) 33 | .map(new Func1() { 34 | @Override public Integer call(ViewClickEvent viewClickEvent) { 35 | return 1; 36 | } 37 | }) 38 | .buffer(2,TimeUnit.SECONDS) 39 | .subscribe(new Action1>() { 40 | @Override public void call(List integers) { 41 | //这里演示buffer每隔一段时间个连个发射一个列表的Observable。 42 | } 43 | }); 44 | ``` 45 | 46 | 3、debounce过滤操作符:过滤掉由Observable发射的速率过快的数据。如果在一个指定的时间间隔过去了仍旧没有发射一个, 那么它将发射最后的那个。 47 | 48 | - 输入框搜索,在指定事件内才收到字体的变化。如果是监听EditText的内容变化事件,每次变化都请求网络,将造成资源的浪费。 49 | 50 | ```Java 51 | RxTextView.textChangeEvents(editText) 52 | .debounce(500, TimeUnit.SECONDS)// default Scheduler is Computation 53 | .filter(new Func1() { 54 | @Override public Boolean call(TextViewTextChangeEvent 55 | textViewTextChangeEvent) { 56 | //过滤掉没有变化的内容,false不发射。 57 | return ; 58 | } 59 | }) 60 | .subscribe(new Action1() { 61 | @Override public void call(TextViewTextChangeEvent 62 | textViewTextChangeEvent) { 63 | Log.e(TAG,textViewTextChangeEvent.text().toString()); 64 | } 65 | }); 66 | ``` 67 | 68 | 4、interval周期操作。不需要该操作的时候取消订阅。 69 | 70 | - 和service搭配后台更新数据 71 | 72 | ```Java 73 | CompositeSubscription cs = new CompositeSubscription(); 74 | Subscription subscribe = Observable.interval(0, 2000, TimeUnit.MILLISECONDS) 75 | .map(new Func1() { 76 | @Override public String call(Long aLong) { 77 | return aLong + "time"; 78 | } 79 | }) 80 | .take(8) //周期发射数据的次数 81 | .doOnSubscribe(new Action0() { 82 | @Override public void call() { 83 | Log.e(TAG, "Start-----MainThread"); 84 | } 85 | }) 86 | .subscribe(new Action1() { 87 | @Override public void call(String s) { 88 | Log.e(TAG, "Next-----NoMainThread"); 89 | } 90 | }); 91 | cs.add(subscribe); 92 | cs.unsubscribe(); 93 | ``` 94 | 95 | 5、RxJava代替Timer做定时任务。 96 | 97 | - 只推荐做一次的任务,即xx时间后做oo操作,循环任务用interval。 98 | 99 | ```Java 100 | Observable.timer(2000, TimeUnit.MILLISECONDS) 101 | .subscribe(new Action1() { 102 | @Override public void call(Long aLong) { 103 | Log.d(TAG, "2秒后执行"); 104 | } 105 | }); 106 | ``` 107 | 108 | 6、RxJava使用combineLatest做注册界面。 109 | 110 | - combineLatest将多个Observable发射的数据,使用一个函数结合每个Observable发射的数据,发射新的数据。 111 | - 第一次发射数据时只有当每个Observable都发射了一个数据才会发射数据。之后任何一个Observable发射了数据都会使用一个函数结合后重新发射数据。zip只有多个Obserable都发射数据才会发射数据。 112 | 113 | ```Java 114 | Observable emailChangeObservable = 115 | RxTextView.textChanges(_email).skip(1); 116 | Observable passwordChangeObservable = 117 | RxTextView.textChanges(_password).skip(1); 118 | Observable.combineLatest( 119 | emailChangeObservable, 120 | passwordChangeObservable, 121 | new Func2() { 122 | @Override public Boolean call( 123 | //定义发射数据的函数。 124 | CharSequence newEmail, 125 | CharSequence newPassword) { 126 | boolean emailValid = !isEmpty(newEmail) && 127 | EMAIL_ADDRESS.matcher(newEmail).matches(); 128 | if (!emailValid) { 129 | _email.setError("Invalid Email!"); 130 | } 131 | 132 | boolean passValid = !isEmpty(newPassword) && newPassword.length() > 8; 133 | if (!passValid) { 134 | _password.setError("Invalid Password!"); 135 | } 136 | return emailValid&&passValid; 137 | } 138 | }) 139 | .subscribe(new Action1() { 140 | @Override public void call(Boolean aBoolean) { 141 | Log.d(TAG, "输入正确:"+aBoolean); 142 | } 143 | }); 144 | ``` 145 | 146 | 7、使用merge合并两个Observable,例如两组数据一组来自本地,另一组来自网络。 147 | 148 | - merge会将多Obserable合并成一个新的Obserable然后将数据一一进行发射。 149 | 150 | ```Java 151 | Observable.merge(getCachedData(),getFreshData()) 152 | .subscribe(new Subscriber() { 153 | @Override public void onCompleted() { 154 | Log.d(TAG, "done loading all data"); 155 | } 156 | 157 | @Override public void onError(Throwable e) { 158 | Log.d(TAG, "something wrong"); 159 | } 160 | 161 | @Override public void onNext(String s) { 162 | Log.d(TAG, "收到的数据"+s); 163 | } 164 | }); 165 | ``` 166 | 167 | 8、 -------------------------------------------------------------------------------- /android studio 使用技巧.md: -------------------------------------------------------------------------------- 1 | ##android studio 设置和快捷键 2 | 3 | ### 设置代码风格 4 | 5 | 可通过Editor->Code Style -> Java设置你喜欢的格式。 6 | 7 | 个人喜好 8 | 9 | Wrapping and Braces 选中 10 | 11 | - Simple Method in one lines 。 12 | - Simple Class in one lines。 13 | - Ensure right margin is not exceeded。 14 | - Extends/implements keyword -> wrap if long 15 | - Class Annotations Methodd Annotation -> Do not wrap 16 | 17 | Code Generation : 18 | 19 | - field 前缀加m,静态field前缀加s。 20 | 21 | 22 | Tabs and Indent: 23 | 24 | - Tab size :2 ; 25 | - Indent :2 26 | - continuation Indent :4; 27 | 28 | JavaDoc: 29 | 30 | - Other -> Wrap at rigth magin 31 | - Other -> Do not wrap one line comments 32 | - Other -> Preserve lines feeds 33 | 34 | ### 提高开发效率的常用功能 35 | 36 | - attach debug :可在快捷工具栏中找到。使开发者可以在不编辑代码的情况下惊醒debug。 37 | - Structurally Search :可在全局查找每个类中的相关代码段,非常好用。可以使用按两下Shift,输入Search Structurally 打开,然后进行全局搜索。 38 | - 自定义代码模版:Editer -> Live Template -> 在Class的File模板里加入即可,通过ctrl + j即可弹出模板。 39 | - 自定已文件头:Editer -> File Template 40 | 41 | ```java 42 | /* 43 | * ${NAME} ${DATE} ${HOUR}:${MINUTE} 44 | * Copyright (c) ${YEAR} Koterwong All right reserved 45 | */ 46 | ``` 47 | 48 | 49 | ### 设置 50 | 51 | - 修改编辑器的字体大小 Setting -> Editor -> Color & Fonts 52 | - 代码提示对大小是否敏感 -> Editot -> General -> CodeCompletion -> Case Sensitive Completion 53 | - 鼠标悬浮提示:Setting -> Editor || Other -> Show quick doc on mouse move Delay ; 54 | - 代码自动提示:Setting -> Editor -> Code Completion 注意大小写是否敏感 55 | - 自动导包 :Editer -> General -> auto import 56 | - 自动编译: compiler - make potject automatically 57 | - 设置编码:encodeing - utf-8 58 | - Gradle setting - 勾选offline work。不去更新下载 59 | - show methed separator 显示方法分割线 ch 60 | 61 | 62 | 63 | - f8 单步调试,add to watch 64 | 65 | - f7 进入要调试的方法 66 | - shift + f8(或点击绿色按钮)跳到下一个断点 67 | `Franes`方法调用栈 68 | `Variables` 变量信息 69 | - 查看方法引用的位置:选中方法->右键->Find Usages。 70 | - 设置Grandle路径:Build -> Build Tools -> Grandle。 71 | - 显示方法分割线:Editor -> General -> Appearance -> show method separators。 72 | - 显示行数:Editor -> General -> Appearance ->show line number。 73 | - logcat日志自动换行 -> 在logcat日志输出左侧工具栏点击换行图标即可。 74 | 75 | ###常用的快捷键 76 | 77 | - Ctrl + B :快速进入方法。 78 | 79 | 80 | - Ctrl + E :显示最近修改的文件。 81 | 82 | 83 | - **Ctrl+F11:加BookMark,简直是非常有用的功能,不过需要去设置添加一下跳转下一个书签或上一个书签的快捷键才能发挥出该功能真正强大。** 84 | 85 | - **ALT + 2 :打开书签,可搭配上面的BookMark使用非常方面。** 86 | 87 | - Ctrl+F12 : 输入关键字快速定位指定的变量或方法,支持模糊搜索。 88 | 89 | - Ctrl +Alt+左箭头或右箭头:返回前一个或下一个光标的位置,在想回溯阅读位置的时候非常有用 90 | 91 | - **Ctrl + J 弹出一些代码模版fori** 92 | 93 | `ifn` `inn` 判断参数时候等于(不等于)null 94 | `fbc` 快速的findViewById 95 | 96 | `visible` 设置view的显示GONE 97 | 98 | `Toast` 打印Toast 99 | `IntentView` 快速的Intent 100 | 101 | - **Ctrl + Alt + T 弹出一些包围结构,例如if..else while for 。Surround With。** 102 | 103 | - Ctrl + [或]可以跳到大括号的开头结尾 104 | 105 | - Ctrl + Tab / Ctrl + E 最近打开的文件,在文件的选项卡中进行切换; 106 | 107 | - F2 或 Shift+F2 高亮错误或警告快速定位 108 | 109 | - Ctrl + plus 打开关闭方法 110 | 111 | - Ctrl + 1 打开关闭工程目录 112 | 113 | - logt 快速导入log标签 114 | 115 | - logd 快速打印debug级别的日志 116 | 117 | - logm 生成参数的log 118 | 119 | - Ctrl + Alt + l :格式化代码 120 | 121 | - Ctrl + Alt + 空格:代码提示 122 | 123 | - Ctrl + 空格:代码提示 124 | 125 | - Ctrl +shift + ↑ 将光标处的代码移动到上一行 126 | 127 | - Alt + F7 : 查找变量在哪里使用过 128 | 129 | - Alt+ ↑ 在方法间快速移动定位 130 | 131 | - Ctrl + N 快速打开工程中的类 132 | 133 | - Ctrl + H 显示类结构图 134 | 135 | - Ctrl + Alt + H 查看一个方法的调用 136 | 137 | - Ctrl + shift + N 快速打开一个xml文件 138 | 139 | - Ctrl + Alt + F 变量全局 140 | 141 | - Ctrl + Alt + V 返回引用 142 | 143 | - Ctrl +shift + i 查看一个方法在类中的实现,弹出窗口的方式 144 | 145 | - Ctrl + M 提取代码到新的方法中 146 | 147 | - Ctrl + U 查看一个类的父类 148 | 149 | - Ctrl + Alt + O:管理包 150 | 151 | - Alt + Enter 自动修正。Ctrl+1 152 | 153 | - Ctrl + Shift + Enter 自动补全代码 154 | 155 | - Ctrl + P 显示方法参数 156 | 157 | - Ctrl + X 删除行 158 | 159 | - Ctrl + D 复制行 160 | 161 | - Ctrl + O 重写实现方法。 162 | 163 | - Ctrl + Q 可以看JavaDoc 164 | 165 | - Ctrl + y 删除行 166 | 167 | - Ctrl + R 替换文本 168 | 169 | - Ctrl + F 查找文本 170 | 171 | - Ctrl+/ 或 Ctrl+Shift+/ 注释(// 或者 ) 172 | 173 | - Alt+Shift+C 对比最近修改的代码 174 | 175 | - Shift + F6 重构-重命名 176 | 177 | - Shift + Click可以关闭文件 178 | 179 | - Ctrl+Shift+Backspace可以跳转到上次编辑的地方 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /android-tips.md: -------------------------------------------------------------------------------- 1 | ## View相关 2 | 3 | - `view.isShown()`判断view和父View是在显示。 4 | 5 | 6 | - 给View控件设置`clickable="true"`则该控件就会消费点击事件。 7 | - View类中的`callOnClick(),performClick(),performLongClick(),`用于触发View的点击事件。 8 | - 关于?attr/xxx 的解释,当我们的activity或者dialog的布局文件通过?attr/xxx 引入了某个属性这个属性的值就是我们当前activity或者dialog的theme主题样式的某个属性的值。 9 | #### TextView、EditText 10 | - TextView字放大后字体变细:`android:fontFamily="sans-serif-light"` 11 | - TextView.setError()。可以设置textView的错误图片,和弹出错误信息。 12 | - TextView:`append(CharSequence)`方法,添加文本。一些特殊文本直接用+连接会变成String 13 | - TextView你真的了解吗?[我是链接](http://blog.csdn.net/sdkfjksf/article/details/51317204) 14 | - 设置EditText字体`mEditText.setTypeface(Typeface.createFromAssets(getAssets(),1.ttf)` 15 | - fab设置背景色`backgroundTint`。 16 | ## 系统相关 17 | 18 | - 直接发送短信API 19 | 20 | > 权限`send_mss`部分机器需要`read_phone_state` 21 | 22 | ```java 23 | //直接使用发送短信的api,无需启动系统的短信应用 24 | SmsManager sm = SmsManager.getDefault(); 25 | ArrayList sms = sm.divideMessage(content); 26 | for (String string : sms) { 27 | //arg0:目标号码 28 | //arg1:短信中心号码,null表示使用默认 29 | //arg2:短信正文 30 | sm.sendTextMessage(phone, null, string, null, null); 31 | } 32 | ``` 33 | - 放个知乎连接[Android开发工具和技巧](https://www.zhihu.com/question/27140400) -------------------------------------------------------------------------------- /dagger2.md: -------------------------------------------------------------------------------- 1 | #### 1、简单介绍 2 | 3 | Dagger2是一款快速的依赖注入框架。 4 | 5 | #### 2、相关概念 6 | 7 | 依赖注入:目标类中所依赖的类的初始化不是通过new 关键字创建出来,而是通过技术手段,将初始化好的类自动注入到目标类中。 8 | 9 | 控制反转:用来削弱类之间的耦合关系,又称为依赖倒置原理,是面向对象的一种全新设计模式。可以理解为依赖注入的实现方式(个人理解)。 10 | 11 | 关于dagger2一些抽象概念的讲解,嗯,讲解的很好: 12 | 13 | [Android:dagger2让你爱不释手-基础依赖注入框架篇](http://www.jianshu.com/p/cd2c1c9f68d4) 14 | 15 | [Android:dagger2让你爱不释手-重点 概念讲解、融合篇](http://www.jianshu.com/p/1d42d2e6f4a5) 16 | 17 | [Android:dagger2让你爱不释手-终结篇](http://www.jianshu.com/p/65737ac39c44) 18 | 19 | 这里小结一下: 20 | 21 | - Inject:标注目标类的属性成员和被注入类的构造函数。在目标类中标注属性成员变量表示我们要这个类的实例;在标注被注入类的构造函数的时候,表示我们可以通过component将该类的实例注入到相关的目标类中。 22 | - Module:可以理解为简单工厂,用于生成被注入类的实例。上面通过Inject标注构造的方式不需要使用Module去提供实例,但是第三方库需要注入的时候就必须使用Module实现。在Module中我们使用@Provides注解对module中提供实例的方法进行标注,Component需要那个实例就会查找Module中使用@Provides标注的方法。 23 | - Component: 注射器,连接器。连接目标类和要被注入类的实例,同时也具有管理该component的module功能,Component中的modules注解可以把Module加入Component,modules可以加入多个Module。一般我们的应用程序要有一个全局的Component用来管理整个App都用到的类实例(这些全局的类实例一般都是单例的)。其次,对于每一个页面也会对应一个Component,这当然不是必须,有些页面依赖的类实例是一样的也可以公用一个Component。 24 | - Provides:Module中创建依赖类实例的方法使用该注解标注。 25 | - Qualifier:用于解决依赖注入的迷失。 26 | - Scope:是一个需要我们自定义注解作用域,理解起来很困难,而且很多文章也讲的不是清楚。大部分文章总结Scope的作用是:可以通过自定义的Scope注解,来限定通过Module和Inject方法创建的类的生命周期和目标类的生命周期保持一致。其实Scope根本没有这些功能,它的真正作用是**管理Component之间的组织方式**。不管是依赖方式还是包含方式,都有必要用自定义的Scope注解来标记这些Component,这些注解最好不要一样了,不一样能更好的体现出Component的组织方式。还有编辑器会检查有依赖关系或包含关系的Component,若发现有Component没有自定义的Scope注解,则会报错;*另外Scope还能更好的管理Component和Module的匹配关系*;提高代码的可读性;后面两点在Singleton中有体现。 27 | - Singleton:Singleton并没有创建单例的能力,它是Scope的一种实现。要想实现单例,我们需要在Module中定义创建实例的方法(Inject的方式不行),使用全局的AppComponent管理Module,保证AppComponent只初始化一次。既然Singleton不能创建单例,那么它的作用是什么呢?保证AppComponent和Module是匹配的,如果AppComponent的Scope个Module的Scope不一样,那么编译时就会报错。提高代码的可读性,让程序员明白Module中创建的类是单例的。 28 | 29 | Component:Component是注入依赖实例的关键的关键,在目标类中使用Inject标注要注入的类的时候,在调用inject方法之后,Component会向自己管理的Module中查找用Provides标志的创建实例的方法,如果没有就会查找用Inject标记的相关构造函数。Module的优先级高于用Inject标注的构造函数。如果在Module找到相关实例,就会停止到Inject标注的构造函数中查找。 30 | 31 | 依赖注入迷失:Component在寻找目标类所依赖的实例时(在Module的Provides或者Inject标注的构造函数),这里假设在Inject标注中寻找,如果多个构造函数是用Inject进行标注,就会造成依赖注入迷失。这个时候就需要使用Qualifier(限定符)告诉Component使用那个构造函数进行实例化,通常我们是用自定义的Qualifier注解标注目标类的属性和Inject的够着函数(@Named注解就是Dagger2的默认实现方式)。这种实例化的方式相当与给使用Inject标志的构造函数加上一个ID。(使用方法后面介绍)。 32 | 33 | **共享类实例,Component提供类的实例,如果Component想把其他Component注入到自己的目标类中,假设Activity的Component需要依赖到全局Component,那么这个时候就涉及共享类实例的问题了** 34 | 35 | **Component之间的组织关系** 36 | 37 | - dependencies :一个Component可以依赖一个或者多个Conmponent。 38 | - SubComponent: 一个Component可以包含一个或多个Component,被包含的Componnet还可以继续包含其他的Component。SubComponent就是被包含的体现。 39 | - 继承:不是解决类共享实例的方式存在的,而是为了更好的管理、维护Component之间的关系,把Component一些共有的方法抽取到父类中,然后之类继承。 40 | 41 | **一次依赖注入具体步骤如下:** 42 | 43 | - 1、在Module中查找是否有创建该实例的方法。 44 | - 2、Module中如果存在创建实例的方法,那么查看该方法是否存在参数。 45 | - 2.1、若存在参数,则按步骤1开始依次初始化每个参数。 46 | - 2.2、若不存在参数,则直接初始化该实例,整个依赖注入过程完成。 47 | - 3、Module中不存在创建实例的方法,则查找Inject注解的构造函数。 48 | - 构造函数存在参数,从步骤1开始初始化每个参数。 49 | - 构造函数不存在参数,直接初始化该类实例,整个依赖注入过程完成。 50 | 51 | #### 3、使用步骤 52 | 53 | (1)配置grandle 54 | 55 | 在工程目录下的build.grandle添加。 56 | 57 | ```java 58 | buildscript { 59 | repositories { 60 | mavenCentral() 61 | } 62 | dependencies { 63 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 64 | } 65 | } 66 | ``` 67 | 68 | 在module的build.grandle中添加。 69 | 70 | ```JAVA 71 | // Apply plugin 72 | apply plugin: 'com.neenbedankt.android-apt' 73 | 74 | // Add Dagger dependencies 75 | dependencies { 76 | compile 'com.google.dagger:dagger:2.x' 77 | apt 'com.google.dagger:dagger-compiler:2.x' 78 | } 79 | ``` 80 | 81 | #### 4、依赖注入 82 | 83 | ##### 4.1@Inject基本注入: 84 | 85 | 使用@Inject完成最基本的注入方式。 86 | 87 | 要注入的类: 88 | 89 | ```java 90 | public class Book { 91 | //使用@Inject注解标注要注入类的构造函数。 92 | @Inject public Book() { 93 | } 94 | } 95 | ``` 96 | 97 | 目标类: 98 | 99 | ```Java 100 | public class MainActivity extends AppCompatActivity { 101 | //在目标类中同样使用@Inject注入依赖的类。但是这时并不会赋值。 102 | @Inject Book book; 103 | } 104 | ``` 105 | 106 | 使用Component(注射器、连接器)连接注入类和目标类。 107 | 108 | ```java 109 | @Component 110 | public interface AppComponent { 111 | void inject(MainActivity mainActivity); 112 | } 113 | ``` 114 | 115 | 完成以上步骤,需要我们手动make project生成相关类,然后进行注入。在目标类中代码如下: 116 | 117 | ```java 118 | @Inject Book book; 119 | @Override protected void onCreate(Bundle savedInstanceState) { 120 | ... ... 121 | //注入依赖的类,这时候我们使用inject标签注入的类就初始化完成了。 122 | DaggerAppComponent.builder().build().inject(this); 123 | } 124 | ``` 125 | 126 | Component连接了我们的目标类和注入类。上述注入方式的工作原理为: 127 | 128 | - 在调用`inject(this)`方法的时候,component会查找目标类使用@Inject标注的属性。 129 | - 查找属性使用@Inject标注的构造函数,并创建实例。 130 | - 对属性进行赋值。 131 | 132 | ##### 4.2使用@Module的方式注入: 133 | 134 | Module是一个静态工厂,用于生产目标类所依赖的实例。 135 | 136 | ```Java 137 | @Module 138 | public class AppModule { 139 | private Context context; 140 | //通过Module的构造函数传参。 141 | public AppModule(Context context) { 142 | this.context = context; 143 | } 144 | //Provides标记方法提供依赖类。@Singleton要 145 | @Singleton @Provides 146 | Book provideBook() { 147 | return new Book(context); 148 | } 149 | } 150 | ``` 151 | 152 | Component 153 | 154 | ```Java 155 | @Singleton 156 | @Component(modules = AppModule.class) 157 | public interface AppComponent { 158 | //直接返回要注入的实例,这样就可以在目标类中直接获取。也可以使用Void inject(Object obj)的方式。 159 | //两种情况的适用场景不同。 160 | Book getBook(); 161 | //void inject(MyApp app); 这种情况使用于在一个类中进行注入, 162 | } 163 | ``` 164 | 165 | 目标类中, 166 | 167 | ```java 168 | //获取Component对象。我们不使用 169 | AppComponent daggerAppComponent = DaggerAppComponent 170 | .builder() 171 | .appModule(new AppModule(this)) 172 | .build(); 173 | //同过Component获取实例。我们可以把component对象共享出去,这样在其他类中我们就可以获取book实例。 174 | Book book = daggerAppComponent.getBook(); 175 | ``` 176 | 177 | ##### 4.3 使用Qualifier解决依赖注入迷失 178 | 179 | Module: 180 | 181 | ```java 182 | @Module 183 | public class AppModule { 184 | public Context context; 185 | public AppModule(Context context) { 186 | this.context = context; 187 | } 188 | //这里有多个提供提供Book的方法, 189 | @Provides @Named(value = "one") 190 | Book providerBookOne() { 191 | return new Book(); 192 | } 193 | @Provides @Named(value = "two") 194 | Book providerBookTwo() { 195 | return new Book(context); 196 | } 197 | } 198 | ``` 199 | 200 | @Component 201 | 202 | ```java 203 | @Component(modules = AppModule.class) 204 | public interface AppComponent { 205 | void inject(MyApp myApp); 206 | } 207 | ``` 208 | 209 | @注入。 210 | 211 | ```java 212 | //这里注入的时候,通过自定义的Qualifier注解告诉Component应该实例化那个实例。 213 | @Named(value = "one") 214 | @Inject Book one; 215 | @Named(value = "two") 216 | @Inject Book two; 217 | 218 | @Override public void onCreate() { 219 | super.onCreate(); 220 | DaggerAppComponent.builder() 221 | .build() 222 | .inject(this); 223 | } 224 | ``` 225 | 226 | ##### 4.4、@Scope 定义作用域 227 | 228 | 这里将Scope理解为用来管理Component实例和App或者Activity的生命周期一致就好。 229 | 230 | ```java 231 | //自定义Scope,这里功能个Singleton类似 232 | @Scope 233 | @Documented 234 | @Retention(RetentionPolicy.RUNTIME) 235 | public @interface PerApp { 236 | } 237 | ``` 238 | 239 | Module 240 | 241 | ```java 242 | @Module 243 | public class AppModule { 244 | private final Context context; 245 | public AppModule(Context context) { 246 | this.context = context; 247 | } 248 | //这里通过@PerApp标注,在Component中也必须有使用@PreApp与之对应。 249 | @Provides @PerApp Context providerContext() { 250 | return context; 251 | } 252 | } 253 | ``` 254 | 255 | Component 256 | 257 | ```Java 258 | //这里Component同样使用PerApp标志,不然编辑会检查错误。 259 | @PerApp 260 | @Component(modules = AppModule.class) 261 | public interface AppComponent { 262 | Context getContext(); 263 | } 264 | ``` 265 | 266 | 注入: 267 | 268 | ```Java 269 | private AppComponent appComponent; 270 | @Override public void onCreate() { 271 | super.onCreate(); 272 | appComponent = DaggerAppComponent 273 | .builder() 274 | .appModule(new AppModule(this)) 275 | .build(); 276 | Context context = appComponent.getContext(); 277 | } 278 | public AppComponent getAppComponent() { 279 | return appComponent; 280 | } 281 | ``` 282 | 283 | 注意点: 284 | 285 | - 一个Module中只能存在一种Scope。 286 | - Scope标注的Component和所依赖的Component的Scope不能一样。 287 | 288 | ##### 4.5、依赖Component 289 | 290 | 有时候,我们的Component可能还依赖另外一个Component所管理的Module产生的实例。例如下面的例子,展示了Activity的component依赖到全局的AppComponent,然后注入全局的AppComponent提供的实例。 291 | 292 | @AppComponent: 293 | 294 | ```java 295 | @PerApp 296 | @Component(modules = AppModule.class) 297 | public interface AppComponent { 298 | Context getContext(); 299 | ToastUtils getToastUtils(); //提供全局的Toastutils 300 | } 301 | ``` 302 | 303 | @AppModule: 304 | 305 | ```java 306 | @Module 307 | public class AppModule { 308 | private final Context context; 309 | public AppModule(Context context) { 310 | this.context = context; 311 | } 312 | @Provides @PerApp Context providerContext() { 313 | return context; 314 | } 315 | //生产ToastUtils实例。 316 | @Provides ToastUtils providerToastUtils() { 317 | return new ToastUtils(context); 318 | } 319 | } 320 | ``` 321 | 322 | @MainComponent: 323 | 324 | 可以看到,MainComponent依赖到了AppComponent。 325 | 326 | ```java 327 | @PerActivity 328 | @Component(dependencies = AppComponent.class ,modules = {MainModule.class,ActivityModule.class}) 329 | public interface MainComponent { 330 | void inject(MainActivity mainActivity); 331 | } 332 | ``` 333 | 334 | @MainModule: 335 | 336 | 在MainModule这里提供了Book实例,我们要做的就是通过注入全局的Toast对象然后弹出Book的书名信息。 337 | 338 | ```java 339 | @Module 340 | public class MainModule { 341 | @Provides @PerActivity Book providesBook() { 342 | return new Book("android开发艺术探索"); 343 | } 344 | } 345 | ``` 346 | 347 | 在MainActivity中注入ToastUtils实例和Book实例。 348 | 349 | ```java 350 | @Inject ToastUtils toastUtils; 351 | @Inject Book book; 352 | 353 | @Override 354 | protected void onCreate(Bundle savedInstanceState) { 355 | super.onCreate(savedInstanceState); 356 | setContentView(R.layout.activity_main); 357 | 358 | MainComponent mainComponent = DaggerMainComponent.builder() 359 | .appComponent(getAppComponent()) //注意 360 | .mainModule(new MainModule()) 361 | .build(); 362 | mainComponent.inject(this); 363 | 364 | Button button = new Button(this); 365 | button.setLayoutParams(new ViewGroup.LayoutParams(200, 200)); 366 | //这里我们通过点击button弹出book.name的信息。 367 | button.setOnClickListener(new View.OnClickListener() { 368 | @Override public void onClick(View view) { 369 | toastUtils.showToast("BookName:" + book.name ); 370 | } 371 | }); 372 | ((ViewGroup) findViewById(android.R.id.content)).addView(button); 373 | 374 | } 375 | ``` 376 | 377 | -------------------------------------------------------------------------------- /imgs/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/imgs/preview.gif -------------------------------------------------------------------------------- /res资源目录.md: -------------------------------------------------------------------------------- 1 | ##Android:res资源目录 2 | 3 | - values 4 | - strings:字符和字符数组资源。 5 | - ids:存放控件的id。预编译性能更高。 6 | - styles:Manifest主题和控件的样式。 7 | - colors:定义颜色资源。方便统一管理。 8 | - dimens:定义尺寸 9 | - drawable:图像 10 | - 定义动画 11 | 12 | 18 | 19 | 20 | - shape 21 | 22 | 矩形(rectangle)、椭圆形(oval)、线性形状(line)、环形(ring) 23 | 1. 在android:shape="ring" 24 | android:innerRadius 尺寸,内环的半径。 25 | android:thickness 尺寸,环的厚度 26 | android:useLevel boolean值,如果当做是LevelListDrawable使用时值为true,否则为false. 27 | 28 | 2. android:shape="rectangle" 29 | 36 | 44 | 47 | 51 | -------------------------------------------------------------------------------- /个人使用的AndroidStudio插件.md: -------------------------------------------------------------------------------- 1 | 1. GsonFormat:快速将Java字符串转换成JavaBean。 2 | 2. Android ButterKnife Zelezny:在Activity、fragment、ViewHolder中,左键布局文件,可快速生成butterknife注解。 3 | 3. Android Code Generator :根据布局文件快速生成对应的Activity,Fragment,Adapter,Menu。 4 | 4. Android parceable code generator : 快速生成parcelable接口的代码。 5 | 5. Android Styler :根据XML自动生成style的插件。选中要生成styler的代码片段,然后使用快捷键 Ctrl + Shift + D 输入Style的名称即可。 6 | 6. ECTranslation :即使翻译插件。 7 | 7. codota:代码搜索神器。 8 | 8. Android Material Design icon Generator:生成Materail Design图标。 9 | 9. [Android Studio Setting ](https://github.com/xinghongfei/android-studio-setting) :Sublime主题的插件。 10 | 10. ​ADB WIFI :在不Root的情况下使用wifi调用应用。 11 | 12 | -------------------------------------------------------------------------------- /个人使用的开发软件.md: -------------------------------------------------------------------------------- 1 | 1、SourceInsight:不多说,看源码的神器。 2 | 3 | 下载地址:[SourceInSight](http://www.sourceinsight.com/down35.html) 4 | 5 | ``` 6 | 注册码: 7 |     SI3US-205035-36448 8 |     SI3US-466908-65897 9 |     SI3US-368932-59383 10 |     SI3US-065458-30661 11 |     SI3US-759512-70207 12 | ``` 13 | 14 | 使用方法:选择Project -> new Project 之后根据操作选择项目目录添加进来即可。如果项目比较大选择add tree 添加源工程的索引即可。 15 | 16 | 2、[locecap:录屏软件](http://www.cockos.com/licecap/) 。 17 | 18 | 3、[GifCam:另一款录制软件](http://blog.bahraniapps.com/gifcam/) 19 | -------------------------------------------------------------------------------- /你应该了解的CrashHandler用法.md: -------------------------------------------------------------------------------- 1 | Android应用开发不可避免的会发生crash,无论我们的程序写的多么完美,总是无法避免crash的发生,可能是由于系统的bug,也可能是不充分的机型适配和糟糕的网络情况。当Crash发生的时候,系统会kill掉我们的程序,现象就是闪退或者提示用户程序已经停止运行,造成不好的用户体验。对于我们开发这来说,我们要尽可能的避免crash的发生,即使Crash发生我们也能够即时的获取crash信息,并修复我们程序的问题。 2 | 3 | 在Thread类中提供了这样一个方法`Thread.setDefaultUncaughtExceptionHandler();`,根据字面的上的意思我们可以了解到它的作用应该是设置默认的异常处理器。我们可以看一下这个方法默认实现。 4 | 5 | ```java 6 | /** 7 | * Sets the default uncaught exception handler. This handler is invoked in 8 | * case any Thread dies due to an unhandled exception. 9 | * 10 | * @param handler 11 | * The handler to set or null. 12 | */ 13 | public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler handler) { 14 | Thread.defaultUncaughtHandler = handler; 15 | } 16 | ``` 17 | 18 | 可以看到,该方法可以设置默认的异常信息处理器,当crash发生的时候系统会回调`UncaughtExceptionHandler`中的`uncaughtException`方法。那么我们就可以在程序崩溃的时候,收集崩溃日志,将崩溃日志写入到sd卡并且在适当的时候上传到服务器,这样我们开发人员就可以根据崩溃日志尽快修复bug。系统默认处理崩溃信息是很不友好的,我们可以在崩溃发生时,弹出对话框告诉用户程序发生了异常需要推出。甚至我们可以通过一些手段在程序推出的时候,马上重启我们的程序到值指定的页面。 19 | 20 | 这里提供了一个默认的异常处理方案,可以参考。 21 | 22 | ```java 23 | public class CrashHandler implements Thread.UncaughtExceptionHandler { 24 | private final String TAG = this.getClass().getSimpleName(); 25 | private static final boolean DEBUG = true; 26 | private static final String DATA_FORMAT = "yyyy-MM-dd_HH:mm"; 27 | //可以设置自己的保存路径 28 | private static final String PATH = Environment.getExternalStorageDirectory().getAbsolutePath() 29 | + "/log"; 30 | 31 | private String mPath = null; 32 | private Context mContext; 33 | private static final String FILE_NAME = "crash"; 34 | private static final String FILE_NAME_SUFFIX = ".trace"; 35 | 36 | private static CrashHandler sCrashHandler = new CrashHandler(); 37 | private Thread.UncaughtExceptionHandler mDefaultCrashHandler; 38 | 39 | private CrashHandler() {} 40 | 41 | public CrashHandler get() { 42 | return sCrashHandler; 43 | } 44 | 45 | public void init(Context context) { 46 | this.init(context, PATH); 47 | } 48 | 49 | public void init(Context context, String filePath) { 50 | if (filePath == null) { 51 | throw new IllegalStateException("need to specify the exception information save the path"); 52 | } 53 | mPath = filePath; 54 | mContext = context.getApplicationContext(); 55 | mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); 56 | Thread.setDefaultUncaughtExceptionHandler(sCrashHandler); 57 | } 58 | 59 | @Override public void uncaughtException(Thread thread, Throwable throwable) { 60 | throwable.printStackTrace(); 61 | try { 62 | dumpExceptionToSDCard(throwable); 63 | uploadExceptionToService(throwable); 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | } 67 | 68 | if (mDefaultCrashHandler != null) { 69 | mDefaultCrashHandler.uncaughtException(thread, throwable); 70 | } else { 71 | Process.killProcess(Process.myPid()); 72 | } 73 | } 74 | 75 | private void dumpExceptionToSDCard(Throwable throwable) throws IOException { 76 | File dir = new File(mPath); 77 | if (!dir.exists()) { 78 | dir.mkdirs(); 79 | } 80 | long current = SystemClock.currentThreadTimeMillis(); 81 | String time = new SimpleDateFormat(DATA_FORMAT).format(new Date(current)); 82 | File file = new File(mPath, FILE_NAME + time + FILE_NAME_SUFFIX); 83 | try { 84 | PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file))); 85 | printWriter.print(time); 86 | dumpPhoneInfo(printWriter); 87 | printWriter.println(); 88 | printWriter.close(); 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | 94 | private void dumpPhoneInfo(PrintWriter printWriter) throws PackageManager.NameNotFoundException { 95 | PackageManager packageManager = mContext.getPackageManager(); 96 | PackageInfo packageInfo = packageManager.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); 97 | printWriter.println("AppVersion:" + packageInfo.versionCode); 98 | printWriter.println("OS Version:" + Build.VERSION.SDK_INT); 99 | printWriter.println("Vendor:" + Build.MANUFACTURER); //手机制造商 100 | printWriter.println("Model:" + Build.MODEL ); //手机型号 101 | printWriter.println("CPU ABI:" + Build.CPU_ABI); 102 | } 103 | 104 | private void uploadExceptionToService(Throwable throwable) { 105 | // if necessary ,upload the information to server 106 | } 107 | } 108 | ``` -------------------------------------------------------------------------------- /值得阅读的Android优秀文章.md: -------------------------------------------------------------------------------- 1 | ### 公众号文章: 2 | 3 | - [鸿洋](https://github.com/hongyangAndroid/hongyangWeixinArticles) 4 | - [2017年Android百大框架排行榜](http://www.cnblogs.com/jincheng-yangchaofan/articles/7018780.html) 5 | 6 | ### 基础 7 | 8 | - [Fragment全解析(一)那些年踩过的坑](http://www.jianshu.com/p/d9143a92ad94) 9 | - [Fragment全解析(二)正确的使用姿势](http://www.jianshu.com/p/fd71d65f0ec6) 10 | - [Fragment之我的解决方案](http://www.jianshu.com/p/fd71d65f0ec6) 11 | - [Android音乐播放模式切换-外放、听筒、耳机](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1007/3548.html) 12 | 13 | ### View相关 14 | 15 | - [教你步步为营掌握自定义View](http://www.jianshu.com/p/d507e3514b65) 16 | - [Android自定义View系列目录](http://www.idtkm.com/customview/android%E8%87%AA%E5%AE%9A%E4%B9%89view%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95/) 17 | - [真实项目运用-RecyclerView封装](http://www.jianshu.com/p/2f2996ef2c75) 18 | - [自定义Behavior的艺术探索-仿UC浏览器主页](http://www.jianshu.com/p/f7989a2a3ec2) 19 | - [Android自定义字体实践](http://www.jianshu.com/p/38f475fc07ad) 20 | 21 | ### WebView 22 | 23 | - [Android WebView常见问题及解决方案汇总](http://blog.csdn.net/t12x3456/article/details/13769731) 24 | - [Android WebView:性能优化不得不说的事](http://motalks.cn/2016/09/11/Android-WebView-JavaScript-3/) 25 | 26 | ### Android权限相关 27 | 28 | - [Androiud6.0权限管理](http://gold.xitu.io/post/57d5de3e2e958a00546a7465) 29 | 30 | ### MVP &&MVVM 31 | 32 | - [Android官方架构项目之MVP + DataBinding](http://www.jianshu.com/p/9c99a4bf7c9d) 33 | - [google 官方mvp实例的实践之mvp-databinding-Rxjava (一)](https://jiangchunyu.github.io/2016/09/06/google-%E5%AE%98%E6%96%B9mvp%E5%AE%9E%E4%BE%8B%E7%9A%84%E5%AE%9E%E8%B7%B5%E4%B9%8Bmvp-databinding-Rxjava-%E4%B8%80/) 34 | - [从零开始的Android新项目7 - Data Binding入门篇](http://blog.zhaiyifan.cn/2016/06/16/android-new-project-from-0-p7/) 35 | 36 | ### 多线程技术 37 | 38 | - [浅谈Android中的任务封装](http://www.jianshu.com/p/1f797ae150ef) 39 | 40 | ### Retrofit & OkHttp 41 | 42 | - [Retrofit 2.0 + OkHttp 3.0 配置](https://drakeet.me/retrofit-2-0-okhttp-3-0-config) 43 | 44 | ### HTTP 45 | 46 | - [关于HTTP协议,一篇就够了](http://www.cnblogs.com/ranyonsue/p/5984001.html) 47 | - [HTTP 协议入门](http://www.ruanyifeng.com/blog/2016/08/http.html) 48 | - [网络基础知识之 HTTP 协议](https://zhuanlan.zhihu.com/p/24913080) 49 | - [Fiddler 教程](http://www.cnblogs.com/FounderBox/p/4653588.html?utm_source=tuicool&utm_medium=referral) 50 | - [浅谈HTTPS以及Fiddler抓取HTTPS协议](http://www.jianshu.com/p/54dd21c50f21) 51 | - [抓包工具 Fiddler 相关知识总结](http://www.jianshu.com/p/4a8dae519efe) 52 | 53 | ### HotFix & Plugin 54 | 55 | - [Android插件化框架和热修复技术的资料收集和汇总](http://www.jianshu.com/p/1f797ae150ef) 56 | - [Android热修复 - 实现原理](http://ownwell.github.io/2016/08/30/android-hot-fix-introduce/) 57 | - [大话插件 - 动态加载插件Activity](http://www.woaitqs.cc/android/2016/08/17/launch-activity-without-registering-in-manifest.html) 58 | - [Android 动态链接库加载原理及 HotFix 方案介绍](http://www.woaitqs.cc/android/2016/08/17/launch-activity-without-registering-in-manifest.html) 59 | 60 | ### RxJava && RxAndroid 61 | 62 | - [给Android开发者的RxJava详解](http://gank.io/post/560e15be2dca930e00da1083) 63 | - [Rx处理服务器请求、缓存的完美封装](http://gank.io/post/560e15be2dca930e00da1083) 64 | - [使用Retrofit2+OkHttp3实现缓存处理](http://werb.github.io/2016/07/29/%E4%BD%BF%E7%94%A8Retrofit2+OkHttp3%E5%AE%9E%E7%8E%B0%E7%BC%93%E5%AD%98%E5%A4%84%E7%90%86/) 65 | - [我的RxJava源码解读笔记](https://zhuanlan.zhihu.com/p/23617414) 66 | - [重新理解响应式编程](http://www.jianshu.com/p/c95e29854cb1) 67 | 68 | ### React Native 69 | 70 | - [React Native - 入门指南](https://github.com/vczero/react-native-lesson) 71 | - [React-Native学习指南汇集了各类react-native学习资源、开源App和组件](https://github.com/reactnativecn/react-native-guide) 72 | 73 | 74 | - [React Native Android 通信原理](https://longv2go.github.io/2016/02/02/react-android-%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86.html) 75 | - [从Java层看React-Native通信原理](http://gold.xitu.io/post/57d4e67fda2f600059f48e11) 76 | - [React Native Android 从学车到补胎和成功发车经历](http://blog.csdn.net/yanbober/article/details/53071792) 77 | - [从Android到React Native开发(一、入门)](http://www.jianshu.com/p/97692b1c451d) 78 | 79 | ### DI 80 | 81 | - [理解Android中的注解与反射](http://www.jianshu.com/p/d4978bbce12a) 82 | 83 | ### 源码解析 84 | 85 | - [OKHttp源码解析](http://www.jianshu.com/p/27c1554b7fee) 86 | - [OkHttp3源码解析](http://www.jianshu.com/p/aad5aacd79bf) 87 | - [Fresco源码解析](https://github.com/desmond1121/Fresco-Source-Analysis) 88 | 89 | ### 性能优化 90 | 91 | - [手机QQ及Qzone速度优化实践 : 有种速度让你望尘莫及](http://mp.weixin.qq.com/s?__biz=MzA4Nzg5Nzc5OA==&mid=2651661643&idx=1&sn=ed4fd90350045cefc38b7436f94193a1&scene=0#wechat_redirect) 92 | - [ 那些你不知道的APK 瘦身,让你的APK更小](http://blog.csdn.net/vfush/article/details/52266843) 93 | - [App瘦身最佳实践](http://www.jianshu.com/p/8f14679809b3) 94 | 95 | 96 | ### Android进程 97 | 98 | - [Android进程绝杀技--forceStop](http://gityuan.com/2016/10/22/force-stop/) 99 | 100 | ### 数据库 101 | 102 | - [如果有人问你数据库的原理,叫他看这篇文章](http://blog.jobbole.com/100349/) 103 | 104 | ### 其它 105 | 106 | - [你应该知道的Http基础知识](http://www.jianshu.com/p/e544b7a76dac) 107 | - [Https是如何工作的](http://blog.csdn.net/dd864140130/article/details/52598107) 108 | - [Gradle 完整指南(Android)](http://www.jianshu.com/p/9df3c3b6067a) 109 | - [重新认识AndroidStudio和Gradle,这些都是你应该知道的](http://mp.weixin.qq.com/s?__biz=MzIwNjQ1NzQxNA==&mid=2247483946&idx=1&sn=da2458e2e6cd87115adec58ac0b0d3c6&chksm=97201d03a05794150a48bf3c0586b11c355c541dcd5b0f76dcad7fd1304d080459e6564d8298&mpshare=1&scene=1&srcid=1017GVivyfq4lSBaNEpMCXJu#rd) 110 | 111 | ### 视频直播 112 | 113 | - [Android中直播视频技术探究之---基础知识大纲介绍](http://blog.csdn.net/jiangwei0910410003/article/details/51871358) 114 | - [Android中直播视频技术探究之---摄像头Camera视频源数据采集解析](http://blog.csdn.net/jiangwei0910410003/article/details/52057543) 115 | - [Android中直播视频技术探究之---桌面屏幕视频数据源采集功能分析](http://blog.csdn.net/jiangwei0910410003/article/details/52134342) -------------------------------------------------------------------------------- /工具类/ACache.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012-2013, Michael Yang 杨福海 (www.yangfuhai.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.koterwong.basis.ClassTools; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.BufferedWriter; 20 | import java.io.ByteArrayInputStream; 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.FileNotFoundException; 25 | import java.io.FileOutputStream; 26 | import java.io.FileReader; 27 | import java.io.FileWriter; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.io.ObjectInputStream; 31 | import java.io.ObjectOutputStream; 32 | import java.io.OutputStream; 33 | import java.io.RandomAccessFile; 34 | import java.io.Serializable; 35 | import java.util.Collections; 36 | import java.util.HashMap; 37 | import java.util.Map; 38 | import java.util.Map.Entry; 39 | import java.util.Set; 40 | import java.util.concurrent.atomic.AtomicInteger; 41 | import java.util.concurrent.atomic.AtomicLong; 42 | 43 | import org.json.JSONArray; 44 | import org.json.JSONObject; 45 | 46 | import android.content.Context; 47 | import android.graphics.Bitmap; 48 | import android.graphics.BitmapFactory; 49 | import android.graphics.Canvas; 50 | import android.graphics.PixelFormat; 51 | import android.graphics.drawable.BitmapDrawable; 52 | import android.graphics.drawable.Drawable; 53 | 54 | /** 55 | * @author Michael Yang(www.yangfuhai.com) update at 2013.08.07 56 | */ 57 | public class ACache { 58 | public static final int TIME_HOUR = 60 * 60; 59 | public static final int TIME_DAY = TIME_HOUR * 24; 60 | private static final int MAX_SIZE = 1000 * 1000 * 50; // 50 mb 61 | private static final int MAX_COUNT = Integer.MAX_VALUE; // 不限制存放数据的数量 62 | private static Map mInstanceMap = new HashMap(); 63 | private ACacheManager mCache; 64 | 65 | public static ACache get(Context ctx) { 66 | return get(ctx, "ACache"); 67 | } 68 | 69 | public static ACache get(Context ctx, String cacheName) { 70 | File f = new File(ctx.getCacheDir(), cacheName); 71 | return get(f, MAX_SIZE, MAX_COUNT); 72 | } 73 | 74 | public static ACache get(File cacheDir) { 75 | return get(cacheDir, MAX_SIZE, MAX_COUNT); 76 | } 77 | 78 | public static ACache get(Context ctx, long max_zise, int max_count) { 79 | File f = new File(ctx.getCacheDir(), "ACache"); 80 | return get(f, max_zise, max_count); 81 | } 82 | 83 | public static ACache get(File cacheDir, long max_zise, int max_count) { 84 | ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid()); 85 | if (manager == null) { 86 | manager = new ACache(cacheDir, max_zise, max_count); 87 | mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager); 88 | } 89 | return manager; 90 | } 91 | 92 | private static String myPid() { 93 | return "_" + android.os.Process.myPid(); 94 | } 95 | 96 | private ACache(File cacheDir, long max_size, int max_count) { 97 | if (!cacheDir.exists() && !cacheDir.mkdirs()) { 98 | throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath()); 99 | } 100 | mCache = new ACacheManager(cacheDir, max_size, max_count); 101 | } 102 | 103 | /** 104 | * Provides a means to save a cached file before the data are available. 105 | * Since writing about the file is complete, and its close method is called, 106 | * its contents will be registered in the cache. Example of use: 107 | * 108 | * ACache cache = new ACache(this) try { OutputStream stream = 109 | * cache.put("myFileName") stream.write("some bytes".getBytes()); // now 110 | * update cache! stream.close(); } catch(FileNotFoundException e){ 111 | * e.printStackTrace() } 112 | */ 113 | class xFileOutputStream extends FileOutputStream { 114 | File file; 115 | 116 | public xFileOutputStream(File file) throws FileNotFoundException { 117 | super(file); 118 | this.file = file; 119 | } 120 | 121 | public void close() throws IOException { 122 | super.close(); 123 | mCache.put(file); 124 | } 125 | } 126 | 127 | // ======================================= 128 | // ============ String数据 读写 ============== 129 | // ======================================= 130 | /** 131 | * 保存 String数据 到 缓存中 132 | * 133 | * @param key 134 | * 保存的key 135 | * @param value 136 | * 保存的String数据 137 | */ 138 | public void put(String key, String value) { 139 | File file = mCache.newFile(key); 140 | BufferedWriter out = null; 141 | try { 142 | out = new BufferedWriter(new FileWriter(file), 1024); 143 | out.write(value); 144 | } catch (IOException e) { 145 | e.printStackTrace(); 146 | } finally { 147 | if (out != null) { 148 | try { 149 | out.flush(); 150 | out.close(); 151 | } catch (IOException e) { 152 | e.printStackTrace(); 153 | } 154 | } 155 | mCache.put(file); 156 | } 157 | } 158 | 159 | /** 160 | * 保存 String数据 到 缓存中 161 | * 162 | * @param key 163 | * 保存的key 164 | * @param value 165 | * 保存的String数据 166 | * @param saveTime 167 | * 保存的时间,单位:秒 168 | */ 169 | public void put(String key, String value, int saveTime) { 170 | put(key, Utils.newStringWithDateInfo(saveTime, value)); 171 | } 172 | 173 | /** 174 | * 读取 String数据 175 | * 176 | * @param key 177 | * @return String 数据 178 | */ 179 | public String getAsString(String key) { 180 | File file = mCache.get(key); 181 | if (!file.exists()) 182 | return null; 183 | boolean removeFile = false; 184 | BufferedReader in = null; 185 | try { 186 | in = new BufferedReader(new FileReader(file)); 187 | String readString = ""; 188 | String currentLine; 189 | while ((currentLine = in.readLine()) != null) { 190 | readString += currentLine; 191 | } 192 | if (!Utils.isDue(readString)) { 193 | return Utils.clearDateInfo(readString); 194 | } else { 195 | removeFile = true; 196 | return null; 197 | } 198 | } catch (IOException e) { 199 | e.printStackTrace(); 200 | return null; 201 | } finally { 202 | if (in != null) { 203 | try { 204 | in.close(); 205 | } catch (IOException e) { 206 | e.printStackTrace(); 207 | } 208 | } 209 | if (removeFile) 210 | remove(key); 211 | } 212 | } 213 | 214 | // ======================================= 215 | // ============= JSONObject 数据 读写 ============== 216 | // ======================================= 217 | /** 218 | * 保存 JSONObject数据 到 缓存中 219 | * 220 | * @param key 221 | * 保存的key 222 | * @param value 223 | * 保存的JSON数据 224 | */ 225 | public void put(String key, JSONObject value) { 226 | put(key, value.toString()); 227 | } 228 | 229 | /** 230 | * 保存 JSONObject数据 到 缓存中 231 | * 232 | * @param key 233 | * 保存的key 234 | * @param value 235 | * 保存的JSONObject数据 236 | * @param saveTime 237 | * 保存的时间,单位:秒 238 | */ 239 | public void put(String key, JSONObject value, int saveTime) { 240 | put(key, value.toString(), saveTime); 241 | } 242 | 243 | /** 244 | * 读取JSONObject数据 245 | * 246 | * @param key 247 | * @return JSONObject数据 248 | */ 249 | public JSONObject getAsJSONObject(String key) { 250 | String JSONString = getAsString(key); 251 | try { 252 | JSONObject obj = new JSONObject(JSONString); 253 | return obj; 254 | } catch (Exception e) { 255 | e.printStackTrace(); 256 | return null; 257 | } 258 | } 259 | 260 | // ======================================= 261 | // ============ JSONArray 数据 读写 ============= 262 | // ======================================= 263 | /** 264 | * 保存 JSONArray数据 到 缓存中 265 | * 266 | * @param key 267 | * 保存的key 268 | * @param value 269 | * 保存的JSONArray数据 270 | */ 271 | public void put(String key, JSONArray value) { 272 | put(key, value.toString()); 273 | } 274 | 275 | /** 276 | * 保存 JSONArray数据 到 缓存中 277 | * 278 | * @param key 279 | * 保存的key 280 | * @param value 281 | * 保存的JSONArray数据 282 | * @param saveTime 283 | * 保存的时间,单位:秒 284 | */ 285 | public void put(String key, JSONArray value, int saveTime) { 286 | put(key, value.toString(), saveTime); 287 | } 288 | 289 | /** 290 | * 读取JSONArray数据 291 | * 292 | * @param key 293 | * @return JSONArray数据 294 | */ 295 | public JSONArray getAsJSONArray(String key) { 296 | String JSONString = getAsString(key); 297 | try { 298 | JSONArray obj = new JSONArray(JSONString); 299 | return obj; 300 | } catch (Exception e) { 301 | e.printStackTrace(); 302 | return null; 303 | } 304 | } 305 | 306 | // ======================================= 307 | // ============== byte 数据 读写 ============= 308 | // ======================================= 309 | /** 310 | * 保存 byte数据 到 缓存中 311 | * 312 | * @param key 313 | * 保存的key 314 | * @param value 315 | * 保存的数据 316 | */ 317 | public void put(String key, byte[] value) { 318 | File file = mCache.newFile(key); 319 | FileOutputStream out = null; 320 | try { 321 | out = new FileOutputStream(file); 322 | out.write(value); 323 | } catch (Exception e) { 324 | e.printStackTrace(); 325 | } finally { 326 | if (out != null) { 327 | try { 328 | out.flush(); 329 | out.close(); 330 | } catch (IOException e) { 331 | e.printStackTrace(); 332 | } 333 | } 334 | mCache.put(file); 335 | } 336 | } 337 | 338 | /** 339 | * Cache for a stream 340 | * 341 | * @param key 342 | * the file name. 343 | * @return OutputStream stream for writing data. 344 | * @throws FileNotFoundException 345 | * if the file can not be created. 346 | */ 347 | public OutputStream put(String key) throws FileNotFoundException { 348 | return new xFileOutputStream(mCache.newFile(key)); 349 | } 350 | 351 | /** 352 | * 353 | * @param key 354 | * the file name. 355 | * @return (InputStream or null) stream previously saved in cache. 356 | * @throws FileNotFoundException 357 | * if the file can not be opened 358 | */ 359 | public InputStream get(String key) throws FileNotFoundException { 360 | File file = mCache.get(key); 361 | if (!file.exists()) 362 | return null; 363 | return new FileInputStream(file); 364 | } 365 | 366 | /** 367 | * 保存 byte数据 到 缓存中 368 | * 369 | * @param key 370 | * 保存的key 371 | * @param value 372 | * 保存的数据 373 | * @param saveTime 374 | * 保存的时间,单位:秒 375 | */ 376 | public void put(String key, byte[] value, int saveTime) { 377 | put(key, Utils.newByteArrayWithDateInfo(saveTime, value)); 378 | } 379 | 380 | /** 381 | * 获取 byte 数据 382 | * 383 | * @param key 384 | * @return byte 数据 385 | */ 386 | public byte[] getAsBinary(String key) { 387 | RandomAccessFile RAFile = null; 388 | boolean removeFile = false; 389 | try { 390 | File file = mCache.get(key); 391 | if (!file.exists()) 392 | return null; 393 | RAFile = new RandomAccessFile(file, "r"); 394 | byte[] byteArray = new byte[(int) RAFile.length()]; 395 | RAFile.read(byteArray); 396 | if (!Utils.isDue(byteArray)) { 397 | return Utils.clearDateInfo(byteArray); 398 | } else { 399 | removeFile = true; 400 | return null; 401 | } 402 | } catch (Exception e) { 403 | e.printStackTrace(); 404 | return null; 405 | } finally { 406 | if (RAFile != null) { 407 | try { 408 | RAFile.close(); 409 | } catch (IOException e) { 410 | e.printStackTrace(); 411 | } 412 | } 413 | if (removeFile) 414 | remove(key); 415 | } 416 | } 417 | 418 | // ======================================= 419 | // ============= 序列化 数据 读写 =============== 420 | // ======================================= 421 | /** 422 | * 保存 Serializable数据 到 缓存中 423 | * 424 | * @param key 425 | * 保存的key 426 | * @param value 427 | * 保存的value 428 | */ 429 | public void put(String key, Serializable value) { 430 | put(key, value, -1); 431 | } 432 | 433 | /** 434 | * 保存 Serializable数据到 缓存中 435 | * 436 | * @param key 437 | * 保存的key 438 | * @param value 439 | * 保存的value 440 | * @param saveTime 441 | * 保存的时间,单位:秒 442 | */ 443 | public void put(String key, Serializable value, int saveTime) { 444 | ByteArrayOutputStream baos = null; 445 | ObjectOutputStream oos = null; 446 | try { 447 | baos = new ByteArrayOutputStream(); 448 | oos = new ObjectOutputStream(baos); 449 | oos.writeObject(value); 450 | byte[] data = baos.toByteArray(); 451 | if (saveTime != -1) { 452 | put(key, data, saveTime); 453 | } else { 454 | put(key, data); 455 | } 456 | } catch (Exception e) { 457 | e.printStackTrace(); 458 | } finally { 459 | try { 460 | oos.close(); 461 | } catch (IOException e) { 462 | } 463 | } 464 | } 465 | 466 | /** 467 | * 读取 Serializable数据 468 | * 469 | * @param key 470 | * @return Serializable 数据 471 | */ 472 | public Object getAsObject(String key) { 473 | byte[] data = getAsBinary(key); 474 | if (data != null) { 475 | ByteArrayInputStream bais = null; 476 | ObjectInputStream ois = null; 477 | try { 478 | bais = new ByteArrayInputStream(data); 479 | ois = new ObjectInputStream(bais); 480 | Object reObject = ois.readObject(); 481 | return reObject; 482 | } catch (Exception e) { 483 | e.printStackTrace(); 484 | return null; 485 | } finally { 486 | try { 487 | if (bais != null) 488 | bais.close(); 489 | } catch (IOException e) { 490 | e.printStackTrace(); 491 | } 492 | try { 493 | if (ois != null) 494 | ois.close(); 495 | } catch (IOException e) { 496 | e.printStackTrace(); 497 | } 498 | } 499 | } 500 | return null; 501 | 502 | } 503 | 504 | // ======================================= 505 | // ============== bitmap 数据 读写 ============= 506 | // ======================================= 507 | /** 508 | * 保存 bitmap 到 缓存中 509 | * 510 | * @param key 511 | * 保存的key 512 | * @param value 513 | * 保存的bitmap数据 514 | */ 515 | public void put(String key, Bitmap value) { 516 | put(key, Utils.Bitmap2Bytes(value)); 517 | } 518 | 519 | /** 520 | * 保存 bitmap 到 缓存中 521 | * 522 | * @param key 523 | * 保存的key 524 | * @param value 525 | * 保存的 bitmap 数据 526 | * @param saveTime 527 | * 保存的时间,单位:秒 528 | */ 529 | public void put(String key, Bitmap value, int saveTime) { 530 | put(key, Utils.Bitmap2Bytes(value), saveTime); 531 | } 532 | 533 | /** 534 | * 读取 bitmap 数据 535 | * 536 | * @param key 537 | * @return bitmap 数据 538 | */ 539 | public Bitmap getAsBitmap(String key) { 540 | if (getAsBinary(key) == null) { 541 | return null; 542 | } 543 | return Utils.Bytes2Bimap(getAsBinary(key)); 544 | } 545 | 546 | // ======================================= 547 | // ============= drawable 数据 读写 ============= 548 | // ======================================= 549 | /** 550 | * 保存 drawable 到 缓存中 551 | * 552 | * @param key 553 | * 保存的key 554 | * @param value 555 | * 保存的drawable数据 556 | */ 557 | public void put(String key, Drawable value) { 558 | put(key, Utils.drawable2Bitmap(value)); 559 | } 560 | 561 | /** 562 | * 保存 drawable 到 缓存中 563 | * 564 | * @param key 565 | * 保存的key 566 | * @param value 567 | * 保存的 drawable 数据 568 | * @param saveTime 569 | * 保存的时间,单位:秒 570 | */ 571 | public void put(String key, Drawable value, int saveTime) { 572 | put(key, Utils.drawable2Bitmap(value), saveTime); 573 | } 574 | 575 | /** 576 | * 读取 Drawable 数据 577 | * 578 | * @param key 579 | * @return Drawable 数据 580 | */ 581 | public Drawable getAsDrawable(String key) { 582 | if (getAsBinary(key) == null) { 583 | return null; 584 | } 585 | return Utils.bitmap2Drawable(Utils.Bytes2Bimap(getAsBinary(key))); 586 | } 587 | 588 | /** 589 | * 获取缓存文件 590 | * 591 | * @param key 592 | * @return value 缓存的文件 593 | */ 594 | public File file(String key) { 595 | File f = mCache.newFile(key); 596 | if (f.exists()) 597 | return f; 598 | return null; 599 | } 600 | 601 | /** 602 | * 移除某个key 603 | * 604 | * @param key 605 | * @return 是否移除成功 606 | */ 607 | public boolean remove(String key) { 608 | return mCache.remove(key); 609 | } 610 | 611 | /** 612 | * 清除所有数据 613 | */ 614 | public void clear() { 615 | mCache.clear(); 616 | } 617 | 618 | /** 619 | * @title 缓存管理器 620 | * @author 杨福海(michael) www.yangfuhai.com 621 | * @version 1.0 622 | */ 623 | public class ACacheManager { 624 | private final AtomicLong cacheSize; 625 | private final AtomicInteger cacheCount; 626 | private final long sizeLimit; 627 | private final int countLimit; 628 | private final Map lastUsageDates = Collections.synchronizedMap(new HashMap()); 629 | protected File cacheDir; 630 | 631 | private ACacheManager(File cacheDir, long sizeLimit, int countLimit) { 632 | this.cacheDir = cacheDir; 633 | this.sizeLimit = sizeLimit; 634 | this.countLimit = countLimit; 635 | cacheSize = new AtomicLong(); 636 | cacheCount = new AtomicInteger(); 637 | calculateCacheSizeAndCacheCount(); 638 | } 639 | 640 | /** 641 | * 计算 cacheSize和cacheCount 642 | */ 643 | private void calculateCacheSizeAndCacheCount() { 644 | new Thread(new Runnable() { 645 | @Override 646 | public void run() { 647 | int size = 0; 648 | int count = 0; 649 | File[] cachedFiles = cacheDir.listFiles(); 650 | if (cachedFiles != null) { 651 | for (File cachedFile : cachedFiles) { 652 | size += calculateSize(cachedFile); 653 | count += 1; 654 | lastUsageDates.put(cachedFile, cachedFile.lastModified()); 655 | } 656 | cacheSize.set(size); 657 | cacheCount.set(count); 658 | } 659 | } 660 | }).start(); 661 | } 662 | 663 | private void put(File file) { 664 | int curCacheCount = cacheCount.get(); 665 | while (curCacheCount + 1 > countLimit) { 666 | long freedSize = removeNext(); 667 | cacheSize.addAndGet(-freedSize); 668 | 669 | curCacheCount = cacheCount.addAndGet(-1); 670 | } 671 | cacheCount.addAndGet(1); 672 | 673 | long valueSize = calculateSize(file); 674 | long curCacheSize = cacheSize.get(); 675 | while (curCacheSize + valueSize > sizeLimit) { 676 | long freedSize = removeNext(); 677 | curCacheSize = cacheSize.addAndGet(-freedSize); 678 | } 679 | cacheSize.addAndGet(valueSize); 680 | 681 | Long currentTime = System.currentTimeMillis(); 682 | file.setLastModified(currentTime); 683 | lastUsageDates.put(file, currentTime); 684 | } 685 | 686 | private File get(String key) { 687 | File file = newFile(key); 688 | Long currentTime = System.currentTimeMillis(); 689 | file.setLastModified(currentTime); 690 | lastUsageDates.put(file, currentTime); 691 | 692 | return file; 693 | } 694 | 695 | private File newFile(String key) { 696 | return new File(cacheDir, key.hashCode() + ""); 697 | } 698 | 699 | private boolean remove(String key) { 700 | File image = get(key); 701 | return image.delete(); 702 | } 703 | 704 | private void clear() { 705 | lastUsageDates.clear(); 706 | cacheSize.set(0); 707 | File[] files = cacheDir.listFiles(); 708 | if (files != null) { 709 | for (File f : files) { 710 | f.delete(); 711 | } 712 | } 713 | } 714 | 715 | /** 716 | * 移除旧的文件 717 | * 718 | * @return 719 | */ 720 | private long removeNext() { 721 | if (lastUsageDates.isEmpty()) { 722 | return 0; 723 | } 724 | 725 | Long oldestUsage = null; 726 | File mostLongUsedFile = null; 727 | Set> entries = lastUsageDates.entrySet(); 728 | synchronized (lastUsageDates) { 729 | for (Entry entry : entries) { 730 | if (mostLongUsedFile == null) { 731 | mostLongUsedFile = entry.getKey(); 732 | oldestUsage = entry.getValue(); 733 | } else { 734 | Long lastValueUsage = entry.getValue(); 735 | if (lastValueUsage < oldestUsage) { 736 | oldestUsage = lastValueUsage; 737 | mostLongUsedFile = entry.getKey(); 738 | } 739 | } 740 | } 741 | } 742 | 743 | long fileSize = calculateSize(mostLongUsedFile); 744 | if (mostLongUsedFile.delete()) { 745 | lastUsageDates.remove(mostLongUsedFile); 746 | } 747 | return fileSize; 748 | } 749 | 750 | private long calculateSize(File file) { 751 | return file.length(); 752 | } 753 | } 754 | 755 | /** 756 | * @title 时间计算工具类 757 | * @author 杨福海(michael) www.yangfuhai.com 758 | * @version 1.0 759 | */ 760 | private static class Utils { 761 | 762 | /** 763 | * 判断缓存的String数据是否到期 764 | * 765 | * @param str 766 | * @return true:到期了 false:还没有到期 767 | */ 768 | private static boolean isDue(String str) { 769 | return isDue(str.getBytes()); 770 | } 771 | 772 | /** 773 | * 判断缓存的byte数据是否到期 774 | * 775 | * @param data 776 | * @return true:到期了 false:还没有到期 777 | */ 778 | private static boolean isDue(byte[] data) { 779 | String[] strs = getDateInfoFromDate(data); 780 | if (strs != null && strs.length == 2) { 781 | String saveTimeStr = strs[0]; 782 | while (saveTimeStr.startsWith("0")) { 783 | saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length()); 784 | } 785 | long saveTime = Long.valueOf(saveTimeStr); 786 | long deleteAfter = Long.valueOf(strs[1]); 787 | if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) { 788 | return true; 789 | } 790 | } 791 | return false; 792 | } 793 | 794 | private static String newStringWithDateInfo(int second, String strInfo) { 795 | return createDateInfo(second) + strInfo; 796 | } 797 | 798 | private static byte[] newByteArrayWithDateInfo(int second, byte[] data2) { 799 | byte[] data1 = createDateInfo(second).getBytes(); 800 | byte[] retdata = new byte[data1.length + data2.length]; 801 | System.arraycopy(data1, 0, retdata, 0, data1.length); 802 | System.arraycopy(data2, 0, retdata, data1.length, data2.length); 803 | return retdata; 804 | } 805 | 806 | private static String clearDateInfo(String strInfo) { 807 | if (strInfo != null && hasDateInfo(strInfo.getBytes())) { 808 | strInfo = strInfo.substring(strInfo.indexOf(mSeparator) + 1, strInfo.length()); 809 | } 810 | return strInfo; 811 | } 812 | 813 | private static byte[] clearDateInfo(byte[] data) { 814 | if (hasDateInfo(data)) { 815 | return copyOfRange(data, indexOf(data, mSeparator) + 1, data.length); 816 | } 817 | return data; 818 | } 819 | 820 | private static boolean hasDateInfo(byte[] data) { 821 | return data != null && data.length > 15 && data[13] == '-' && indexOf(data, mSeparator) > 14; 822 | } 823 | 824 | private static String[] getDateInfoFromDate(byte[] data) { 825 | if (hasDateInfo(data)) { 826 | String saveDate = new String(copyOfRange(data, 0, 13)); 827 | String deleteAfter = new String(copyOfRange(data, 14, indexOf(data, mSeparator))); 828 | return new String[] { saveDate, deleteAfter }; 829 | } 830 | return null; 831 | } 832 | 833 | private static int indexOf(byte[] data, char c) { 834 | for (int i = 0; i < data.length; i++) { 835 | if (data[i] == c) { 836 | return i; 837 | } 838 | } 839 | return -1; 840 | } 841 | 842 | private static byte[] copyOfRange(byte[] original, int from, int to) { 843 | int newLength = to - from; 844 | if (newLength < 0) 845 | throw new IllegalArgumentException(from + " > " + to); 846 | byte[] copy = new byte[newLength]; 847 | System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); 848 | return copy; 849 | } 850 | 851 | private static final char mSeparator = ' '; 852 | 853 | private static String createDateInfo(int second) { 854 | String currentTime = System.currentTimeMillis() + ""; 855 | while (currentTime.length() < 13) { 856 | currentTime = "0" + currentTime; 857 | } 858 | return currentTime + "-" + second + mSeparator; 859 | } 860 | 861 | /* 862 | * Bitmap → byte[] 863 | */ 864 | private static byte[] Bitmap2Bytes(Bitmap bm) { 865 | if (bm == null) { 866 | return null; 867 | } 868 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 869 | bm.compress(Bitmap.CompressFormat.PNG, 100, baos); 870 | return baos.toByteArray(); 871 | } 872 | 873 | /* 874 | * byte[] → Bitmap 875 | */ 876 | private static Bitmap Bytes2Bimap(byte[] b) { 877 | if (b.length == 0) { 878 | return null; 879 | } 880 | return BitmapFactory.decodeByteArray(b, 0, b.length); 881 | } 882 | 883 | /* 884 | * Drawable → Bitmap 885 | */ 886 | private static Bitmap drawable2Bitmap(Drawable drawable) { 887 | if (drawable == null) { 888 | return null; 889 | } 890 | // 取 drawable 的长宽 891 | int w = drawable.getIntrinsicWidth(); 892 | int h = drawable.getIntrinsicHeight(); 893 | // 取 drawable 的颜色格式 894 | Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 895 | // 建立对应 bitmap 896 | Bitmap bitmap = Bitmap.createBitmap(w, h, config); 897 | // 建立对应 bitmap 的画布 898 | Canvas canvas = new Canvas(bitmap); 899 | drawable.setBounds(0, 0, w, h); 900 | // 把 drawable 内容画到画布中 901 | drawable.draw(canvas); 902 | return bitmap; 903 | } 904 | 905 | /* 906 | * Bitmap → Drawable 907 | */ 908 | @SuppressWarnings("deprecation") 909 | private static Drawable bitmap2Drawable(Bitmap bm) { 910 | if (bm == null) { 911 | return null; 912 | } 913 | BitmapDrawable bd=new BitmapDrawable(bm); 914 | bd.setTargetDensity(bm.getDensity()); 915 | return new BitmapDrawable(bm); 916 | } 917 | } 918 | 919 | } 920 | -------------------------------------------------------------------------------- /工具类/Acache.md: -------------------------------------------------------------------------------- 1 | ### 保存字符串 ### 2 | //保存 3 | ACache mCache = ACache.get(this); 4 | mCache.put("test_key1", "test value"); 5 | mCache.put("test_key2", "test value", 10);//保存10秒,如果超过10秒去获取这个key,将为null 6 | mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);//保存两天,如果超过两天去获取这个key,将为null 7 | //读取 8 | String value = mCache.getAsString("test_key1"); 9 | ### 保存多媒体 ### 10 | //保存 11 | @Override 12 | public void run() { 13 | OutputStream ostream = null; 14 | try { 15 | ostream = mCache.put(CACHE_KEY); 16 | } catch (FileNotFoundException e) { 17 | e.printStackTrace(); 18 | } 19 | if (ostream == null){ 20 | Toast.makeText(this, "Open stream error!", Toast.LENGTH_SHORT) 21 | .show(); 22 | return; 23 | } 24 | try { 25 | URL u = new URL(mUrl); 26 | HttpURLConnection conn = (HttpURLConnection) u.openConnection(); 27 | conn.connect(); 28 | InputStream stream = conn.getInputStream(); 29 | 30 | byte[] buff = new byte[1024]; 31 | int counter; 32 | 33 | while ((counter = stream.read(buff)) > 0){ 34 | ostream.write(buff, 0, counter); 35 | } 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } finally { 39 | try { 40 | // cache update 41 | ostream.close(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | runOnUiThread(new Runnable() { 46 | @Override 47 | public void run() { 48 | text = (TextView) findViewById(R.id.text); 49 | text.setText("done..."); 50 | } 51 | }); 52 | } 53 | } 54 | //读取 55 | public void read(View v) { 56 | InputStream stream = null; 57 | try { 58 | stream = mCache.get(CACHE_KEY); 59 | } catch (FileNotFoundException e) { 60 | e.printStackTrace(); 61 | } 62 | if (stream == null) { 63 | Toast.makeText(this, "Bitmap cache is null ...", Toast.LENGTH_SHORT) 64 | .show(); 65 | text.setText("file not found"); 66 | return; 67 | } 68 | try { 69 | text.setText("file size: " + stream.available()); 70 | } catch (IOException e) { 71 | text.setText("error " + e.getMessage()); 72 | } 73 | } 74 | 75 | # ASimpleCache 76 | 77 | ------ 78 | 79 | ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。 80 | 81 | ------ 82 | 83 | ## 1、它可以缓存什么东西? 84 | 85 | 普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。 86 | 87 | ## 2、它有什么特色? 88 | 89 | - 特色主要是: 90 | - 1:轻,轻到只有一个JAVA文件。 91 | - 2:可配置,可以配置缓存路径,缓存大小,缓存数量等。 92 | - 3:可以设置缓存超时时间,缓存超时自动失效,并被删除。 93 | - 4:支持多进程。 94 | 95 | ## 3、它在android中可以用在哪些场景? 96 | 97 | - 1、替换SharePreference当做配置文件 98 | - 2、可以缓存网络请求数据,比如oschina的android客户端可以缓存http请求的新闻内容,缓存时间假设为1个小时,超时后自动失效,让客户端重新请求新的数据,减少客户端流量,同时减少服务器并发量。 99 | - 3、您来说... 100 | 101 | ## 4、如何使用 ASimpleCache? 102 | 103 | 以下有个小的demo,希望您能喜欢: 104 | 105 | ```java 106 | ACache mCache = ACache.get(this); 107 | mCache.put("test_key1", "test value"); 108 | mCache.put("test_key2", "test value", 10);//保存10秒,如果超过10秒去获取这个key,将为null 109 | mCache.put("test_key3", "test value", 2 * ACache.TIME_DAY);//保存两天,如果超过两天去获取这个key,将为null 110 | ``` 111 | 112 | 获取数据 113 | 114 | ```java 115 | ACache mCache = ACache.get(this); 116 | String value = mCache.getAsString("test_key1"); 117 | ``` 118 | 119 | 更多示例请见Demo 120 | 121 | # 关于作者michael 122 | 123 | - 屌丝程序员一枚,喜欢开源。 124 | - 个人博客:[http://www.yangfuhai.com](http://www.yangfuhai.com) 125 | - 交流QQ群 : 192341294(已满) 246710918(未满) 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /工具类/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | 7 | /** 8 | * Author:Koterwong, 9 | * Date:2016/5/21 20:21 10 | * Description:App相关辅助类。 11 | */ 12 | public class AppUtils { 13 | 14 | private AppUtils() { 15 | throw new UnsupportedOperationException("cannot be instantiated"); 16 | } 17 | 18 | /** 19 | * 获取应用程序名称 20 | * 21 | * @param context context 22 | * @return label 23 | */ 24 | public static String getAppName(Context context) { 25 | try { 26 | PackageManager packageManager = context.getPackageManager(); 27 | PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 28 | int labelRes = packageInfo.applicationInfo.labelRes; 29 | return context.getResources().getString(labelRes); 30 | } catch (PackageManager.NameNotFoundException e) { 31 | e.printStackTrace(); 32 | } 33 | return null; 34 | } 35 | 36 | /** 37 | * 获取应用程序版本名称信息 38 | * 39 | * @param context context 40 | * @return versionName 41 | */ 42 | public static String getVersionName(Context context) { 43 | try { 44 | PackageManager packageManager = context.getPackageManager(); 45 | PackageInfo packageInfo = packageManager 46 | .getPackageInfo(context.getPackageName(), 0); 47 | return packageInfo.versionName; 48 | } catch (PackageManager.NameNotFoundException e) { 49 | e.printStackTrace(); 50 | } 51 | return null; 52 | } 53 | 54 | /** 55 | * 获取版本号 56 | * 57 | * @param context context 58 | * @return versionCode 59 | */ 60 | public static int getVersionCode(Context context) { 61 | PackageManager packageManager = context.getPackageManager(); 62 | try { 63 | PackageInfo packageInfo = packageManager. 64 | getPackageInfo(context.getPackageName(), 0); 65 | return packageInfo.versionCode; 66 | } catch (PackageManager.NameNotFoundException e) { 67 | e.printStackTrace(); 68 | } 69 | return 0; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /工具类/ColorUtil.java: -------------------------------------------------------------------------------- 1 | package com.itheima74.qqslidemenu; 2 | 3 | public class ColorUtil { 4 | public static Object evaluateColor(float fraction, Object startValue, 5 | Object endValue) { 6 | int startInt = (Integer) startValue; 7 | int startA = (startInt >> 24) & 0xff; 8 | int startR = (startInt >> 16) & 0xff; 9 | int startG = (startInt >> 8) & 0xff; 10 | int startB = startInt & 0xff; 11 | 12 | int endInt = (Integer) endValue; 13 | int endA = (endInt >> 24) & 0xff; 14 | int endR = (endInt >> 16) & 0xff; 15 | int endG = (endInt >> 8) & 0xff; 16 | int endB = endInt & 0xff; 17 | 18 | return (int) ((startA + (int) (fraction * (endA - startA))) << 24) 19 | | (int) ((startR + (int) (fraction * (endR - startR))) << 16) 20 | | (int) ((startG + (int) (fraction * (endG - startG))) << 8) 21 | | (int) ((startB + (int) (fraction * (endB - startB)))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /工具类/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import java.text.ParseException; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Calendar; 8 | import java.util.Date; 9 | 10 | /** 11 | * Author:Koterwong,Data:2016/4/26. 12 | * Description: 13 | */ 14 | public class DateUtils { 15 | 16 | /** 17 | * yyyy-MM-dd HH:mm:ss 18 | * 19 | * @return 20 | */ 21 | @SuppressLint("SimpleDateFormat") 22 | public static String getNowYMDHMSTime() { 23 | 24 | SimpleDateFormat mDateFormat = new SimpleDateFormat( 25 | "yyyy-MM-dd HH:mm:ss"); 26 | String date = mDateFormat.format(new Date()); 27 | return date; 28 | } 29 | 30 | /** 31 | * MM-dd HH:mm:ss 32 | * 33 | * @return 34 | */ 35 | @SuppressLint("SimpleDateFormat") 36 | public static String getNowMDHMSTime() { 37 | 38 | SimpleDateFormat mDateFormat = new SimpleDateFormat( 39 | "MM-dd HH:mm:ss"); 40 | String date = mDateFormat.format(new Date()); 41 | return date; 42 | } 43 | 44 | /** 45 | * yyyy-MM-dd 46 | * 47 | * @return 48 | */ 49 | @SuppressLint("SimpleDateFormat") 50 | public static String getNowYMD() { 51 | 52 | SimpleDateFormat mDateFormat = new SimpleDateFormat( 53 | "yyyy-MM-dd"); 54 | String date = mDateFormat.format(new Date()); 55 | return date; 56 | } 57 | 58 | /** 59 | * yyyy-MM-dd 60 | * 61 | * @param date 62 | * @return 63 | */ 64 | @SuppressLint("SimpleDateFormat") 65 | public static String getYMD(Date date) { 66 | 67 | SimpleDateFormat mDateFormat = new SimpleDateFormat( 68 | "yyyy-MM-dd"); 69 | String dateS = mDateFormat.format(date); 70 | return dateS; 71 | } 72 | 73 | @SuppressLint("SimpleDateFormat") 74 | public static String dayForWeek(String pTime) { 75 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); 76 | Calendar c = Calendar.getInstance(); 77 | try { 78 | c.setTime(format.parse(pTime)); 79 | } catch (ParseException e) { 80 | e.printStackTrace(); 81 | } 82 | int dayForWeek = 0; 83 | String week = ""; 84 | dayForWeek = c.get(Calendar.DAY_OF_WEEK); 85 | switch (dayForWeek) { 86 | case 1: 87 | week = "周日"; 88 | break; 89 | case 2: 90 | week = "周一"; 91 | break; 92 | case 3: 93 | week = "周二"; 94 | break; 95 | case 4: 96 | week = "周三"; 97 | break; 98 | case 5: 99 | week = "周四"; 100 | break; 101 | case 6: 102 | week = "周五"; 103 | break; 104 | case 7: 105 | week = "周六"; 106 | break; 107 | } 108 | return week; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /工具类/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Rect; 7 | import android.util.DisplayMetrics; 8 | import android.view.View; 9 | import android.view.WindowManager; 10 | 11 | /** 12 | * Author:Koterwong, 13 | * Date:2016/5/21 20:48 14 | * Description: 15 | * 密度工具类。 16 | * 截图工具类 17 | */ 18 | public class DensityUtils { 19 | 20 | /** 21 | * 获取屏幕高度 22 | * 23 | * @param context context 24 | * @return heightPixels 25 | */ 26 | public static int getHeightInPx(Context context) { 27 | return context.getResources().getDisplayMetrics().heightPixels; 28 | } 29 | 30 | /** 31 | * 获取屏幕宽度 32 | * 33 | * @param context context 34 | * @return widthPixels 35 | */ 36 | public static int getWidthInPx(Context context) { 37 | return context.getResources().getDisplayMetrics().widthPixels; 38 | } 39 | 40 | 41 | /** 42 | * 获得屏幕高度 43 | * 44 | * @param context context 45 | * @return ScreenWidth 46 | */ 47 | public static int getScreenWidth(Context context) { 48 | WindowManager wm = (WindowManager) context 49 | .getSystemService(Context.WINDOW_SERVICE); 50 | DisplayMetrics outMetrics = new DisplayMetrics(); 51 | wm.getDefaultDisplay().getMetrics(outMetrics); 52 | return outMetrics.widthPixels; 53 | } 54 | 55 | /** 56 | * 获得屏幕宽度 57 | * 58 | * @param context context 59 | * @return ScreenHeight 60 | */ 61 | public static int getScreenHeight(Context context) { 62 | WindowManager wm = (WindowManager) context 63 | .getSystemService(Context.WINDOW_SERVICE); 64 | DisplayMetrics outMetrics = new DisplayMetrics(); 65 | wm.getDefaultDisplay().getMetrics(outMetrics); 66 | return outMetrics.heightPixels; 67 | } 68 | 69 | /** 70 | * 获取状态栏高度 71 | * 72 | * @param context context 73 | * @return StatusHeight 74 | */ 75 | public static int getStatusHeight(Context context) { 76 | int result = 0; 77 | int resourceId = context.getResources().getIdentifier( 78 | "status_bar_height", 79 | "dimen", 80 | "android"); 81 | if (resourceId > 0) { 82 | result = context.getResources().getDimensionPixelSize(resourceId); 83 | } 84 | return result; 85 | } 86 | 87 | /** 88 | * 获得状态栏的高度另一种方式 89 | * 90 | * @param context context 91 | * @return StatusBarHeight 92 | */ 93 | public static int getStatusBarHeight(Context context) { 94 | 95 | int statusHeight = -1; 96 | try { 97 | Class clazz = Class.forName("com.android.internal.R$dimen"); 98 | Object object = clazz.newInstance(); 99 | int height = Integer.parseInt(clazz.getField("status_bar_height") 100 | .get(object).toString()); 101 | statusHeight = context.getResources().getDimensionPixelSize(height); 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | } 105 | return statusHeight; 106 | } 107 | 108 | /** 109 | * 获取当前屏幕截图,包含状态栏 110 | * 111 | * @param activity activity 112 | * @return Bitmap 113 | */ 114 | public static Bitmap snapShotWithStatusBar(Activity activity) { 115 | View view = activity.getWindow().getDecorView(); 116 | view.setDrawingCacheEnabled(true); 117 | view.buildDrawingCache(); 118 | Bitmap bmp = view.getDrawingCache(); 119 | int width = getScreenWidth(activity); 120 | int height = getScreenHeight(activity); 121 | Bitmap bp = null; 122 | bp = Bitmap.createBitmap(bmp, 0, 0, width, height); 123 | view.destroyDrawingCache(); 124 | return bp; 125 | 126 | } 127 | 128 | /** 129 | * 获取当前屏幕截图,不包含状态栏 130 | * 131 | * @param activity activity 132 | * @return Bitmap 133 | */ 134 | public static Bitmap snapShotWithoutStatusBar(Activity activity) { 135 | View view = activity.getWindow().getDecorView(); 136 | view.setDrawingCacheEnabled(true); 137 | view.buildDrawingCache(); 138 | Bitmap bmp = view.getDrawingCache(); 139 | Rect frame = new Rect(); 140 | activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); 141 | int statusBarHeight = frame.top; 142 | 143 | int width = getScreenWidth(activity); 144 | int height = getScreenHeight(activity); 145 | Bitmap bp = null; 146 | bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height 147 | - statusBarHeight); 148 | view.destroyDrawingCache(); 149 | return bp; 150 | } 151 | 152 | public static int dp2px(Context context, float dpValue) { 153 | final float scale = context.getResources().getDisplayMetrics().density; 154 | return (int) (dpValue * scale + 0.5f); 155 | } 156 | 157 | public static int px2dp(Context context, float pxValue) { 158 | final float scale = context.getResources().getDisplayMetrics().density; 159 | return (int) (pxValue / scale + 0.5f); 160 | } 161 | 162 | public static int px2sp(Context context, float pxValue) { 163 | final float scale = context.getResources().getDisplayMetrics().density; 164 | return (int) (pxValue / scale + 0.5f); 165 | } 166 | 167 | public static int sp2px(Context context, float spValue) { 168 | final float scale = context.getResources().getDisplayMetrics().density; 169 | return (int) (spValue * scale + 0.5f); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /工具类/DeviceUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.kkgank.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.app.Application; 6 | import android.content.ClipData; 7 | import android.content.Context; 8 | import android.content.pm.ApplicationInfo; 9 | import android.content.pm.PackageInfo; 10 | import android.content.pm.PackageManager; 11 | import android.database.Cursor; 12 | import android.net.ConnectivityManager; 13 | import android.net.NetworkInfo; 14 | import android.net.Uri; 15 | import android.os.Build; 16 | import android.os.Environment; 17 | import android.provider.MediaStore; 18 | import android.telephony.TelephonyManager; 19 | import android.util.Log; 20 | 21 | import com.koterwong.kkgank.R; 22 | 23 | import java.io.File; 24 | import java.util.Iterator; 25 | import java.util.List; 26 | 27 | /** 28 | * Author:Koterwong, 29 | * Date:2016/5/25 16:24 30 | * Description: 31 | */ 32 | public class DeviceUtils { 33 | /** 34 | * dp 转化为 px 35 | * 36 | * @param context context 37 | * @param dpValue dpValue 38 | * @return int 39 | */ 40 | public static int dp2px(Context context, float dpValue) { 41 | final float scale = context.getResources().getDisplayMetrics().density; 42 | return (int) (dpValue * scale + 0.5f); 43 | } 44 | 45 | 46 | /** 47 | * px 转化为 dp 48 | * 49 | * @param context context 50 | * @param pxValue pxValue 51 | */ 52 | public static int px2dp(Context context, float pxValue) { 53 | final float scale = context.getResources().getDisplayMetrics().density; 54 | return (int) (pxValue / scale + 0.5f); 55 | } 56 | 57 | 58 | /** 59 | * 获取设备宽度(px) 60 | * 61 | * @param context context 62 | * @return int 63 | */ 64 | public static int deviceWidth(Context context) { 65 | return context.getResources().getDisplayMetrics().widthPixels; 66 | } 67 | 68 | 69 | /** 70 | * 获取设备高度(px) 71 | */ 72 | public static int deviceHeight(Context context) { 73 | return context.getResources().getDisplayMetrics().heightPixels; 74 | } 75 | 76 | 77 | /** 78 | * SD卡判断 79 | * 80 | * @return boolean 81 | */ 82 | public static boolean isSDCardAvailable() { 83 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 84 | } 85 | 86 | 87 | /** 88 | * 是否有网 89 | * 90 | * @param context context 91 | * @return boolean 92 | */ 93 | public static boolean isNetworkConnected(Context context) { 94 | if (context != null) { 95 | ConnectivityManager mConnectivityManager 96 | = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 97 | NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); 98 | if (mNetworkInfo != null) { 99 | return mNetworkInfo.isAvailable(); 100 | } 101 | } 102 | return false; 103 | } 104 | 105 | 106 | /** 107 | * 返回版本名字 108 | * 对应build.gradle中的versionName 109 | * 110 | * @param context context 111 | * @return String 112 | */ 113 | public static String getVersionName(Context context) { 114 | String versionName = ""; 115 | try { 116 | PackageManager packageManager = context.getPackageManager(); 117 | PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 118 | versionName = packInfo.versionName; 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | } 122 | return versionName; 123 | } 124 | 125 | 126 | /** 127 | * 返回版本号 128 | * 对应build.gradle中的versionCode 129 | * 130 | * @param context context 131 | * @return String 132 | */ 133 | public static String getVersionCode(Context context) { 134 | String versionCode = ""; 135 | try { 136 | PackageManager packageManager = context.getPackageManager(); 137 | PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); 138 | versionCode = String.valueOf(packInfo.versionCode); 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | } 142 | return versionCode; 143 | } 144 | 145 | 146 | /** 147 | * 获取设备的唯一标识,deviceId 148 | * 149 | * @param context context 150 | * @return String 151 | */ 152 | public static String getDeviceId(Context context) { 153 | TelephonyManager tm = (TelephonyManager) context.getSystemService( 154 | Context.TELEPHONY_SERVICE); 155 | String deviceId = tm.getDeviceId(); 156 | if (deviceId == null) { 157 | return "-"; 158 | } else { 159 | return deviceId; 160 | } 161 | } 162 | 163 | 164 | /** 165 | * 获取手机品牌 166 | * 167 | * @return String 168 | */ 169 | public static String getPhoneBrand() { 170 | return android.os.Build.BRAND; 171 | } 172 | 173 | 174 | /** 175 | * 获取手机型号 176 | * 177 | * @return String 178 | */ 179 | public static String getPhoneModel() { 180 | return android.os.Build.MODEL; 181 | } 182 | 183 | 184 | /** 185 | * 获取手机Android API等级(22、23 ...) 186 | * 187 | * @return int 188 | */ 189 | public static int getBuildLevel() { 190 | return android.os.Build.VERSION.SDK_INT; 191 | } 192 | 193 | /** 194 | * 获取手机Android的版本 2.3 4.0 5.0 6.0 195 | * 196 | * @return string 197 | */ 198 | public static String getBuildVersion() { 199 | return Build.VERSION.RELEASE; 200 | } 201 | /** 202 | * 获取当前App进程的Name 203 | * 204 | * @param context context 205 | * @param processId processId 206 | * @return String 207 | */ 208 | public static String getAppProcessName(Context context, int processId) { 209 | String processName = null; 210 | ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 211 | // 获取所有运行App的进程集合 212 | List l = am.getRunningAppProcesses(); 213 | Iterator i = l.iterator(); 214 | PackageManager pm = context.getPackageManager(); 215 | while (i.hasNext()) { 216 | ActivityManager.RunningAppProcessInfo info 217 | = (ActivityManager.RunningAppProcessInfo) (i.next()); 218 | try { 219 | if (info.pid == processId) { 220 | CharSequence c = pm.getApplicationLabel( 221 | pm.getApplicationInfo(info.processName, PackageManager.GET_META_DATA)); 222 | 223 | processName = info.processName; 224 | return processName; 225 | } 226 | } catch (Exception e) { 227 | Log.e(DeviceUtils.class.getName(), e.getMessage(), e); 228 | } 229 | } 230 | return processName; 231 | } 232 | 233 | 234 | /** 235 | * 创建App文件夹 236 | * 237 | * @param appName appName 238 | * @param application application 239 | * @return String 240 | */ 241 | public static String createAPPFolder(String appName, Application application) { 242 | return createAPPFolder(appName, application, null); 243 | } 244 | 245 | 246 | /** 247 | * 创建App文件夹 248 | * 249 | * @param appName appName 250 | * @param application application 251 | * @param folderName folderName 252 | * @return String 253 | */ 254 | public static String createAPPFolder(String appName, Application application, String folderName) { 255 | File root = Environment.getExternalStorageDirectory(); 256 | File folder; 257 | /** 258 | * 如果存在SD卡 259 | */ 260 | if (DeviceUtils.isSDCardAvailable() && root != null) { 261 | folder = new File(root, appName); 262 | if (!folder.exists()) { 263 | folder.mkdirs(); 264 | } 265 | } else { 266 | /** 267 | * 不存在SD卡,就放到缓存文件夹内 268 | */ 269 | root = application.getCacheDir(); 270 | folder = new File(root, appName); 271 | if (!folder.exists()) { 272 | folder.mkdirs(); 273 | } 274 | } 275 | if (folderName != null) { 276 | folder = new File(folder, folderName); 277 | if (!folder.exists()) { 278 | folder.mkdirs(); 279 | } 280 | } 281 | return folder.getAbsolutePath(); 282 | } 283 | 284 | 285 | /** 286 | * 通过Uri找到File 287 | * 288 | * @param context context 289 | * @param uri uri 290 | * @return File 291 | */ 292 | public static File uri2File(Activity context, Uri uri) { 293 | File file; 294 | String[] project = { MediaStore.Images.Media.DATA }; 295 | Cursor actualImageCursor = context.getContentResolver() 296 | .query(uri, project, null, null, null); 297 | if (actualImageCursor != null) { 298 | int actual_image_column_index = actualImageCursor.getColumnIndexOrThrow( 299 | MediaStore.Images.Media.DATA); 300 | actualImageCursor.moveToFirst(); 301 | String img_path = actualImageCursor.getString(actual_image_column_index); 302 | file = new File(img_path); 303 | } else { 304 | file = new File(uri.getPath()); 305 | } 306 | if (actualImageCursor != null) actualImageCursor.close(); 307 | return file; 308 | } 309 | 310 | 311 | /** 312 | * 获取AndroidManifest.xml里 的值 313 | * 314 | * @param context context 315 | * @param name name 316 | * @return String 317 | */ 318 | public static String getMetaData(Context context, String name) { 319 | String value = null; 320 | try { 321 | ApplicationInfo appInfo = context.getPackageManager() 322 | .getApplicationInfo(context.getPackageName(), 323 | PackageManager.GET_META_DATA); 324 | value = appInfo.metaData.getString(name); 325 | } catch (PackageManager.NameNotFoundException e) { 326 | e.printStackTrace(); 327 | } 328 | return value; 329 | } 330 | 331 | 332 | /** 333 | * 复制到剪贴板 334 | * 335 | * @param context context 336 | * @param content content 337 | */ 338 | public static void copy2Clipboard(Context context, String content) { 339 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 340 | android.content.ClipboardManager clipboardManager 341 | = (android.content.ClipboardManager) context.getSystemService( 342 | Context.CLIPBOARD_SERVICE); 343 | ClipData clipData = ClipData.newPlainText(context.getString(R.string.app_name), 344 | content); 345 | clipboardManager.setPrimaryClip(clipData); 346 | } else { 347 | android.text.ClipboardManager clipboardManager = (android.text.ClipboardManager) context 348 | .getSystemService(Context.CLIPBOARD_SERVICE); 349 | clipboardManager.setText(content); 350 | } 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /工具类/FlowLayout.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Koterwong/Android-Notes/de44be1eec611c1cac0af074ea2ab359fe70a916/工具类/FlowLayout.java -------------------------------------------------------------------------------- /工具类/KeyBoardUtil.java: -------------------------------------------------------------------------------- 1 | package com.sun.bingo.util; 2 | 3 | import android.app.Activity; 4 | import android.view.View; 5 | import android.view.inputmethod.InputMethodManager; 6 | 7 | public class KeyBoardUtil { 8 | 9 | /** 10 | * 收起软键盘 11 | */ 12 | public static void hideKeyboard(Activity activity) { 13 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); 14 | if (imm != null) { 15 | imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); 16 | } 17 | } 18 | 19 | /** 20 | * 弹出软键盘 21 | */ 22 | public static void showKeyboard(final View view) { 23 | view.setFocusable(true); 24 | view.setFocusableInTouchMode(true); 25 | view.requestFocus(); 26 | 27 | view.postDelayed(new Runnable() { 28 | @Override 29 | public void run() { 30 | InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 31 | imm.showSoftInput(view, InputMethodManager.RESULT_UNCHANGED_SHOWN); 32 | } 33 | }, 400); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /工具类/NetUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | import android.telephony.TelephonyManager; 10 | 11 | /** 12 | * Author:Koterwong, 13 | * Date:2016/5/21 20:21 14 | * Description: 15 | */ 16 | public class NetUtils { 17 | 18 | private NetUtils() { 19 | throw new UnsupportedOperationException("cannot be instantiated"); 20 | } 21 | 22 | /** 23 | * 打开网络设置界面 24 | */ 25 | public static void openSetting(Activity activity) { 26 | Intent intent = new Intent("/"); 27 | ComponentName cm = new ComponentName("com.android.settings", 28 | "com.android.settings.WirelessSettings"); 29 | intent.setComponent(cm); 30 | intent.setAction("android.intent.action.VIEW"); 31 | activity.startActivityForResult(intent, 0); 32 | } 33 | 34 | /** 35 | * 判断网络是否可用 36 | * 37 | * @param context context 38 | * @return boolean 39 | */ 40 | public static boolean isNetworkAvailable(Context context) { 41 | ConnectivityManager cm = (ConnectivityManager) context 42 | .getSystemService(Context.CONNECTIVITY_SERVICE); 43 | if (cm != null) { 44 | //如果仅仅是用来判断网络连接 45 | //则可以使用 cm.getActiveNetworkInfo().isAvailable(); 46 | NetworkInfo[] info = cm.getAllNetworkInfo(); 47 | if (info != null) { 48 | for (int i = 0; i < info.length; i++) { 49 | if (info[i].getState() == NetworkInfo.State.CONNECTED) { 50 | return true; 51 | } 52 | } 53 | } 54 | } 55 | return false; 56 | } 57 | 58 | /** 59 | * 判断WIFI是否打开 60 | * 61 | * @param context context 62 | * @return boolean 63 | */ 64 | public static boolean isWifiEnabled(Context context) { 65 | ConnectivityManager mgrConn = (ConnectivityManager) context 66 | .getSystemService(Context.CONNECTIVITY_SERVICE); 67 | TelephonyManager mgrTel = (TelephonyManager) context 68 | .getSystemService(Context.TELEPHONY_SERVICE); 69 | return ((mgrConn.getActiveNetworkInfo() != null && mgrConn 70 | .getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED) || mgrTel 71 | .getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS); 72 | } 73 | 74 | /** 75 | * 判断是否是3G网络 76 | * 77 | * @param context context 78 | * @return boolean 79 | */ 80 | public static boolean is3rd(Context context) { 81 | ConnectivityManager cm = (ConnectivityManager) context 82 | .getSystemService(Context.CONNECTIVITY_SERVICE); 83 | NetworkInfo networkINfo = cm.getActiveNetworkInfo(); 84 | if (networkINfo != null 85 | && networkINfo.getType() == ConnectivityManager.TYPE_MOBILE) { 86 | return true; 87 | } 88 | return false; 89 | } 90 | 91 | /** 92 | * 判断是否是wifi 93 | * 94 | * @param context context 95 | * @return boolean 96 | */ 97 | public static boolean isWifi(Context context) { 98 | ConnectivityManager cm = (ConnectivityManager) context 99 | .getSystemService(Context.CONNECTIVITY_SERVICE); 100 | NetworkInfo networkINfo = cm.getActiveNetworkInfo(); 101 | if (networkINfo != null 102 | && networkINfo.getType() == ConnectivityManager.TYPE_WIFI) { 103 | return true; 104 | } 105 | return false; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /工具类/SDCardUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import android.os.Build; 4 | import android.os.Environment; 5 | import android.os.StatFs; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * Author:Koterwong, 11 | * Date:2016/5/21 20:48 12 | * Description: 13 | */ 14 | public class SDCardUtils { 15 | /** 16 | * 判断SD卡是否挂载 17 | */ 18 | public static boolean isSDCardAvailable() { 19 | return Environment.MEDIA_MOUNTED 20 | .equals(Environment.getExternalStorageState()); 21 | } 22 | 23 | /** 24 | * 获取SD卡路径 25 | */ 26 | public static String getSDCardPath() { 27 | return Environment.getExternalStorageDirectory().getAbsolutePath() 28 | + File.separator; 29 | } 30 | 31 | /** 32 | * 获取SD卡的剩余容量 单位byte 33 | */ 34 | public static long getSDCardAllSize() { 35 | if (isSDCardAvailable()) { 36 | File path = Environment.getExternalStorageDirectory(); 37 | StatFs stat = new StatFs(path.getPath()); 38 | long blockSize; 39 | long totalBlocks; 40 | long availableBlocks; 41 | //判断当前版本是否是4.3或以上 42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 43 | blockSize = stat.getBlockSizeLong(); 44 | totalBlocks = stat.getBlockCountLong(); 45 | availableBlocks = stat.getAvailableBlocksLong(); 46 | } else { 47 | blockSize = stat.getBlockSize(); 48 | totalBlocks = stat.getBlockCount(); 49 | availableBlocks = stat.getAvailableBlocks(); 50 | } 51 | return blockSize * availableBlocks; 52 | } 53 | return 0; 54 | } 55 | 56 | /** 57 | * 获取指定路径所在空间的剩余可用容量字节数,单位byte 58 | * 59 | * @param filePath filePath 60 | * @return 容量字节 SDCard可用空间,内部存储可用空间 61 | */ 62 | public static long getFreeBytes(String filePath) { 63 | // 如果是sd卡的下的路径,则获取sd卡可用容量 64 | if (filePath.startsWith(getSDCardPath())) { 65 | filePath = getSDCardPath(); 66 | } else {// 如果是内部存储的路径,则获取内存存储的可用容量 67 | filePath = Environment.getDataDirectory().getAbsolutePath(); 68 | } 69 | StatFs stat = new StatFs(filePath); 70 | long availableBlocks = (long) stat.getAvailableBlocks() - 4; 71 | return stat.getBlockSize() * availableBlocks; 72 | } 73 | 74 | /** 75 | * 获取系统存储路径 76 | * 77 | * @return 78 | */ 79 | public static String getRootDirectoryPath() { 80 | return Environment.getRootDirectory().getAbsolutePath(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /工具类/SPUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import java.util.Map; 6 | 7 | import android.content.Context; 8 | import android.content.SharedPreferences; 9 | 10 | public class SPUtils 11 | { 12 | public SPUtils() 13 | { 14 | /* cannot be instantiated */ 15 | throw new UnsupportedOperationException("cannot be instantiated"); 16 | } 17 | 18 | /** 19 | * 保存在手机里面的文件名 20 | */ 21 | public static final String FILE_NAME = "share_data"; 22 | 23 | /** 24 | * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法 25 | * 26 | * @param context 27 | * @param key 28 | * @param object 29 | */ 30 | public static void put(Context context, String key, Object object) 31 | { 32 | 33 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, 34 | Context.MODE_PRIVATE); 35 | SharedPreferences.Editor editor = sp.edit(); 36 | 37 | if (object instanceof String) 38 | { 39 | editor.putString(key, (String) object); 40 | } else if (object instanceof Integer) 41 | { 42 | editor.putInt(key, (Integer) object); 43 | } else if (object instanceof Boolean) 44 | { 45 | editor.putBoolean(key, (Boolean) object); 46 | } else if (object instanceof Float) 47 | { 48 | editor.putFloat(key, (Float) object); 49 | } else if (object instanceof Long) 50 | { 51 | editor.putLong(key, (Long) object); 52 | } else 53 | { 54 | editor.putString(key, object.toString()); 55 | } 56 | 57 | SharedPreferencesCompat.apply(editor); 58 | } 59 | 60 | /** 61 | * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值 62 | * 63 | * @param context 64 | * @param key 65 | * @param defaultObject 66 | * @return 67 | */ 68 | public static Object get(Context context, String key, Object defaultObject) 69 | { 70 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, 71 | Context.MODE_PRIVATE); 72 | 73 | if (defaultObject instanceof String) 74 | { 75 | return sp.getString(key, (String) defaultObject); 76 | } else if (defaultObject instanceof Integer) 77 | { 78 | return sp.getInt(key, (Integer) defaultObject); 79 | } else if (defaultObject instanceof Boolean) 80 | { 81 | return sp.getBoolean(key, (Boolean) defaultObject); 82 | } else if (defaultObject instanceof Float) 83 | { 84 | return sp.getFloat(key, (Float) defaultObject); 85 | } else if (defaultObject instanceof Long) 86 | { 87 | return sp.getLong(key, (Long) defaultObject); 88 | } 89 | 90 | return null; 91 | } 92 | 93 | /** 94 | * 移除某个key值已经对应的值 95 | * 96 | * @param context 97 | * @param key 98 | */ 99 | public static void remove(Context context, String key) 100 | { 101 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, 102 | Context.MODE_PRIVATE); 103 | SharedPreferences.Editor editor = sp.edit(); 104 | editor.remove(key); 105 | SharedPreferencesCompat.apply(editor); 106 | } 107 | 108 | /** 109 | * 清除所有数据 110 | * 111 | * @param context 112 | */ 113 | public static void clear(Context context) 114 | { 115 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, 116 | Context.MODE_PRIVATE); 117 | SharedPreferences.Editor editor = sp.edit(); 118 | editor.clear(); 119 | SharedPreferencesCompat.apply(editor); 120 | } 121 | 122 | /** 123 | * 查询某个key是否已经存在 124 | * 125 | * @param context 126 | * @param key 127 | * @return 128 | */ 129 | public static boolean contains(Context context, String key) 130 | { 131 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, 132 | Context.MODE_PRIVATE); 133 | return sp.contains(key); 134 | } 135 | 136 | /** 137 | * 返回所有的键值对 138 | * 139 | * @param context 140 | * @return 141 | */ 142 | public static Map getAll(Context context) 143 | { 144 | SharedPreferences sp = context.getSharedPreferences(FILE_NAME, 145 | Context.MODE_PRIVATE); 146 | return sp.getAll(); 147 | } 148 | 149 | /** 150 | * 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类 151 | * 152 | * @author zhy 153 | * 154 | */ 155 | private static class SharedPreferencesCompat 156 | { 157 | private static final Method sApplyMethod = findApplyMethod(); 158 | 159 | /** 160 | * 反射查找apply的方法 161 | * 162 | * @return 163 | */ 164 | @SuppressWarnings({ "unchecked", "rawtypes" }) 165 | private static Method findApplyMethod() 166 | { 167 | try 168 | { 169 | Class clz = SharedPreferences.Editor.class; 170 | return clz.getMethod("apply"); 171 | } catch (NoSuchMethodException e) 172 | { 173 | } 174 | 175 | return null; 176 | } 177 | 178 | /** 179 | * 如果找到则使用apply执行,否则使用commit 180 | * 181 | * @param editor 182 | */ 183 | public static void apply(SharedPreferences.Editor editor) 184 | { 185 | try 186 | { 187 | if (sApplyMethod != null) 188 | { 189 | sApplyMethod.invoke(editor); 190 | return; 191 | } 192 | } catch (IllegalArgumentException e) 193 | { 194 | } catch (IllegalAccessException e) 195 | { 196 | } catch (InvocationTargetException e) 197 | { 198 | } 199 | editor.commit(); 200 | } 201 | } 202 | 203 | } -------------------------------------------------------------------------------- /工具类/ShareUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.kkgank.utils; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | 7 | /** 8 | * Author:Koterwong, 9 | * Date:2016/5/25 21:15 10 | * Description: 11 | */ 12 | public class ShareUtils { 13 | 14 | public static void shareImage(Context context, Uri uri, String title) { 15 | Intent shareIntent = new Intent(); 16 | shareIntent.setAction(Intent.ACTION_SEND); 17 | shareIntent.putExtra(Intent.EXTRA_STREAM, uri); 18 | shareIntent.setType("image/jpeg"); 19 | context.startActivity(Intent.createChooser(shareIntent, title)); 20 | } 21 | 22 | public static void share(Context context, String content) { 23 | Intent intent = new Intent(Intent.ACTION_SEND); 24 | intent.setType("text/plain"); 25 | intent.putExtra(Intent.EXTRA_SUBJECT, "分享"); 26 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 27 | intent.putExtra(Intent.EXTRA_TEXT, content); 28 | context.startActivity(Intent.createChooser(intent, "分享")); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /工具类/ThreadManager.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import java.util.concurrent.LinkedBlockingQueue; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * 管理线程池 9 | * 10 | * @author itcast 11 | * 12 | */ 13 | public class ThreadManager { 14 | 15 | private ThreadManager() { 16 | 17 | } 18 | 19 | private static ThreadManager instance = new ThreadManager(); 20 | private ThreadPoolProxy longPool; 21 | private ThreadPoolProxy shortPool; 22 | 23 | public static ThreadManager getInstance() { 24 | return instance; 25 | } 26 | 27 | // 联网比较耗时 28 | // cpu的核数*2+1 29 | public synchronized ThreadPoolProxy createLongPool() { 30 | if (longPool == null) { 31 | longPool = new ThreadPoolProxy(5, 5, 5000L); 32 | } 33 | return longPool; 34 | } 35 | // 操作本地文件 36 | public synchronized ThreadPoolProxy createShortPool() { 37 | if(shortPool==null){ 38 | shortPool = new ThreadPoolProxy(3, 3, 5000L); 39 | } 40 | return shortPool; 41 | } 42 | 43 | public class ThreadPoolProxy { 44 | private ThreadPoolExecutor pool; 45 | private int corePoolSize; 46 | private int maximumPoolSize; 47 | private long time; 48 | 49 | public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long time) { 50 | this.corePoolSize = corePoolSize; 51 | this.maximumPoolSize = maximumPoolSize; 52 | this.time = time; 53 | 54 | } 55 | /** 56 | * 执行任务 57 | * @param runnable 58 | */ 59 | public void execute(Runnable runnable) { 60 | if (pool == null) { 61 | // 创建线程池 62 | /* 63 | * 1. 线程池里面管理多少个线程2. 如果排队满了, 额外的开的线程数3. 如果线程池没有要执行的任务 存活多久4. 64 | * 时间的单位 5 如果 线程池里管理的线程都已经用了,剩下的任务 临时存到LinkedBlockingQueue对象中 排队 65 | */ 66 | pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 67 | time, TimeUnit.MILLISECONDS, 68 | new LinkedBlockingQueue(10)); 69 | } 70 | pool.execute(runnable); // 调用线程池 执行异步任务 71 | } 72 | /** 73 | * 取消任务 74 | * @param runnable 75 | */ 76 | public void cancel(Runnable runnable) { 77 | if (pool != null && !pool.isShutdown() && !pool.isTerminated()) { 78 | pool.remove(runnable); // 取消异步任务 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /工具类/ToastUtils.java: -------------------------------------------------------------------------------- 1 | package com.koterwong.basis.ClassTools; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | /** 7 | * Author:Koterwong,Data:2016/5/20. 8 | * Description: ToastUtils 9 | */ 10 | public class ToastUtils { 11 | 12 | public static Context mContext; 13 | 14 | 15 | private ToastUtils() { 16 | throw new UnsupportedOperationException("cannot be instantiated"); 17 | } 18 | 19 | public static void register(Context context) { 20 | mContext = context.getApplicationContext(); 21 | } 22 | 23 | 24 | private static void check() { 25 | if (mContext == null) { 26 | throw new NullPointerException( 27 | "Must initial call ToastUtils.register(Context context) in your " + 28 | "< ? extends Application class>"); 29 | } 30 | } 31 | 32 | 33 | public static void showShort(int resId) { 34 | check(); 35 | Toast.makeText(mContext, resId, Toast.LENGTH_SHORT).show(); 36 | } 37 | 38 | 39 | public static void showShort(String message) { 40 | check(); 41 | Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); 42 | } 43 | 44 | 45 | public static void showLong(int resId) { 46 | check(); 47 | Toast.makeText(mContext, resId, Toast.LENGTH_LONG).show(); 48 | } 49 | 50 | 51 | public static void showLong(String message) { 52 | check(); 53 | Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); 54 | } 55 | 56 | 57 | public static void showLongX2(String message) { 58 | showLong(message); 59 | showLong(message); 60 | } 61 | 62 | 63 | public static void showLongX2(int resId) { 64 | showLong(resId); 65 | showLong(resId); 66 | } 67 | 68 | 69 | public static void showLongX3(int resId) { 70 | showLong(resId); 71 | showLong(resId); 72 | showLong(resId); 73 | } 74 | 75 | 76 | public static void showLongX3(String message) { 77 | showLong(message); 78 | showLong(message); 79 | showLong(message); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /工具类/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #F44336 8 | #FF4081 9 | #9C27B0 10 | #673AB7 11 | #536dfe 12 | #2196f3 13 | #02a9f4 14 | #00bcd4 15 | #009688 16 | #4caf50 17 | #8bc34a 18 | #cddc39 19 | #ffeb3b 20 | #ffc107 21 | #ff9800 22 | #ff5722 23 | #795548 24 | #9e9e9e 25 | #607d8b 26 | 27 | #0DFFFFFF 28 | #1AFFFFFF 29 | #26FFFFFF 30 | #33FFFFFF 31 | #40FFFFFF 32 | #4DFFFFFF 33 | #59FFFFFF 34 | #66FFFFFF 35 | #73FFFFFF 36 | #80FFFFFF 37 | #8CFFFFFF 38 | #99FFFFFF 39 | #A6FFFFFF 40 | #B3FFFFFF 41 | #BFFFFFFF 42 | #CCFFFFFF 43 | #D9FFFFFF 44 | #E6FFFFFF 45 | #F2FFFFFF 46 | #FFFFFFFF 47 | 48 | #0D000000 49 | #1A000000 50 | #26000000 51 | #33000000 52 | #40000000 53 | #4D000000 54 | #59000000 55 | #66000000 56 | #73000000 57 | #80000000 58 | #8C000000 59 | #99000000 60 | #A6000000 61 | #B3000000 62 | #BF000000 63 | #CC000000 64 | #D9000000 65 | #E6000000 66 | #F2000000 67 | #FF000000 68 | 69 | #00000000 70 | #8f000000 71 | 72 | 73 | #FFF5F5F5 74 | #FFF2F2F2 75 | 76 | 77 | #212121 78 | #424242 79 | #616161 80 | #757575 81 | #9e9e9e 82 | #bdbdbd 83 | #e0e0e0 84 | 85 | 86 | #e2e2e2 87 | #e6e6e6 88 | 89 | 90 | -------------------------------------------------------------------------------- /开发艺术探索第三章View事件体系.md: -------------------------------------------------------------------------------- 1 | ### View事件体系 2 | 3 | #### 1. View的位置参数 4 | 5 | View的位置属性,top、left、right、bottom,是相对于自己的父容器来取值的,在父容器执行完layout方法即可使用getTop、getLeft、getRight、getBottom获取它们的值。另外通过这四个属性可以方便得道View的width和height属性,与之对应的方法分别为getWidth()和getHeight()方法。 6 | 7 | 在Android3.0以后,View新增了`x`、`y` 、`translationX` 、`translationY` 属性,其中`x` 、`y` 分别对应了View左上角的坐标,这个坐标同样是相对于父容器的,并且它们之间的关系可以通过`x = left + translationX `公式表示 。需要注意的是,View在平移的过程中,top和left表示的是原始View的位置信息,其值不会反生改变,改变的是`x`、`y` 、`translationX` 、`translationY` 。 8 | 9 | 如果想获取View在屏幕上的坐标`view.getLocationOnScreen(int[] arr);` 。 10 | 11 | #### 2. MotionEvent、TouchSlop、VelocityTracker、GestrueDetector 12 | 13 | (1)MotionEvent:触控事件 14 | 15 | `getX` 、`getY` 获取相对于当前View的触控坐标。 16 | 17 | `getRawX()` 、`getRawY()` 获取相对于手机屏幕触控坐标。 18 | 19 | (2)TouchSlop:系统识别的被认为是滑动的最小距离. 20 | 21 | 我们可通过`ViewConfiguration.get(context).getScaledTouchSlop()`获取该常量的值。我们可以使用该常量在自定义View处理获取动事件时过滤掉小于这个值的事件进而提升用户体验。我们也可以在系统的源码中找到这个常量的定义,framework/base/core/res/res/values/config.xml,其中config_viewConfigurationTouchSlop就是系统定义的常量,这个常量的值一般为8dp。 22 | 23 | (3)VelocityTracker:追踪手指在滑动过程中的速度。 24 | 25 | ```java 26 | //获取对象 27 | VelocityTracker velocityTracker = VelocityTracker.obtain(); 28 | velocityTracker.addMovement(event); 29 | //在获取速度前必须调用computeCirrentVelocity方法,获取当前速度的计算时间。 30 | velocityTracker.computeCurrentVelocity(1000); 31 | //获取速度,这里需要注意从右想做滑动时速度为负 32 | velocityTracker.getXVelocity(); 33 | velocityTracker.getYVelocity(); 34 | //当不使用它的时候,进行回收。(场景?) 35 | velocityTracker.clear(); 36 | velocityTracker.recycle(); 37 | ``` 38 | 39 | (4)GestureDetector:辅助检验用户的单击、滑动、长按、双击行为。 40 | 41 | 使用GestureDetector并不复杂,只需要创建GestureDetector对象,并实现onGestureListener接口,最后需要接管View的onTouchEvent事件。 42 | 43 | ```java 44 | GestureDetector gestureDetector = new GestureDetector(context, this); 45 | //解决长按屏幕后无法拖动现象。 46 | gestureDetector.setIsLongpressEnabled(false); 47 | //接管onTouchEvent事件 48 | @Override 49 | public boolean onTouchEvent(MotionEvent event) { 50 | boolean consume = gestureDetector.onTouchEvent(event); 51 | return consume; 52 | } 53 | ``` 54 | onDestureListener有一系列的回调方法,我们可根据回调方法,处理不同的View的事件。 55 | 56 | - onSingleTapUp:单击行为,相当于ACTION_DOWN和ACTION_UP的触发。 57 | - onFling:触摸屏幕,快速滑动松开。一个ACTION_DOWN,多个ACTION_MOVE和一个ACTION_UP触发。 58 | - onScorll:拖动事件。一个ACTION_DOWN,多个ACTION_MOVE和一个ACTION_UP触发。 59 | - onLoogPress:长按。 60 | - onDoubleTap:双击。 61 | 62 | 在日常的开发中,完全可以不使用onDestrueDetector,监听相关滑动完全可以在onTouchEvent中实现。如果要监听用户的双击行为,可以使用onDoubleTap实现。 63 | 64 | #### 3.View的滑动。 65 | 66 | (1)使用ScrollTo/ScrollBy 67 | 68 | View类提供了`ScrollTo` 和`ScrollBy` 来实现View的滑动,需要注意的是,它们实现View的滑动**实现的是View内容区域的滑动,View本身的位置并没有经过滑动,也就是说不可能将View滑动附近View所在的区域。** 69 | 70 | 使用`ScrollTo` 和`ScrollBy` 实现View的滑动,需要注意两个重要的参数,`mSrollX`和`mSrollY`这连个参数表示View内容区域滑动的偏移量,仅当View的内容区域在View原位置的左边缘的时候这个值为正。 71 | 72 | ```java 73 | /** 74 | * 使View滑动的偏移量。如果要实现内容区域向右滑动,那么传入参数的X的值为负。 75 | * 76 | * Set the scrolled position of your view. This will cause a call to 77 | * {@link #onScrollChanged(int, int, int, int)} and the view will be 78 | * invalidated. 79 | * @param x the x position to scroll to 80 | * @param y the y position to scroll to 81 | */ 82 | public void scrollTo(int x, int y) { 83 | if (mScrollX != x || mScrollY != y) { 84 | int oldX = mScrollX; 85 | int oldY = mScrollY; 86 | mScrollX = x; //可以看出mScrollX就等于我们传入的参数。 87 | mScrollY = y; 88 | invalidateParentCaches(); 89 | onScrollChanged(mScrollX, mScrollY, oldX, oldY); 90 | if (!awakenScrollBars()) { 91 | postInvalidateOnAnimation(); 92 | } 93 | } 94 | } 95 | /** 96 | * 实现相对位置的滑动。同样是向右传负 97 | * 98 | * Move the scrolled position of your view. This will cause a call to 99 | * {@link #onScrollChanged(int, int, int, int)} and the view will be 100 | * invalidated. 101 | * @param x the amount of pixels to scroll by horizontally 102 | * @param y the amount of pixels to scroll by vertically 103 | */ 104 | public void scrollBy(int x, int y) { 105 | scrollTo(mScrollX + x, mScrollY + y); 106 | } 107 | ``` 108 | 109 | (2)使用动画 110 | 111 | 主要通过View的`translationX`和`translationY`属性来实现View的平移。需要注意的是使用View动画,是对View的影响操作,并不能改变View的属性。使用属性动画,只能兼容到3.0以上,要向兼容到低版本需要使用nineOldAndroid动画库,但是在3.0以下实现的还是View动画。 112 | 113 | 这里提供一个使用View动画平移之后,同样能响应点击的方案。可以在View平移的目标位置,新建一个和View一模一样的View,在动画执行完成后,将旧的View设置为GONE。 114 | 115 | (3)改变布局参数,LayoutParams。 116 | 117 | 通过改变View的LeftMargin参数,实现View的滑动。 118 | 119 | > 这个本提到的实现View的滑动方法不是很全,可参考Android群英传第五章,使用Layout方法和ViewDragHelper。 120 | 121 | #### 4.Scroller:弹性滑动对象。 122 | 123 | 在我们自定义View的过程中,有时候处理滑动事件,当手指松开的时,我们需要让View移动到指定的位置。如果使用ScrollTo/ScrollBy或者其他方法(layout方法),那么这个时候我们就需要使用弹性滚动对象Scroller。 124 | 125 | Scroller的基本使用: 126 | 127 | ```java 128 | Scroller scroller = new Scroller(getContext()); 129 | 130 | private void smoothScrollTo(int dx, int dy, int duration) { 131 | //在duration的时间内,缓慢滑动到dx + getScrollX,dy + getScrollY. 132 | scroller.startScroll(getScrollX(), getScrollY(), dx, dy, duration); 133 | //Scroller本身不能使View进行滑动,需要使View重绘。 134 | invalidate(); 135 | } 136 | //重写View的computeScroll方法 137 | @Override public void computeScroll() { 138 | super.computeScroll(); 139 | if(scroller.computeScrollOffset()){ 140 | scrollTo(scroller.getCurrX(),scroller.getCurrY()); 141 | postInvalidate(); 142 | } 143 | } 144 | ``` 145 | 146 | 以上就是Scroller的经典使用场景,我们调用srcoller的`startScroll()`方法来让View开始弹性滚动,其他startScroll方法并没有让View弹性滚动的功能,下面来看startScroll的实现。 147 | 148 | ```java 149 | /** 150 | * Start scrolling by providing a starting point, the distance to travel, 151 | * and the duration of the scroll. 152 | */ 153 | public void startScroll(int startX, int startY, int dx, int dy, int duration) { 154 | mMode = SCROLL_MODE; 155 | mFinished = false; 156 | mDuration = duration; 157 | mStartTime = AnimationUtils.currentAnimationTimeMillis(); 158 | mStartX = startX; 159 | mStartY = startY; 160 | mFinalX = startX + dx; 161 | mFinalY = startY + dy; 162 | mDeltaX = dx; 163 | mDeltaY = dy; 164 | mDurationReciprocal = 1.0f / (float) mDuration; 165 | } 166 | ``` 167 | 168 | 以上就是`startScroll()`的实现,可以看到,它只是对View的滑动参数做了记录,并没有实现滑动,而真正实现滑动的就是我们手动调用`invalidate()`方法来使View重新重绘,在View的`onDraw()`方法中又会调用到View默认预留的`computeScroll()`方法。而这个时候我们只需要重写`computeScroll()`方法,在`computeScroll()`方法中去调用`scroller.computeScrollOffset()`方法判断scroller对象是否完成滚动,如果未完成,继续调用srcollTo方法,参数需要传入scroller对象所计算的需要滑动的距离 ,接下来继续调用`postInvalidate` 方法,来引起View的重绘,不断重复这个过程知道Scroller滑动完成。Scroller对像的作用就是不断的引起View的重绘,并计算出View重绘时所需要的滑动的距离。其中还有一个重要的方法就是Scroller计算滑动距离是否完成的方法。我们来看一下`scroller.computeScrollOffset()`的实现。 169 | 170 | ```java 171 | /** 172 | * Call this when you want to know the new location. If it returns true, 173 | * the animation is not yet finished. 174 | */ 175 | public boolean computeScrollOffset() { 176 | if (mFinished) { 177 | return false; 178 | } 179 | //计算滑动经过的时间,如果时间小时滑动需要执行的时间,那么继续计算mCurrX、mCurrY。 180 | int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime) 181 | if (timePassed < mDuration) { 182 | switch (mMode) { 183 | case SCROLL_MODE: 184 | //通过默认插补器获取需要进行滑动的距离。 185 | final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal); 186 | mCurrX = mStartX + Math.round(x * mDeltaX); 187 | mCurrY = mStartY + Math.round(x * mDeltaY); 188 | break; 189 | case FLING_MODE: 190 | ......//这里不再分析 191 | } 192 | else { 193 | mCurrX = mFinalX; 194 | mCurrY = mFinalY; 195 | mFinished = true; 196 | } 197 | return true; 198 | } 199 | ``` 200 | 201 | 看到这里,Scroller的设计简直不要太棒。我们也能很明显的看出来Scroller对象真正职责,它会根据我们需要滑动的距离和时间计算出下一次小幅度滑动的距离,并配合View的`computeScroll`方法完成View的滑动。 202 | 203 | 实现View的弹性滑动还可以使用动画和采用一定的延时策略这里就不再意义做介绍啦。 204 | 205 | ### 5.View的事件分发机制 206 | 207 | (1)View事件分发的概述。 208 | 209 | 所谓View事件的分发就是对MotionEvent事件的分发过程,即当一个MotionEvent事件产生,系统会把这个事件传递给一个具体的View处理。 210 | 211 | (2)事件分发的三个方法概述。 212 | 213 | - `dispatchTouchEvent()` :用于事件分发,当触摸事件传递给当前View的时候此方法一定会调用。返回结果受当前View的`onTouchEvent()`和下级View的`dispatchTouchEvent()`影响,表示是否消耗当前事件。 214 | - `onInterceptTouchEvent()`:在`dispatchTouchEvetn()`方法的内部调用,表示是否拦截当前事件,如果当前View拦截了某个事件,那么在这个事件序列中,该方法不会在调用。拦截的事件交给`onTouchEvent`处理,如果`onTouchEvent`返回false那在事件重新交给父View的`onTouchEvent`。 215 | - `onTouchEvent()`:处理事件,表示是否消耗当前事件,如果不消耗,同一事件序列中,当前View无法收到事件。 216 | 217 | 218 | 当一个点击事件产生后,它的传递顺序遵循如下顺序 Activity -> Window -> View 。事件总是先传递给Activity ,Activity在传递给Window,最后Window在传递给顶级的View。顶级View接受到事件之后就会对事件进行分发。 219 | 220 | (3)关于事件分发的总结。 221 | 222 | - 一个事件序列是指,手指从按下屏幕的那一刻起到离开屏幕结束。中间会有一个ACTION_DOWN事件,多个ACTION_MOVE事件和一个ACTION_UP事件。 223 | - 一个View一旦决定拦截某个事件,那么整个事件必须交给该View去处理,View的`onInterceptTouchEvent`也不会在此调用,因为View一旦决定拦截某个事件,那么这个事件序列就会交给他处理 。不过我们可以通过特殊手段将View的onTouchEvent事件强制交给其他View来处理。 224 | - 某个View一旦开始处理某个事件,它的`onTouchEvent`就会调用,如果View不对` ACTION_DOWN`进行消费,那么接下来的事件就不会交给该View去处理,父类的`ouTouchEvent`就会调用,以此类推。如果View不消耗除了`ACTION_DOWN`以外的事件,那么这个事件就会流失。 225 | - ViewGroup默认不拦截任何事件。它的`onInterceptTouchEvent`默认返回false。 226 | - View没有`onInterceptEvent`方法,一旦事件传递给他,那么它的`onTouchEvent`方法就会调用。 227 | - View的`ouTouchEvent`一般会消耗当前事件(返回true),除非它是不可点击的(clickable和longclickable同时为false)。View的longclickable属性默认都为false,click属性分情况,比如Button的clickable属性默认为true,而TextView的clickable属性为false。 228 | - View的`enable`属性,不影响onTouchEvent的返回值。那么它是disable状态的,只要它的clickable或者longclickable属性有一个为true,那么它的onTouchEvent就返回true。也就是说View的onTouchEvent的返回默认情况只和View的`clickable`和`longclickable`属性有关。 229 | - 当一个View处理事件时,如果他设置了`onTouchListener`那么它的`onTouch`方法就会调用,如果onTouch方法返回返回false,那么它的onTouchEvent就会调用,如果返回true那么onTouchEvent就不会调用。在`onTouchEvent`中,如果View设置了onClickListener,那么它的`onClick`方法会调用。 230 | - `onClick`发生的前提是View是可以点击的,并且收到了down和up的事件。 231 | - `requestDisallowInterceptTouchEvent`会干预父View的事件分发过程,但是ACTION_DOWN除外。 232 | 233 | ### 5.1View的事件分发源码解析。 234 | 235 | 前面了解到View事件分发顺序为:Activity -> Window ->View。 236 | 237 | ViewGroup的`dispatchTouchEvent` 方法,关于事件是否拦截方面做的处理。 238 | 239 | ```java 240 | // Handle an initial down. 241 | if (actionMasked == MotionEvent.ACTION_DOWN) { 242 | // Throw away all previous state when starting a new touch gesture. 243 | // The framework may have dropped the up or cancel event for the previous gesture 244 | // due to an app switch, ANR, or some other state change. 245 | cancelAndClearTouchTargets(ev); 246 | resetTouchState(); 247 | } 248 | // Check for interception. 249 | final boolean intercepted; 250 | if (actionMasked == MotionEvent.ACTION_DOWN 251 | || mFirstTouchTarget != null) { 252 | final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 253 | if (!disallowIntercept) { 254 | intercepted = onInterceptTouchEvent(ev); 255 | ev.setAction(action); // restore action in case it was changed 256 | } else { 257 | intercepted = false; 258 | } 259 | } else { 260 | // There are no touch targets and this action is not an initial down 261 | // so this view group continues to intercept touches. 262 | intercepted = true; 263 | } 264 | ``` 265 | 266 | 当ViewGroup不拦截事件时,在`dispatchTouchEvent`中对点击事件分发的源码如下: 267 | 268 | ```java 269 | final View[] children = mChildren; 270 | for (int i = childrenCount - 1; i >= 0; i--) { 271 | final int childIndex = customOrder 272 | ? getChildDrawingOrder(childrenCount, i) : i; 273 | final View child = (preorderedList == null) 274 | ? children[childIndex] : preorderedList.get(childIndex 275 | // If there is a view that has accessibility focus we want it 276 | // to get the event first and if not handled we will perform a 277 | // normal dispatch. We may do a double iteration but this is 278 | // safer given the timeframe. 279 | if (childWithAccessibilityFocus != null) { 280 | if (childWithAccessibilityFocus != child) { 281 | continue; 282 | } 283 | childWithAccessibilityFocus = null; 284 | i = childrenCount - 1; 285 | } 286 | if (!canViewReceivePointerEvents(child) 287 | || !isTransformedTouchPointInView(x, y, child, null)) 288 | ev.setTargetAccessibilityFocus(false); 289 | continue; 290 | } 291 | newTouchTarget = getTouchTarget(child); 292 | if (newTouchTarget != null) { 293 | // Child is already receiving touch within its bounds. 294 | // Give it the new pointer in addition to the ones it is h 295 | newTouchTarget.pointerIdBits |= idBitsToAssign; 296 | break; 297 | } 298 | resetCancelNextUpFlag(child); 299 | if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAs 300 | // Child wants to receive touch within its bounds. 301 | mLastTouchDownTime = ev.getDownTime(); 302 | if (preorderedList != null) { 303 | // childIndex points into presorted list, find origina 304 | for (int j = 0; j < childrenCount; j++) { 305 | if (children[childIndex] == mChildren[j]) { 306 | mLastTouchDownIndex = j; 307 | break; 308 | } 309 | } 310 | } else { 311 | mLastTouchDownIndex = childIndex; 312 | } 313 | mLastTouchDownX = ev.getX(); 314 | mLastTouchDownY = ev.getY(); 315 | newTouchTarget = addTouchTarget(child, idBitsToAssign); 316 | alreadyDispatchedToNewTouchTarget = true; 317 | break; 318 | } 319 | } 320 | ``` -------------------------------------------------------------------------------- /开发艺术探索第六章Drawable.md: -------------------------------------------------------------------------------- 1 | ### Drawable简介 2 | 3 | Drawable是可以在Canvas上绘制的进行绘制的抽象物件,之所以说是抽象,是它要依附在View上去显示。而View就是显示在UI界面上实实在在存在的图形了,Drawable和View都可以进行绘制,但是drawable一般是依附在View而存在的,通常作为View的背景来使用。Drawable的种类可以分为好多种,通常通过xml的方式定义Drawable,特殊情况下也可以在代码来定义Drawable。下面将对每一种Drawable进行实例讲解。 4 | 5 | 在讲解之前需要了解Drawable两个重要的方法,getIntrinsicWidth和getIntrinsicHeight,它们表示drawable的内部宽和高,但不是所有的drawable都有内部宽高,一张图片形成的Drawable内部宽高就是Bitmap的宽高。由于drawable没有的大小的概念,当做View的背景时,会被拉伸致View的宽高。 6 | 7 | ### BitmapDrawable 8 | 9 | BitmapDrawable代表一张图片,在开发中,我们可以直接引用原始图片,也可以通过xml的方式描述图片并且可以给图片设置更多的效果。 10 | 11 | ```xml 12 | 21 | ``` 22 | 23 | ### ShapeDrawable 24 | 25 | shapedrawable是一种常见的drawable,可以用颜色构建出图形,它的语法如下所示。 26 | 27 | ```xml 28 | 30 | 31 | 37 | 38 | 49 | 50 | 56 | 57 | 60 | 61 | 62 | 63 | 68 | 69 | 70 | ``` 71 | 72 | - android:shape表示图片的形状,一般来包含四种:rectangle、oval、line、ring,需要主意的是,line和ring需要指定``来指定宽度和颜色信息。针对ring这个形状,还需要5个特殊属性。 73 | 74 | | | | 75 | | ---- | ---- | 76 | | | | 77 | | | | 78 | | | | 79 | | | | 80 | 81 | ### LayerDrawble 82 | 83 | 使用`layer-list`标签来实现,可以包含多个item,每个item代表一个drawable。下面这个例子来实现一个输入框。 84 | 85 | ```xml 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 101 | 102 | 103 | 104 | 105 | 106 | ``` 107 | 108 | ### StateListDrawable 109 | 110 | 使用`selector`标签来实现,设置View背景时会根据View的状态来选择不同的drawabel。 111 | 112 | ```xml 113 | 114 | 115 | 116 | 117 | //checkbox选中状态 118 | 119 | ``` 120 | 121 | ### LevelListDrawable 122 | 123 | 使用`level-list`来实现,drawable的集合,通过drawable的`setLevel`或者ImageView的`setImageLevel`来切换不同level的item背景。 124 | 125 | ```xml 126 | 127 | 132 | 135 | 136 | ``` 137 | 138 | ### TransitionDrawable 139 | 140 | 使用``标签来实现,用于实现两个drawable之前的淡入淡出效果。 141 | 142 | ```xml 143 | 144 | 145 | 146 | 147 | ``` 148 | 149 | 当这个drawabel作为View的背景使用时,可使用以下方式来实现View的淡入淡出切换。 150 | 151 | ```java 152 | TransitionDrawable transitionDrawable = (TransitionDrawable)textView.getBackground(); 153 | transitionDrawable.startTransition(2000); 154 | transitionDrawable.reverseTransition(2000); 155 | ``` 156 | 157 | ### InsertDrawable 158 | 159 | 使用``标签来实现,将Drawable镶嵌到自己的内部,当View希望自己的背景比实际区域小时可以使用InsertDrawable。 160 | 161 | ```xml 162 | 167 | 168 | 169 | 170 | 171 | ``` 172 | 173 | ### ScaleDrawable 174 | 175 | 使用`scale`标签来实现 176 | 177 | ### ClipDrawable 178 | 179 | 使用`clip`标签,它可以根据自己当前的等级来裁剪一个drawable。clipOrientation表示开始裁剪的方向,和gravity搭配使用。通过clipDrawable的setLevel方法设置裁剪的大小。等级0代表全部裁剪,10000代表不裁减,即等级越高drawable的显示范围越大。 180 | 181 | 下面这个例子会实现从上往下裁剪。 182 | 183 | ```xml 184 | 188 | ``` -------------------------------------------------------------------------------- /收集Android开源项目.md: -------------------------------------------------------------------------------- 1 | ##1.UI框架 2 | ##2.开源App 3 | 4 | 5 | ### 1. expanding-collection-android 6 | 7 | ExpandingCollection is a card peek/pop controller 8 | 9 | ![](E:\Android\my_github\Android-Notes\imgs\preview.gif) 10 | 11 | 12 | 13 | ### 2.UltraViewPager 14 | 15 | UltraViewPager是一个封装多种特性的ViewPager,主要是为多页面切换场景提供统一解决方案。由AliBaBa开源。 16 | 17 | #### 主要功能 18 | 19 | - 支持横向滑动/纵向滑动 20 | - 支持一屏内显示多页 21 | - 支持循环滚动 22 | - 支持定时滚动,计时器使用Handler实现 23 | - 支持设置ViewPager的最大宽高 24 | - setRatio按比例显示UltraviewPager 25 | - 内置indicator,只需简单设置几个属性就可以完成展示,支持圆点和Icon; 26 | - 内置两种页面切换动效 27 | 28 | ### 3.EventNote - SDK 29 | 30 | 印象笔记开源的sdk:https://github.com/evernote/evernote-sdk-android/ 31 | 32 | ### 4. Android_Data : 33 | 34 | 整理Android学习材料,博客、社区、开源项目、书籍、Awesome系列、等其他开源资料。[Android_Data](https://github.com/Freelander/Android_Data) 35 | 36 | ### 5.Awesome-android-ui 37 | 38 | 不错的Android-UI/UX手机库,很多UI效果可以在这里找。[Awesome-android-ui](https://github.com/Freelander/Android_Data) 39 | 40 | ### 6.circular-progress-button 41 | 42 | ![](https://raw.githubusercontent.com/dmytrodanylyk/circular-progress-button/dev/screenshots/intro.gif) 43 | 44 | [circular-progress-button](https://github.com/dmytrodanylyk/circular-progress-button) 45 | 46 | ### 7.SmartRefreshLayout 47 | 48 | [Android智能下拉刷新框架,支持越界回弹,集成了几十种炫酷的Header和 Footer。 RefreshLayout,OverScroll](https://github.com/scwang90/SmartRefreshLayout) 49 | 50 | -------------------------------------------------------------------------------- /网络请求框架.md: -------------------------------------------------------------------------------- 1 | 网络请求框架的一般步骤: 2 | 3 | 1、构造请求客户端。 4 | 5 | 2、 6 | 7 | ### Retrofit 8 | 9 | 鸿翔博客:[Retrofit2详解](http://blog.csdn.net/lmj623565791/article/details/51304204) 10 | 11 | 简书:[Retrofit完全解析,比较全面](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0518/4270.html) 12 | 13 | 简单介绍: 14 | 15 | - 底层对网络的访问默认也是基于OKHttp。适合restfil url格式的请求。更多的注解提供功能 16 | 17 | 18 | - get,post 19 | - 动态url,动态查询参数设置 20 | - 更多注解:表单提交。 21 | - 上传文件(单文件,多文件) 22 | - 下载文件() 23 | - 自定义ConverterFactory。对返回`requestBody`进行转化和特殊的`requestBody`的构造 24 | - 源码解析,自定义`Converter.Factory`等一些细节的探索 25 | 26 | 27 | ### OKHttp 28 | 29 | ### Volley 30 | 31 | 谷歌13年I/O大会发布android网络请求通讯库。 32 | 33 | 特点: 34 | 35 | - 网络通讯更快,更简单 36 | - Get,Post高效网络请求以及网络图片的加载和缓存高效化。 37 | - 排序 38 | - 网络请求缓存 39 | - 多级别取消请求 40 | - Activity生命周期联动。在onStop()取消请求。 41 | - 不适合数据的上传和下载。 42 | 43 | #### Voleey的基本用法介绍。 44 | 45 | []() 46 | 47 | -------------------------------------------------------------------------------- /跳过Flash界面的正确姿势.md: -------------------------------------------------------------------------------- 1 | 一般情况下,用户打开我们的App第一屏显示的是SplashActivity或者WelcomeActivity,在这个页面中我们一般用于展示我们App的基本信息和公司Log等,有时候也会获取一些广告信息和基本的资源操作等。但是现在有一个场景,用户不小心点击了两次返回键(点击两次返回键退出)退出了我们的App,在重新打开时,显示的第一屏仍是SplashActivity,这对用户来说体验是很不友好的。我们现在的解决办法是`moveTaskToBack`,没错就是只有这个方法。它的实现如下: 2 | 3 | ```java 4 | /** 5 | * Move the task containing this activity to the back of the activity 6 | * stack. The activity's order within the task is unchanged. 7 | * 8 | * @param nonRoot If false then this only works if the activity is the root 9 | * of a task; if true it will work for any activity in 10 | * a task. 11 | * 12 | * @return If the task was moved (or it was already at the 13 | * back) true is returned, else false. 14 | */ 15 | public boolean moveTaskToBack(boolean nonRoot) { 16 | try { 17 | return ActivityManagerNative.getDefault().moveActivityTaskToBack( 18 | mToken, nonRoot); 19 | } catch (RemoteException e) { 20 | // Empty 21 | } 22 | return false; 23 | } 24 | ``` 25 | 26 | 在我们的程序退出前,我们可以调用该方法将我们的mainActiviry移动到返回栈,并且不会销毁当前的Activity,不执行onDestroy方法。那么当程序下次启动的时候,默认打开的就是我们MainActivity,当前这个需要保证我们的程序没有被系统回收。具体的实现方法如下: 27 | 28 | ```java 29 | @Override 30 | public boolean dispatchKeyEvent(KeyEvent event) { 31 | if (event.getAction() == KeyEvent.ACTION_DOWN 32 | && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 33 | moveTaskToBack(true); 34 | return true; 35 | } 36 | return super.dispatchKeyEvent(event); 37 | } 38 | ``` 39 | 40 | 我们在MainAcitivity的`dispatchKeyEvent` 中判断用户点击的是否是返回键,如果是,就调用`moveTaskToBack` 方法。这个方法也可以写到onKeyDown方法,前提onKeyDown能接收到键盘事件。但是不能写在 `onBackPressed`中,因为这个时候我们的activity就要被销毁了。 --------------------------------------------------------------------------------