├── .gitignore ├── AndroidNote ├── Android基础 │ ├── Activity详细解析.md │ ├── Android-SQLite的基本使用.md │ ├── Android中相机与相册的详细使用.md │ ├── Android异步任务机制之AsycTask.md │ ├── Android数据存储的五种方式.md │ ├── Android获取SHA1.md │ ├── Android跟随手指移动的view.md │ ├── BroadcastReceiver详细解析.md │ ├── ContentProvider实例详解.md │ ├── Handler,Looper,MessageQueue关系.md │ ├── IntentService详细解析.md │ ├── RecyclerView的简介.md │ ├── Service详细解析.md │ ├── tablayout记录.md │ ├── test.kt │ └── 图片缓存原理.md ├── Android开源框架相关 │ ├── Android当下最流行的开源框架总结.md │ ├── Android黑科技——ButterKnifeZelezny.md │ ├── Picasso-android-load-image-layout.md │ ├── RxJava+retrofit2实现安卓中网络操作.md │ ├── 一款Android的Log、Toast的库.md │ └── 动态申请权限库:easypermissions使用与源码解析.md ├── Android性能优化相关 │ └── LeakCanary工作过程以及原理.md ├── Android打包相关 │ ├── Android发布sdk到jcenter.md │ └── Android将library打包成jar文件或aar文件.md ├── Android报错记录 │ ├── Android报错-Manifest merger failed with multiple errors, see logs.md │ └── Android报错2.md ├── Android编译器相关 │ ├── AndroidStudio使用教程(第一弹).md │ ├── AndroidStudio使用教程(第七弹).md │ ├── AndroidStudio使用教程(第三弹).md │ ├── AndroidStudio使用教程(第二弹).md │ ├── AndroidStudio使用教程(第五弹).md │ ├── AndroidStudio使用教程(第六弹).md │ └── AndroidStudio使用教程(第四弹).md ├── Android自定义View │ ├── Android事件分发机制.md │ ├── PathMeasure.md │ ├── 三阶贝塞尔曲线.md │ ├── 二阶贝塞尔曲线.md │ ├── 自定义ViewGroup入门.md │ ├── 自定义View——CameraView.md │ ├── 自定义View——CheckView.md │ ├── 自定义View——CircleView.md │ ├── 自定义View——FlowLayout.md │ ├── 自定义View——PieView.md │ ├── 自定义View入门.md │ └── 自定义view——sideslipListView.md ├── Android进阶 │ ├── AndroidStudio导入工程一直在Building的解决方案.md │ ├── Android中的动画.md │ ├── Android内存泄漏总结.md │ ├── Android性能优化.md │ ├── Android项目总结.md │ ├── Android项目总结2.md │ ├── Android项目总结3.md │ ├── Handler引起的内存泄漏以及分析.md │ ├── MVP+RxJava+Retrofit2+Dagger实战.md │ ├── Recyclerview和Listview的异同.md │ ├── iterm2+vim打造完美终端.md │ ├── jvm-serializers.md │ ├── 基于OTP算法的双向认证.md │ ├── 检查app是否有推送权限.md │ ├── 深入了解MVXX模式.md │ └── 自定义RadioGroup.md ├── Android面试相关 │ ├── Android5.0-6.0-7.0新特性.md │ ├── Android中常见面试题.md │ ├── Android中弱引用与软引用.md │ ├── Android图片三级缓存.md │ ├── Android推送实现原理.md │ ├── Asset目录与res目录的区别.md │ ├── JSON的定义.md │ ├── Java中Error和Exception.md │ ├── ListView性能优化.md │ ├── Service保活.md │ ├── 如何实现Activity切换的动画.md │ ├── 如何提高Activity启动速度.md │ ├── 如何终止App的运行.md │ └── 面试题.md ├── Kotlin相关 │ └── Kotlin-for-android.md ├── img │ ├── android-note.jpg │ ├── android-note2.jpg │ ├── androidnote.jpg │ ├── background.jpg │ ├── background2.jpg │ └── linsir.jpg └── webRTC相关 │ ├── WebRTC-Android源码解析.md │ ├── WebRTC——Android入门.md │ ├── WebRTC——AudioRenderer解析.md │ ├── WebRTC——AudioSource、VideoSource解析.md │ ├── WebRTC——AudioTrack-VideoTrack解析.md │ ├── WebRTC——IceCandidate、SdpObserver、CameraSession解析.md │ ├── WebRTC——MediaSource-java解析.md │ ├── WebRTC——MeidaStreamTrack解析.md │ ├── WebRTC——PeerConnection-java解析.md │ ├── WebRTC——PeerConnectionFactory-java解析.md │ ├── WebRTC——VideoFileRenderer解析.md │ └── WebRTC——VideoRenderer解析.md ├── Git └── git详细教程.md ├── Go └── Go的练习代码.md ├── IOSNote └── Ios上架app需要的图标尺寸.md ├── JavaNote ├── Javaee │ └── Spring-boot入门.md ├── Java相关 │ ├── ArrayList、LinkedList、Vector的异同.md │ ├── Des加密算法.md │ ├── HashTable和HashMap的异同.md │ ├── JVM类加载器.md │ ├── JVM虚拟机基础知识.md │ ├── Java中Error和Exception.md │ ├── Java利用ExecutorService实现同步执行大量线程.md │ ├── Java利用listener实现回调,即观察者模式.md │ ├── Java回调的原理与实现.md │ ├── Java基础知识.md │ ├── Java注解的编写与Java的反射机制.md │ ├── 发布jar包到Maven中央仓库.md │ └── 面向对象的六大原则以及常见的十七种设计模式.md └── 设计模式相关 │ ├── 单例模式.md │ ├── 单例模式的四种实现方式.md │ ├── 观察者模式.md │ └── 设计模式概括.md ├── LICENSE ├── Linux └── Android-GitLabCi配置.md ├── MacNote ├── Mac平台重新设置MySQL的root密码.md ├── SSH原理与应用.md ├── mac上常用命令.md ├── mac本地生成ssh-key.md ├── mac终端与服务器保持连接.md ├── nodejs与npm的更新.md ├── paw-for-mac.md ├── 一些mac上面安装环境的指令.md ├── 如何在mac上安装java1-8.md └── 项目中遇到的单词.md ├── README.md ├── ReactNative相关 ├── React Native 的ES5 ES6写法对照表.md ├── ReactNative入门.md ├── ReactNative利用CodePush实现热更新.md ├── ReactNative报错记录.md ├── ReactNative调试心得.md ├── Touchable系列组建讲解.md └── 短信验证码倒计时控件.md ├── ScriptNote ├── GitHub基础操作.md ├── 一篇文章学懂Shell脚本.md ├── 封装一些GitHub常用命令.md └── 简单的Shell脚本.md ├── WebNote ├── MySQL相关 │ ├── ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localho.md │ ├── Error--ER_TRUNCATED_WRONG_VALUE_FOR_FIELD.md │ ├── Mysql导出数据库、表(有无数据).md │ ├── mysql基础操作.md │ └── 云服务器linux下安装MySQL.md └── NodeJS相关 │ ├── koa框架对post内容读取并解析.md │ ├── nodejs查询数据库后将值返回前端.md │ ├── nodejs项目在云服务器的部署.md │ ├── test.html │ └── 淘宝cnpm.md └── 网络协议 ├── SSH原理与应用.md ├── 浅析Hessian协议.md ├── 浅析RPC协议.md ├── 浅析dubbo服务.md └── 浅析socket.md /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | *~ 9 | -------------------------------------------------------------------------------- /AndroidNote/Android基础/Android异步任务机制之AsycTask.md: -------------------------------------------------------------------------------- 1 | 2 | > 在Android中实现异步任务机制有两种方式,**Handler**和**AsyncTask**。 3 | > 4 | > Handler已经在上一篇文章 [异步消息处理机制(Handler 、 Looper 、MessageQueue)源码解析](http://blog.csdn.net/amazing7/article/details/51424038#reply) 说过了。 5 | > 6 | > 本篇就说说AsyncTask的异步实现。 7 | 8 | 9 | ## 1、什么时候使用 AsnyncTask 10 | 11 |   在上一篇文章已经说了,主线程主要负责控制UI页面的显示、更新、交互等。 为了有更好的用户体验,UI线程中的操作要求越短越好。 12 | 13 |   我们把耗时的操作(例如网络请求、数据库操作、复杂计算)放到单独的子线程中操作,以避免主线程的阻塞。但是在子线程中不能更新UI界面,这时候需要使用handler。 14 | 15 |   但如果耗时的操作太多,那么我们需要开启太多的子线程,这就会给系统带来巨大的负担,随之也会带来性能方面的问题。在这种情况下我们就可以考虑使用类AsyncTask来异步执行任务,不需要子线程和handler,就可以完成异步操作和刷新UI。 16 | 17 | 18 |   不要随意使用AsyncTask,除非你必须要与UI线程交互.默认情况下使用Thread即可,要注意需要将线程优先级调低.AsyncTask适合处理短时间的操作,长时间的操作,比如下载一个很大的视频,这就需要你使用自己的线程来下载,不管是断点下载还是其它的. 19 | 20 | ## 2、AsnyncTask原理 21 | 22 | 23 |   AsyncTask主要有二个部分:一个是与主线程的交互,另一个就是线程的管理调度。虽然可能多个AsyncTask的子类的实例,但是AsyncTask的内部Handler和ThreadPoolExecutor都是进程范围内共享的,其都是static的,也即属于类的,类的属性的作用范围是CLASSPATH,因为一个进程一个VM,所以是AsyncTask控制着进程范围内所有的子类实例。  24 | 25 |   AsyncTask内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了AsyncTask的execute()方法后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。 26 | 27 | 28 | ## 3、AsyncTask介绍 29 |   Android的AsyncTask比Handler更轻量级一些(只是代码上轻量一些,而实际上要比handler更耗资源),适用于简单的异步处理。 30 |    31 |   Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),因为UI的更新只能在主线程中完成,因此异步处理是不可避免的。 32 | 33 |   AsyncTask:对线程间的通讯做了包装,是后台线程和UI线程可以简易通讯:后台线程执行异步任务,将result告知UI线程。 34 | 35 | 使用AsyncTask分为两步:  36 | 37 | ① 继承AsyncTask类实现自己的类 38 | 39 | ``` 40 | public abstract class AsyncTask { 41 | ``` 42 | 43 | > Params: 输入参数,对应excute()方法中传递的参数。如果不需要传递参数,则直接设为void即可。 44 | > 45 | > Progress:后台任务执行的百分比 46 | > 47 | > Result:返回值类型,和doInBackground()方法的返回值类型保持一致。 48 | 49 | ②复写方法 50 | 51 | 最少要重写以下这两个方法: 52 | 53 | - doInBackground(Params…) 54 | 55 |   在**子线程**(其他方法都在主线程执行)中执行比较耗时的操作,不能更新UI,可以在该方法中调用publishProgress(Progress…)来更新任务的进度。Progress方法是AsycTask中一个final方法只能调用不能重写。 56 | 57 | - onPostExecute(Result) 58 | 59 |   使用在doInBackground 得到的结果处理操作UI, 在主线程执行,任务执行的结果作为此方法的参数返回。 60 |    61 | 有时根据需求还要实现以下三个方法: 62 | 63 | - onProgressUpdate(Progress…) 64 | 65 |   可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。 66 | 67 | - onPreExecute() 68 | 69 |   这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。 70 | 71 | - onCancelled() 72 | 73 |   用户调用取消时,要做的操作 74 | 75 | 76 | 77 | ## 4、AsyncTask示例 78 | 79 | 按照上面的步骤定义自己的异步类: 80 | 81 | ``` 82 | public class MyTask extends AsyncTask { 83 | //执行的第一个方法用于在执行后台任务前做一些UI操作 84 | @Override 85 | protected void onPreExecute() { 86 | 87 | } 88 | 89 | //第二个执行方法,在onPreExecute()后执行,用于后台任务,不可在此方法内修改UI 90 | @Override 91 | protected String doInBackground(String... params) { 92 | //处理耗时操作 93 | return "后台任务执行完毕"; 94 | } 95 | 96 | /*这个函数在doInBackground调用publishProgress(int i)时触发,虽然调用时只有一个参数 97 | 但是这里取到的是一个数组,所以要用progesss[0]来取值 98 | 第n个参数就用progress[n]来取值 */ 99 | @Override 100 | protected void onProgressUpdate(Integer... progresses) { 101 | //"loading..." + progresses[0] + "%" 102 | super.onProgressUpdate(progress); 103 | } 104 | 105 | /*doInBackground返回时触发,换句话说,就是doInBackground执行完后触发 106 | 这里的result就是上面doInBackground执行后的返回值,所以这里是"后台任务执行完毕" */ 107 | @Override 108 | protected void onPostExecute(String result) { 109 | 110 | } 111 | 112 | //onCancelled方法用于在取消执行中的任务时更改UI 113 | @Override 114 | protected void onCancelled() { 115 | 116 | } 117 | } 118 | ``` 119 | 120 | 在主线程申明该类的对象,调用对象的execute()函数开始执行。 121 | 122 | ``` 123 | MyTask t= new MyTask(); 124 | t.execute();//这里没有参数 125 | ``` 126 | 127 | 128 | ## 5、使用AsyncTask需要注意的地方 129 | 130 | - AsnycTask内部的Handler需要和主线程交互,所以AsyncTask的实例必须在UI线程中创建 131 | 132 | - AsyncTaskResult的doInBackground(mParams)方法执行异步任务运行在子线程中,其他方法运行在主线程中,可以操作UI组件。 133 | 134 | - 一个AsyncTask任务只能被执行一次。 135 | 136 | - 运行中可以随时调用AsnycTask对象的cancel(boolean)方法取消任务,如果成功,调用isCancelled()会返回true,并且不会执行 onPostExecute() 方法了,而是执行 onCancelled() 方法。 137 | 138 | - 对于想要立即开始执行的异步任务,要么直接使用Thread,要么单独创建线程池提供给AsyncTask。默认的AsyncTask不一定会立即执行你的任务,除非你提供给他一个单独的线程池。如果不与主线程交互,直接创建一个Thread就可以了。 139 | 140 |    141 | 142 | -------------------------------------------------------------------------------- /AndroidNote/Android基础/Android获取SHA1.md: -------------------------------------------------------------------------------- 1 | # Android获取SHA1 2 | 3 | 4 | 5 | 1. 在Android Studio中打开Terminal或者进入控制台 6 | 2. cd到jdk的bin目录下 7 | 3. 输入命令 ``keytool -list -keystore /Users/mac/WorkSpace/linGitHub/Daily/key/fuyizhulao.jks`` 8 | 4. 上面这个命令,大家需要选择自己的jks文件 9 | 5. 输入密码 10 | 6. 然后就可以看到自己的证书指纹(SHA1)了 11 | 12 | 13 | 效果图: 14 | 15 | 16 | ![](https://ws1.sinaimg.cn/large/006tNbRwly1fh96jjtx73j31dk0q2gnw.jpg) -------------------------------------------------------------------------------- /AndroidNote/Android基础/Android跟随手指移动的view.md: -------------------------------------------------------------------------------- 1 | > 实现一个跟随手指移动的view其实是特别容易实现的,不过有的时候还是挺有用的,最近做的视频互动软件就有这样的需求,大概几十行代码就可以搞定,然后记录一下吧。 2 | 3 | 实现的主要思想,就是利用onTouchListener,然后判断出手指按下的点,同时监听移动的事件,然后稍微计算一下就可以求出来view最终应该呈现的位置了,然后通过改变LayoutParams的值就可以是实现view的跟随手指拖拽的效果了,当然还可以优化,例如通过计算如果移到屏幕边缘就停下来之类的,或者哪里是不能移到地方。 4 | 5 | ``` 6 | public class TestActivity extends AppCompatActivity implements View.OnTouchListener { 7 | 8 | private ImageView imageView; 9 | private RelativeLayout relativeLayout; 10 | 11 | private int lastX, lastY; //保存手指点下的点的坐标 12 | final static int IMAGE_SIZE = 150; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_test); 18 | 19 | imageView = (ImageView) findViewById(R.id.image); 20 | relativeLayout = (RelativeLayout) findViewById(R.id.layout); 21 | //初始设置一个layoutParams 22 | RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(IMAGE_SIZE,IMAGE_SIZE); 23 | imageView.setLayoutParams(layoutParams); 24 | //设置屏幕触摸事件 25 | imageView.setOnTouchListener(this); 26 | } 27 | 28 | 29 | public boolean onTouch(View view, MotionEvent event) { 30 | switch (event.getAction() & MotionEvent.ACTION_MASK) { 31 | case MotionEvent.ACTION_DOWN: 32 | //将点下的点的坐标保存 33 | lastX = (int) event.getRawX(); 34 | lastY = (int) event.getRawY(); 35 | break; 36 | 37 | case MotionEvent.ACTION_MOVE: 38 | //计算出需要移动的距离 39 | int dx = (int) event.getRawX() - lastX; 40 | int dy = (int) event.getRawY() - lastY; 41 | //将移动距离加上,现在本身距离边框的位置 42 | int left = view.getLeft() + dx; 43 | int top = view.getTop() + dy; 44 | //获取到layoutParams然后改变属性,在设置回去 45 | RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view 46 | .getLayoutParams(); 47 | layoutParams.height = IMAGE_SIZE; 48 | layoutParams.width = IMAGE_SIZE; 49 | layoutParams.leftMargin = left; 50 | layoutParams.topMargin = top; 51 | view.setLayoutParams(layoutParams); 52 | //记录最后一次移动的位置 53 | lastX = (int) event.getRawX(); 54 | lastY = (int) event.getRawY(); 55 | break; 56 | } 57 | //刷新界面 58 | relativeLayout.invalidate(); 59 | return true; 60 | } 61 | } 62 | ``` 63 | 64 | 65 | 66 | 67 | ``` 68 | 74 | 75 | 83 | 84 | 85 | 86 | ``` 87 | 88 | ---- 89 | 90 | 以上便是这个简单的view啦,思路还是很清晰的,当然能够改造的地方有很多,例如加一个惯性的效果啊,或者弄一个加速度的效果啊,都是可以的 91 | -------------------------------------------------------------------------------- /AndroidNote/Android基础/IntentService详细解析.md: -------------------------------------------------------------------------------- 1 | # IntentService定义 2 | 3 |   IntentService继承与Service,用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。  4 |    5 |   这样以免事务处理阻塞主线程(ANR)。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent请求达到,则**自动停止**Service;否则执行下一个Intent请求所对应的任务。  6 |    7 |   IntentService在处理事务时,还是采用的Handler方式,创建一个名叫ServiceHandler的内部Handler,并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent所对应的事务都封装到叫做**onHandleIntent**的虚函数;因此我们直接实现虚函数onHandleIntent,再在里面根据Intent的不同进行不同的事务处理就可以了。 8 | 另外,IntentService默认实现了Onbind()方法,返回值为null。 9 | 10 | 使用IntentService需要实现的两个方法: 11 |    12 | 13 | - 构造函数  14 | 15 |   IntentService的构造函数一定是**参数为空**的构造函数,然后再在其中调用super("name")这种形式的构造函数。因为Service的实例化是系统来完成的,而且系统是用参数为空的构造函数来实例化Service的 16 | 17 | - 实现虚函数onHandleIntent 18 | 19 |   在里面根据Intent的不同进行不同的事务处理。  20 |    21 | 好处:处理异步请求的时候可以减少写代码的工作量,比较轻松地实现项目的需求。 22 | 23 | # IntentService与Service的区别 24 | 25 |   Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。 26 | 27 |   IntentService 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents(把intent插入到工作队列中)。通过工作队列把intent逐个发送给onHandleIntent()。  28 |    29 |   不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。 30 | 31 |    默认实现的onBind()返回null。 32 | 33 | # IntentService实例介绍 34 | 35 |   首先是myIntentService.java 36 | 37 | ``` 38 | public class myIntentService extends IntentService { 39 | 40 | //------------------必须实现----------------------------- 41 | 42 | public myIntentService() { 43 | super("myIntentService"); 44 | // 注意构造函数参数为空,这个字符串就是worker thread的名字 45 | } 46 | 47 | @Override 48 | protected void onHandleIntent(Intent intent) { 49 | //根据Intent的不同进行不同的事务处理 50 | String taskName = intent.getExtras().getString("taskName"); 51 | switch (taskName) { 52 | case "task1": 53 | Log.i("myIntentService", "do task1"); 54 | break; 55 | case "task2": 56 | Log.i("myIntentService", "do task2"); 57 | break; 58 | default: 59 | break; 60 | } 61 | } 62 | //--------------------用于打印生命周期-------------------- 63 | @Override 64 | public void onCreate() { 65 | Log.i("myIntentService", "onCreate"); 66 | super.onCreate(); 67 | } 68 | 69 | @Override 70 | public int onStartCommand(Intent intent, int flags, int startId) { 71 | Log.i("myIntentService", "onStartCommand"); 72 | return super.onStartCommand(intent, flags, startId); 73 | } 74 | 75 | @Override 76 | public void onDestroy() { 77 | Log.i("myIntentService", "onDestroy"); 78 | super.onDestroy(); 79 | } 80 | } 81 | 82 | ``` 83 | 然后记得在Manifest.xml中注册服务 84 | 85 | ``` 86 | 87 | 88 | 89 | 90 | 91 | ``` 92 | 93 | 最后在Activity中开启服务 94 | 95 | ``` 96 | public class MainActivity extends Activity { 97 | @Override 98 | protected void onCreate(Bundle savedInstanceState) { 99 | // TODO Auto-generated method stub 100 | super.onCreate(savedInstanceState); 101 | 102 | //同一服务只会开启一个worker thread,在onHandleIntent函数里依次处理intent请求。 103 | 104 | Intent i = new Intent("cn.scu.finch"); 105 | Bundle bundle = new Bundle(); 106 | bundle.putString("taskName", "task1"); 107 | i.putExtras(bundle); 108 | startService(i); 109 | 110 | Intent i2 = new Intent("cn.scu.finch"); 111 | Bundle bundle2 = new Bundle(); 112 | bundle2.putString("taskName", "task2"); 113 | i2.putExtras(bundle2); 114 | startService(i2); 115 | 116 | startService(i); //多次启动 117 | } 118 | } 119 | ``` 120 | 121 | 运行结果: 122 | 123 | ![这里写图片描述](http://img.blog.csdn.net/20160513135411037)  124 | 125 |   IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。  126 |    127 |   通过onStartCommand()传递给服务intent被**依次**插入到工作队列中。工作队列又把intent逐个发送给onHandleIntent()。 128 | 129 |    130 | 131 | > 注意: 132 | > 它只有一个工作线程,名字就是构造函数的那个字符串,也就是“myIntentService”,我们知道多次开启service,只会调用一次onCreate方法(创建一个工作线程),多次onStartCommand方法(用于传入intent通过工作队列再发给onHandleIntent函数做处理)。 133 | 134 | -------------------------------------------------------------------------------- /AndroidNote/Android基础/tablayout记录.md: -------------------------------------------------------------------------------- 1 | # TabLayout记录 2 | 3 | 今天用TabLayout的时候发现,TabLayout的setOnPageChangeListener的方法过期了,良好的编程习惯是不在项目中使用过时的方法的,所以要找一个替代的方案: 4 | 5 | ``addOnPageChangeListener`` 6 | 7 | - setOnPageChangeListener 8 | 9 | ``` 10 | mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 11 | @Override 12 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 13 | 14 | } 15 | 16 | @Override 17 | public void onPageSelected(int position) { 18 | 19 | } 20 | 21 | @Override 22 | public void onPageScrollStateChanged(int state) { 23 | 24 | } 25 | }); 26 | ``` 27 | 28 | - addOnPageChangeListener 29 | 30 | ``` 31 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 32 | @Override 33 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 34 | 35 | } 36 | 37 | @Override 38 | public void onPageSelected(int position) { 39 | selectedTab(position); 40 | } 41 | 42 | @Override 43 | public void onPageScrollStateChanged(int state) { 44 | 45 | } 46 | }); 47 | ``` 48 | -------------------------------------------------------------------------------- /AndroidNote/Android基础/test.kt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /AndroidNote/Android开源框架相关/Android黑科技——ButterKnifeZelezny.md: -------------------------------------------------------------------------------- 1 | > 先上一张效果图: 2 | 3 | ![效果图](https://raw.githubusercontent.com/avast/android-butterknife-zelezny/master/img/zelezny_animated.gif) 4 | 5 | 6 | 当一个XML中元素实在过多的时候,手动绑定id,不但浪费时间,而且非常的麻烦,有了这个黑科技之后,就可以轻松很多,解放双手啦。这是一个Android的插件,它是基于ButterKnife开发的,想要安装这个需要先添加Butterknife的 依赖,具体步骤如下: 7 | 8 | ---- 9 | ## 添加Butterknife依赖 10 | 11 | ``` 12 | dependencies { 13 | compile 'com.jakewharton:butterknife:8.5.1' 14 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' 15 | } 16 | ``` 17 | 18 | ``` 19 | buildscript { 20 | repositories { 21 | mavenCentral() 22 | } 23 | dependencies { 24 | classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1' 25 | } 26 | } 27 | ``` 28 | 29 | ``` 30 | apply plugin: 'com.android.library' 31 | apply plugin: 'com.jakewharton.butterknife' 32 | ``` 33 | 34 | ## 依赖添加完毕,安装插件 35 | 36 | 37 | > in Android Studio: go to Preferences → Plugins → Browse repositories and search for ButterKnife Zelezny 38 | 39 | 安装完毕后,只需要重新启动一下studio,然后右键R.layout.activity_main,选择Generate,然后选择Generate ButterKnife Injections,就ok啦~ 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AndroidNote/Android开源框架相关/Picasso-android-load-image-layout.md: -------------------------------------------------------------------------------- 1 | > 以前一直都是用Picasso将图片设置在imageView上面,今天有个项目需求,是将imageView设置在layout的background上面,特意查了一下,现在打算记录一下。 2 | 3 | ``` 4 | picasso一般的用法 5 | 6 | Picasso 7 | .with(mContext) 8 | .load(url) 9 | .fit() 10 | .centerCrop() 11 | .into(holder.imageView); 12 | ``` 13 | 14 | ---- 15 | 16 | 如果想将图片设置在其它地方: 17 | 18 | ``` 19 | 20 | Picasso.with(this).load("http://imageUrl").into(new Target() { 21 | @Override 22 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { 23 | mYourLayout.setBackground(new BitmapDrawable(bitmap)); 24 | } 25 | 26 | @Override 27 | public void onBitmapFailed(Drawable errorDrawable) { 28 | 29 | } 30 | 31 | @Override 32 | public void onPrepareLoad(Drawable placeHolderDrawable) { 33 | 34 | } 35 | }); 36 | 37 | ``` 38 | 39 | 40 | 或者采用: 41 | 42 | ``` 43 | 44 | ImageView img = new ImageView(this); 45 | Picasso.with(this) 46 | .load(imageUri) 47 | .fit() 48 | .centerCrop() 49 | .into(img, new Callback() { 50 | @Override 51 | public void onSuccess() { 52 | 53 | myLayout.setBackgroundDrawable(img.getDrawable()); 54 | } 55 | 56 | @Override 57 | public void onError() { 58 | 59 | } 60 | }); 61 | ``` 62 | 63 | 64 | that's all. 65 | -------------------------------------------------------------------------------- /AndroidNote/Android开源框架相关/一款Android的Log、Toast的库.md: -------------------------------------------------------------------------------- 1 | > 没错,是我闲的无聊写了一个用来Log和Toast的库,这个库目前能实现将Log和Toast变得更简单,并且可以实现Log的快速定位,以及Debug和Release的切换,可以非常简单的配置在Realease情况下不打印Log。 2 | 当然这些都还非常基础~也准备在接下来的时间里,封装一些网络操作的框架,还有一些BaseAdapter的框架。 3 | 4 | 效果图: 5 | 6 | ![效果图1](http://upload-images.jianshu.io/upload_images/2585384-173ddab4b5a04e20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 7 | 8 | ## 配置方法 9 | ``` 10 | compile 'com.linsir:linLog:1.0.0' 11 | ``` 12 | 经过以上的配置,已经可以正常的使用了,如果我们想配置的更加轻便的话,是可以这样的: 13 | ``` 14 | public class App extends Application { 15 | public static final boolean DEBUG = BuildConfig.DEBUG; 16 | @Override public void onCreate() { 17 | super.onCreate(); 18 | LinToast.init(getApplicationContext()); 19 | LinLog.init(DEBUG, "lin"); 20 | } 21 | ``` 22 | 23 | ``` 24 | defaultConfig { 25 | buildConfigField("boolean", "LOG", "true") 26 | } 27 | buildTypes { 28 | release { 29 | buildConfigField("boolean", "LOG", "false") 30 | } 31 | } 32 | ``` 33 | 34 | 好了,以上便完全配置完成了~ 35 | 36 | ## 使用 37 | ``` 38 | public class MainActivity extends AppCompatActivity { 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | LinLog.lLog("今天很开心"); 44 | LinToast.showToast("啦啦啦啦~~~"); 45 | test(); 46 | } 47 | 48 | private void test(){ 49 | LinLog.lLog("我也是!"); 50 | } 51 | } 52 | 53 | ``` 54 | 55 | ## 源码 56 | [源码下载](https://github.com/linsir6/linLog) 57 | 58 | 整个代码也不超过100行,就是进行了简单的封装,以及```Thread.currentThread().getStackTrace()```这样一个方法,便可以获取到打印Log的位置,以及一些基本的信息,然后展示出来就可以了。 59 | 60 | ``` 61 | /** 62 | * Created by linSir 63 | * date at 2017/5/3. 64 | * describe: 一个专门用来展示log的工具类 65 | */ 66 | 67 | public class LinLog { 68 | 69 | private static boolean Debug = true; 70 | private static String Tag = "null"; 71 | 72 | public static void init(boolean debug, String tag) { 73 | LinLog.Debug = debug; 74 | LinLog.Tag = tag; 75 | } 76 | 77 | public static void lLog(String text){ 78 | if (!Debug){ 79 | return; 80 | } 81 | String dividingLine = "╔================================================================\n"; 82 | String dividingLine2 = "╚================================================================\n"; 83 | Log.e(Tag,dividingLine); 84 | setUpContent(text); 85 | Log.e(Tag,dividingLine2); 86 | } 87 | 88 | private static void setUpContent(String content) { 89 | StackTraceElement targetStackTraceElement = getStackTraceElement(); 90 | Log.e(Tag, "║ 出现log的位置-> (" + targetStackTraceElement.getFileName() + ":" 91 | + targetStackTraceElement.getLineNumber() + ")" + " -> " + targetStackTraceElement.getMethodName()); 92 | Log.e(Tag, "║ log的内容-> "+content); 93 | } 94 | 95 | private static StackTraceElement getStackTraceElement(){ 96 | StackTraceElement targetStackTrace = null; 97 | boolean shouldTrace = false; 98 | StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 99 | for (StackTraceElement stackTraceElement : stackTrace) { 100 | boolean isLogMethod = stackTraceElement.getClassName().equals(LinLog.class.getName()); 101 | if (shouldTrace && !isLogMethod) { 102 | targetStackTrace = stackTraceElement; 103 | break; 104 | } 105 | shouldTrace = isLogMethod; 106 | } 107 | return targetStackTrace; 108 | } 109 | } 110 | ``` 111 | 112 | ``` 113 | public class LinToast { 114 | 115 | private static LinToast linToast; 116 | private static Context mContext; 117 | private static Toast mToast; 118 | 119 | public static void init(Context context) { 120 | mContext = context.getApplicationContext(); 121 | mToast = Toast.makeText(context,"",Toast.LENGTH_SHORT); 122 | } 123 | 124 | public static void showToast(String txt) { 125 | mToast.setText(txt); 126 | mToast.setDuration(Toast.LENGTH_SHORT); 127 | mToast.show(); 128 | } 129 | } 130 | 131 | ``` 132 | 133 | ## 总结 134 | 其实整体流程还是挺简单的,就是新建一个Library,然后写一下逻辑,写完之后,上传到[bintray.com](https://bintray.com),然后就可以了。然后说句题外话,非常欢迎大家有事没事,引用一下这个库,增加一下下载量,也欢迎大家上我的github提issue或者star,follow的。 135 | 总体感觉,写一个这样的库还是挺有意义的吧,可以把项目中经常用到的工具类封装一下,日后用着也方便,大家可以点开源码看一下,自己也尝试着写一下。之后我也会持续更新一些网络框架的封装,还有BaseAdapter的封装~如果大家在写类似东西的时候,遇到了问题也欢迎和我讨论。 136 | 137 | 138 | ---- 139 | 欢迎大家点💕~~ 140 | [GitHub地址](https://github.com/linsir6),欢迎star,follow~~ 141 | -------------------------------------------------------------------------------- /AndroidNote/Android打包相关/Android发布sdk到jcenter.md: -------------------------------------------------------------------------------- 1 | # Android发布sdk到jcenter 2 | 3 | > 当我们写好一个开源项目或者写好一个商用的sdk的时候,我们可能需要将它上传到jcenter这样可以更好的提供给别人或者用户使用。当我们们上传之后,用户便可以在gradle里面通过compile来引用我们的项目了。 4 | 5 | 本文介绍的是通过`bintray-release`这个插件来上传我们的项目。 6 | 7 | 8 | 9 | ### 1. 注册bintray账号 10 | 11 | [注册链接](https://bintray.com/signup/oss),一定要选择这个注册链接,因为这个注册链接是面向个人的,是免费的,如果选择了公司版是需要收费的。注册的过程就很简单了,我们也可以通过github进行第三方登录。 12 | 13 | 14 | 15 | ### 2. 在bintray新建一个项目 16 | 17 | 18 | 19 | ![](http://upload-images.jianshu.io/upload_images/2585384-a5161d9e13cf3f53.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 20 | 21 | 22 | 23 | ![](http://upload-images.jianshu.io/upload_images/2585384-dcf3a947ddc635ec.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | 26 | 27 | 按照上述两幅图,填好项目描述等内容之后,点击Create就可以了。 28 | 29 | 30 | 31 | ### 3. 查看自己的key 32 | 33 | 34 | 35 | ![](http://upload-images.jianshu.io/upload_images/2585384-18643fd9ba680211.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 36 | 37 | ### 4. 在项目中配置上传所需的内容 38 | 39 | 40 | 41 | 首先修改项目的gradle(Project) 42 | 43 | ```java 44 | buildscript { 45 | 46 | repositories { 47 | mavenCentral() 48 | jcenter() 49 | 50 | } 51 | dependencies { 52 | classpath 'com.android.tools.build:gradle:2.3.0' 53 | classpath 'com.github.dcendents:android-maven-plugin:1.2' 54 | // NOTE: Do not place your application dependencies here; they belong 55 | // in the individual module build.gradle files 56 | 57 | //新增内容 58 | classpath 'com.novoda:bintray-release:0.3.4' 59 | //==========新增结束========== 60 | } 61 | } 62 | 63 | allprojects { 64 | repositories { 65 | jcenter() 66 | } 67 | //新增内容,防止一些注释在编译过程报错 68 | tasks.withType(Javadoc) { 69 | options{ 70 | encoding "UTF-8" 71 | charSet 'UTF-8' 72 | links "http://docs.oracle.com/javase/7/docs/api" 73 | } 74 | } 75 | //==========新增结束========== 76 | } 77 | ``` 78 | 79 | 80 | 81 | 修改想要上传的module的gradle(Moudle) 82 | 83 | 84 | 85 | ```java 86 | apply plugin: 'com.android.library' 87 | //新增内容 88 | apply plugin: 'com.novoda.bintray-release' 89 | //==========新增结束========== 90 | 91 | android { 92 | compileSdkVersion 25 93 | buildToolsVersion "25.0.2" 94 | 95 | sourceSets.main { 96 | jniLibs.srcDir 'jni' 97 | jni.srcDirs = [] //disable automatic ndk-build call 98 | } 99 | 100 | defaultConfig { 101 | minSdkVersion 14 102 | targetSdkVersion 25 103 | versionCode 1 104 | versionName "1.0.0" 105 | multiDexEnabled true 106 | 107 | } 108 | buildTypes { 109 | release { 110 | 111 | } 112 | 113 | debug { 114 | 115 | } 116 | 117 | } 118 | 119 | lintOptions { 120 | abortOnError false 121 | } 122 | } 123 | 124 | dependencies { 125 | provided 'com.squareup.okhttp3:okhttp:3.5.0' 126 | 127 | } 128 | //新增内容 129 | publish { 130 | userOrg = 'linsir'//bintray.com用户名 131 | repoName = 'linlog' 132 | groupId = 'com.linsir'//jcenter上的路径 133 | artifactId = 'linlog'//项目名称 134 | publishVersion = '1.0.0'//版本号 135 | desc = 'An android sdk for easy to log and toast.'//描述 136 | website = 'https://github.com/linsir6/linLog'//网站,尽量采用同样的格式 137 | } 138 | //==========新增结束========== 139 | ``` 140 | 141 | 142 | 143 | ### 5. 执行上传命令 144 | 145 | 在androidstudio中的Terminal中,找到当前的路径,然后执行以下命令: 146 | 147 | ```java 148 | ./gradlew clean build bintrayUpload -PbintrayUser=linsir -PbintrayKey=XXX -PdryRun=false 149 | ``` 150 | 151 | 152 | 153 | 上述命令有两个地方需要替换成自己的,``-PbintrayUser=linsir``这个里面的linsir需要替换成自己在bintray上面的用户名,``-PbintrayKey=XXX``这里面的XXX需要替换成我们在``3. 查看自己的key``这步获取到的key,然后按下回车执行命令,当看到build success就完成了。 154 | 155 | 156 | 157 | ### 6.add to jcenter 158 | 159 | 当以上步骤全部完成之后,我们就可以在网站项目的界面中看到了,然后点击Add to Jcenter,然后添加一段描述就可以了。 160 | 161 | ![](http://upload-images.jianshu.io/upload_images/2585384-e55d80a7a32b55f3.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 162 | 163 | 大概就应该在红色的这个位置,然后就可以等待工作人员的审核了,大概两个小时左右,审核通过之后会有站内信,邮箱内也收到邮件的,然后就可以通过``compile 'com.linsir:linLog:1.0.0``这种形式引用了。 164 | 165 | 166 | 167 | > 我自己也写了一个简单的这样的库,大家如果参考的话可以看一下,[代码地址](https://github.com/linsir6/linLog)。 168 | -------------------------------------------------------------------------------- /AndroidNote/Android打包相关/Android将library打包成jar文件或aar文件.md: -------------------------------------------------------------------------------- 1 | > Android Library就是一个没有界面的应用程序,一般很少单独存在,一般我们是把经常用到的应用层的逻辑抽出来放在Library里面,当然一些常用的第三方的库也会采用这种方式。 2 | 3 | # 打包jar 4 | 1. 新建一个Library,这个在studio里面很简单就可以做到。 5 | 2. 当逻辑写完之后,需要配置grade文件,代码如下: 6 | ``` 7 | task makeJar(type: Copy) { 8 | //删除存在的 9 | delete 'build/libs/mysdk.jar' 10 | //设置拷贝的文件 11 | from('build/intermediates/bundles/release/') 12 | //打进jar包后的文件目录 13 | into('build/libs/') 14 | //将classes.jar放入build/libs/目录下 15 | //include ,exclude参数来设置过滤 16 | //(我们只关心classes.jar这个文件) 17 | include('classes.jar') 18 | //重命名 19 | rename ('classes.jar', 'mysdk.jar') 20 | } 21 | ``` 22 | 3. 配置之后,在AndroidStudio中的Terminal中输入: 23 | ``` 24 | ./gradlew makeJar 25 | ``` 26 | 4. 完成之后,生成的jar包就会出现在libs路径下面了。 27 | 5. 引用jar包,添加如下代码: 28 | 29 | ``` 30 | repositories { 31 | flatDir { //添加在android()里面 32 | dirs 'libs' 33 | } 34 | } 35 | 36 | compile files('libs/mysdk.jar') 37 | 38 | ``` 39 | 40 | 以上便可以开始使用jar包了,简单说一下jar包里面最好是不要有静态资源文件的,因为是访问不到的,如果要访问静态文件需要利用java的反射机制,来获取添加依赖项目的静态资源。当然,如果我们的Library里面有动态库(用c写的so文件),也是访问不到的,这时我们可以采用aar文件。 41 | 42 | ---- 43 | 44 | # 打包aar文件 45 | 46 | 1.新建一个Library 47 | 2.Rebuild一下代码,就可以在Library -> build -> outputs -> aar -> xxx.aar 找到了 48 | 3.添加到想要依赖的项目的libs目录下 49 | 4.修改gradle代码 50 | 51 | ``` 52 | repositories { 53 | flatDir { //添加在android()里面 54 | dirs 'libs' 55 | } 56 | } 57 | 58 | compile(name:'DotEngine-debug', ext:'aar') 59 | ``` 60 | 61 | 以上我们便可以使用了。 62 | -------------------------------------------------------------------------------- /AndroidNote/Android报错记录/Android报错-Manifest merger failed with multiple errors, see logs.md: -------------------------------------------------------------------------------- 1 | # Android报错:Manifest merger failed with multiple errors, see logs 2 | 3 | 在编写Android代码的时候可能会遇到这样的错误:``Manifest merger failed with multiple errors, see logs``,但是遇到这样的问题,我们的内心是崩溃的,因为它告诉我们,我们的代码有问题,编译过过去,但是又没有告诉我们问题在哪里,所以我们需要自己找问题。 4 | 5 | 6 | 7 | 在Android studio自带的命令行里面,我们输入:``gradlew processDebugManifest —stacktrace`` 8 | 9 | 10 | 11 | 这样我们就能获得更多的信息,我们便可以根据信息进行处理了。 12 | 13 | 14 | 15 | 常遇到这样问题的场景,一般都是,在自己的项目中引用到了,第三方的库,但是我们的最低版本的要求和库的版本要求可能不太一样,这个时候两个.gradle的文件在merage的时候就会产生冲突,就会报错了,我们只需要将他们两个改成一样的就可以。 16 | 17 | 18 | 19 | 一般来说遇到这种问题的大多数的场景,都是多个.gradle文件在合并的过程中遇到了问题,我们可以根据报错,和我们的回忆来检查到底是哪里出现的问题。 20 | -------------------------------------------------------------------------------- /AndroidNote/Android报错记录/Android报错2.md: -------------------------------------------------------------------------------- 1 | ## Client not ready yet.. 2 | 3 | 4 | 这个错误导致的原因是,我们的AndroidManifest.xml中没有配置,默认的启动项。 5 | 6 | 还有一个原因就是可能我们的编辑器bug了,我们可以删掉再加回来。 7 | -------------------------------------------------------------------------------- /AndroidNote/Android编译器相关/AndroidStudio使用教程(第三弹).md: -------------------------------------------------------------------------------- 1 | # AndroidStudio使用教程(第三弹) 2 | 3 | 本文为转载文章:原文地址:https://github.com/CharonChui/AndroidNote 4 | 5 | 6 | 熟悉了基本的使用之后,可能关心的就是版本控制了。 7 | 8 | - `SVN` 9 | - 下载`Subversion command line` 10 | - 方法一 11 | 下载地址是[Subversion](http://subversion.apache.org/packages.html)里面有不同系统的版本。 12 | 以`Windows`为例,我们采用熟悉的`VisualSVN`. 13 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_1.png?raw=true) 14 | 进入下载页后下载`Apache Subversion command line tools`, 解压即可。 15 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_2.png?raw=true) 16 | 17 | - 方法二 18 | `Windows`下的`Tortoise SVN`也是带有`command line`的,但是安装的时候默认是不安装这个选项的,所以安装时要注意选择一下。 19 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_5.png?raw=true) 20 | 选择安装即可 21 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_6.png?raw=true) 22 | 23 | - 配置`SVN` 24 | 进入设置中心,搜索`Version Control`后选择`Subversion`, 将右侧的`Use command line client`设置你本地的`command line`路径即可。 25 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_3.png?raw=true) 26 | 如果是用第二种方式安装`Tortoise SVN`的话地址就是: 27 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_4.png?raw=true) 28 | PS: 设置成自己的路径啊,不要写我的... 29 | 30 | - `Git` 31 | 安装`Git`, [Git](http://git-scm.com/) 32 | 选择相应系统版本安装即可。 33 | 安装完成后进入`Android Studio`设置中心-> `Version Control` -> `Git`设置`Path to Git executable`即可。 34 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_7.png?raw=true) 35 | 36 | - `Github` 37 | 怎么能少了它呢?哈哈 38 | 这个就不用安装了,直接配置下用户名和密码就好了。 39 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_8.png?raw=true) 40 | 41 | - `Checkout` 42 | 在工具栏中点击 `VCS` 能看到相应检出和导入功能。这里以`Github`检出为例介绍一下。 43 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_9.png?raw=true) 44 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_10.png?raw=true) 45 | 确定之后就能在底部导航栏看到检出进度 46 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_11.png?raw=true) 47 | 48 | 完成之后会提示是否想要把刚才检出的项目导入`AndroidStudio`中。 49 | Why not? 50 | 以`Gradle`方式导入, 然后`next`, 然后`next`然后就没有然后了。 51 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_3_12.png?raw=true) 52 | 53 | --- 54 | 55 | - 邮箱 :charon.chui@gmail.com 56 | - Good Luck! -------------------------------------------------------------------------------- /AndroidNote/Android编译器相关/AndroidStudio使用教程(第二弹).md: -------------------------------------------------------------------------------- 1 | # AndroidStudio使用教程(第二弹) 2 | 3 | 4 | 本文为转载文章:原文地址:https://github.com/CharonChui/AndroidNote 5 | 6 | 7 | - 迁移`Eclipse`工程到`Android Studio` 8 | 9 | 官方文档中说`Android Studio`可以兼容`Eclipse`的现有工程,但需要做一些操作: 10 | 11 | - `Eclipse`进行项目构建 12 | 首先升级`ADT`到最新版本, 好像是22之后,选择需要从`Eclipse`导出的工程,右键选择`Export`并选择`Android`下的`Generate Gradle Build Files`, 13 | 运行完成之后你会发现在项目目录中多了一个`build.gradle`, 这就是`Android Studio`所识别的文件。 14 | PS:官方文档中说明如果没有`Grade build`文件,也是可以将项目导入到`Android Studio`中,它会用现有的`Ant build`文件进行配置. 15 | 但为了更好地使用之后的功能和充分使用构建变量, 16 | 还是强烈地建议先从`ADT`插件中生成`Gradle`文件再导入`Android Studio`. 17 | 18 | - 导入 19 | 在`Android Studio`中选择`Import Project`,并选择刚才工程目录下的`build.gradle`即可。 20 | 21 | 有些时候会发现导入之后在运行按钮左边显示不出`Module`来,可能是你导入之前的`SDK`版本不同导致的,只要在`build.gradle`中配置相应的`SDK`版本就可以了。 22 | ```java 23 | android { 24 | compileSdkVersion 19 25 | buildToolsVersion "21.1.1" 26 | ... 27 | } 28 | ``` 29 | 30 | - 创建工程 31 | 创建工程和`Eclipse`流程基本差不多,大家一看就明白了,这里就不说了。 32 | 33 | - 使用`Android`项目视图 34 | 这里纯粹看个人爱好,不过对于标准的`AndroidStudio`工程,一般我们常用的部分,都在`Android`项目视图中显示出来了。 35 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_1.png?raw=true) 36 | 效果如下图: 37 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_2.png?raw=true) 38 | 39 | - 使用布局编辑器 40 | 41 | 布局编辑器的适时和多屏幕预览的确是一个亮点。 42 | 43 | - 点击布局页面右侧的`Preview`按钮,可以进行预览。 44 | 45 | 想预览多屏幕效果时可以在预览界面设备的下拉菜单上选择`Preview All Screen Sizes`. 46 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_3.png?raw=true) 47 | 48 | - 选择主题 49 | 50 | 想给应用设置一个主题,可以点击`Theme`图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_4.png?raw=true), 51 | 就会显示出选择对话框。 52 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_5.png?raw=true) 53 | 54 | - 国际化 55 | 对于国际化的适配选择国际化图标![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_6.png?raw=true), 56 | 然后在弹出的列表中选择需要进行国际化的国家进行适配即可。 57 | 58 | - 常用功能 59 | 有些人进来之后可能找不到`DDMS`了. 60 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_2_7.png?raw=true) 61 | 上图中的三个图标分别为, `AVD Manager`、`SDK Manager`、`DDMS` 62 | 63 | 64 | 65 | --- 66 | 67 | - 邮箱 :charon.chui@gmail.com 68 | - Good Luck! -------------------------------------------------------------------------------- /AndroidNote/Android编译器相关/AndroidStudio使用教程(第六弹).md: -------------------------------------------------------------------------------- 1 | # AndroidStudio使用教程(第六弹) 2 | 3 | 本文为转载文章:原文地址:https://github.com/CharonChui/AndroidNote 4 | 5 | 6 | 7 | Debug 8 | --- 9 | 10 | `Andorid Studio`中进行`debug`: 11 | - 在`Android Studio`中打开应用程序。 12 | - 点击状态栏中的`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_1.png?raw=true)图标。 13 | - 在接下来的选择设备窗口选择相应的设备或创建虚拟机, 点击`OK`即可。 14 | `Android Studio`在`debug`时会打开`Debug`工具栏, 可以点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`窗口。 15 | 16 | ###设置断点 17 | 与`Eclipse`十分相似, 在代码左侧位置点击一下即可, 圆点的颜色变了。 18 | 19 | ###Attach the debugger to a running process 20 | 在`debug`时不用每次都去重启应用程序。 我们可以对正在运行的程序进行`debug`: 21 | - 点击`Attach debugger to Android proccess`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_3.png?raw=true)图标。 22 | - 设备选择窗口选择想要`debug`的设备。 23 | - 点击`Debug`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_2.png?raw=true)图标打开`Debug`工具栏。 24 | - 在`Debug`工具栏中有`Stop Over`, `Stop Into`等图标和快捷键,这些就不仔细说明了, 和`Eclipse`都差不多。 25 | 26 | ### View the system log 27 | 在`Android DDMS`中和`Debug`工具栏中都可以查看系统`log`日志, 28 | - 在窗口底部栏点击`Android`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_4.png?raw=true) 图标打开`Android DDMS`工具栏。 29 | - 如果此时`Logcat`窗口中的日志是空得,点击`Restart`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_5.png?raw=true)图标。 30 | - 如果想要只显示当前某个进程的信息点击`Only Show Logcat from Selected Process`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_6.png?raw=true). 如果当时设备窗口不可见,点击右上角的`Restore Devices View`![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_7.png?raw=true)图标,该图标只有设备窗口不可见时才会显示。 31 | 32 | 删除`Project`及`Module` 33 | --- 34 | 35 | 很多人都在问`AndroidStudio`中如何删除`Project`,如何删除`Module`?怎么和`Eclipse`不同啊,找不到`delete`或`remove`选项。 36 | - 删除`Project` 37 | 点击左侧`File`-->`Close project`,关闭当前工程, 然后直接找到工程所在本地文件进行删除(慎重啊), 删除完之后点击最近列表中的该项目就会提示不存在,我们把他从最近项目中移除即可.![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_11.png?raw=true)你会发现,点击`remove`之后没效果,以后估计会解决。 38 | - 删除`Module` 39 | 在该`Module`邮件选择`Open Module Settings`。 40 | ![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_8.png?raw=true) 41 | 进入设置页后选中要删除的`Module`点击左上角的删除图标`-`后点击确定。 42 | ![Image](https://github.com/CharonChui/Pictures/blob/master/AndroidStudio_6_9.png?raw=true) 43 | 44 | 45 | --- 46 | 47 | - 邮箱 :charon.chui@gmail.com 48 | - Good Luck! -------------------------------------------------------------------------------- /AndroidNote/Android编译器相关/AndroidStudio使用教程(第四弹).md: -------------------------------------------------------------------------------- 1 | # AndroidStudio使用教程(第四弹) 2 | 3 | 本文为转载文章:原文地址:https://github.com/CharonChui/AndroidNote 4 | 5 | 6 | Gradle 7 | --- 8 | 9 | 讲解到这里我感觉有必要说明一下`Gradle`。 10 | 11 | `Gradle`是一个基于`Apache Ant`和`Apache Maven`概念的项目自动化建构工具。它使用一种基于`Groovy`的特定领域语言来声明项目设置,而不是传统的`XML`. 12 | 更多介绍请直接参考[Gradle](http://www.gradle.org/)或`Google`搜索。 13 | 14 | 以下是为什么Android Studio选择Gradle的主要原因: 15 | - 使用领域专用语言(Domain Specific Language)来描述和处理构建逻辑。(以下简称DSL) 16 | - 基于Groovy。DSL可以混合各种声明元素,用代码操控这些DSL元素达到逻辑自定义。 17 | - 支持已有的Maven或者Ivy仓库基础建设 18 | - 非常灵活,允许使用best practices,并不强制让你遵照它的原则来。 19 | - 其它插件时可以暴露自己的DSL和API来让Gradle构建文件使用。 20 | - 允许IDE集成,是很好的API工具 21 | 22 | Overview 23 | --- 24 | 25 | `AndroidStudio build`系统是一个你可以用来`build, test, run, package your apps`的工具。 `build`系统与`Android Studio`之间是独立的, 26 | 所以你可以在`Android Studio`中或者 27 | `command line`中去执行。写完自己的应用程序之后,你可以用`build`系统来做以下事情:      28 | - 自定义、配置和扩展`build`过程.。 29 | - 用同一个工程根据不同的特性来创建多个`APK`。 30 | - 重复利用代码和资源。 31 | `Android Studio`灵活的`build`系统能让你不用修改项目和核心文件而完成上面所有的工作。 32 | 33 | Overview of the Build System 34 | --- 35 | 36 | `Android Studio`的构建系统包含一个`Gradle`的`Android`插件,`Gradle`是一个管理依赖关系并且允许自定义构建逻辑的高级构建工具。 许多软件都用`Gradle`来进行构建。 37 | `Gradle`的`Android`插件并不依赖`Android Studio`, 虽然`Android Studio`全部集成了`Gralde`, 这就意味着:     38 | - 你可以用用命令行的方式去构建`Android`应用或者是在一些没有安装`Android Studio`的机器上。 39 | - 你可以用命令行构建时的配置和逻辑来在`Android Studio`中进行构建`Android`项目。 40 | 不管你是通过命令行还是远程机器或者是`Android Studio`来进行构建的产出都是一致的。 41 | 42 | Build configuration 43 | --- 44 | 45 | 项目的构建配置都在`Gralde build files`中, 都是些符合`Gradle`要求的选项和语法,`Android`插件并不依赖`Android 46 | 通过`build`文件来配置一下几个方面: 47 | - `Build variants` 构建系统能对同一个项目通过不同的配置生成多个`APK`,当你想构建多个不同版本时这是非常有用的,因为不用为他们建立多个不同的项目。 48 | - `Dependencies` 构建系统管理项目的依赖关系,并且支持本地已经远程仓库的依赖关系。 这样你就不用再去搜索、下载,然后再拷贝相应的包到你工程的目录了。 49 | - `Manifest entries` 构建系统能够通过构建配置去指定清单文件中中某些元素的值。 当你想生成一些包名、最低`SDK`版本或目标`SDK`版本不同的`APK`时是非常有用的。 50 | - `Signing` 构建系统能在`build`配置中设置指定的签名, 在构建的构成会给`APK`进行签名。 51 | - `ProGuard` 构建系统允许根据不同的构建配置设置不同的混淆规则, 在构建的过程中会运行`ProGuard`来对`class`文件进行混淆。 52 | - `Testing` 构建系统会根据项目中的测试代码生成一个测试`APK`, 这样就不需要创建一个单独的测试工程了, 在构建的过程中会运行相应的测试功能。 53 | `Gradle`构建文件使用`Groovy`语法,`Groovy`是一门可以自定义构建逻辑并且能通过`Gradle`的`Android`插件与`Android`一些特定元素通信的语言。 54 | 55 | Build by convention 56 | --- 57 | 58 | `Android Studio`构建系统对项目结构和一些其他的构建选项做了一些很好的默认规范声明,如果你的项目符合这些条件,`Gradle`的构建文件就非常简单了。 如果你的项目不符合 59 | 其中的一些要求, 灵活的构建系统也允许你去配置几乎所有的构建选项。例如你项目的源码没有放在默认的文件夹中,你可以通过`build`文件去配置它的位置。 60 | 61 | Projects and modules 62 | --- 63 | 64 | `Android Studio`中的`Project`代表了一个完整的`Android`应用,每个`Project`中可以有一个或多个`Module`。 `Module`是应用中可以单独`build, test`或`debug`的组件。 65 | `Module`中包含应用中的源码和资源文件, `Android Studio`中的`Project`包含以下三种`Module`: 66 | - 包含可复用代码的`Java library modules`. 构建系统对`Java library module`会生成一个`JAR`包。 67 | - 有可复用`Android`代码和资源的`Android library modules`. 对该`library modules`构建系统会生成一个`AAR(Android ARchive)`包。 68 | - 有应用代码或者也可能是依赖其他`library modules`的`Android application modules`, 虽然很很多`Android`应用都只包含一个`application module`. 69 | 对于`application modules` 70 | 构建系统会生成一个`APK`包。 71 | 72 | `Android Studio projects`在`project`的最外城都包含一个列出所有`modules`的`Gradle build file`, 每个`module`也都包含自己的`Gradle build file`. 73 | 74 | Dependencies 75 | --- 76 | 77 | `Android Studio`的构建系统管理着依赖项目并且支持`module`依赖, 本地二进制文件依赖和远程二进制文件的依赖。 78 | 79 | - `Module Dependencies` 80 | 一个项目的`module`可以在构建未见中包含一系列所依赖的其他`modules`, 在你构建这个`module`的时候,系统回去组装这些所包含的`modules`. 81 | - `Local Dependencies` 82 | 如果本地文件系统中有`module`所依赖的二进制包如`JAR`包, 你可以在该`module`中的构建文件中声明这些依赖关系。 83 | - `Remote Dependencies` 84 | 当你的依赖是在远程仓库中,你不需要去下载他们然后拷贝到自己的工程中。 `Android Studio`支持远程`Maven`依赖。 `Maven`是一个流行的项目管理工具, 85 | 它可以使用仓库帮助组织项目依赖。 86 | 87 | 许多优秀的软件类库和工具都在公共的`Maven`仓库中, 对于这些依赖只需按照远程仓库中不同元素的定义来指定他们的`Moven`位置即可。 88 | 构建系统使用的`Maven`位置格式是`group:name:version`. 例如`Google Guava`16.0.1版本类库的`Maven`坐标是 89 | `com.google.guava:guava:16.0.1`. 90 | 91 | ` Maven Central Repository`现在被广泛用于分发许多类库和工具。 92 | 93 | 下面分别为以上三种依赖关系的配置; 94 | ``` 95 | dependencies { 96 | compile project(":name") 97 | compile fileTree(dir: 'libs', include: ['*.jar']) 98 | compile 'com.google.guava:guava:16.0.1' 99 | } 100 | ``` 101 | 102 | Build tasks 103 | --- 104 | 105 | `Android Studio`构建系统定义了一些列的构建任务, 高级别的任务调用一些产出必要输出的任务。 构建系统提供了`project tasks`来构建`app`和`module tasks` 106 | 来独立的构建`modules`. 107 | 108 | 可以通过`Andorid Studio`或者命令行看到当前可用任务的列表,并且执行里面的任务。 109 | 110 | The Gradle wrapper 111 | --- 112 | 113 | `Android Studio`项目包含了`Gradle wrapper`, 包括:      114 | - A JAR file 115 | - A properties file 116 | - A shell script for Windows platforms 117 | - A shell script for Mac and Linux platforms 118 | *声明:*需要把这些文件提交到代码控制系统。 119 | 120 | 使用`Gradle wrapper`(而不用本地安装的`Gradle`)能确保经常运行配置文件中配置的`Gralde`版本。 通过在配置文件中定义最新的版本来确保你的工程一直使用最新版的`Gradle`。 121 | 122 | `Android Studio`从你项目中的`Gradle wrapper`目录读取配置文件,并且在该目录运行`wrapper`, 这样在处理多个需要不同`Gradle`版本的项目时就会游刃有余。 123 | *声明:*`Android Studio`不使用`shell`脚本,所以对于他们的任何改变在`IDE`构建时都不会生效,你应该在`Gradle build files`中去设置自定义的逻辑。 124 | 125 | 你可以在开发及其或者是一些没有安装`Android Studio`的及其上使用命令行运行`shell`脚本来构建项目。 126 | 127 | 直接上图: 128 | ![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/AndroidStudio_4_1.png?raw=true) 129 | 130 | --- 131 | 132 | - 邮箱 :charon.chui@gmail.com 133 | - Good Luck! -------------------------------------------------------------------------------- /AndroidNote/Android自定义View/PathMeasure.md: -------------------------------------------------------------------------------- 1 | # PathMeasure 2 | 3 | PathMeasure是一个测量Path的路径的一个方法,我们今天的demo就是要做这样一个小东西,它会反复测量,运动的路径,并且沿着路径运行。 4 | 5 | 效果图: 6 | 7 | ![](https://ws3.sinaimg.cn/large/006tNc79ly1fh3i8c4k9ag309q0gagp9.gif) 8 | 9 | 实现的具体思路,就是,采用一个循环,反复的调用画图的方法,然后每当满一周之后,清零,再重新开始,为什么能沿着切线运动呢,是调用了一个参数,动态测量这个tan值。 10 | 11 | 代码: 12 | 13 | ``` 14 | public class CustomView extends View { 15 | 16 | private float currentValue = 0; // 用于纪录当前的位置,取值范围[0,1]映射Path的整个长度 17 | 18 | private float[] pos; // 当前点的实际位置 19 | private float[] tan; // 当前点的tangent值,用于计算图片所需旋转的角度 20 | private Bitmap mBitmap; // 箭头图片 21 | private Matrix mMatrix; // 矩阵,用于对图片进行一些操作 22 | 23 | private Paint mPaint; 24 | 25 | private int mViewWidth; 26 | private int mViewHeight; 27 | 28 | public CustomView(Context context) { 29 | this(context, null); 30 | } 31 | 32 | public CustomView(Context context, @Nullable AttributeSet attrs) { 33 | super(context, attrs); 34 | init(context); 35 | mPaint = new Paint(); 36 | mPaint.setColor(Color.BLACK); 37 | mPaint.setStrokeWidth(8); 38 | mPaint.setStyle(Paint.Style.STROKE); 39 | mPaint.setTextSize(60); 40 | 41 | 42 | } 43 | 44 | private void init(Context context) { 45 | pos = new float[2]; 46 | tan = new float[2]; 47 | BitmapFactory.Options options = new BitmapFactory.Options(); 48 | options.inSampleSize = 15; // 缩放图片 49 | mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrowhead, options); 50 | mMatrix = new Matrix(); 51 | } 52 | 53 | 54 | @Override 55 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 56 | super.onSizeChanged(w, h, oldw, oldh); 57 | mViewHeight = h; 58 | mViewWidth = w; 59 | 60 | } 61 | 62 | @Override 63 | protected void onDraw(Canvas canvas) { 64 | super.onDraw(canvas); 65 | canvas.translate(mViewWidth / 2, mViewHeight / 2); // 平移坐标系 66 | 67 | Path path = new Path(); // 创建 Path 68 | 69 | path.addCircle(0, 0, 200, Path.Direction.CW); // 添加一个圆形 70 | 71 | PathMeasure measure = new PathMeasure(path, false); // 创建 PathMeasure 72 | 73 | currentValue += 0.005; // 计算当前的位置在总长度上的比例[0,1] 74 | if (currentValue >= 1) { 75 | currentValue = 0; 76 | } 77 | 78 | measure.getPosTan(measure.getLength() * currentValue, pos, tan); // 获取当前位置的坐标以及趋势 79 | 80 | mMatrix.reset(); // 重置Matrix 81 | float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI); // 计算图片旋转角度 82 | 83 | mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2); // 旋转图片 84 | mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2); // 将图片绘制中心调整到与当前点重合 85 | 86 | canvas.drawPath(path, mPaint); // 绘制 Path 87 | canvas.drawBitmap(mBitmap, mMatrix, mPaint); // 绘制箭头 88 | 89 | invalidate(); 90 | } 91 | 92 | } 93 | 94 | ``` 95 | 96 | 代码地址:https://github.com/linsir6/mCustomView/tree/master/TestPathMeasure -------------------------------------------------------------------------------- /AndroidNote/Android自定义View/三阶贝塞尔曲线.md: -------------------------------------------------------------------------------- 1 | # 三阶贝塞尔曲线 2 | 3 | 如果您对贝塞尔曲线还不是很了解的话,可以看一下[二阶贝塞尔曲线](https://github.com/linsir6/AndroidNote/blob/master/Android%E8%87%AA%E5%AE%9A%E4%B9%89View/%E4%BA%8C%E9%98%B6%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF.md),下面我们看一下三阶曲线的效果图: 4 | 5 | 6 | 7 | ![bezier3](https://ws4.sinaimg.cn/large/006tKfTcly1fgwiac2u2qg308u0gm7nz.gif) 8 | 9 | 10 | 11 | 12 | 13 | 实现的思路和二阶的几乎一样,只是换了个函数,加了个控制点。 14 | 15 | 16 | 17 | 代码: 18 | 19 | 20 | 21 | ```java 22 | public class Bezier3 extends View { 23 | 24 | 25 | private Paint mPaint; 26 | private int centerX, centerY; 27 | 28 | private PointF start, end, control1, control2; 29 | private boolean mode = true; 30 | 31 | public Bezier3(Context context) { 32 | this(context, null); 33 | 34 | } 35 | 36 | public Bezier3(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | 39 | mPaint = new Paint(); 40 | mPaint.setColor(Color.BLACK); 41 | mPaint.setStrokeWidth(8); 42 | mPaint.setStyle(Paint.Style.STROKE); 43 | mPaint.setTextSize(60); 44 | 45 | start = new PointF(0, 0); 46 | end = new PointF(0, 0); 47 | control1 = new PointF(0, 0); 48 | control2 = new PointF(0, 0); 49 | } 50 | 51 | public void setMode(boolean mode) { 52 | this.mode = mode; 53 | } 54 | 55 | @Override 56 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 57 | super.onSizeChanged(w, h, oldw, oldh); 58 | centerX = w / 2; 59 | centerY = h / 2; 60 | 61 | // 初始化数据点和控制点的位置 62 | start.x = centerX - 200; 63 | start.y = centerY; 64 | end.x = centerX + 200; 65 | end.y = centerY; 66 | control1.x = centerX; 67 | control1.y = centerY - 100; 68 | control2.x = centerX; 69 | control2.y = centerY - 100; 70 | 71 | } 72 | 73 | @Override 74 | public boolean onTouchEvent(MotionEvent event) { 75 | // 根据触摸位置更新控制点,并提示重绘 76 | if (mode) { 77 | control1.x = event.getX(); 78 | control1.y = event.getY(); 79 | } else { 80 | control2.x = event.getX(); 81 | control2.y = event.getY(); 82 | } 83 | invalidate(); 84 | return true; 85 | } 86 | 87 | @Override 88 | protected void onDraw(Canvas canvas) { 89 | super.onDraw(canvas); 90 | //drawCoordinateSystem(canvas); 91 | 92 | // 绘制数据点和控制点 93 | mPaint.setColor(Color.GRAY); 94 | mPaint.setStrokeWidth(20); 95 | canvas.drawPoint(start.x, start.y, mPaint); 96 | canvas.drawPoint(end.x, end.y, mPaint); 97 | canvas.drawPoint(control1.x, control1.y, mPaint); 98 | canvas.drawPoint(control2.x, control2.y, mPaint); 99 | 100 | // 绘制辅助线 101 | mPaint.setStrokeWidth(4); 102 | canvas.drawLine(start.x, start.y, control1.x, control1.y, mPaint); 103 | canvas.drawLine(control1.x, control1.y, control2.x, control2.y, mPaint); 104 | canvas.drawLine(control2.x, control2.y, end.x, end.y, mPaint); 105 | 106 | // 绘制贝塞尔曲线 107 | mPaint.setColor(Color.RED); 108 | mPaint.setStrokeWidth(8); 109 | 110 | Path path = new Path(); 111 | 112 | path.moveTo(start.x, start.y); 113 | path.cubicTo(control1.x, control1.y, control2.x, control2.y, end.x, end.y); 114 | 115 | canvas.drawPath(path, mPaint); 116 | } 117 | 118 | 119 | } 120 | 121 | ``` 122 | 123 | 124 | [代码地址](https://github.com/linsir6/mCustomView/tree/master/Bezier3):https://github.com/linsir6/mCustomView/tree/master/Bezier3 125 | 126 | 127 | -------------------------------------------------------------------------------- /AndroidNote/Android自定义View/自定义View——CameraView.md: -------------------------------------------------------------------------------- 1 | # 自定义view——CameraView 2 | 3 | > 作者:https://github.com/linsir6 4 | > 5 | > 文章:http://www.jianshu.com/p/bd91a2b1245d 6 | 7 | 8 | 9 | ![效果图](/img/linsir.jpg) 10 | 11 | 12 | 13 | 14 | 15 | 今天我们我们同样是要做一个自定义view,不过和其他自定义view稍有不同的是,其他的自定view可能继承自的是view,或者viewGroup,今天写的自定义view继承自的是SurfaceView,它具有的功能是,可以直接在view上看到我们自己,并且可以获取到这个流,可以把流编码存储,也可以上传至服务器,当然我这里面只演示了将流中的一帧取出来,然后转乘Bitmap然后加载在了图片上。 16 | 17 | 18 | 19 | 20 | 21 | 既然我们想在view中看到自己,那么不可避免的是我们需要调用系统的相机: 22 | 23 | 24 | 25 | 26 | 27 | 当然这里我比较懒,就把平时常用到的权限全都添加上了,其实如果仅仅想添加相机的权限的话,只需要前几行就可以。 28 | 29 | ```java 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ``` 48 | 49 | 50 | 51 | 相机相关的操作: 52 | 53 | ```java 54 | private Camera mCamera; 55 | private int mCamId = Camera.CameraInfo.CAMERA_FACING_FRONT; 56 | 57 | mCamera = Camera.open(mCamId); 58 | Camera.Parameters params = mCamera.getParameters(); 59 | Camera.Size size = mCamera.new Size(previewWidth, previewHeight); 60 | //错略的计算相机一帧的大小 61 | mYuvPreviewFrame = new byte[previewWidth * previewHeight * 3 / 2]; 62 | 63 | params.setPreviewSize(previewWidth, previewHeight); 64 | //设置编码格式 65 | params.setPreviewFormat(ImageFormat.NV21); 66 | // 获取可以对焦的列表 67 | List supportedFocusModes = params.getSupportedFocusModes(); 68 | //判断是否有我们想要的,有的话就设置成这个 69 | if (!supportedFocusModes.isEmpty()) { 70 | if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 71 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 72 | } 73 | } 74 | 75 | mCamera.setParameters(params); 76 | 77 | mCamera.setDisplayOrientation(mPreviewRotation); 78 | //回调这个流 79 | mCamera.addCallbackBuffer(mYuvPreviewFrame); 80 | mCamera.setPreviewCallbackWithBuffer(this); 81 | try { 82 | mCamera.setPreviewDisplay(getHolder()); 83 | } catch (IOException e) { 84 | e.printStackTrace(); 85 | } 86 | mCamera.startPreview(); 87 | 88 | 89 | 90 | 91 | ``` 92 | 93 | 94 | 95 | surfaceView相关操作: 96 | 97 | 98 | 99 | ```java 100 | @Override 101 | public void onPreviewFrame(byte[] data, Camera camera) { 102 | mPrevCb.onGetYuvFrame(data); 103 | camera.addCallbackBuffer(mYuvPreviewFrame); 104 | } 105 | 106 | @Override 107 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 108 | } 109 | 110 | @Override 111 | public void surfaceCreated(SurfaceHolder arg0) { 112 | if (mCamera != null) { 113 | try { 114 | mCamera.setPreviewDisplay(getHolder()); 115 | } catch (IOException e) { 116 | e.printStackTrace(); 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public void surfaceDestroyed(SurfaceHolder arg0) { 123 | } 124 | ``` 125 | 126 | 127 | 128 | 到这里为止,我们就可以在view上看到动态的摄像头捕捉的视频了,也可以回调到这个流了,下面就是对流的截取的操作。 129 | 130 | 131 | 132 | 133 | 134 | 将流转换成图片的操作: 135 | 136 | ```java 137 | //设置编码格式 138 | final YuvImage image = new YuvImage(temp, ImageFormat.NV21, 240, 320, null); 139 | ByteArrayOutputStream os = new ByteArrayOutputStream(temp.length); 140 | if(!image.compressToJpeg(new Rect(0, 0, 240, 320), 100, os)){ 141 | return; 142 | } 143 | byte[] tmp = os.toByteArray(); 144 | //转换成bitmap 145 | Bitmap bmp = BitmapFactory.decodeByteArray(tmp, 0,tmp.length); 146 | 147 | //设置旋转 148 | Matrix matrix = new Matrix(); 149 | matrix.setRotate(270f); 150 | 151 | Bitmap newBM = Bitmap.createBitmap(bmp, 0, 0, 240, 320, matrix, false); 152 | 153 | imageView.setImageBitmap(newBM); 154 | 155 | ``` 156 | 157 | 158 | 159 | 项目源码地址:https://github.com/linsir6/mCustomView/tree/master/CameraView 160 | 161 | 欢迎star~ -------------------------------------------------------------------------------- /AndroidNote/Android自定义View/自定义View——CheckView.md: -------------------------------------------------------------------------------- 1 | # 自定义View —— drawBitmap 2 | 3 | > 最近在复习自定义view,学习了GitHub上面有一个非常详细的自定义view的教程,就打算跟着把里面的demo都做一遍,然后记录一下学习到的checkView。[教程地址](http://www.gcssloop.com/customview/CustomViewIndex) 4 | 5 | 6 | ![效果图](https://ws2.sinaimg.cn/large/006tNbRwly1ffxxfb2a80g308i0f80vk.gif) 7 | 8 | 9 | ``drawBitmap``是指将一张图片一张图片画在画布上面。这里面调用的: 10 | 11 | ``` 12 | public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {} 13 | ``` 14 | 15 | 参数的含义就很好理解了: 16 | bitmap:画什么图片 17 | src:要画图片中的哪个部分 18 | dst:要把图片画到画布中的哪个位置 19 | paint:画笔 20 | 21 | 我们这里需要的一个长图: 22 | 23 | ![](http://upload-images.jianshu.io/upload_images/2585384-e55222c86cd8af0c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 24 | 25 | 这个drawBitmap方法是可以重复使用的,可以多次画,然后画出来的效果会叠加在一起,那么我们便可以第一次画一个非常小的对号的一部分,然后一点一点地多,最后就成为一个动态的效果了,我们事分成了13次画的,通过的事handler来控制实现的。 26 | 27 | 28 | 核心代码: 29 | 30 | 31 | ```java 32 | 33 | mHandler = new Handler() { 34 | 35 | @Override public void handleMessage(Message msg) { 36 | super.handleMessage(msg); 37 | //确定当前没有view 38 | if (animCurrentPage < animMaxPage && animCurrentPage >= 0) { 39 | //画一下 40 | invalidate(); 41 | if (animState == ANIM_NULL) { 42 | return; 43 | } 44 | //判断当前是要画对号,然后每次动态移动一点 45 | if (animState == ANIM_CHECK) { 46 | animCurrentPage++; 47 | } else if (animState == ANIM_UNCHECK) { 48 | animCurrentPage--; 49 | } 50 | 51 | // 延时发送消息,然后自己接收,形成了一个循环 52 | this.sendEmptyMessageDelayed(0, animDuration / animMaxPage); 53 | 54 | 55 | } else { 56 | //不需要画了,恢复到默认值 57 | if (isCheck) { 58 | animCurrentPage = animMaxPage - 1; 59 | } else { 60 | animCurrentPage = -1; 61 | } 62 | 63 | invalidate(); 64 | animState = ANIM_NULL; 65 | } 66 | } 67 | }; 68 | 69 | ``` 70 | 71 | 72 | ```java 73 | 74 | @Override 75 | protected void onDraw(Canvas canvas) { 76 | super.onDraw(canvas); 77 | //将画笔移到view的中央 78 | canvas.translate(mWidth / 2, mHeight / 2); 79 | //画一个圆 80 | canvas.drawCircle(0, 0, 240, mPaint); 81 | 82 | //找到高度 83 | int sideLength = okBitmap.getHeight(); 84 | 85 | //控制画出图片上面的哪一个部分 86 | Rect src = new Rect(sideLength * animCurrentPage, 0, sideLength * (animCurrentPage + 1), sideLength); 87 | 88 | //画在画布上面的哪个位置 89 | Rect dst = new Rect(-200, -200, 200, 200); 90 | 91 | //画 92 | canvas.drawBitmap(okBitmap, src, dst, null); 93 | } 94 | 95 | ``` 96 | 97 | 98 | ```java 99 | 100 | public void check() { 101 | //触发想要绘画的方法 102 | if (animState != ANIM_NULL || isCheck) { 103 | return; 104 | } 105 | animState = ANIM_CHECK; 106 | animCurrentPage = 0; 107 | mHandler.sendEmptyMessageDelayed(0, animDuration / animMaxPage); 108 | isCheck = true; 109 | } 110 | ``` 111 | 112 | 113 | ---- 114 | 115 | 到这里一个简单的自定义view就已经实现咯~ 116 | 117 | 118 | [源码地址](https://github.com/linsir6/mCustomView/tree/master/CheckView):https://github.com/linsir6/mCustomView/tree/master/CheckView 119 | -------------------------------------------------------------------------------- /AndroidNote/Android自定义View/自定义View——PieView.md: -------------------------------------------------------------------------------- 1 | # 自定义view基础入门——实现饼状图 2 | 3 | 4 | 5 | > 自定义view应该是Android开发的基本功吧,最近无聊打算再重头过一遍自定义view,今天写的是一个比较简单的demo了,是一个自定义的饼状图,我是根据[自定义view教程](http://www.gcssloop.com/customview/CustomViewIndex)学习的。 6 | 7 | 8 | 9 | ![效果图](http://upload-images.jianshu.io/upload_images/2585384-8bbc24b0f7f3b4f8.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 10 | 11 | 其实这个自定义view还是挺简单的,只需要让用户传入一个list,然后根据list里面的数据,找出不同数据占的权重,然后在绘制扇形的过程中,上不同的色就可以了,当然这只是一个入门级的自定义view。 12 | 13 | 14 | 15 | 核心代码: 16 | 17 | ```java 18 | public class PieData { 19 | 20 | private String name; //颜色 21 | private float value; //数值 22 | private float percentage; //百分比 23 | 24 | private int color = 0; //颜色 25 | private float angle = 0; //角度 26 | 27 | public PieData(@NonNull String name, @NonNull float value) { 28 | this.name = name; 29 | this.value = value; 30 | } 31 | } 32 | ``` 33 | 34 | 35 | 36 | ```java 37 | /** 38 | * Created by linSir 39 | * date at 2017/5/22. 40 | * describe: 自定义的饼状图 41 | */ 42 | 43 | public class PieView extends View { 44 | 45 | private int[] mColors = {Color.RED, Color.BLACK, Color.BLUE, Color.GREEN, Color.YELLOW}; 46 | 47 | private float mStartAngle = 0; //初始化绘制的角度 48 | 49 | private ArrayList mData; //数据 50 | 51 | private int mWidth, mHeight; //宽,高 52 | 53 | private Paint mPaint = new Paint(); 54 | 55 | 56 | public PieView(Context context) { 57 | super(context); 58 | } 59 | 60 | public PieView(Context context, @Nullable AttributeSet attrs) { 61 | super(context, attrs); 62 | mPaint.setStyle(Paint.Style.FILL); //设置画笔的模式为填充 63 | mPaint.setAntiAlias(true); //设置抗锯齿 64 | } 65 | 66 | 67 | @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { 68 | super.onSizeChanged(w, h, oldw, oldh); 69 | mWidth = w; 70 | mHeight = h; 71 | } 72 | 73 | @Override protected void onDraw(Canvas canvas) { 74 | super.onDraw(canvas); 75 | if (null == mData) 76 | return; 77 | float currentStartAngle = mStartAngle; 78 | canvas.translate(mWidth / 2, mHeight / 2); 79 | float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8); 80 | RectF rect = new RectF(-r, -r, r, r); 81 | 82 | for (int i = 0; i < mData.size(); i++) { 83 | PieData pie = mData.get(i); 84 | mPaint.setColor(pie.getColor()); 85 | canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint); 86 | currentStartAngle += pie.getAngle(); 87 | } 88 | } 89 | 90 | /** 91 | * 设置起始角度 92 | */ 93 | public void setStartAngle(int mStartAngle) { 94 | this.mStartAngle = mStartAngle; 95 | invalidate(); //刷新 96 | } 97 | 98 | 99 | public void setData(ArrayList mData) { 100 | this.mData = mData; 101 | initData(mData); 102 | invalidate(); 103 | } 104 | 105 | private void initData(ArrayList mData) { 106 | if (null == mData || mData.size() == 0) 107 | return; 108 | 109 | float sumValue = 0; 110 | for (int i = 0; i < mData.size(); i++) { 111 | PieData pie = mData.get(i); 112 | sumValue += pie.getValue(); 113 | int j = i % mColors.length; 114 | pie.setColor(mColors[j]); 115 | } 116 | 117 | float sumAngle = 0; 118 | for (int i = 0; i < mData.size(); i++) { 119 | PieData pie = mData.get(i); 120 | 121 | float percentage = pie.getValue() / sumValue; //占的百分比 122 | float angle = percentage * 360; 123 | 124 | pie.setPercentage(percentage); 125 | pie.setAngle(angle); 126 | sumAngle += angle; 127 | 128 | } 129 | 130 | } 131 | 132 | } 133 | 134 | ``` 135 | 136 | 137 | 138 | ```java 139 | public class MainActivity extends AppCompatActivity { 140 | 141 | private PieView mPieView; 142 | 143 | @Override 144 | protected void onCreate(Bundle savedInstanceState) { 145 | super.onCreate(savedInstanceState); 146 | setContentView(R.layout.activity_main); 147 | mPieView = (PieView) findViewById(R.id.mPieView); 148 | 149 | ArrayList list = new ArrayList(); 150 | PieData data = new PieData("test",1); 151 | PieData data2 = new PieData("test",2); 152 | PieData data3 = new PieData("test",1); 153 | PieData data4 = new PieData("test",4); 154 | 155 | list.add(data); 156 | list.add(data2); 157 | list.add(data3); 158 | list.add(data4); 159 | 160 | mPieView.setData(list); 161 | 162 | } 163 | } 164 | ``` 165 | 166 | 167 | 168 | 写到这里,我们的自定义view就写完了,效果如上图所示,当然这只能算是自定义view家族中最为简单的自定义view了,在之后的学习生活中吧,我打算总结一下自定义view的基本知识点,还有一些常见的问题,并且多做一些例子,大家一起讨论~ 169 | 170 | 171 | 172 | [本文代码的源码链接](https://github.com/linsir6/mCustomView/tree/master/PieView) 173 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/AndroidStudio导入工程一直在Building的解决方案.md: -------------------------------------------------------------------------------- 1 | # Android导入项目一直在Building的解决方案 2 | 3 | 这种问题的发生的场景,一般是因为项目中的gradle的版本,或者sdk的版本我们本地环境中没有,所以需要先下载,然后才能导入,但是下载起来又特别的慢,所以我们需要修改一下待导入项目中用的配置。 4 | 5 | 我们可以找一个,我们可以运行的项目,然后将这些配置替换掉。 6 | 7 | 1.修改待倒入项目的gradle版本 8 | 9 | 找到 ``项目名称/gradle/wrapper/gradle-wrapper.properties`` 10 | 将 ``distributionUrl=...`` 这一行,修改成我们已知项目一样即可 11 | 12 | 13 | 2.修改 ``项目名称/app/build.gradle`` 14 | 将 ``targetSdkVersion`` 和 ``buildToolsVersion`` 和``compileSdkVersion``修改成和我们能跑起来项目一样就可以了。 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/Android项目总结2.md: -------------------------------------------------------------------------------- 1 | 2 | # Android项目总结 3 | 4 | Android项目总结,最近在公司里,写了几个Android的小项目,在学习过程中学习了一些新的东西,也学到了很多东西,所以打算记录一下。 5 | 6 | 7 | 1. 涉及到多选和单选的时候,可以用checkbox和radiobutton,要是有必选项目的时候可以用&&符号来判断,确保用户都已经选择了。 8 | 9 | 2. 想检测editext输入的情况的时候,可以用mEditext.addTextChangdListener(); 10 | 11 | ``` 12 | erIdCode.addTextChangedListener(new TextWatcher() { 13 | @Override 14 | public void beforeTextChanged(CharSequence sequence, int i, int i1, int i2) { 15 | 16 | } 17 | 18 | @Override 19 | public void onTextChanged(CharSequence sequence, int i, int i1, int i2) { 20 | 21 | } 22 | 23 | @Override 24 | public void afterTextChanged(Editable editable) { 25 | 26 | 27 | } 28 | } 29 | 30 | ``` 31 | 用这个方法,可以监测到用户输入的过程,并且可以同时获取到用户的操作信息。 32 | 33 | 3. 获取手机imei码 34 | 35 | ``` 36 | //需要的权限 37 | 38 | 39 | //获取手机唯一标识符的代码 40 | 41 | TelephonyManager telephonyManager = (TelephonyManager) this.getApplicationContext().getSystemService(this.getApplicationContext().TELEPHONY_SERVICE); 42 | String imei = telephonyManager.getDeviceId(); 43 | 44 | 45 | ``` 46 | 47 | 4. 将时间戳转换为时间 48 | 49 | ``` 50 | 51 | /** 52 | * 将时间戳转换为时间 53 | */ 54 | public static String stampToDate(String s) { 55 | String res; 56 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 57 | long lt = new Long(s); 58 | Date date = new Date(lt); 59 | res = simpleDateFormat.format(date); 60 | return res; 61 | } 62 | 63 | ``` 64 | 65 | 66 | 5. 将时间转换为时间戳 67 | 68 | ``` 69 | 70 | /** 71 | * 将时间转换成时间戳 72 | */ 73 | public static String dateToStamp(String s) throws ParseException { 74 | String res; 75 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); 76 | Date date = simpleDateFormat.parse(s); 77 | long ts = date.getTime(); 78 | res = String.valueOf(ts); 79 | return res; 80 | } 81 | 82 | ``` 83 | 84 | 85 | 6. 设置editext的提示文字的颜色和字体大小 86 | 87 | 88 | ``` 89 | harSequence hint = mCode.getHint(); 90 | SpannableString ss = new SpannableString(hint); 91 | AbsoluteSizeSpan ass = new AbsoluteSizeSpan(17, true); 92 | mCode.setHintTextColor(0xffeeeeee); 93 | ss.setSpan(ass, 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 94 | mCode.setHint(new SpannedString("短信验证码")); 95 | 96 | ``` 97 | 98 | 7. 让Fragment拥有和Activity一样的onResume事件 99 | 100 | 101 | ``` 102 | protected boolean isCreate = false; 103 | 104 | @Override 105 | public void onCreate(@Nullable Bundle savedInstanceState) { 106 | super.onCreate(savedInstanceState); 107 | isCreate = true; 108 | } 109 | 110 | @Override 111 | public void setUserVisibleHint(boolean isVisibleToUser) { 112 | super.setUserVisibleHint(isVisibleToUser); 113 | if (isVisibleToUser && isCreate) { 114 | initData(true, 1); 115 | LinLog.lLog("====lin====> onResume"); 116 | //相当于Fragment的onResume 117 | //在这里处理加载数据等操作 118 | } else { 119 | //相当于Fragment的onPause 120 | } 121 | } 122 | 123 | ``` 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/Handler引起的内存泄漏以及分析.md: -------------------------------------------------------------------------------- 1 | # Handler内存泄漏分析及解决 2 | --- 3 | 4 | ### 一、介绍 5 | 6 | 首先,请浏览下面这段handler代码: 7 | 8 | ``` 9 | public class SampleActivity extends Activity { 10 | private final Handler mLeakyHandler = new Handler() { 11 | @Override 12 | public void handleMessage(Message msg) { 13 | // ... 14 | } 15 | } 16 | } 17 | ``` 18 | 19 | 在使用handler时,这是一段很常见的代码。但是,它却会造成严重的内存泄漏问题。在实际编写中,我们往往会得到如下警告: 20 | 21 | ``` 22 | ⚠ In Android, Handler classes should be static or leaks might occur. 23 | 24 | ``` 25 | 26 | ### 二、分析 27 | 28 | 1、 Android角度 29 | 30 | 当Android应用程序启动时,framework会为该应用程序的主线程创建一个Looper对象。这个Looper对象包含一个简单的消息队列Message Queue,并且能够循环的处理队列中的消息。这些消息包括大多数应用程序framework事件,例如Activity生命周期方法调用、button点击等,这些消息都会被添加到消息队列中并被逐个处理。 31 | 32 | 另外,主线程的Looper对象会伴随该应用程序的整个生命周期。 33 | 34 | 然后,当主线程里,实例化一个Handler对象后,它就会自动与主线程Looper的消息队列关联起来。所有发送到消息队列的消息Message都会拥有一个对Handler的引用,所以当Looper来处理消息时,会据此回调[Handler#handleMessage(Message)]方法来处理消息。 35 | 36 | 2、 Java角度 37 | 38 | 在java里,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类。但是,静态内部类却不会。 39 | 40 | ###三、泄漏来源 41 | 42 | 请浏览下面一段代码: 43 | 44 | ``` 45 | public class SampleActivity extends Activity { 46 | 47 | private final Handler mLeakyHandler = new Handler() { 48 | @Override 49 | public void handleMessage(Message msg) { 50 | // ... 51 | } 52 | } 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | 58 | // Post a message and delay its execution for 10 minutes. 59 | mLeakyHandler.postDelayed(new Runnable() { 60 | @Override 61 | public void run() { /* ... */ } 62 | }, 1000 * 60 * 10); 63 | 64 | // Go back to the previous Activity. 65 | finish(); 66 | } 67 | } 68 | ``` 69 | 70 | 当activity结束(finish)时,里面的延时消息在得到处理前,会一直保存在主线程的消息队列里持续10分钟。而且,由上文可知,这条消息持有对handler的引用,而handler又持有对其外部类(在这里,即SampleActivity)的潜在引用。这条引用关系会一直保持直到消息得到处理,从而,这阻止了SampleActivity被垃圾回收器回收,同时造成应用程序的泄漏。 71 | 72 | <<<<<<< HEAD 73 | 注意,上面代码中的Runnable类--非静态匿名类--同样持有对其外部类的引用。从而也导致泄漏。 74 | 75 | ### 四、泄漏解决方案 76 | 77 | 首先,上面已经明确了内存泄漏来源: 78 | 79 | 只要有未处理的消息,那么消息会引用handler,非静态的handler又会引用外部类,即Activity,导致Activity无法被回收,造成泄漏; 80 | 81 | Runnable类属于非静态匿名类,同样会引用外部类。 82 | 83 | 为了解决遇到的问题,我们要明确一点:静态内部类不会持有对外部类的引用。所以,我们可以把handler类放在单独的类文件中,或者使用静态内部类便可以避免泄漏。 84 | 85 | 另外,如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样统一不会导致内存泄漏。 86 | 87 | 对于匿名类Runnable,同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。 88 | 89 | ``` 90 | public class SampleActivity extends Activity { 91 | 92 | /** 93 | * Instances of static inner classes do not hold an implicit 94 | * reference to their outer class. 95 | */ 96 | private static class MyHandler extends Handler { 97 | private final WeakReference mActivity; 98 | 99 | public MyHandler(SampleActivity activity) { 100 | mActivity = new WeakReference(activity); 101 | } 102 | 103 | @Override 104 | public void handleMessage(Message msg) { 105 | SampleActivity activity = mActivity.get(); 106 | if (activity != null) { 107 | // ... 108 | } 109 | } 110 | } 111 | 112 | private final MyHandler mHandler = new MyHandler(this); 113 | 114 | /** 115 | * Instances of anonymous classes do not hold an implicit 116 | * reference to their outer class when they are "static". 117 | */ 118 | private static final Runnable sRunnable = new Runnable() { 119 | @Override 120 | public void run() { /* ... */ } 121 | }; 122 | 123 | @Override 124 | protected void onCreate(Bundle savedInstanceState) { 125 | super.onCreate(savedInstanceState); 126 | 127 | // Post a message and delay its execution for 10 minutes. 128 | mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 129 | 130 | // Go back to the previous Activity. 131 | finish(); 132 | } 133 | } 134 | ``` 135 | 136 | ### 五、小结 137 | 138 | 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。 139 | 140 | 原文链接: 141 | 142 | [http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820) 143 | ======= 144 | 虽然静态类与非静态类之间的区别并不大,但是对于Android开发者而言却是必须理解的。至少我们要清楚,如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。 145 | 146 | 原文链接: 147 | 148 | [http://www.jianshu.com/p/cb9b4b71a820](http://www.jianshu.com/p/cb9b4b71a820) 149 | 150 | 151 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/MVP+RxJava+Retrofit2+Dagger实战.md: -------------------------------------------------------------------------------- 1 | # MVP + RxJava + Retrofit2 + Dagger2 项目实战 2 | 3 | 4 | 最近要开始一个公司的小项目,虽然项目的功能是非常简单的,但是还是打算好好搞一搞,毕竟这些技术还是有一定的上手难度的,所以如果直接在大型项目中尝试的话,可能会出问题,所以打算还是现在小的项目中尝试一下,然后把项目精益求精一下。 5 | 6 | 本文便是博主开始这个项目的一个笔记吧。 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/Recyclerview和Listview的异同.md: -------------------------------------------------------------------------------- 1 | recyclerView和ListView的异同 2 | 3 | --- 4 | 5 | * ViewHolder是用来保存视图引用的类,无论是ListView亦或是RecyclerView。只不过在ListView中,ViewHolder需要自己来定义,且这只是一种推荐的使用方式,不使用当然也可以,这不是必须的。只不过不使用ViewHolder的话,ListView每次getView的时候都会调用findViewById(int),这将导致ListView性能展示迟缓。而在RecyclerView中使用RecyclerView.ViewHolder则变成了必须,尽管实现起来稍显复杂,但它却解决了ListView面临的上述不使用自定义ViewHolder时所面临的问题。 6 | * 我们知道ListView只能在垂直方向上滚动,Android API没有提供ListView在水平方向上面滚动的支持。或许有多种方式实现水平滑动,但是请相信我,ListView并不是设计来做这件事情的。但是RecyclerView相较于ListView,在滚动上面的功能扩展了许多。它可以支持多种类型列表的展示要求,主要如下: 7 | 8 | 1. LinearLayoutManager,可以支持水平和竖直方向上滚动的列表。 9 | 2. StaggeredGridLayoutManager,可以支持交叉网格风格的列表,类似于瀑布流或者Pinterest。 10 | 3. GridLayoutManager,支持网格展示,可以水平或者竖直滚动,如展示图片的画廊。 11 | 12 | * 列表动画是一个全新的、拥有无限可能的维度。起初的Android API中,删除或添加item时,item是无法产生动画效果的。后面随着Android的进化,Google的Chat Hasse推荐使用ViewPropertyAnimator属性动画来实现上述需求。 13 | 相比较于ListView,RecyclerView.ItemAnimator则被提供用于在RecyclerView添加、删除或移动item时处理动画效果。同时,如果你比较懒,不想自定义ItemAnimator,你还可以使用DefaultItemAnimator。 14 | 15 | * ListView的Adapter中,getView是最重要的方法,它将视图跟position绑定起来,是所有神奇的事情发生的地方。同时我们也能够通过registerDataObserver在Adapter中注册一个观察者。RecyclerView也有这个特性,RecyclerView.AdapterDataObserver就是这个观察者。ListView有三个Adapter的默认实现,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter。然而,RecyclerView的Adapter则拥有除了内置的内DB游标和ArrayList的支持之外的所有功能。RecyclerView.Adapter的实现的,我们必须采取措施将数据提供给Adapter,正如BaseAdapter对ListView所做的那样。 16 | * 在ListView中如果我们想要在item之间添加间隔符,我们只需要在布局文件中对ListView添加如下属性即可: 17 | 18 | ``` 19 | android:divider="@android:color/transparent" 20 | android:dividerHeight="5dp" 21 | ``` 22 | * ListView通过AdapterView.OnItemClickListener接口来探测点击事件。而RecyclerView则通过RecyclerView.OnItemTouchListener接口来探测触摸事件。它虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限。 23 | * ListView可以设置选择模式,并添加MultiChoiceModeListener,如下所示: 24 | 25 | 26 | ``` 27 | listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); 28 | listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { 29 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { ... } 30 | public void onItemCheckedStateChanged(ActionMode mode, int position, 31 | long id, boolean checked) { ... } 32 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 33 | switch (item.getItemId()) { 34 | case R.id.menu_item_delete_crime: 35 | CrimeAdapter adapter = (CrimeAdapter)getListAdapter(); 36 | CrimeLab crimeLab = CrimeLab.get(getActivity()); 37 | for (int i = adapter.getCount() - 1; i >= 0; i--) { 38 | if (getListView().isItemChecked(i)) { 39 | crimeLab.deleteCrime(adapter.getItem(i)); 40 | } 41 | } 42 | mode.finish(); 43 | adapter.notifyDataSetChanged(); 44 | return true; 45 | default: 46 | return false; 47 | } 48 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { ... } 49 | public void onDestroyActionMode(ActionMode mode) { ... } 50 | }); 51 | 52 | ``` 53 | 而RecyclerView则没有此功能。 54 | 55 | [http://www.cnblogs.com/littlepanpc/p/4497290.html](http://www.cnblogs.com/littlepanpc/p/4497290.html) 56 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/iterm2+vim打造完美终端.md: -------------------------------------------------------------------------------- 1 | # iterm2+vim打造完美的终端 2 | 3 | 最近有点迷上了vim了,就打算来一波vim的最佳实践,刚开始选择的是macVim,但是感觉有的时候vim不和命令行一起用没有那么好用,所有又决定换回命令行了。 4 | 5 | 在mac上经常用命令行的人应该配置的都是 iterm2 + oh my zsh这样一个基础的配置,感觉还是挺好用的。 6 | 7 | 这两个都是非常好安装配置的,在这里就先不介绍了,如果大家遇到问题的话,可以随时一起讨论。 8 | 9 | 下面记录一下,iterm2的用法和快捷键: 10 | 11 | 1. 新建标签:``command + t`` 12 | 2. 关闭标签:``command + w`` 13 | 3. 切换标签:``command + 数字`` ``command + 左右方向键`` 14 | 4. 切换全屏:``command + enter`` 15 | 5. 查找: ``command + f`` 16 | 6. 垂直分屏:``command + d`` 17 | 7. 水平分屏:``command + shift + d`` 18 | 8. 切换屏幕:``command + option + 方向键`` ``command + [ or ]`` 19 | 9. 查看历史命令 :``command + ;`` 20 | 10. 查看剪贴板历史:``command + shift + h`` 21 | 11. 清除当前行:``ctrl + u`` 22 | 12. 到行首:``ctrl + a`` 23 | 13. 到行尾:``ctrl + e`` 24 | 14. 左右方向键:``ctrl + f/b`` 25 | 16. 上一条命令:``ctrl + p`` 26 | 17. 搜索命令历史:``ctrl + r`` 27 | 18. 删除当前光标的字符:``ctrl + d`` 28 | 19. 删除光标之前的字符:``ctrl + h`` 29 | 20. 删除光标之前的单词:``ctrl + w`` 30 | 21. 删除到文本末尾:``ctrl + k`` 31 | 22. 交换光标处文本:``ctrl + t`` 32 | 23. 清屏:``command + r`` ``ctrl + l`` 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/jvm-serializers.md: -------------------------------------------------------------------------------- 1 | # Jvm-serializers 2 | 3 | 4 | # Home 5 | 6 | Pascal S. de Kloe edited this page on 9 Jul 2016 · 68 revisions 7 | 8 | 9 | # 测试平台 10 | 11 | OS:Mac OS X 12 | JVM:Oracle Corporation 1.8.0_91 13 | CPU:2.8 GHz Intel Core i7 os-arch:Darwin Kernel Version 15.5.0 14 | Cores (incl HT):8 15 | 16 | 17 | # 公开声明 18 | 19 | 这个测试关注点在于对于一个循环的结构体的编码和解码,但是不同特征集对于不同的算法有着不一样的结果: 20 | 21 | - 一些序列化支持循环侦查,分享其它正好写非循环树结构 22 | - 一些包含充满元数据在序列化的过程中,一些不能 23 | - 一些是跨平台的,一些是语言特有的 24 | - 一些是以文字为基础的,一些是以二进制为基础的 25 | - 一些提供了版本向前/向后兼容,但是有的没有提供 26 | 27 | 28 | 不同的数据将会产生不同的结果(例如添加一个非ascii的字符到每一个字符串,然而将会给出一个未加工的关于库的性能 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/基于OTP算法的双向认证.md: -------------------------------------------------------------------------------- 1 | ## 基于otp算法的双向认证 2 | 3 | 4 | 先举例一个应用场景吧,我们应该都用U盾,或者将军令这种生成动态密钥的工具,其实它内部就是基于OTP算法来实现的。 5 | 6 | 谷歌也有开源这样的开源项目,本文就是博主通读了谷歌开源项目源码来实现的,项目地址: 7 | 8 | [Google Android端项目源码](https://github.com/google/google-authenticator-android) 9 | 10 | [Google 后台项目源码](https://github.com/wstrange/GoogleAuth) 11 | 12 | 13 | ## 算法概要 14 | 15 | TOTP(基于时间的一次性密码算法)是支持时间作为动态因素基于HMAC一次性密码算法的扩展。 16 | 17 | 本算法是一个对称算法,也就是说,后台和移动端采用同样的密钥,同时这个算法是依赖于当前的系统时间的,所以可以用于动态验证。 18 | 19 | **TOTP = HMAC-SHA-1(K, (T - T0) / X)** 20 | 21 | - K 共享密钥 22 | 23 | - T 当前时间戳 24 | 25 | - T0 开始的时间戳 26 | 27 | - X 时间步长 28 | 29 | 30 | 多唠叨几句:我们整体算法采用处理密钥的方式,是要将密钥转换成byte数组之后进行操作的,还有就是谷歌的这套双向认证算法中严格的采用了Base32字符串的方式,Base32中只有A-Z和2-7这些字符。 31 | 32 | 33 | 34 | 代码效果: 35 | 36 | ``` 37 | 2017/08/09 20:39:56 38 | 983452 39 | 2017/08/09 20:39:57 40 | 983452 41 | 2017/08/09 20:39:58 42 | 983452 43 | 2017/08/09 20:39:59 44 | 983452 45 | 2017/08/09 20:40:00 46 | 977560 47 | 2017/08/09 20:40:01 48 | 977560 49 | 2017/08/09 20:40:02 50 | 977560 51 | 2017/08/09 20:40:03 52 | 977560 53 | 2017/08/09 20:40:04 54 | 977560 55 | ``` 56 | 57 | 58 | 应用在app中的效果图: 59 | 60 | ![](https://ws1.sinaimg.cn/large/006tNc79ly1fidrkhuhz9j30ei0pwmx9.jpg) 61 | 62 | 63 | 应用在app中的效果图: 64 | 65 | 66 | 67 | 核心代码算法: 68 | 69 | 算法GitHub地址:https://github.com/linsir6/TOTP 70 | 71 | 72 | 算法的核心类: 73 | 74 | ``` 75 | /** 76 | * Created by linSir 77 | * date at 2017/8/8. 78 | * describe: 算法的核心类 79 | */ 80 | 81 | public class PasscodeGenerator { 82 | private static final int MAX_PASSCODE_LENGTH = 9; 83 | 84 | private static final int[] DIGITS_POWER 85 | // 0 1 2 3 4 5 6 7 8 9 86 | = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000}; 87 | 88 | private final Signer signer; 89 | private final int codeLength; 90 | 91 | interface Signer { 92 | byte[] sign(byte[] data) throws GeneralSecurityException; 93 | } 94 | 95 | public PasscodeGenerator(Signer signer, int passCodeLength) { 96 | if ((passCodeLength < 0) || (passCodeLength > MAX_PASSCODE_LENGTH)) { 97 | throw new IllegalArgumentException( 98 | "PassCodeLength must be between 1 and " + MAX_PASSCODE_LENGTH 99 | + " digits."); 100 | } 101 | this.signer = signer; 102 | this.codeLength = passCodeLength; 103 | } 104 | 105 | private String padOutput(int value) { 106 | String result = Integer.toString(value); 107 | for (int i = result.length(); i < codeLength; i++) { 108 | result = "0" + result; 109 | } 110 | return result; 111 | } 112 | 113 | public String generateResponseCode(long state) 114 | throws GeneralSecurityException { 115 | byte[] value = ByteBuffer.allocate(8).putLong(state).array(); 116 | return generateResponseCode(value); 117 | } 118 | 119 | public String generateResponseCode(byte[] challenge) 120 | throws GeneralSecurityException { 121 | byte[] hash = signer.sign(challenge); 122 | 123 | int offset = hash[hash.length - 1] & 0xF; 124 | int truncatedHash = hashToInt(hash, offset) & 0x7FFFFFFF; 125 | int pinValue = truncatedHash % DIGITS_POWER[codeLength]; 126 | return padOutput(pinValue); 127 | } 128 | 129 | private int hashToInt(byte[] bytes, int start) { 130 | DataInput input = new DataInputStream( 131 | new ByteArrayInputStream(bytes, start, bytes.length - start)); 132 | int val; 133 | try { 134 | val = input.readInt(); 135 | } catch (IOException e) { 136 | throw new IllegalStateException(e); 137 | } 138 | return val; 139 | } 140 | } 141 | 142 | 143 | ``` 144 | 145 | 146 | 147 | 整体算法的思想和代码实现大概就是这样,如果有什么问题,欢迎大家在GitHub上面提Issues 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/检查app是否有推送权限.md: -------------------------------------------------------------------------------- 1 | # 检查app是否具有推送权限 2 | 3 | 检查是否有推送权限 4 | 5 | ```java 6 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 7 | private boolean isNotificationEnabled(Context context) { 8 | 9 | String CHECK_OP_NO_THROW = "checkOpNoThrow"; 10 | String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION"; 11 | 12 | AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 13 | ApplicationInfo appInfo = context.getApplicationInfo(); 14 | String pkg = context.getApplicationContext().getPackageName(); 15 | int uid = appInfo.uid; 16 | 17 | Class appOpsClass = null; 18 | /* Context.APP_OPS_MANAGER */ 19 | try { 20 | appOpsClass = Class.forName(AppOpsManager.class.getName()); 21 | Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, 22 | String.class); 23 | Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION); 24 | 25 | int value = (Integer) opPostNotificationValue.get(Integer.class); 26 | return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED); 27 | 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | return false; 32 | } 33 | ``` 34 | 35 | 36 | 跳转到登录页面 37 | 38 | ```java 39 | 40 | private void toSetting() { 41 | Intent localIntent = new Intent(); 42 | localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 43 | if (Build.VERSION.SDK_INT >= 9) { 44 | localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); 45 | localIntent.setData(Uri.fromParts("package", getPackageName(), null)); 46 | } else if (Build.VERSION.SDK_INT <= 8) { 47 | localIntent.setAction(Intent.ACTION_VIEW); 48 | localIntent.setClassName("com.android.settings", "com.android.setting.InstalledAppDetails"); 49 | localIntent.putExtra("com.android.settings.ApplicationPkgName", getPackageName()); 50 | } 51 | startActivity(localIntent); 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /AndroidNote/Android进阶/深入了解MVXX模式.md: -------------------------------------------------------------------------------- 1 | # 深入了解MV**模式 2 | 3 | 4 | 5 | > 前言:做客户端开发、前端开发,大致都应该听说过这么几个名词MVC、MVP、MVVM,这些架构的思想大多是为了解决界面应用程序复杂的逻辑问题。同时这些框架的核心目的在于,职责分离,不同的层次要做不同的事情。 6 | 7 | 无论是哪种MV**系列,都涉及到了Model和View,如果单纯的只有Model和View,他们是没有办法一起协同工作的,所以就有了各种MV..的设计模式 8 | 9 | 10 | 11 | **MVXX模式:** 12 | 13 | - MVC 14 | - MVP 15 | - MVVM 16 | 17 | 这三种架构模式都是现在比较流行的,在不同的项目中,可能采用不同的架构模式,今天我们就围绕着这三种架构模式的试用场景介绍一下他们的概念,以及异同。 18 | 19 | ## MVC 20 | 21 | 22 | ![](https://camo.githubusercontent.com/8de6460e4d41c88ad2cf5432caae6b10f82d196e/687474703a2f2f6c69766f7261732e6769746875622e696f2f626c6f672f6d76632f6d76632d6465702e706e67) 23 | 24 | 25 | MVC(Model-View-Controller),Model:逻辑模型,View:视图模型,Controller控制器。简单说这就是一种设计应用程序的思想,目的在于将业务逻辑、数据、界面分离,将业务逻辑聚集到一个部件里面,在改进或者想要定制界面及用户交互时,不需要重写编写业务逻辑。Controller和View都依赖Model层,Controller和View相互依赖。 26 | 27 | 操作的过程:用户在界面上进行操作(例如手机屏幕),这个时候View会捕捉到用户的操作,然后将这个操作的处理权利交给Controller,Controller会对来自View的数据进行预处理,决定调用哪个Model的接口,然后由Model执行相应的逻辑,当Model变更之后,再利用观察者模式通知View,View通过观察者模式收到Model的消息之后,向Model请求最新的数据,然后更新页面,如下图: 28 | 29 | ![](https://camo.githubusercontent.com/b89ac314c2fd554e7bf33ba1553e10dd91be44fc/687474703a2f2f6c69766f7261732e6769746875622e696f2f626c6f672f6d76632f6d76632d63616c6c2e706e67) 30 | 31 | 32 | 看似没有什么特别的地方,但是由几个需要特别关注的关键点: 33 | 34 | 1. View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。 35 | 2. Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。 36 | 3. View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。 37 | 38 | 需要特别注意的是MVC模式的精髓在于第三点:Model的更新是通过观察者模式告知View的,具体表现形式可以是Pub/Sub或者是触发Events。而网上很多对于MVC的描述都没有强调这一点。通过观察者模式的好处就是:不同的MVC三角关系可能会有共同的Model,一个MVC三角中的Controller操作了Model以后,两个MVC三角的View都会接受到通知,然后更新自己。保持了依赖同一块Model的不同View显示数据的实时性和准确性。我们每天都在用的观察者模式,在几十年前就已经被大神们整合到MVC的架构当中。 39 | 40 | 41 | ### 优点 42 | 1. 首先最重要的一点是多个视图能共享一个模型,同一个模型可以被不同的视图重用,大大提高了代码的可重用性。 43 | 2. 由于MVC的三个模块相互独立,在其中改变一个,其他两个可以保持不变,这样可以把耦合降得很低,这样可以使开发人员可以只关注整个系统中的某一层 44 | 3. 控制器提高了应用程序的灵活性和可配置型。控制器可以用来连接不同的模型和视图去完成用户的需求,这样控制器可以为构造应用程序提供强有力的手段 45 | 46 | ### 缺点 47 | 1. 增加了系统结构的实现的复杂性 48 | 2. 视图与控制器之间的联系过于紧密,视图如果没有控制器的存在,能够做的事情少之又少,这样视图就很难独立应用了 49 | 3. 视图对模型的数据的访问效率过低,因为之间需要多次的调用 50 | 4. View无法组件化,View依赖于特定的Model,如果需要把这个View抽出来用在下一个应用程序复用就比较困难了 51 | 52 | 53 | 54 | ## MVP 55 | 56 | 57 | Model(Model View Presenter),Model:逻辑模型,View:视图模型,Presenter:接口。 58 | 59 | 关于MVP的定义: 60 | 61 | - MVC的演化版本,让Model和View完全解耦 62 | - 代码清晰,不过增加了很多类 63 | 64 | 65 | MVP中的P,是Presenter的含义,和MVC比较类似,都是将用户对View的操作交付给Presenter,Presenter会执行相应的逻辑,在这过程中会操作Model,当Model执行完业务逻辑之后,同样是通过观察者模式把自己的结果传递出去,不过不是告诉View,而是告诉Presenter,Presenter得到消息后,通过View的接口更新页面。 66 | 67 | 68 | 在Android中,我们的View可能仅仅就是个布局文件,它能做的事情少之又少,真正与布局文件进行数据绑定操作的事Activity,所以这样做就使Activity即像View又像Controller。 69 | 70 | 应用了MVP之后: 71 | 72 | - View:对应Activity,负责View的绘制以及与用户交互 73 | - Model:业务逻辑和实体模型 74 | - Presenter:负责完成View与Model之间的交互 75 | 76 | 77 | 78 | 应用两张图来说明上述内容: 79 | 80 | ![](http://img.blog.csdn.net/20150622212835554) 81 | 82 | ![](http://img.blog.csdn.net/20150622212856011) 83 | 84 | 85 | ### MVP的优点 86 | 87 | 1. 便于测试。Presenter对View是通过接口进行,在对Presenter进行不依赖UI环境的单元测试的时候。可以通过Mock一个View对象,这个对象只需要实现了View的接口即可。然后依赖注入到Presenter中,单元测试的时候就可以完整的测试Presenter应用逻辑的正确性。这里根据上面的例子给出了Presenter的单元测试样例。 88 | 2. View可以进行组件化。在MVP当中,View不依赖Model。这样就可以让View从特定的业务场景中脱离出来,可以说View可以做到对业务完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做到高度可复用的View组件。 89 | 90 | ### MVP的缺点 91 | 92 | 1. 代码量会一些,实现的难度也会增加一些 93 | 94 | 95 | ### MVP与MVC区别 96 | 97 | 如下图所示: 98 | 99 | ![](http://img.blog.csdn.net/20150622212916054) 100 | 101 | ## MVVM 102 | 103 | 104 | MVVM是Model-View-ViewModel的简写,MVVM是思想上的一种变革,也可以看成是MVP的一种变革,它是将"数据模型数据双向绑定"的思想作为核心,因此在View和Model之间便不需要我们写联系了,我们在修改数据的时候视图就可以发生变化,我们在修改视图的时候数据也是会发生变化的,可以在一定程度上提高我们的开发效率的。 105 | 106 | ### 调用关系 107 | 108 | MVVM的调用关系和MVP一样。但是,在ViewModel当中会有一个叫Binder,或者是Data-binding engine的东西。以前全部由Presenter负责的View和Model之间数据同步操作交由给Binder处理。你只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模版引擎,但是会根据数据变更实时渲染。 109 | 110 | ![](https://camo.githubusercontent.com/61ef7578cd46b1d37dd3ea52ce0a3be570e427cc/687474703a2f2f6c69766f7261732e6769746875622e696f2f626c6f672f6d76632f6d76766d2d63616c6c2e706e67) 111 | 112 | 113 | 也就是说,MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作,而是交由框架所提供的Binder进行负责。只需要告诉Binder,View显示的数据对应的是Model哪一部分即可。 114 | 115 | Android官方推出的MVVM的DataBinding,便是一个双向绑定的库。 116 | 117 | ### 优点 118 | 119 | 1. 提高可维护性。解决了MVP大量的手动View和Model同步的问题,提供双向绑定机制。提高了代码的可维护性。 120 | 2. 简化测试。因为同步逻辑是交由Binder做的,View跟着Model同时变更,所以只需要保证Model的正确性,View就正确。大大减少了对View同步更新的测试。 121 | 122 | ### 缺点 123 | 124 | 1. 过于简单的图形界面不适用,或说牛刀杀鸡。 125 | 2. 对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高。 126 | 3. 数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的。 127 | 128 | 129 | 130 | 131 | ## 相关资料 132 | 133 | - [浅谈MVP in Android](http://blog.csdn.net/lmj623565791/article/details/46596109) 134 | - [MVC三层框架详细解析](http://blog.csdn.net/u011225629/article/details/47857979) 135 | - [还原真实的MV*模式](https://github.com/livoras/blog/issues/11) 136 | 137 | 138 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Android5.0-6.0-7.0新特性.md: -------------------------------------------------------------------------------- 1 | # Android 5.0 6.0 7.0特性 2 | 3 | ## Android 7.0 4 | 5 | 分屏多任务支持 6 | 7 | 画中画 8 | 9 | 通知栏快速回复 10 | 11 | OpenJDK替换Java API 12 | 13 | Android 7.0中采用了一项具有实时代码剖析功能的ARI JIT编译器,它能够在安卓应用程序在运行时不断提高自身的性能 14 | 15 | [art虚拟机介绍](http://www.cnblogs.com/manuosex/p/3634375.html) 16 | http://www.cnblogs.com/manuosex/p/3634375.html 17 | 18 | 19 | 20 | 21 | 22 | 23 | ---- 24 | 25 | ## Android6.0 26 | 27 | 1、大量漂亮流畅的动画 28 | 安卓6.0系统增加了大量漂亮的过度动画,可以从视觉上减少卡顿感,给用户带来流畅的体验。 29 | 30 | 2、相机新增专业模式 31 | 一直以来,原生的安卓相机都长被吐槽太过简单甚至简陋了,在此次的安卓6.0中,相机中新增了Pro专业模式,增加了快门速度调节和曝光度调节等新功能。 32 | 33 | 3、全新的电源键菜单 34 | 一般来说,安卓的电源键菜单都是关机/重启/飞行,安卓6.0变成了关机/重启/紧急,关机和重启就不用赘述了,这个紧急模式是为了手机快没电的时候设计的,相当于飞行模式的高级版,可以关闭一切耗电应用,尽最大可能节省电量。 35 | 36 | 4、可自定义锁界面样式 37 | 支持电话、信息、相机等快捷方式在锁屏界面的定制,用户可以根据自己的喜好调整这些图标的位置,或者开启或关闭这些快捷方式。 38 | 39 | 5、全新的快速设置风格 40 | 不但是锁屏界面可以定制,安卓6.0还采用了全新的快速面板的色彩方案,用户可以通过更换主题换颜色。 41 | 42 | 6、支持快速充电的切换 43 | 快速充电是手机厂商们的一大新发明,很多厂商都声称“充电X分钟,通话两小时”,这个功能虽然方便,但其实也有弊端,容易造成手机和电池发热。所以除非是在紧急情况下,一般不建议快速充电,安卓6.0原生支持关闭和开启快速充电功能。 44 | 45 | 7、支持文件夹拖拽应用 46 | 可在应用从一个文件夹内直接拖到另一个文件夹,简化了此前繁琐的操作方式,拖拽的过程和Windows的拖拽功能有点相似。 47 | 48 | 8、原生的应用权限管理 49 | 无需第三方应用和Root权限,原生的安卓6.0就支持应用权限管理,用户可以在安装应用时选择关闭一些应用权限,这一功能非常方便,再也不用担心流量偷跑和扣费了。 50 | 51 | 9、Now on Tap功能 52 | “Now on Tap ”功能,是指将Google Now(一种语音助手)作为底层植入到安卓6.0系统中,用户只要只要双击home键启动Google Now,“这意味着用户随时都能启动搜索功能,目前暂时不知道这个功能进入国内会不会阉割掉。 53 | 54 | 10、支持RAW格式照片 55 | RAW格式的支持是众多拍照爱好者梦寐以求的, 然而绝大多数的安卓手机都没有或者剔除了这项功能。由于照片保存为jpg格式时或多或少都会损失一些画质,所以支持RAW格式是非常明智的。 56 | 57 | 58 | ---- 59 | 60 | 61 | ## Android5.0 62 | 63 | - 全新 Material Design 设计风格 64 | 65 | - 支持多种设备(手机、平板电脑、笔记本电脑、智能电视、汽车、智能手表甚至是各种家用电子产品) 66 | 67 | - 全新的通知中心设计(在锁屏界面也可以直接查看通知消息了,用户还可以直接在锁屏的情况下就行回复或进入应用。) 68 | 69 | - 支持 64 位 ART 虚拟机 70 |   71 | >   新系统不仅在视觉效果上带来了巨大的变化,Android Lollipop 还在内部的性能上进行了飞跃。首先,新系统放弃了之前一直使用的 Dalvik 虚拟机,改用了 ART 模式,实现了真正的跨平台编译,在 ARM、X86、MIPS 等,无处不在。 72 |   ART 虚拟机编译器在内存占用及应用程序加载时间上进行了大幅提升,谷歌承诺所有性能都会比原来提升一倍。另外,对 64 位的支持也让 ART 虚拟机如鱼得水,开发者可以针对像 ARM Cortex-A57 这样的 64 位架构核心开发应用程序。Android Lollipop 支持更大的寄存器,支持新的指令集,提升了内存寻址空间,未来 Android 智能手机将支持 4GB 以上的内存。 73 | 74 | - Project Volta 电池续航改进计划 75 | 76 | 增加了 Battery Saver 模式,在低电量的时候系统会自动降低屏幕亮度、限制自动更换背景等功能。 77 | 78 | - 全新的“最近应用程序” 79 | 80 | 除了界面风格设计的改变之外,新的最近应用界面还借鉴了 Chrome 浏览器的理念,采用单独的标签展示方式。更重要的是,谷歌已经向开发者开放了 API,所以第三方开发人员可以利用这个改进为特定的应用增加全新的功能。 81 | 82 | - 新的 API 支持,蓝牙 4.1、USB Audio、多人分享等其它特性 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Android中弱引用与软引用.md: -------------------------------------------------------------------------------- 1 | # Android中弱引用与软引用 2 | 3 | 在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。 下面以使用软引用为例来详细说明。弱引用的使用方式与软引用是类似的。 4 | 5 | 假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常。这时,我们可以考虑使用软引用技术来避免这个问题发生。 6 | 7 | 使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。        8 | 9 |
10 | 需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。 -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Android图片三级缓存.md: -------------------------------------------------------------------------------- 1 | 2 | **Android图片三级缓存** 3 | 4 | 1.简介   5 | 6 |   现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。 7 | 8 |   现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。 9 | 10 | 2.图片缓存的原理   11 | 12 |   实现图片缓存也不难,需要有相应的cache策略。一般采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。 13 | 14 | 关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃 圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。 -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Android推送实现原理.md: -------------------------------------------------------------------------------- 1 | > 心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。 2 | 3 | android系统的推送和iOS的推送有什么区别: 4 | 5 |    首先我们必须知道,所有的推送功能必须有一个客户端和服务器的长连接,因为推送是由服务器主动向客户端发送消息,如果客户端和服务器之间不存在一个长连接那么服务器是无法来主动连接客户端的。因而推送功能都是基于长连接的基础是上的。 6 |    7 |   IOS长连接是由系统来维护的,也就是说苹果的IOS系统在系统级别维护了一个客户端和苹果服务器的长链接,IOS上的所有应用上的推送都是先将消息推送到苹果的服务器然后将苹果服务器通过这个系统级别的长链接推送到手机终端上,这样的的几个好处为: 8 | 9 | 1.在手机终端始终只要维护一个长连接即可,而且由于这个长链接是系统级别的不会出现被杀死而无法推送的情况。   10 | 11 | 2.省电,不会出现每个应用都各自维护一个自己的长连接。  12 | 13 | 3.安全,只有在苹果注册的开发者才能够进行推送,等等。  14 | 15 |  android的长连接是由每个应用各自维护的,但是google也推出了和苹果技术架构相似的推送框架,C2DM,云端推送功能,但是由于google的服务器不在中国境内,其他的原因你懂的。所以导致这个推送无法使用,android的开发者不得不自己去维护一个长链接,于是每个应用如果都24小时在线,那么都得各自维护一个长连接,这种电量和流量的消耗是可想而知的。虽然国内也出现了各种推送平台,但是都无法达到只维护一个长连接这种消耗的级别。  16 |   17 | 推送的实现方式: 18 | 一:客户端不断的查询服务器,检索新内容,也就是所谓的pull 或者轮询方式  19 | 20 | 二:客户端和服务器之间维持一个TCP/IP长连接,服务器向客户端push  21 | 22 | 三:服务器有新内容时,发送一条类似短信的信令给客户端,客户端收到后从服务器中下载新内容,也就是SMS的推送方式  23 | 24 |   苹果的推送系统和googleC2DM推送系统其实都是在系统级别维护一个TCP/IP长连接,都是基于第二种的方式进行推送的。第三种方式由于运营商没有免费开放这种信令导致了这种推送在成本上是无法接受的,虽然这种推送的方式非常的稳定,高效和及时。 25 | 如果想了解android中各种推送方式请参考这个链接:Android实现推送方式解决方案 这篇博客已经介绍的非常好了。 26 | 27 |    所谓的心跳包就是客户端定时放送简单的信息给服务器端,告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务器端,服务器端回复一个固定信息。如果服务器端几分钟后没有收到客户端信息则视客户端断开。比如有些通信软件长时间不适用,要想知道它的状态是在线还是离线,就需要心跳包,定时发包收包。  28 |     29 |  心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活在。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。 30 | 在TCP机制里面,本身是存在有心跳包机制的,也就是TCP选项:SO_KEEPALIVE. 系统默认是设置的2小时的心跳频率。 31 | 32 |   心跳包的机制,其实就是传统的长连接。或许有的人知道消息推送的机制,消息推送也是一种长连接 ,是将数据有服务器端推送到客户端这边从而改变传统的“拉”的请求方式。下面我来介绍一下安卓和客户端两个数据请求的方式 33 | 1、push 这个也就是有服务器推送到客户端这边 现在有第三方技术 比如极光推送。 34 | 2、pull 这种方式就是客户端向服务器发送请求数据(http请求) 35 | 36 | 实现轮询  37 | 38 | ● 原理   39 | 40 |   其原理在于在android端的程序中,让一个SERVICE一直跑在后台,在规定时间之内调用服务器接口进行数据获取。这里的原理很简单,当然实现起来也不难;然后,这个类之中肯定要做网络了数据请求,所以我们在Service中建立一个线程(因为在android系统中网络请求属于长时间操作,不能放主线程,不然会导致异常),在线程中和服务器进行通信。 41 | 42 |   最后,这个逻辑写完后,我们需要考虑一个问题,如何进行在规定时间内调用该服务器,当然可以用Thread+Handler(这个不是那么稳定),也可以使用AlamManager+Thread(比较稳定),因为我们需要其在后台一直运行,所以可以依靠系统的Alammanager这个类来实现,Alammanager是属于系统的一个闹钟提醒类,通过它我们能实现在规定间隔时间调用,并且也比较稳定,这个service被杀后会自己自动启动服务。 43 | 44 | 45 | 46 | > 心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。 47 | 48 | android系统的推送和iOS的推送有什么区别: 49 | 50 |    首先我们必须知道,所有的推送功能必须有一个客户端和服务器的长连接,因为推送是由服务器主动向客户端发送消息,如果客户端和服务器之间不存在一个长连接那么服务器是无法来主动连接客户端的。因而推送功能都是基于长连接的基础是上的。 51 |    52 |   IOS长连接是由系统来维护的,也就是说苹果的IOS系统在系统级别维护了一个客户端和苹果服务器的长链接,IOS上的所有应用上的推送都是先将消息推送到苹果的服务器然后将苹果服务器通过这个系统级别的长链接推送到手机终端上,这样的的几个好处为: 53 | 54 | 1.在手机终端始终只要维护一个长连接即可,而且由于这个长链接是系统级别的不会出现被杀死而无法推送的情况。   55 | 56 | 2.省电,不会出现每个应用都各自维护一个自己的长连接。  57 | 58 | 3.安全,只有在苹果注册的开发者才能够进行推送,等等。  59 | 60 |  android的长连接是由每个应用各自维护的,但是google也推出了和苹果技术架构相似的推送框架,C2DM,云端推送功能,但是由于google的服务器不在中国境内,其他的原因你懂的。所以导致这个推送无法使用,android的开发者不得不自己去维护一个长链接,于是每个应用如果都24小时在线,那么都得各自维护一个长连接,这种电量和流量的消耗是可想而知的。虽然国内也出现了各种推送平台,但是都无法达到只维护一个长连接这种消耗的级别。  61 |   62 | 推送的实现方式: 63 | 一:客户端不断的查询服务器,检索新内容,也就是所谓的pull 或者轮询方式  64 | 65 | 二:客户端和服务器之间维持一个TCP/IP长连接,服务器向客户端push  66 | 67 | 三:服务器有新内容时,发送一条类似短信的信令给客户端,客户端收到后从服务器中下载新内容,也就是SMS的推送方式  68 | 69 |   苹果的推送系统和googleC2DM推送系统其实都是在系统级别维护一个TCP/IP长连接,都是基于第二种的方式进行推送的。第三种方式由于运营商没有免费开放这种信令导致了这种推送在成本上是无法接受的,虽然这种推送的方式非常的稳定,高效和及时。 70 | 如果想了解android中各种推送方式请参考这个链接:Android实现推送方式解决方案 这篇博客已经介绍的非常好了。 71 | 72 |    所谓的心跳包就是客户端定时放送简单的信息给服务器端,告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务器端,服务器端回复一个固定信息。如果服务器端几分钟后没有收到客户端信息则视客户端断开。比如有些通信软件长时间不适用,要想知道它的状态是在线还是离线,就需要心跳包,定时发包收包。  73 |     74 |  心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活在。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。 75 | 在TCP机制里面,本身是存在有心跳包机制的,也就是TCP选项:SO_KEEPALIVE. 系统默认是设置的2小时的心跳频率。 76 | 77 |   心跳包的机制,其实就是传统的长连接。或许有的人知道消息推送的机制,消息推送也是一种长连接 ,是将数据有服务器端推送到客户端这边从而改变传统的“拉”的请求方式。下面我来介绍一下安卓和客户端两个数据请求的方式 78 | 1、push 这个也就是有服务器推送到客户端这边 现在有第三方技术 比如极光推送。 79 | 2、pull 这种方式就是客户端向服务器发送请求数据(http请求) 80 | 81 | 实现轮询  82 | 83 | ● 原理   84 | 85 |   其原理在于在android端的程序中,让一个SERVICE一直跑在后台,在规定时间之内调用服务器接口进行数据获取。这里的原理很简单,当然实现起来也不难;然后,这个类之中肯定要做网络了数据请求,所以我们在Service中建立一个线程(因为在android系统中网络请求属于长时间操作,不能放主线程,不然会导致异常),在线程中和服务器进行通信。 86 | 87 |   最后,这个逻辑写完后,我们需要考虑一个问题,如何进行在规定时间内调用该服务器,当然可以用Thread+Handler(这个不是那么稳定),也可以使用AlamManager+Thread(比较稳定),因为我们需要其在后台一直运行,所以可以依靠系统的Alammanager这个类来实现,Alammanager是属于系统的一个闹钟提醒类,通过它我们能实现在规定间隔时间调用,并且也比较稳定,这个service被杀后会自己自动启动服务。 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Asset目录与res目录的区别.md: -------------------------------------------------------------------------------- 1 | - res/raw和assets的相同点:  2 | 3 |   两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。 4 | 5 | - res/raw和assets的不同点: 6 | 7 |   res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。  8 | 9 |   res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹  10 | 11 | 读取文件资源:  12 | 13 |   读取res/raw下的文件资源,通过以下方式获取输入流来进行写操作. 14 | 15 | ``` 16 | InputStream is =getResources().openRawResource(R.id.filename); 17 | ``` 18 | 19 |   读取assets下的文件资源,通过以下方式获取输入流来进行写操作 20 | 21 | 22 | ``` 23 | /** 24 | * 从assets中读取图片 25 | */ 26 | private Bitmap getImageFromAssetsFile(String fileName) 27 | { 28 | Bitmap image = null; 29 | AssetManager am = getResources().getAssets(); 30 | try 31 | { 32 | InputStream is = am.open(fileName); 33 | image = BitmapFactory.decodeStream(is); 34 | is.close(); 35 | } 36 | catch (IOException e) 37 | { 38 | e.printStackTrace(); 39 | } 40 | return image; 41 | } 42 | ``` 43 | 44 | 注意1:Google的Android系统处理Assert有个bug,在AssertManager中不能处理单个超过1MB的文件,不然会报异常,raw没这个限制可以放个4MB的Mp3文件没问题。  45 | 46 | 注意2:assets 文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像 xml, java 文件被预编译,可以存放一些图片,html,js, css 等文件。 47 | 48 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/JSON的定义.md: -------------------------------------------------------------------------------- 1 | **JSON的定义** 2 | 3 |   一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。业内主流技术为其提供了完整的解决方案(有点类似于正则表达式 ,获得了当今大部分语言的支持),从而可以在不同平台间进行数据交换。JSON采用兼容性很高的文本格式,同时也具备类似于C语言体系的行为。 4 | 5 | **XML的定义**  6 | 7 |    扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 (SGML) 的子集,非常适合 Web 传输。XML 提供统一的方法来描述和交换独立于应用程序或供应商的结构化数据。 8 | 9 | **JSON 和 XML 优缺点的比较**  10 | 11 | 1.在可读性方面,JSON和XML的数据可读性基本相同。JSON和XML的可读性可谓不相上下,一边是建议的语法,一边是规范的标签形式,很难分出胜负。  12 | 13 | 2.在可扩展性方面,XML天生有很好的扩展性,JSON当然也有,没有什么是XML能扩展,JSON不能的。  14 | 15 | 3.在编码难度方面,XML有丰富的编码工具,比如Dom4j、JDom等,JSON也有json.org提供的工具,但是JSON的编码明显比XML容易许多,即使不借助工具也能写出JSON的代码,可是要写好XML就不太容易了。  16 | 17 | 4.在解码难度方面,XML的解析得考虑子节点父节点,让人头昏眼花,而JSON的解析难度几乎为0。这一点XML输的真是没话说。  18 | 19 | 5.在流行度方面,XML已经被业界广泛的使用,而JSON才刚刚开始,但是在Ajax这个特定的领域,未来的发展一定是XML让位于JSON。到时Ajax应该变成Ajaj(Asynchronous JavaScript and JSON)了。  20 | 21 | 6.JSON和XML同样拥有丰富的解析手段。  22 | 23 | 7.JSON相对于XML来讲,数据的体积小。 24 | 25 | 8.JSON与JavaScript的交互更加方便。 26 | 27 | 9.JSON对数据的描述性比XML较差。 28 | 29 | 10.JSON的速度要远远快于XML。 30 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Java中Error和Exception.md: -------------------------------------------------------------------------------- 1 | # Java中Error和Exception 2 | 3 | Java 异常类继承关系图: 4 | 5 | ![](http://img.blog.csdn.net/20160412143252629) 6 | 7 | 8 | (一)Throwable 9 | 10 |   Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出,才可以是 catch 子句中的参数类型。 11 |   Throwable 类及其子类有两个构造方法,一个不带参数,另一个带有 String 参数,此参数可用于生成详细消息。 12 |   Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。 13 |    14 | Java将可抛出(Throwable)的结构分为三种类型: 15 |   1. 错误(Error) 16 |   2. 运行时异常(RuntimeException) 17 |   3. 被检查的异常(Checked Exception) 18 | 19 |  1.**Error** 20 |   Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。 21 |   和RuntimeException一样, 编译器也不会检查Error。 22 |   当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误,程序本身无法修复这些错误的。 23 |    24 |  2.**Exception** 25 |   Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。 26 |    对于可以恢复的条件使用**被检查异常**(Exception的子类中除了RuntimeException之外的其它子类),对于程序错误使用运行时异常。  27 |    28 | 2.1 ClassNotFoundException 29 |    30 | 当应用程序试图使用以下方法通过字符串名加载类时: 31 | Class 类中的 forName 方法。 32 | ClassLoader 类中的 findSystemClass 方法。 33 | ClassLoader 类中的 loadClass 方法。 34 | 但是没有找到具有指定名称的类的定义,抛出该异常。 35 | 36 | 2.2 CloneNotSupportedException 37 | 38 | 当调用 Object 类中的 clone 方法复制对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。重写 clone 方法的应用程序也可能抛出此异常,指示不能或不应复制一个对象。 39 |   40 | 41 | 2.3 IOException 42 | 当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。 43 | 44 | - EOFException 45 | 46 | 当输入过程中意外到达文件或流的末尾时,抛出此异常。 47 | 此异常主要被**数据输入流**用来表明到达流的末尾。 48 | 注意:其他许多输入操作返回一个特殊值表示到达流的末尾,而不是抛出异常。 49 |      50 | - FileNotFoundException 51 | 52 | 当试图打开指定路径名表示的文件失败时,抛出此异常。在不存在具有指定路径名的文件时,此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在,但是由于某些原因不可访问,比如试图打开一个只读文件进行写入,则此时这些构造方法仍然会抛出该异常。 53 | 54 | - MalformedURLException 55 | 56 | 抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议,或者无法解析字符串。  57 |   58 | -UnknownHostException 59 |   指示主机 IP 地址无法确定而抛出的异常。 60 | 61 | 2.4 RuntimeException 62 |    是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。 63 |    Java编译器不会检查它。当程序中可能出现这类异常时,还是会编译通过。 64 |    虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。 65 | 66 | - ArithmeticException 67 | 68 | 当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。 69 | 70 | - ClassCastException 71 | 72 | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 73 | 例如:Object x = new Integer(0); 74 | 75 | - LllegalArgumentException 76 | 77 | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 78 | 79 | - IllegalStateException 80 | 81 | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 82 | 83 | - IndexOutOfBoundsException  84 | 85 | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 86 | 应用程序可以为这个类创建子类,以指示类似的异常。 87 | 88 | - NoSuchElementException 89 | 90 | 由 Enumeration 的 nextElement 方法抛出,表明枚举中没有更多的元素。 91 | 92 | - NullPointerException 93 | 94 | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括: 95 | 调用 null 对象的实例方法。 96 | 访问或修改 null 对象的字段。 97 | 将 null 作为一个数组,获得其长度。 98 | 将 null 作为一个数组,访问或修改其时间片。 99 | 将 null 作为 Throwable 值抛出。 100 | 应用程序应该抛出该类的实例,指示其他对 null 对象的非法使用。 101 | 102 | 103 | 104 | (二) SOF (堆栈溢出 StackOverflow) 105 | 106 | > StackOverflowError 的定义: 107 | > 当应用程序递归太深而发生堆栈溢出时,抛出该错误。 108 | > 109 | 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。 110 | 111 | 栈溢出的原因: 112 | 113 | 1. 递归调用 114 | 115 | 2. 大量循环或死循环 116 | 117 | 3. 全局变量是否过多 118 | 119 | 4. 数组、List、map数据过大 120 | 121 | 122 | 123 | (三)Android的OOM(Out Of Memory) 124 | 125 |   当内存占有量超过了虚拟机的分配的最大值时就会产生内存溢出(VM里面分配不出更多的page)。 126 |    127 | 一般出现情况:加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放。 128 | 129 | 解决方法: 130 | 131 | ①在内存引用上做处理 132 | 133 | 软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。 134 | 135 | ②对图片做边界压缩,配合软引用使用 136 |   137 | ③显示的调用GC来回收内存  138 |   139 | 140 | ``` 141 | if(bitmapObject.isRecycled()==false) //如果没有回收 142 | bitmapObject.recycle(); 143 | ``` 144 |   145 | ④优化Dalvik虚拟机的堆内存分配 146 |    147 | 1.增强程序堆内存的处理效率 148 |    149 | 150 | ``` 151 | //在程序onCreate时就可以调用 即可 152 | private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 153 | 154 | VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 155 | ``` 156 | 157 | 2.设置缓存大小 158 | 159 | ``` 160 | private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; 161 | //设置最小heap内存为6MB大小 162 | VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); 163 | ``` 164 | 165 | ⑤ 用LruCache 和 AsyncTask<>解决 166 | 167 |   从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。 168 |   如果缓存中不存在,那么启动一个task去加载(可能从文件来,也可能从网络)。 169 | 170 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/ListView性能优化.md: -------------------------------------------------------------------------------- 1 | 2 | **ListView性能优化** 3 | 4 |   重用了convertView,很大程度上的减少了内存的消耗。通过判断convertView是否为null,是的话就需要产生一个视图出来,然后给这个视图数据,最后将这个视图返回给底层,呈献给用户。 5 | 6 |    每次在getVIew的时候,都需要重新的findViewById,重新找到控件,然后进行控件的赋值以及事件相应设置。这样其实在做重复的事情,因为的geiview中,其实包含有这些控件,而且这些控件的id还都是一样的,也就是其实只要在view中findViewById一次,后面无需要每次都要findViewById了。 7 | 8 |   通过线程来异步加载图片,把Http的相关操作放在线程里,最好使用线程池来控制线程数。返回的bitmap通过Handler来更新每个Item布局上的ImageView(就是赋上图片)。 9 | 10 |   当遇到大图片的话,可以对图片处理一下再使用,比如压缩,压缩到480*800。网上有很多关于图片压缩的资料。 11 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/Service保活.md: -------------------------------------------------------------------------------- 1 | # Service保活 2 | 3 | 一、onStartCommand方法,返回START_STICKY 4 | 5 |   在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。 6 | 7 |    【结论】 手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了....  8 | 9 | 二、提升service优先级 10 | 11 |   在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。 12 | 13 | 三、提升service进程优先级 14 | 15 |   Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是: 16 | 1. 前台进程(FOREGROUND_APP) 17 | 18 | 2. 可视进程(VISIBLE_APP ) 19 | 20 | 3. 次要服务进程(SECONDARY_SERVER ) 21 | 22 | 4. 后台进程 (HIDDEN_APP) 23 | 24 | 5. 内容供应节点(CONTENT_PROVIDER) 25 | 26 | 6. 空进程(EMPTY_APP) 27 | 28 | 当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground 将service放到前台状态。这样在低内存时被kill的几率会低一些。 29 | 30 | 在onStartCommand方法内添加如下代码: 31 | 32 | 33 | 34 | ``` 35 | Notification notification = new Notification(R.drawable.ic_launcher, 36 | getString(R.string.app_name), System.currentTimeMillis()); 37 | 38 | PendingIntent pendingintent = PendingIntent.getActivity(this, 0, 39 | new Intent(this, AppMain.class), 0); 40 | notification.setLatestEventInfo(this, "uploadservice", "请保持程序在后台运行", 41 | pendingintent); 42 | startForeground(0x111, notification); 43 | ``` 44 | 45 | 注意在onDestroy里还需要stopForeground(true),运行时在下拉列表会看到自己的APP在: 46 | 【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart 47 | 48 | 四、onDestroy方法里重启service 49 | 50 |   直接在onDestroy()里startService或service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service; 51 | 52 | 【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证~.~ 53 | 54 | 五、监听系统广播判断Service状态 55 | 56 |   通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活,别忘记加权限啊。 57 | 58 | 六、将APK安装到/system/app,变身系统级应用 59 | 60 | 61 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/如何提高Activity启动速度.md: -------------------------------------------------------------------------------- 1 |    2 | # 如何提高Activity启动速度 3 | 4 | 减少onCreate的时间,那就精简onCreate里的代码。放在onResume里好了。为了用户体验更好一些,把页面显示的View细分一下,放在AsyncTask里逐步显示,如果你够熟练,用handler更好,这样用户的看到的就是有层次有步骤的一个个的view的展示,不会是先看到一个黑屏,然后一下显示所有view。最好作成动画,效果更自然些。利用多线程的目的就是尽可能的减少onCreate和onReume的时间,使得用户能尽快看到页面,操作页面。 5 |    6 | 7 | 但是,很多操作是只需要一次初始化的,都放在onResume里每次进入activity都会浪费初始化时间。这个好解决,做一个boolean变量,在onCreate里标记为true。在onResume里判断为true就进行初始化,初始化完成立刻置为false。搞定。 8 | 9 | 10 | 1.减小主线程的阻塞时间  11 | 12 |  若一个操作耗时教长(超过5秒 用户无响应5秒 网络和数据库阻塞10秒 广播接收者执行超过10秒会导致ANR),我们应该将其放入后台线程中执行,只在需要修改UI界面时通知主线程进行修改。 13 | Android已经提供了AsynTask以实现从主线程生成新的异步任务的方法。具体用法参见异步任务。  14 | 15 | 2.提高Adapter和AdapterView的效率 16 | (1)重用已生成过的Item View 17 | (2) 添加ViewHolder 18 | (3) 缓存Item的数据 19 | (4)分段显示  20 | 21 | 3.优化布局文件 22 | 如果我们的布局层次过多,那么在我们用findViewById的时间必然会变多,一个变多可能不要紧,但是有很多调用必然会影响性能。 23 | 24 | (1) 使用观察布局的工具 Hierarchy Viewer 25 | 26 | (2)使用布局优化工具: Layoutopt 27 | 28 | (3)优化布局的层次结构 29 | 30 | -------------------------------------------------------------------------------- /AndroidNote/Android面试相关/如何终止App的运行.md: -------------------------------------------------------------------------------- 1 | # 如何终止App的运行 2 | 3 | - 在application中定义一个单例模式的Activity栈来管理所有Activity。并提供退出所有Activity的方法。 4 | 5 | 6 | - AndroidManifest.xml 添加权限 7 | 8 | ```` 9 | 10 | 退出应用的方法: 11 | 12 | 13 | ``` 14 | ActivityManager am= (ActivityManager) this 15 | .getSystemService(Context.ACTIVITY_SERVICE); 16 | am.killBackgroundProcesses(this.getPackageName()); 17 | ``` 18 | 19 | - Dalvik VM的本地方法 20 | android.os.Process.killProcess(android.os.Process.myPid()) //获取PID 21 | System.exit(0); //常规java、c#的标准退出法,返回值为0代表正常退出 22 | 23 | -Android的窗口类提供了历史栈 24 | 25 | 我们可以通过stack的原理来巧妙的实现,这里我们在A窗口打开B窗口时在Intent中直接加入标 志 Intent.FLAG_ACTIVITY_CLEAR_TOP,这样开启B时将会清除该进程空间的所有Activity。 26 | 在A窗口中使用下面的代码调用B窗口 27 | 28 | ``` 29 | Intent intent = new Intent(); 30 | intent.setClass(this, B.class); 31 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //注意本行的FLAG设置 32 | startActivity(intent); 33 | ``` 34 | 35 | 接下来在B窗口中需要退出时直接使用finish方法即可全部退出。 -------------------------------------------------------------------------------- /AndroidNote/Kotlin相关/Kotlin-for-android.md: -------------------------------------------------------------------------------- 1 | # Kotlin for android 2 | 3 | > 作者:https://github.com/linsir6 4 | > 5 | > 原文:http://www.jianshu.com/p/e713ba6f7c47 6 | 7 | 8 | 9 | > kotlin最近真的是大热啊,总让人有一种不明觉厉的感觉,但是其实网上的学习资料少之又少,下面推荐几个学习的平台,顺便展示一个实现登录注册的demo 10 | 11 | 12 | 13 | - [Kotlin 示例教程](https://github.com/linsir6/Kotlin) 14 | 15 | 16 | - [kotlin中文官网](https://www.kotlincn.net/) 17 | - [kotlin官网](https://kotlinlang.org/) 18 | - [kotlin官网翻译](https://github.com/huanglizhuo/kotlin-in-chinese) 19 | - [kotlin书籍](https://github.com/wangjiegulu/kotlin-for-android-developers-zh) 20 | - [kotlin demo](https://github.com/linsir6/PoiShuhui-Kotlin) 21 | 22 | 23 | 24 | 下面就我们就开始一个入门级别的demo吧,现在谷歌已经推出了android studio3.0已经支持了Kotlin这门语言,下载地址:https://developer.android.google.cn/studio/preview/index.html ,只需要在这里新建一个工程,然后在是否要加入kotlin的选项上面勾一下就可以了。 25 | 26 | 27 | 28 | 29 | 30 | 下面看一下登录注册的代码: 31 | 32 | ```kotlin 33 | class MainActivity : AppCompatActivity() { 34 | 35 | var userName: EditText? = null 36 | var userPwd: EditText? = null 37 | var register: Button? = null 38 | var login: Button? = null 39 | 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | super.onCreate(savedInstanceState) 42 | setContentView(R.layout.activity_main) 43 | 44 | userName = findViewById(R.id.user_name) as EditText 45 | userPwd = findViewById(R.id.user_pwd) as EditText 46 | 47 | register = findViewById(R.id.register) as Button 48 | login = findViewById(R.id.login) as Button 49 | 50 | login!!.setOnClickListener { 51 | if (userName!!.text.toString() == "123456" && userPwd!!.text.toString() == "abc") { 52 | Toast.makeText(this, "login succeed1", Toast.LENGTH_SHORT).show() 53 | val intent = Intent(this,HomeActivity::class.java) 54 | startActivity(intent) 55 | } 56 | } 57 | 58 | register!!.setOnClickListener { 59 | Toast.makeText(this, "the function has not open ...", Toast.LENGTH_SHORT).show() 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | 67 | ``` 68 | 69 | 70 | 71 | 72 | 73 | 当然实现的代码就非常简单啦,只是可能我们在刚开始接触这门语言的时候有一些的不理解。大家可以看一下上面的代码,要是有什么不理解的地方欢迎issue。 74 | 75 | 76 | 77 | 源码地址:https://github.com/linsir6/Kotlin 78 | 79 | 欢迎star,issue,fork 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /AndroidNote/img/android-note.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsir6/AndroidNote/50e0b973abfc3d4454dd7f84f6cafdf0748be713/AndroidNote/img/android-note.jpg -------------------------------------------------------------------------------- /AndroidNote/img/android-note2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsir6/AndroidNote/50e0b973abfc3d4454dd7f84f6cafdf0748be713/AndroidNote/img/android-note2.jpg -------------------------------------------------------------------------------- /AndroidNote/img/androidnote.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsir6/AndroidNote/50e0b973abfc3d4454dd7f84f6cafdf0748be713/AndroidNote/img/androidnote.jpg -------------------------------------------------------------------------------- /AndroidNote/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsir6/AndroidNote/50e0b973abfc3d4454dd7f84f6cafdf0748be713/AndroidNote/img/background.jpg -------------------------------------------------------------------------------- /AndroidNote/img/background2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsir6/AndroidNote/50e0b973abfc3d4454dd7f84f6cafdf0748be713/AndroidNote/img/background2.jpg -------------------------------------------------------------------------------- /AndroidNote/img/linsir.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linsir6/AndroidNote/50e0b973abfc3d4454dd7f84f6cafdf0748be713/AndroidNote/img/linsir.jpg -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC-Android源码解析.md: -------------------------------------------------------------------------------- 1 | > 博主本人目前在一家做流媒体的公司里面实习,虽然我本人就是一个苦逼的搞Android应用层开发的boy,但是本着干一行爱一行的心态,准备好好钻研一下WebRTC的源码。 2 | 3 | 目前博主打算先对WebRTC-Android-sdk进行解析,目前不会涉及到底层C++的知识。 4 | 5 | 由于博主每天也有很多工作要做,基本上也得10-8-6吧,所以可能不会有特别大量的时间用在源码的阅读与解析上面,但是只要一有时间我就会总结一下这些源码的,争取能够做到日更,或者两天更新一次吧。 6 | 7 | ## WebRTC入门 8 | - [WebRTC入门](http://www.jianshu.com/p/1f173ef8664f) 9 | 10 | ## WebRTC-Android-sdk源码解析 11 | - [PeerConnectionFactory](http://www.jianshu.com/p/57f690413fe8) 12 | 13 | - [PeerConnection](http://www.jianshu.com/p/acb230c020eb) 14 | 15 | - [MediaSource](http://www.jianshu.com/p/dbbeb030cf16) 16 | 17 | - [AudioSource、VideoSource](http://www.jianshu.com/p/bca301fff046) 18 | 19 | - [MediaStreamTrack](http://www.jianshu.com/p/9ad24ca4354f) 20 | 21 | - [AudioTrack、VideoTrack](http://www.jianshu.com/p/f33d403b374f) 22 | 23 | - [AudioRenderer](http://www.jianshu.com/p/dad0d14a3392) 24 | 25 | - [VideoRenderer](http://www.jianshu.com/p/428dec590721) 26 | 27 | - [VideoFileRenderer](http://www.jianshu.com/p/e3d7d71522ee) 28 | 29 | - [IceCandidate、SdpObserver、CameraSession](http://www.jianshu.com/p/2ad790626e02) 30 | 31 | - VideoRendererGui 32 | 33 | - VideoCapturer 34 | 35 | - VideoCapturerAndroid 36 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——AudioRenderer解析.md: -------------------------------------------------------------------------------- 1 | > AudioRenderer是Audio的渲染类,负责音频的渲染 2 | 3 | ``` 4 | public static class AudioFrame { 5 | public byte[] audio_data; //音频数据 6 | public int bits_per_sample; //bit音频的示例 7 | public int sample_rate; //示例比特率 8 | public int number_of_channels; //声道的数量 9 | public int number_of_frames; //帧的数量 10 | 11 | //构造方法 12 | public AudioFrame(byte[] audio_data, int bits_per_sample, int sample_rate, int number_of_channels, int number_of_frames) { 13 | this.audio_data = audio_data; 14 | this.sample_rate = sample_rate; 15 | this.bits_per_sample = bits_per_sample; 16 | this.number_of_channels = number_of_channels; 17 | this.number_of_frames = number_of_frames; 18 | } 19 | } 20 | ``` 21 | 22 | ``` 23 | //回调接口,当有音频帧的时候产生回调用的 24 | public static interface Callbacks { 25 | public void onAudioFrame(AudioFrame frame); 26 | } 27 | ``` 28 | 29 | ``` 30 | long nativeAudioRenderer; 31 | //创建AudioRenderer 32 | public AudioRenderer(Callbacks callbacks) { 33 | nativeAudioRenderer = nativeWrapAudioRenderer(callbacks); 34 | } 35 | private static native long nativeWrapAudioRenderer(Callbacks callbacks); 36 | 37 | ``` 38 | 39 | ``` 40 | /销毁掉audioRenderer 41 | public void dispose() { 42 | if (nativeAudioRenderer == 0) { 43 | return; 44 | } 45 | // todo free native audio renderer 46 | nativeAudioRenderer = 0; 47 | } 48 | ``` 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——AudioSource、VideoSource解析.md: -------------------------------------------------------------------------------- 1 | > AudioSource和VideoSource都是MediaSource的子类,它们同样是包裹在C++外的一层,它们的功能是承载一个或多个AudioTrack/VideoTrack。 2 | 3 | ``` 4 | //AudioSource完全继承了父类,并没有任何的重写 5 | public class AudioSource extends MediaSource { 6 | public AudioSource(long nativeSource) { 7 | super(nativeSource); 8 | } 9 | } 10 | ``` 11 | 12 | ``` 13 | //同样是继承了父类的所有的方法,但是新增了一个方法是,适应外界输出的分辨率 14 | public VideoSource(long nativeSource) { 15 | super(nativeSource); 16 | } 17 | 18 | /** 19 | * Calling this function will cause frames to be scaled down to the requested resolution. Also, 20 | * frames will be cropped to match the requested aspect ratio, and frames will be dropped to match 21 | * the requested fps. The requested aspect ratio is orientation agnostic and will be adjusted to 22 | * maintain the input orientation, so it doesn't matter if e.g. 1280x720 or 720x1280 is requested. 23 | */ 24 | public void adaptOutputFormat(int width, int height, int fps) { 25 | nativeAdaptOutputFormat(nativeSource, width, height, fps); 26 | } 27 | 28 | private static native void nativeAdaptOutputFormat( 29 | long nativeSource, int width, int height, int fps); 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——AudioTrack-VideoTrack解析.md: -------------------------------------------------------------------------------- 1 | > AudioTrack是MediaStreamTrack的一个子类,负责音频的调节。 2 | VideoTrack和Audio几乎完全一样,只是多了一个free的方法,然后添加的Renderer的类型不一样。 3 | 4 | ``` 5 | //一个list里面存的是音频的渲染器 6 | private final LinkedList renderers = new LinkedList(); 7 | ``` 8 | 9 | ``` 10 | //构造方法 11 | public AudioTrack(long nativeTrack) { 12 | super(nativeTrack); 13 | } 14 | ``` 15 | 16 | ``` 17 | //添加音频渲染器,与去除音频渲染器 18 | public void addRenderer(AudioRenderer renderer){ 19 | renderers.add(renderer); 20 | nativeAddRenderer(nativeTrack, renderer.nativeAudioRenderer); 21 | } 22 | 23 | public void removeRenderer(AudioRenderer renderer){ 24 | if(!renderers.remove(renderer)){ 25 | return; 26 | } 27 | nativeRemoveRenderer(nativeTrack,renderer.nativeAudioRenderer); 28 | renderer.dispose(); 29 | } 30 | 31 | private static native void nativeAddRenderer(long nativeTrack, long nativeRenderer); 32 | private static native void nativeRemoveRenderer(long nativeTrack, long nativeRenderer); 33 | 34 | ``` 35 | 36 | ``` 37 | //释放掉AudioTrack 38 | public void dispose(){ 39 | while (!renderers.isEmpty()) { 40 | removeRenderer(renderers.getFirst()); 41 | } 42 | super.dispose(); 43 | } 44 | ``` 45 | 46 | ``` 47 | //方法含义同AudioTrack 48 | private final LinkedList renderers = new LinkedList(); 49 | 50 | public VideoTrack(long nativeTrack) { 51 | super(nativeTrack); 52 | } 53 | 54 | public void addRenderer(VideoRenderer renderer) { 55 | renderers.add(renderer); 56 | nativeAddRenderer(nativeTrack, renderer.nativeVideoRenderer); 57 | } 58 | 59 | public void removeRenderer(VideoRenderer renderer) { 60 | if (!renderers.remove(renderer)) { 61 | return; 62 | } 63 | nativeRemoveRenderer(nativeTrack, renderer.nativeVideoRenderer); 64 | renderer.dispose(); 65 | } 66 | 67 | public void dispose() { 68 | while (!renderers.isEmpty()) { 69 | removeRenderer(renderers.getFirst()); 70 | } 71 | super.dispose(); 72 | } 73 | 74 | private static native void free(long nativeTrack); 75 | 76 | private static native void nativeAddRenderer(long nativeTrack, long nativeRenderer); 77 | 78 | private static native void nativeRemoveRenderer(long nativeTrack, long nativeRenderer); 79 | ``` 80 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——IceCandidate、SdpObserver、CameraSession解析.md: -------------------------------------------------------------------------------- 1 | > IceCandidate是一个模板类,里面主要包含着会话描述协议。 2 | 3 | ``` 4 | public class IceCandidate { 5 | public final String sdpMid;//描述协议的id 6 | public final int sdpMLineIndex;//描述协议的行索引 7 | public final String sdp;//会话描述协议 8 | 9 | public IceCandidate(String sdpMid, int sdpMLineIndex, String sdp) { 10 | this.sdpMid = sdpMid; 11 | this.sdpMLineIndex = sdpMLineIndex; 12 | this.sdp = sdp; 13 | } 14 | 15 | public String toString() { 16 | return sdpMid + ":" + sdpMLineIndex + ":" + sdp; 17 | } 18 | } 19 | ``` 20 | 21 | ---- 22 | 23 | > SdpObserver是来回调sdp是否创建(offer,answer)成功,是否设置描述成功(local,remote)的一个接口。 24 | 25 | ``` 26 | /** Called on success of Create{Offer,Answer}(). */ 27 | public void onCreateSuccess(SessionDescription sdp); 28 | 29 | /** Called on success of Set{Local,Remote}Description(). */ 30 | public void onSetSuccess(); 31 | 32 | /** Called on error of Create{Offer,Answer}(). */ 33 | public void onCreateFailure(String error); 34 | 35 | /** Called on error of Set{Local,Remote}Description(). */ 36 | public void onSetFailure(String error); 37 | ``` 38 | ---- 39 | 40 | > CameraSession是用来回调相机信息的一个接口 41 | 42 | ``` 43 | public interface CreateSessionCallback {//创建相机描述的回调 44 | void onDone(CameraSession session);//成功 45 | void onFailure(String error);//不成功 46 | } 47 | ``` 48 | 49 | ``` 50 | public interface Events { 51 | void onCameraOpening();//当相机打开 52 | void onCameraError(CameraSession session, String error);//相机发生故障 53 | void onCameraDisconnected(CameraSession session);//断开连接 54 | void onCameraClosed(CameraSession session);//关闭 55 | void onByteBufferFrameCaptured( 56 | CameraSession session, byte[] data, int width, int height, int rotation, long timestamp); 57 | void onTextureFrameCaptured(CameraSession session, int width, int height, int oesTextureId, 58 | float[] transformMatrix, int rotation, long timestamp); 59 | } 60 | ``` 61 | 62 | ``` 63 | void stop();//回调到相机停止工作 64 | ``` 65 | ---- 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——MediaSource-java解析.md: -------------------------------------------------------------------------------- 1 | > MediaSource是AudioSource和VideoSource的基类,它里面定义了一些方法,供子类继承。 2 | 它是一层包裹在C++外面的一层,C++里面也是有MediaSource的。 3 | 4 | ``` 5 | //一个媒体资源类具有以下四个状态,初始化中,工作中,结束,消音/消去视频 6 | public enum State { INITIALIZING, LIVE, ENDED, MUTED } 7 | ``` 8 | 9 | ``` 10 | 创建时需要传进来一个nativeSource 11 | final long nativeSource; // Package-protected for PeerConnectionFactory. 12 | 13 | public MediaSource(long nativeSource) { 14 | this.nativeSource = nativeSource; 15 | } 16 | ``` 17 | 18 | ``` 19 | //获取当前的状态,通过调用native层方法获取到 20 | public State state() { 21 | return nativeState(nativeSource); 22 | } 23 | ``` 24 | 25 | ``` 26 | //销毁当前的媒体资源 27 | public void dispose() { 28 | free(nativeSource); 29 | } 30 | ``` 31 | 32 | ``` 33 | //两个native层的方法,用来获取状态和释放资源的 34 | private static native State nativeState(long pointer); 35 | 36 | private static native void free(long nativeSource); 37 | ``` 38 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——MeidaStreamTrack解析.md: -------------------------------------------------------------------------------- 1 | > MeidaStreamTrack是媒体流的一部分 2 | 3 | ``` 4 | //它的两种状态分别是工作状态和结束状态 5 | public enum State { LIVE, ENDED } 6 | ``` 7 | 8 | ``` 9 | // 构造方法,在创建MediaStream的时候,需要传入一个nativeTrack 10 | final long nativeTrack; 11 | 12 | public MediaStreamTrack(long nativeTrack) { 13 | this.nativeTrack = nativeTrack; 14 | } 15 | ``` 16 | 17 | ``` 18 | //这里面的方法和native层的方法是一一对应的 19 | //获取Id 20 | public String id() { 21 | return nativeId(nativeTrack); 22 | } 23 | 24 | //获取类别 25 | public String kind() { 26 | return nativeKind(nativeTrack); 27 | } 28 | 29 | //获取是否被mute 30 | public boolean enabled() { 31 | return nativeEnabled(nativeTrack); 32 | } 33 | 34 | //mute或者取消 35 | public boolean setEnabled(boolean enable) { 36 | return nativeSetEnabled(nativeTrack, enable); 37 | } 38 | 39 | //获取当前的状态 40 | public State state() { 41 | return nativeState(nativeTrack); 42 | } 43 | 44 | //释放掉 45 | public void dispose() { 46 | free(nativeTrack); 47 | } 48 | 49 | private static native String nativeId(long nativeTrack); 50 | 51 | private static native String nativeKind(long nativeTrack); 52 | 53 | private static native boolean nativeEnabled(long nativeTrack); 54 | 55 | private static native boolean nativeSetEnabled(long nativeTrack, boolean enabled); 56 | 57 | private static native State nativeState(long nativeTrack); 58 | 59 | private static native void free(long nativeTrack); 60 | 61 | ``` 62 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——VideoFileRenderer解析.md: -------------------------------------------------------------------------------- 1 | > VideoFileRenderer实现了VideoRenderer的Callbacks,可以回调到产生的Frame。 2 | 它的主要功能是将产生的视频流以文件的形式存储在本地。 3 | 4 | ``` 5 | //定义一个新的工作的线程 6 | private final HandlerThread renderThread; 7 | 8 | //定义一个线程锁 9 | private final Object handlerLock = new Object(); 10 | 11 | //定义一个Handler 12 | private final Handler renderThreadHandler; 13 | 14 | //文件输出的流 15 | private final FileOutputStream videoOutFile; 16 | 17 | //流的属性 18 | private final int outputFileWidth; 19 | private final int outputFileHeight; 20 | 21 | //帧的数量 22 | private final int outputFrameSize; 23 | 24 | //一个输出的Buffer 25 | private final ByteBuffer outputFrameBuffer; 26 | 27 | //全局的上下文 28 | private EglBase eglBase; 29 | 30 | //信号的转化 31 | private YuvConverter yuvConverter; 32 | ``` 33 | 34 | ``` 35 | //构造方法,四个参数分别是目标文件,文件的宽,文件的高度,上下文 36 | public VideoFileRenderer(String outputFile, int outputFileWidth, int outputFileHeight, 37 | final EglBase.Context sharedContext) throws IOException { 38 | //向外写文件宽高都应该是偶数 39 | if ((outputFileWidth % 2) == 1 || (outputFileHeight % 2) == 1) { 40 | throw new IllegalArgumentException("Does not support uneven width or height"); 41 | } 42 | 43 | this.outputFileWidth = outputFileWidth; 44 | this.outputFileHeight = outputFileHeight; 45 | 46 | //计算frameSize 47 | outputFrameSize = outputFileWidth * outputFileHeight * 3 / 2; 48 | outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize); 49 | 50 | //写文件 51 | videoOutFile = new FileOutputStream(outputFile); 52 | videoOutFile.write( 53 | ("YUV4MPEG2 C420 W" + outputFileWidth + " H" + outputFileHeight + " Ip F30:1 A1:1\n") 54 | .getBytes()); 55 | 56 | //开启一个新线程 57 | renderThread = new HandlerThread(TAG); 58 | renderThread.start(); 59 | renderThreadHandler = new Handler(renderThread.getLooper()); 60 | 61 | ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() { 62 | @Override 63 | public void run() { 64 | //在新的线程中初始化全局,并且进行编码转化 65 | eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PIXEL_BUFFER); 66 | eglBase.createDummyPbufferSurface(); 67 | eglBase.makeCurrent(); 68 | yuvConverter = new YuvConverter(); 69 | } 70 | }); 71 | } 72 | 73 | ``` 74 | 75 | ``` 76 | @Override 77 | public void renderFrame(final VideoRenderer.I420Frame frame) { 78 | renderThreadHandler.post(new Runnable() { 79 | @Override 80 | public void run() { 81 | //当有流产生的时候要进行回调4 82 | renderFrameOnRenderThread(frame); 83 | } 84 | }); 85 | } 86 | 87 | private void renderFrameOnRenderThread(VideoRenderer.I420Frame frame) { 88 | //呈现画面在新的线程中 89 | 90 | //画面呈现的比例 91 | final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight(); 92 | 93 | //旋转抽样矩阵 94 | final float[] rotatedSamplingMatrix = 95 | RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree); 96 | 97 | 98 | final float[] layoutMatrix = RendererCommon.getLayoutMatrix( 99 | false, frameAspectRatio, (float) outputFileWidth / outputFileHeight); 100 | final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix); 101 | 102 | //==========以下是图像处理的代码,真的看不太懂,日后钻研明白再来解析==================== 103 | try { 104 | videoOutFile.write("FRAME\n".getBytes()); 105 | if (!frame.yuvFrame) { 106 | yuvConverter.convert(outputFrameBuffer, outputFileWidth, outputFileHeight, outputFileWidth, 107 | frame.textureId, texMatrix); 108 | 109 | int stride = outputFileWidth; 110 | byte[] data = outputFrameBuffer.array(); 111 | int offset = outputFrameBuffer.arrayOffset(); 112 | 113 | // Write Y 114 | videoOutFile.write(data, offset, outputFileWidth * outputFileHeight); 115 | 116 | // Write U 117 | for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) { 118 | videoOutFile.write(data, offset + r * stride, stride / 2); 119 | } 120 | 121 | // Write V 122 | for (int r = outputFileHeight; r < outputFileHeight * 3 / 2; ++r) { 123 | videoOutFile.write(data, offset + r * stride + stride / 2, stride / 2); 124 | } 125 | } else { 126 | nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1], 127 | frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height, 128 | outputFrameBuffer, outputFileWidth, outputFileHeight); 129 | videoOutFile.write( 130 | outputFrameBuffer.array(), outputFrameBuffer.arrayOffset(), outputFrameSize); 131 | } 132 | } catch (IOException e) { 133 | Logging.e(TAG, "Failed to write to file for video out"); 134 | throw new RuntimeException(e); 135 | } finally { 136 | VideoRenderer.renderFrameDone(frame); 137 | } 138 | } 139 | 140 | ``` 141 | 142 | ``` 143 | //释放资源,关闭文件等处理 144 | public void release() { 145 | final CountDownLatch cleanupBarrier = new CountDownLatch(1); 146 | renderThreadHandler.post(new Runnable() { 147 | @Override 148 | public void run() { 149 | try { 150 | videoOutFile.close(); 151 | } catch (IOException e) { 152 | Logging.d(TAG, "Error closing output video file"); 153 | } 154 | yuvConverter.release(); 155 | eglBase.release(); 156 | renderThread.quit(); 157 | cleanupBarrier.countDown(); 158 | } 159 | }); 160 | ThreadUtils.awaitUninterruptibly(cleanupBarrier); 161 | } 162 | 163 | ``` 164 | 165 | -------------------------------------------------------------------------------- /AndroidNote/webRTC相关/WebRTC——VideoRenderer解析.md: -------------------------------------------------------------------------------- 1 | > VideoRenderer的目的是让链接定义他们自己的渲染行为,这个是通过回调产生的,这个方法同样体统了一个创建GUI的方法,用来创建GUI渲染器在各种各样的平台上面。 2 | 需要注意的是,frame只能通过native层进行构建。 3 | 4 | ``` 5 | //这是I420的一个对象的类,I420是视频编码的一种方式 6 | public static class I420Frame { 7 | public final int width; 8 | public final int height; 9 | public int[] yuvStrides; //信号的频幅 10 | public ByteBuffer[] yuvPlanes; //平面的色差信号 11 | public final boolean yuvFrame; //是否有帧的色差信号 12 | // Matrix that transforms standard coordinates to their proper sampling locations in 13 | // the texture. This transform compensates for any properties of the video source that 14 | // cause it to appear different from a normalized texture. This matrix does not take 15 | // |rotationDegree| into account. 16 | //抽样矩阵 17 | //将标准坐标转换为纹理中适当采样位置的矩阵。该转换补偿视频源的任何属性,使其与标准化纹理不同。这个矩阵不采取rotationdegree考虑 18 | public final float[] samplingMatrix; 19 | //结构Id 20 | public int textureId; 21 | // Frame pointer in C++.指针 22 | private long nativeFramePointer; 23 | 24 | // rotationDegree is the degree that the frame must be rotated clockwisely 25 | // to be rendered correctly. 26 | //旋转的角度应该是以顺时针的角度为标准 27 | public int rotationDegree; 28 | 29 | /** 30 | * Construct a frame of the given dimensions with the specified planar data. 31 | */ 32 | //构造方法,并且旋转的角度必须是90的整数倍 33 | I420Frame(int width, int height, int rotationDegree, int[] yuvStrides, ByteBuffer[] yuvPlanes, 34 | long nativeFramePointer) { 35 | this.width = width; 36 | this.height = height; 37 | this.yuvStrides = yuvStrides; 38 | this.yuvPlanes = yuvPlanes; 39 | this.yuvFrame = true; 40 | this.rotationDegree = rotationDegree; 41 | this.nativeFramePointer = nativeFramePointer; 42 | if (rotationDegree % 90 != 0) { 43 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); 44 | } 45 | // The convention in WebRTC is that the first element in a ByteBuffer corresponds to the 46 | // top-left corner of the image, but in glTexImage2D() the first element corresponds to the 47 | // bottom-left corner. This discrepancy is corrected by setting a vertical flip as sampling 48 | // matrix. 49 | // clang-format off 50 | samplingMatrix = new float[] { 51 | 1, 0, 0, 0, 52 | 0, -1, 0, 0, 53 | 0, 0, 1, 0, 54 | 0, 1, 0, 1}; 55 | // clang-format on 56 | } 57 | 58 | /** 59 | * Construct a texture frame of the given dimensions with data in SurfaceTexture 60 | */ 61 | //另一个构造方法,只是需要手动传入一个矩阵 62 | I420Frame(int width, int height, int rotationDegree, int textureId, float[] samplingMatrix, 63 | long nativeFramePointer) { 64 | this.width = width; 65 | this.height = height; 66 | this.yuvStrides = null; 67 | this.yuvPlanes = null; 68 | this.samplingMatrix = samplingMatrix; 69 | this.textureId = textureId; 70 | this.yuvFrame = false; 71 | this.rotationDegree = rotationDegree; 72 | this.nativeFramePointer = nativeFramePointer; 73 | if (rotationDegree % 90 != 0) { 74 | throw new IllegalArgumentException("Rotation degree not multiple of 90: " + rotationDegree); 75 | } 76 | } 77 | 78 | //获取宽和高 79 | public int rotatedWidth() { 80 | return (rotationDegree % 180 == 0) ? width : height; 81 | } 82 | 83 | public int rotatedHeight() { 84 | return (rotationDegree % 180 == 0) ? height : width; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return width + "x" + height + ":" + yuvStrides[0] + ":" + yuvStrides[1] + ":" + yuvStrides[2]; 90 | } 91 | } 92 | ``` 93 | 94 | ``` 95 | //释放掉所有frame的做法 96 | public static void renderFrameDone(I420Frame frame) { 97 | frame.yuvPlanes = null; 98 | frame.textureId = 0; 99 | if (frame.nativeFramePointer != 0) { 100 | releaseNativeFrame(frame.nativeFramePointer); 101 | frame.nativeFramePointer = 0; 102 | } 103 | } 104 | ``` 105 | 106 | ``` 107 | long nativeVideoRenderer; 108 | 109 | //构造方法,需要传进来一个callbacks 110 | public VideoRenderer(Callbacks callbacks) { 111 | nativeVideoRenderer = nativeWrapVideoRenderer(callbacks); 112 | } 113 | ``` 114 | 115 | ``` 116 | //销毁掉所有的方法 117 | public void dispose() { 118 | if (nativeVideoRenderer == 0) { 119 | // Already disposed. 120 | return; 121 | } 122 | 123 | freeWrappedVideoRenderer(nativeVideoRenderer); 124 | nativeVideoRenderer = 0; 125 | } 126 | ``` 127 | 128 | ``` 129 | //native层的初始化的方法 130 | private static native long nativeWrapVideoRenderer(Callbacks callbacks); 131 | ``` 132 | 133 | ``` 134 | //销毁 135 | private static native void freeWrappedVideoRenderer(long nativeVideoRenderer); 136 | //释放 137 | private static native void releaseNativeFrame(long nativeFramePointer); 138 | ``` 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Go/Go的练习代码.md: -------------------------------------------------------------------------------- 1 | # 平时练习Go的代码 2 | 3 | 1.go实现递归: 4 | 5 | ``` 6 | package main 7 | 8 | import "fmt" 9 | 10 | func main() { 11 | var result = fibonacci(10) 12 | fmt.Printf("fibonacci(%d) is: %d\n", 10, result) 13 | 14 | } 15 | 16 | func fibonacci(n int) (res int) { 17 | if n <= 0 { 18 | res = 0 19 | }else { 20 | res = n + fibonacci(n-1) 21 | } 22 | return 23 | } 24 | ``` 25 | 26 | 27 | 2.go实现回调 28 | 29 | ``` 30 | package main 31 | 32 | import "fmt" 33 | 34 | func main() { 35 | callback(1,Add) 36 | } 37 | 38 | func Add(a,b int) { 39 | fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b) 40 | } 41 | 42 | func callback(y int,f func(int,int)) { 43 | f(y,2) 44 | } 45 | ``` 46 | 47 | 3.函数调用 48 | 49 | ``` 50 | package main 51 | 52 | import "fmt" 53 | 54 | func main() { 55 | var f = Adder2() 56 | fmt.Print(f(1), " - ") 57 | fmt.Print(f(20), " - ") 58 | fmt.Print(f(300)) 59 | } 60 | 61 | func Adder2() func(int) int { 62 | var x int 63 | return func(delta int) int { 64 | x += delta 65 | return x 66 | } 67 | } 68 | ``` 69 | 70 | 71 | 4.把函数付给变量 72 | 73 | ``` 74 | package main 75 | 76 | import "fmt" 77 | 78 | func main() { 79 | // make an Add2 function, give it a name p2, and call it: 80 | p2 := Add2() 81 | fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3)) 82 | // make a special Adder function, a gets value 3: 83 | TwoAdder := Adder(2) 84 | fmt.Printf("The result is: %v\n", TwoAdder(3)) 85 | } 86 | 87 | func Add2() func(b int) int { 88 | return func(b int) int { 89 | return b + 2 90 | } 91 | } 92 | 93 | func Adder(a int) func(b int) int { 94 | return func(b int) int { 95 | return a + b 96 | } 97 | } 98 | ``` 99 | 100 | 5.切片实例 101 | 102 | ``` 103 | package main 104 | import "fmt" 105 | 106 | func main() { 107 | var arr1 [6]int 108 | var slice1 []int = arr1[2:5] // item at index 5 not included! 109 | 110 | // load the array with integers: 0,1,2,3,4,5 111 | for i := 0; i < len(arr1); i++ { 112 | arr1[i] = i 113 | } 114 | 115 | // print the slice 116 | for i := 0; i < len(slice1); i++ { 117 | fmt.Printf("Slice at %d is %d\n", i, slice1[i]) 118 | } 119 | 120 | fmt.Printf("The length of arr1 is %d\n", len(arr1)) 121 | fmt.Printf("The length of slice1 is %d\n", len(slice1)) 122 | fmt.Printf("The capacity of slice1 is %d\n", cap(slice1)) 123 | 124 | // grow the slice 125 | slice1 = slice1[0:4] 126 | for i := 0; i < len(slice1); i++ { 127 | fmt.Printf("Slice at %d is %d\n", i, slice1[i]) 128 | } 129 | fmt.Printf("The length of slice1 is %d\n", len(slice1)) 130 | fmt.Printf("The capacity of slice1 is %d\n", cap(slice1)) 131 | 132 | // grow the slice beyond capacity 133 | //slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range 134 | } 135 | ``` 136 | -------------------------------------------------------------------------------- /IOSNote/Ios上架app需要的图标尺寸.md: -------------------------------------------------------------------------------- 1 | 2 | # IOS上架所需要的图标的尺寸 3 | 4 | ## iocn 5 | 6 | 1. Icon.png - 57 * 57 iPhone应用图标 7 | 2. Icon@2x.png - 114 * 114 iPhone Retina显示应用图标 8 | 3. Icon-Small.png - 29 * 29 系统设置和搜索结果图标 9 | 4. Icon-Small@2x.png - 58 * 58 iPhone Retina显示屏 系统设置和搜索结果图标 10 | 5. app store 上线需要两版图标 1024 * 1024 512 * 512 11 | 12 | 13 | 14 | ios里面的切图: 15 | 需要三版,以适应不同的分辨率,命名就是前半部分一样,后面加上@2x就可以。用的时候正常用前半部分名字,系统会根据分辨率自动选择。 16 | 17 | 如果设计稿是iPhone6: 750 * 1334 18 | 19 | 那么我们切图,切出来的图直接就是@2x的,我们还需要切出一套@1x的,还需要一套@3x的。 20 | 21 | 转换关系是,如果一个图在ps的大小是,10px的话(750 * 1334的设计稿)。 22 | 23 | 那么@1x : 5px @2px : 10px @3px : 15px 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /JavaNote/Javaee/Spring-boot入门.md: -------------------------------------------------------------------------------- 1 | # Spiring-boot入门 2 | 3 | > 最入门级别的Spring框架,省了很多配置的过程,比较适合初学者,或者微站的使用,所以打算从这里开始学起。 4 | 5 | 学习的地址:https://www.bysocket.com/?p=1627 6 | 7 | 包目录结构: 8 | 9 | ![](https://ws1.sinaimg.cn/large/006tNc79ly1flcxydakxlj30km0zyt9d.jpg) 10 | 11 | 这里面分了很多层,主要有的层就是,controller,dao,domain,service,Application。 12 | 13 | SpringBoot的优势就在于它内置了TomCat,并且不需要过多的配置就可以直接使用,也不用配置Spring那一套。 14 | 15 | 这次实现的是springboot-restful的过程,rest是web的一种框架风格,主要用于前后端分离。 16 | 17 | 首先我们要实现一个controller,这里面负责拦截用户的url,然后可以设置是GET还是POST的方法,同时我们还需要实现一个dao层,这层是用来负责数据持久化的,简单说就是这里和数据进行的直接接触。接下来要实现一个domain层,这一层大家应该都能明白有的时候我们叫它model有的时候叫它bean,接下来就要实现service层了,这一层中我们需要定义一个接口,并且编写一个它的实现类就可以了,我们在controller中调用的便是这一层的内容。 18 | 19 | 其实我们的代码,以上便已经编写完成了,但是我们还有几个点是没有完成的,我们的sql语句还没有配置呢。我们需要在resources/mapper/CityMapper.xml内中写入。在resources/application.properties中写入数据库的信息,配置一下就好。 20 | 21 | 代码地址:https://github.com/linsir6/Javaee-Spring-demo 22 | 23 | 博主在Spring方面也是刚刚起步的小菜鸟,写以上内容只是为了,记录学习笔记,要是有什么错误,欢迎大家提出指正。 24 | -------------------------------------------------------------------------------- /JavaNote/Java相关/ArrayList、LinkedList、Vector的异同.md: -------------------------------------------------------------------------------- 1 | # ArrayList、LinkedList、Vector的异同 2 | 3 | 我们可以看出ArrayList、LinkedList、Vector都实现了List的接口。 4 | 5 | 接下来分别看一下三个数据结构的说明: 6 | 7 | - 首先是ArrayList 8 | 9 | ``public class **ArrayList** extends AbstractList 10 | implements List, RandomAccess, Cloneable, Serializable`` 11 | 12 | List 接口的**大小可变数组**的实现。实现了所有可选列表操作,并**允许包括 null 在内的所有元素**。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了**此类是不同步的**。) 13 | 14 | 每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。 15 | 16 | 在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。 17 | 18 | ``List list = Collections.synchronizedList(new ArrayList(...)); `` 19 | 20 | 此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 21 | 注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。 22 | 23 | - 然后是LinkedList 24 | 25 | 26 | ``public class **LinkedList** extends AbstractSequentialList 27 | implements List, Deque, Cloneable, Serializable`` 28 | 29 | List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(**包括 null**)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。 30 | 31 | 此类实现 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。 32 | 33 | 所有操作都是按照**双重链接列表**的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。 34 | 35 | 注意,此实现**不是同步**的。如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须 保持外部同步。(结构修改指添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这一般通过**对自然封装该列表的对象进行同步操作来完成**。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示: 36 | 37 | ``List list = Collections.synchronizedList(new LinkedList(...));`` 38 | 39 | 此类的 iterator 和 listIterator 方法返回的迭代器是快速失败 的:在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。 40 | 41 | 注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。 42 | 43 | - 最后是Vector 44 | 45 | public class **Vector** extends AbstractList 46 | implements List, RandomAccess, Cloneable, Serializable 47 | 48 | Vector 类可以**实现可增长的对象数组**。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector 的大小可以根据需要增大或缩小,以适应创建 Vector 后进行添加或移除项的操作。 49 | 50 | 每个向量会试图通过维护 capacity 和 capacityIncrement 来优化存储管理。capacity 始终至少应与向量的大小相等;这个值通常比后者大些,因为随着将组件添加到向量中,其存储将按 capacityIncrement 的大小增加存储块。应用程序可以在插入大量组件前增加向量的容量;这样就减少了增加的重分配的量。 51 | 52 | 由 Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的:如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。 53 | 54 | 注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测 bug。 55 | 56 | 从 Java 2 平台 v1.2 开始,此类改进为可以实现 List 接口,使它成为 Java Collections Framework 的成员。与新 collection 实现不同,**Vector 是同步的**。 57 | 58 | ---- 59 | 60 | - 区别 61 | 62 | 63 | ArrayList 本质上是一个可改变大小的**数组**.当元素加入时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问.元素顺序存储 ,**随机访问很快,删除非头尾元素慢,新增元素慢而且费资源** ,较适用于无频繁增删的情况 ,比数组效率低,如果不是需要可变数组,可考虑使用数组 ,**非线程安全**. 64 | 65 | LinkedList 是一个**双链表**,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList. 适用于 :没有大规模的随机读取,大量的增加/删除操作.**随机访问很慢,增删操作很快**,不耗费多余资源 ,允许null元素,**非线程安全.** 66 | 67 | Vector (类似于ArrayList)但其是**同步**的,开销就比ArrayList要大。如果你的程序本身是线程安全的,那么使用ArrayList是更好的选择。 68 | Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%. 69 | 70 | -------------------------------------------------------------------------------- /JavaNote/Java相关/Des加密算法.md: -------------------------------------------------------------------------------- 1 | # Des加密算法 2 | 3 | > des加密算法,是一个对称的加密算法,目前被广泛应用,所以打算写一个demo。 4 | 5 | 6 | ``` 7 | package com.dao; 8 | 9 | import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; 10 | import sun.misc.BASE64Decoder; 11 | import sun.misc.BASE64Encoder; 12 | 13 | import javax.crypto.Cipher; 14 | import javax.crypto.SecretKey; 15 | import javax.crypto.SecretKeyFactory; 16 | import javax.crypto.spec.DESKeySpec; 17 | import java.io.IOException; 18 | import java.security.SecureRandom; 19 | 20 | /** 21 | * Created by linSir on 2017/6/22.des加密算法 22 | */ 23 | public class Test { 24 | 25 | public static void main(String args[]) throws IOException { 26 | 27 | 28 | //加密 29 | //byte[] result = Test.encrypt(str.getBytes(),password); 30 | //BASE64Encoder base64encoder = new BASE64Encoder(); 31 | //String encode=base64encoder.encode(result); 32 | 33 | String miwen = "ZraEmkLPeVT1CBGRpcbXTfVRhUWt6riMMh8UoWcVEClwLcCRuJoMmZW+IS5MYshasXVUu1VIFeqE\n" + 34 | "ySjMvDvu4z6GxUR7BVq95mfILIT6kvCLW1rvgJoZGlkXzDW7R+n8R/POzu61cfKejnMnW0HiRmsK\n" + 35 | "CNLB3zf0KYfB5H0x0+GUtXQmtQyG0x5tQSyHSWOdQVyEj7mYFw4h6uFhN94ifgZq8ohpUduWZBgU\n" + 36 | "EN3B4akKt8+oPQPFv1GvrFucOmrfDpyTy+YuLZZ0nlPA5AYTa2TnC++ZPPo62XW4O2EZ0qGXcuO1\n" + 37 | "3zHfq8mmtdQ7DbGN2JIBNLL/EN97o7pHRkVNbB9/eHElf37MghHZWTUfIlvRtSTwaWkW3IR2aWzj\n" + 38 | "GQXrdqErVUdcTvLH2fGnInYU6XAtwJG4mgyZG6OZZ89Yg9iOcWG4GruJvFEa/UQNDmbS+vyvWpP/\n" + 39 | "75zOiDos5s5yeJUcUaJt+SkUR7z5yr7bbK/DHkS5aEvfNI/nL4Z4DrGN++9Uzv34XD4ZTg0csEuL\n" + 40 | "96+LAUKED43iaJUo6wruiZ/7KmpvP5p3ii5p03Z1ymscmTlqUTZ55YFBCz3dZg8OSGIlKj+7uaYF\n" + 41 | "umweL38ksAtVL1wjgWMVF+9oYUie/jf6+mAdmvwiACoGu71ZziWc4tz1UPb27Qx4Qf0h/nItAkuT\n" + 42 | "yLK6+Hx0+GQ2weK2q95kgf8zUs1igyhu1VdMGHbp/Ma3DyJIo6wPgwRlpFedCq0/w7ECGGPHfLUb\n" + 43 | "eNmBK3nCqqN7TABiLfHfzR8mBjjMcJQ1MtGGwZB6H6zAGkcSEQqHgsTbnG6t8GvS06t9eepMn6VG\n" + 44 | "7X+dS4LUS9LpIZ/OgNwxvyxd3vw/dKn9u0OLgvJRGv1EDQ5m0t80qIo6RxHCLmnTdnXKaxxFIhNG\n" + 45 | "caq19CPEthsSFziTHlX/qM5g/yCwbN9+qClQ0z5VI/ZGUAcs9Cz3WjimPGKNyLa+AKgUE7dh4sFr\n" + 46 | "vQGrlRxRom35KRRRd/VE9Goz3EAcQQ1NhiDMYobeoH0as5XkG3hTF2zZyfn/QJZnNwh4GxCLkZPS\n" + 47 | "VKFdg0bpAy3irJouw+IG69DUewM4W4a1u7h8i76pCLLxP5gIYqKqKgm97itSQe8ZV3qbG9gNxMrg\n" + 48 | "aUQ+fCE3TvsP07RdLW9Dn6Mazxnq7wnw9X2Qj9+sTl+hLLKhL+ZlHIJk5wvvmTz6OikBCmYmdEZb\n" + 49 | "Q1Prg0CgHVrFy4JOc9rTCmLnieHBG/xVwI5AOp42NUOk/Ycc7EIuzQ4tKEGS7RjmcpKEUMkog5c5\n" + 50 | "k693mGsn2VUQNeRPmfrcN7Ra+L18fvKMs1ESEjrUR/GpHwg6UcCBfBh8r/B5bYdoV2ik02liSVzX\n" + 51 | "Kt5vzA3ZjC5mvkF/RJJUoCUa6j3xqznjJhmABsN23gOcRh8RRWb8VGI2xD8ErYwuZr5Bf0SSTyTL\n" + 52 | "p6dacHJIz1u6+TEr9OfinHoMzBwXETOkAnPOt/YdwGw8/MM41w=="; 53 | 54 | BASE64Decoder base64decoder = new BASE64Decoder(); 55 | byte[] encodeByte = base64decoder.decodeBuffer(miwen); 56 | 57 | 58 | //直接将如上内容解密 59 | try { 60 | byte[] decryResult = Test.decrypt(encodeByte, ""); 61 | System.out.println("解密后:" + new String(decryResult)); 62 | } catch (Exception e1) { 63 | e1.printStackTrace(); 64 | } 65 | 66 | } 67 | 68 | /** 69 | * 加密 70 | */ 71 | public static byte[] encrypt(byte[] datasource, String password) { 72 | try { 73 | SecureRandom random = new SecureRandom(); 74 | DESKeySpec desKey = new DESKeySpec(password.getBytes()); 75 | //创建一个密匙工厂,然后用它把DESKeySpec转换成 76 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); 77 | SecretKey securekey = keyFactory.generateSecret(desKey); 78 | //Cipher对象实际完成加密操作 79 | Cipher cipher = Cipher.getInstance("DES"); 80 | //用密匙初始化Cipher对象 81 | cipher.init(Cipher.ENCRYPT_MODE, securekey, random); 82 | //现在,获取数据并加密 83 | //正式执行加密操作 84 | return cipher.doFinal(datasource); 85 | } catch (Throwable e) { 86 | e.printStackTrace(); 87 | } 88 | return null; 89 | } 90 | 91 | /* 92 | * 解密 93 | */ 94 | private static byte[] decrypt(byte[] src, String password) throws Exception { 95 | // DES算法要求有一个可信任的随机数源 96 | SecureRandom random = new SecureRandom(); 97 | // 创建一个DESKeySpec对象 98 | DESKeySpec desKey = new DESKeySpec(password.getBytes()); 99 | // 创建一个密匙工厂 100 | SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); 101 | // 将DESKeySpec对象转换成SecretKey对象 102 | SecretKey securekey = keyFactory.generateSecret(desKey); 103 | // Cipher对象实际完成解密操作 104 | Cipher cipher = Cipher.getInstance("DES"); 105 | // 用密匙初始化Cipher对象 106 | cipher.init(Cipher.DECRYPT_MODE, securekey, random); 107 | // 真正开始解密操作 108 | return cipher.doFinal(src); 109 | } 110 | } 111 | 112 | ``` -------------------------------------------------------------------------------- /JavaNote/Java相关/HashTable和HashMap的异同.md: -------------------------------------------------------------------------------- 1 | # HashTable和HashMap的异同 2 | 3 | - HashTable 4 | 5 | 1. Hashtable继承于Dictionary字典,实现Map接口 6 | 7 | 2. 键、值都不能是空对象 8 | 9 | 3. 多次访问,映射元素的顺序相同 10 | 11 | 4. 线程安全 12 | 13 | 14 | 5. hash算法 ,Hashtable则直接利用key本身的hash码来做验证 15 | 16 | 6. 数据遍历的方式 Iterator (支持fast-fail)和 Enumeration (不支持fast-fail) 17 | 18 | 7. 缺省初始长度为11,内部都为抽象方法,需要 它的实现类一一作自己的实现 19 | 20 | 备注:程序在对 collection 进行迭代时,某个线程对该 collection 在结构上对其做了修改,这时迭代器就会抛出 ConcurrentModificationException 异常信息,从而产生 fail-fast。 21 | 22 | ---- 23 | 24 | - HashMap 25 | 26 | 1. HashMap继承于AbstractMap抽象类 27 | 28 | 2. 键和值都可以是空对象 29 | 30 | 3. 多次访问,映射元素的顺序可能不同 31 | 32 | 4. 非线程安全 33 | HashMap可以通过下面的语句进行同步: 34 | ``Map m = Collections.synchronizeMap(hashMap);`` 35 | 36 | 5. 检测是否含有key时,HashMap内部需要将key的hash码重新计算一边再检测数据遍历的方式 Iterator (支持fast-fail) 37 | 38 | 6. 缺省初始长度为16,其内部已经实现了Map所需 要做的大部分工作, 它的子类只需要实现它的少量方法 39 | 40 | -------------------------------------------------------------------------------- /JavaNote/Java相关/JVM类加载器.md: -------------------------------------------------------------------------------- 1 | # 虚拟机类加载机制 2 | 3 | **虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被Java虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。** 4 | 5 | 类从被加载到虚拟内存中开始,到卸载内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中,验证,准备和解析三个部分统称为连接(Linking)。 6 | 7 | ### 类加载的过程 8 | 类加载的全过程,加载,验证,准备,解析和初始化这五个阶段。 9 | 10 | 11 | 12 | #### 加载 13 | 在加载阶段,虚拟机需要完成以下三件事情: 14 | 15 | * 通过一个类的全限定名来获取定义此类的二进制字节流 16 | * 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构 17 | * 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口 18 | 19 | #### 验证 20 | 这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能有所不同,但大致上都会完成下面四个阶段的检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。 21 | 22 | **文件格式验证** 23 | 24 | 第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。 25 | 26 | **元数据验证** 27 | 28 | 第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。 29 | 30 | **字节码验证** 31 | 32 | 第三阶段时整个验证过程中最复杂的一个阶段,主要工作是数据流和控制流的分析。在第二阶段对元数据信息中的数据类型做完校验后,这阶段将对类的方法体进行校验分析。这阶段的任务是保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。 33 | 34 | **符号引用验证** 35 | 36 | 最后一个阶段的校验发生在虚拟机将符号引用直接转化为直接引用的时候,这个转化动作将在连接的第三个阶段-解析阶段产生。符号引用验证可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性的校验。 37 | 38 | #### 准备 39 | 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区进行分配。 40 | 41 | #### 解析 42 | 解析阶段是虚拟机将常量池的符号引用转换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。 43 | 44 | * 类或接口的解析 45 | * 字段解析 46 | * 类方法解析 47 | * 接口方法解析 48 | 49 | #### 初始化 50 | 前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由Java虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者说初始化阶段是执行类构造器()方法的过程。 51 | 52 | ### 类加载器 53 | --- 54 | #### 类与类加载器 55 | 虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让程序自己决定如何去获取所需的类。实现这个动作的代码模块被称为"类加载器"。 56 | 57 | #### 双亲委派模型 58 | 站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。从Java开发人员的角度来看,类加载器还可以分得更细致一些,绝大部分Java程序都会使用到以下三种系统提供的类加载器: 59 | 60 | * 启动类加载器 61 | * 扩展类加载器 62 | * 应用程序类加载器 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /JavaNote/Java相关/Java中Error和Exception.md: -------------------------------------------------------------------------------- 1 | # Java中Error和Exception 2 | 3 | Java 异常类继承关系图: 4 | 5 | ![](http://img.blog.csdn.net/20160412143252629) 6 | 7 | 8 | (一)Throwable 9 | 10 |   Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出,才可以是 catch 子句中的参数类型。 11 |   Throwable 类及其子类有两个构造方法,一个不带参数,另一个带有 String 参数,此参数可用于生成详细消息。 12 |   Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。 13 |    14 | Java将可抛出(Throwable)的结构分为三种类型: 15 |   1. 错误(Error) 16 |   2. 运行时异常(RuntimeException) 17 |   3. 被检查的异常(Checked Exception) 18 | 19 |  1.**Error** 20 |   Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。 21 |   和RuntimeException一样, 编译器也不会检查Error。 22 |   当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误,程序本身无法修复这些错误的。 23 |    24 |  2.**Exception** 25 |   Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。 26 |    对于可以恢复的条件使用**被检查异常**(Exception的子类中除了RuntimeException之外的其它子类),对于程序错误使用运行时异常。  27 |    28 | 2.1 ClassNotFoundException 29 |    30 | 当应用程序试图使用以下方法通过字符串名加载类时: 31 | Class 类中的 forName 方法。 32 | ClassLoader 类中的 findSystemClass 方法。 33 | ClassLoader 类中的 loadClass 方法。 34 | 但是没有找到具有指定名称的类的定义,抛出该异常。 35 | 36 | 2.2 CloneNotSupportedException 37 | 38 | 当调用 Object 类中的 clone 方法复制对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。重写 clone 方法的应用程序也可能抛出此异常,指示不能或不应复制一个对象。 39 |   40 | 41 | 2.3 IOException 42 | 当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。 43 | 44 | - EOFException 45 | 46 | 当输入过程中意外到达文件或流的末尾时,抛出此异常。 47 | 此异常主要被**数据输入流**用来表明到达流的末尾。 48 | 注意:其他许多输入操作返回一个特殊值表示到达流的末尾,而不是抛出异常。 49 |      50 | - FileNotFoundException 51 | 52 | 当试图打开指定路径名表示的文件失败时,抛出此异常。在不存在具有指定路径名的文件时,此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在,但是由于某些原因不可访问,比如试图打开一个只读文件进行写入,则此时这些构造方法仍然会抛出该异常。 53 | 54 | - MalformedURLException 55 | 56 | 抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议,或者无法解析字符串。  57 |   58 | -UnknownHostException 59 |   指示主机 IP 地址无法确定而抛出的异常。 60 | 61 | 2.4 RuntimeException 62 |    是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。 63 |    Java编译器不会检查它。当程序中可能出现这类异常时,还是会编译通过。 64 |    虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。 65 | 66 | - ArithmeticException 67 | 68 | 当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。 69 | 70 | - ClassCastException 71 | 72 | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 73 | 例如:Object x = new Integer(0); 74 | 75 | - LllegalArgumentException 76 | 77 | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 78 | 79 | - IllegalStateException 80 | 81 | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 82 | 83 | - IndexOutOfBoundsException  84 | 85 | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 86 | 应用程序可以为这个类创建子类,以指示类似的异常。 87 | 88 | - NoSuchElementException 89 | 90 | 由 Enumeration 的 nextElement 方法抛出,表明枚举中没有更多的元素。 91 | 92 | - NullPointerException 93 | 94 | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括: 95 | 调用 null 对象的实例方法。 96 | 访问或修改 null 对象的字段。 97 | 将 null 作为一个数组,获得其长度。 98 | 将 null 作为一个数组,访问或修改其时间片。 99 | 将 null 作为 Throwable 值抛出。 100 | 应用程序应该抛出该类的实例,指示其他对 null 对象的非法使用。 101 | 102 | 103 | 104 | (二) SOF (堆栈溢出 StackOverflow) 105 | 106 | > StackOverflowError 的定义: 107 | > 当应用程序递归太深而发生堆栈溢出时,抛出该错误。 108 | > 109 | 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。 110 | 111 | 栈溢出的原因: 112 | 113 | 1. 递归调用 114 | 115 | 2. 大量循环或死循环 116 | 117 | 3. 全局变量是否过多 118 | 119 | 4. 数组、List、map数据过大 120 | 121 | 122 | 123 | (三)Android的OOM(Out Of Memory) 124 | 125 |   当内存占有量超过了虚拟机的分配的最大值时就会产生内存溢出(VM里面分配不出更多的page)。 126 |    127 | 一般出现情况:加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放。 128 | 129 | 解决方法: 130 | 131 | ①在内存引用上做处理 132 | 133 | 软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。 134 | 135 | ②对图片做边界压缩,配合软引用使用 136 |   137 | ③显示的调用GC来回收内存  138 |   139 | 140 | ``` 141 | if(bitmapObject.isRecycled()==false) //如果没有回收 142 | bitmapObject.recycle(); 143 | ``` 144 |   145 | ④优化Dalvik虚拟机的堆内存分配 146 |    147 | 1.增强程序堆内存的处理效率 148 |    149 | 150 | ``` 151 | //在程序onCreate时就可以调用 即可 152 | private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 153 | 154 | VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 155 | ``` 156 | 157 | 2.设置缓存大小 158 | 159 | ``` 160 | private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ; 161 | //设置最小heap内存为6MB大小 162 | VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); 163 | ``` 164 | 165 | ⑤ 用LruCache 和 AsyncTask<>解决 166 | 167 |   从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。 168 |   如果缓存中不存在,那么启动一个task去加载(可能从文件来,也可能从网络)。 169 | 170 | -------------------------------------------------------------------------------- /JavaNote/Java相关/Java利用ExecutorService实现同步执行大量线程.md: -------------------------------------------------------------------------------- 1 | >自从java1.5以后,官网就推出了Executor这样一个类,这个类,可以维护我们的大量线程在操作临界资源时的稳定性。 2 | 3 | 先上一段代码吧: 4 | ```` 5 | TestRunnable.java 6 | public class TestRunnable implements Runnable { 7 | private String name; 8 | 9 | public TestRunnable(String name) { 10 | this.name = name; 11 | } 12 | 13 | @Override 14 | public void run() { 15 | while (true) { 16 | if (Main.Surplus < 0) 17 | return; 18 | Main.Surplus--; 19 | System.out.println(name + " " + Main.Surplus); 20 | } 21 | } 22 | } 23 | ```` 24 | 25 | ```` 26 | main入口 27 | public static void main(String[] args) { 28 | 29 | TestRunnable runnable = new TestRunnable("runnable1"); 30 | TestRunnable runnable2 = new TestRunnable("runnable2"); 31 | 32 | Thread t1 = new Thread(runnable); 33 | Thread t2 = new Thread(runnable2); 34 | 35 | t1.start(); 36 | t2.start(); 37 | 38 | } 39 | ```` 40 | 41 | ![result](http://upload-images.jianshu.io/upload_images/2585384-b60f973afce827b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 42 | 43 | 这样,我们就看到了,数据肯定是乱了的,当然这个时候我们可以加上一个synchronized的关键字,但是这样也会出现点小问题的 44 | 45 | ![result2](http://upload-images.jianshu.io/upload_images/2585384-3cc19f76464d6831.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 46 | 47 | ---- 48 | 下面我打算采用一种java内置的线程管理的机制,来解决这个问题,解决这个问题的思路大概就是,我们维护了一个线程池,当有请求操作的时候统统进入线程池,并且我们只开了一个线程,可以让请求顺序执行,顺序调用临界资源,就很安全了。 49 | 50 | ```` 51 | import java.util.concurrent.Callable; 52 | import java.util.concurrent.ExecutionException; 53 | import java.util.concurrent.ExecutorService; 54 | import java.util.concurrent.Executors; 55 | import java.util.concurrent.Future; 56 | 57 | public class Main { 58 | public static int Surplus = 10; 59 | 60 | private ExecutorService executor = Executors.newSingleThreadExecutor(); 61 | 62 | void addTask(Runnable runnable) { 63 | executor.execute(runnable); 64 | } 65 | 66 | V addTask(Callable callable) { 67 | Future submit = executor.submit(callable); 68 | try { 69 | return submit.get(); 70 | } catch (InterruptedException e) { 71 | System.out.println("InterruptedException" + e.toString()); 72 | } catch (ExecutionException e) { 73 | System.out.println("ExecutionException" + e.toString()); 74 | } 75 | return null; 76 | } 77 | 78 | public void testAddTask(String name) { 79 | addTask(new Runnable() { 80 | @Override 81 | public void run() { 82 | for (int i = 0; i < 3; i++) { 83 | if (Main.Surplus <= 0) 84 | return; 85 | Main.Surplus--; 86 | System.out.println(name + " " + Main.Surplus); 87 | } 88 | 89 | } 90 | }); 91 | } 92 | 93 | public void testAddTask2(String name) { 94 | int count = addTask(new Callable() { 95 | @Override 96 | public Integer call() throws Exception { 97 | for (int i = 0; i < 3; i++) { 98 | if (Main.Surplus <= 0) 99 | return 0; 100 | Main.Surplus--; 101 | System.out.println(name + " " + Main.Surplus); 102 | } 103 | return Main.Surplus; 104 | } 105 | }); 106 | 107 | } 108 | 109 | public void close() { 110 | executor.shutdown(); 111 | } 112 | 113 | public static void main(String[] args) { 114 | Main main = new Main(); 115 | main.testAddTask("task1"); 116 | main.testAddTask2("task2"); 117 | main.testAddTask("task3"); 118 | main.testAddTask2("task4"); 119 | main.close(); 120 | } 121 | } 122 | 123 | ```` 124 | 125 | 在这里,我们定义了两种方法,分别是addTask,具有泛型的addTask,这两种方法实现原理都是一样的,其中一个是有回调的,一个是没有回调的,就看项目需求了吧。 126 | 127 | ![result3](http://upload-images.jianshu.io/upload_images/2585384-ee988c2286ef99a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 128 | 然后分别调用这两个方法咯,就可以看到结果是非常有序,且不会混乱的。 129 | 130 | ---- 131 | 当然啊,系统为我们提供这样一个类,肯定不是为了实现这么小的一个功能的,它还有很多功能,我也在进一步的学习中~ 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /JavaNote/Java相关/Java利用listener实现回调,即观察者模式.md: -------------------------------------------------------------------------------- 1 | > java中实现观察者模式有很多种方式,上一篇文章介绍到了,[利用callback的方式实现了回调](http://www.jianshu.com/p/67190bdce647),这篇文章准备介绍的是利用listener实现回调。 2 | 3 | ---- 4 | # Java回调机制 5 | 6 | #### 根据实时性划分: 7 | 8 | - [同步回调](http://www.jianshu.com/p/67190bdce647) 9 | - [异步回调](http://www.jianshu.com/p/67190bdce647) 10 | 11 | #### 实现方式 12 | 13 | * [利用匿名内部类即callbck来实现](http://www.jianshu.com/p/67190bdce647) 14 | * 用listener来实现 15 | 16 | 这两种实现方式本质上是类似的,应用场景略有不同,如果有熟知安卓的朋友应该可以知道,在为一个view添加点击实现的时候是有两种方式的 17 | 18 | 1. 利用callback来实现 19 | 20 | ```` 21 | view.setOnClickListener(new View.OnClickListener() { 22 | @Override public void onClick(View view) { 23 | 24 | } 25 | }); 26 | ```` 27 | 28 | 2.实现View.OnClickListener接口 29 | 30 | ```` 31 | public class Test extends AppCompatActivity implements View.OnClickListener { 32 | private Button button; 33 | 34 | @Override protected void onCreate(@Nullable Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | button = (Button) findViewById(R.id.ac_video_btn_getValue); 37 | button.setOnClickListener(this); 38 | } 39 | 40 | @Override public void onClick(View view) { 41 | //TODO 42 | } 43 | } 44 | 45 | 46 | 47 | ```` 48 | 49 | #### 回调的本质 50 | 51 | 其实无论哪种方式来实现回调,利用的思想都是观察者模式,即在我们选择订阅之后,当对方做出任何举动的时候会给我们发送一条信息,这样做的好处是省着我们用一个新的线程轮训检测对方的状态,可以节省很多的资源。 52 | 53 | #### 应用的场景 54 | 55 | - 如果我们需要将信息一层一层的返回去的时候,正如我下面的例子,那么可能用listener更为适合我们,因为我们可以将这个listener进行传递,在需要查看数据的时候进行回调它。或者当我们有很多事件需要回调的时候,可以实现一个listener然后发送不同的信息,进行区分。这样代码看起来会简洁一些,不会像callback一样,会嵌套很多层,也不会写出很多个callback来。 56 | 57 | - 如果我们只有一处,或者简单的几处需要回调的话,那么我们完全可以不用实现这个接口,而是用callback的方式来进行处理。 58 | 59 | ---- 60 | 61 | 还是举一个简单的生活中的例子吧,在公司中有一件事情,老板想要问员工,但是老板只能联系到部门经理,那么便有了,A问B,B问C,C经过思考,回答了B,B又将答案告诉了A,A知道了答案,便高兴的说了出来。 62 | 63 | ---- 64 | 我下面的代码采用了单例模式来写: 65 | ```` 66 | Listner.java 67 | 68 | public interface Listener { 69 | void onFinish(String msg); 70 | } 71 | ```` 72 | 73 | ```` 74 | People.java 75 | 76 | public class People implements Listener { 77 | 78 | private static People people; 79 | 80 | private People() { 81 | 82 | } 83 | 84 | synchronized public static People getInstance() { 85 | if (people == null) { 86 | people = new People(); 87 | } 88 | return people; 89 | } 90 | 91 | public void askPeople2() { 92 | People2.getInstance().askPeople3(this); 93 | } 94 | 95 | @Override 96 | public void onFinish(String msg) { 97 | System.out.println("收到的消息是 ---> " + msg); 98 | } 99 | 100 | } 101 | ```` 102 | 103 | ```` 104 | People2.java 105 | 106 | public class People2 { 107 | 108 | private static People2 people2; 109 | 110 | private People2() { 111 | 112 | } 113 | 114 | synchronized public static People2 getInstance() { 115 | if (people2 == null) 116 | people2 = new People2(); 117 | return people2; 118 | } 119 | 120 | public void askPeople3(Listener listener){ 121 | People3.getInstance().thinking(listener); 122 | } 123 | 124 | } 125 | ```` 126 | 127 | ```` 128 | People3.java 129 | 130 | public class People3 { 131 | 132 | private static People3 people3; 133 | 134 | private People3() { 135 | 136 | } 137 | 138 | synchronized public static People3 getInstance() { 139 | if (people3 == null) 140 | people3 = new People3(); 141 | return people3; 142 | } 143 | 144 | public void thinking(Listener listener3) { 145 | try { 146 | Thread.sleep(2000); 147 | } catch (InterruptedException e) { 148 | e.printStackTrace(); 149 | } 150 | listener3.onFinish("我已经思考完毕"); 151 | } 152 | 153 | } 154 | 155 | ```` 156 | 157 | 158 | ![result](http://upload-images.jianshu.io/upload_images/2585384-74c3bfc1ef9ee2aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 159 | 160 | -------------------------------------------------------------------------------- /JavaNote/Java相关/Java回调的原理与实现.md: -------------------------------------------------------------------------------- 1 | > 回调原本应该是一个非常简单的概念,但是可能因为平时只用系统为我们写好的回调的接口了,自己很少实现回调,所以在自己实现回调的时候还是有一点点晕的,现在写这篇文章记录一下,也和大家分享一下怎么写回调接口。 2 | 3 | ##回调 4 | 回调的概念:举个例子就是,我们想要问别人一道题,我们把题跟对方说了一下,对方说好,等我做完这道题,我就告诉你,这个时候就用到了回调,因为我们并不知道对方什么时候会做完,而是对方做完了来主动找我们。 5 | ####同步回调 6 | 代码运行到某一个位置的时候,如果遇到了需要回调的代码,会在这里等待,等待回调结果返回后再继续执行。 7 | ####异步回调 8 | 代码执行到需要回调的代码的时候,并不会停下来,而是继续执行,当然可能过一会回调的结果会返回回来。 9 | 10 | 11 | ---- 12 | ###具体代码: 13 | 14 | [代码链接](https://github.com/linsir6/TestCallback) 15 | 16 | 总体的代码还是很简单的,就是模拟了一个打印机,还有一个人,打印机具有打印的功能,但是打印需要时间,不能在收到任务的同时给出反馈,需要等待一段时间才能给出反馈。这个人想做的就是打印一份简历,然后知道打印的结果。这里面代码实现了这两种方式。 17 | 18 | ```` 19 | Callback.java 20 | 21 | public interface Callback { 22 | void printFinished(String msg); 23 | } 24 | 25 | ```` 26 | 27 | ```` 28 | Printer.java 29 | 30 | public class Printer { 31 | public void print(Callback callback, String text) { 32 | System.out.println("正在打印 . . . "); 33 | try { 34 | Thread.currentThread(); 35 | Thread.sleep(3000);// 毫秒 36 | } catch (Exception e) { 37 | } 38 | callback.printFinished("打印完成"); 39 | } 40 | } 41 | ```` 42 | 43 | ```` 44 | People.java 45 | 46 | public class People { 47 | 48 | Printer printer = new Printer(); 49 | 50 | /* 51 | * 同步回调 52 | */ 53 | public void goToPrintSyn(Callback callback, String text) { 54 | printer.print(callback, text); 55 | } 56 | 57 | /* 58 | * 异步回调 59 | */ 60 | public void goToPrintASyn(Callback callback, String text) { 61 | new Thread(new Runnable() { 62 | public void run() { 63 | printer.print(callback, text); 64 | } 65 | }).start(); 66 | } 67 | } 68 | ```` 69 | 70 | ```` 71 | Main.java 72 | 73 | public class Main {//测试类,同步回调 74 | public static void main(String[] args) { 75 | People people = new People(); 76 | Callback callback = new Callback() { 77 | @Override 78 | public void printFinished(String msg) { 79 | System.out.println("打印机告诉我的消息是 ---> " + msg); 80 | } 81 | }; 82 | System.out.println("需要打印的内容是 ---> " + "打印一份简历"); 83 | people.goToPrintSyn(callback, "打印一份简历"); 84 | System.out.println("我在等待 打印机 给我反馈"); 85 | } 86 | } 87 | ```` 88 | 89 | 90 | ![同步回调](http://upload-images.jianshu.io/upload_images/2585384-38d968042a37386f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 91 | 92 | 93 | ```` 94 | Main.java 95 | 96 | public class Main {//异步回调 97 | public static void main(String[] args) { 98 | People people = new People(); 99 | Callback callback = new Callback() { 100 | @Override 101 | public void printFinished(String msg) { 102 | System.out.println("打印机告诉我的消息是 ---> " + msg); 103 | } 104 | }; 105 | System.out.println("需要打印的内容是 ---> " + "打印一份简历"); 106 | people.goToPrintASyn(callback, "打印一份简历"); 107 | System.out.println("我在等待 打印机 给我反馈"); 108 | } 109 | } 110 | ```` 111 | 112 | 113 | ![异步回调](http://upload-images.jianshu.io/upload_images/2585384-2b0788ad5d711c05.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 114 | -------------------------------------------------------------------------------- /JavaNote/Java相关/Java注解的编写与Java的反射机制.md: -------------------------------------------------------------------------------- 1 | > 最近我的助理,“娃娃”问了我一次注解应该怎么写,我想注解应该太简单了吧,从我刚开始看java代码就能看到的@Override,到后来经常挺行我加上的@SuppressWarnings,当然这些注解我们用起来,看起来都是非常简单的。并且我本人是做安卓开发的,经常用各种注解框架做界面的绑定,我就简单的说了几句,利用反射啊,不改变代码逻辑,可以起到拦截的作用啊等等。但是当我自己想从头想写一个的时候,就发现并非那么简单了,经过了几个小时的研究,打算写这么一篇文章记录一下。 2 | 3 | 4 | 想了解注解标签的原理,我们最好先搞懂,java的反射机制。所以可能这篇文章的篇幅较长,希望大家可以认真的读完。 5 | 6 | > JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。 7 | 8 | 以上内容,来自百度文库。 9 | 10 | 用我的话说java的反射机制就是,可以通过程序的编写,让程序在运行时加载使用一个全新的Classes。 11 | 12 | Java反射机制主要提供了以下功能: 13 | 在运行时判断任意一个对象所属的类,在运行时构造任意一个类的对象,在运行时判断任意一个类所具有的成员变量和方法,在运行时调用任意一个对象的方法,生成动态代理。 14 | 15 | 16 | 在java中,所有类的基类是Object类,在这个函数中为我们提供了getClass()的方法。这个Class是一个非常特殊的类,由于我对这部分知识理解的也不是很到位,下面借用一下百度百科的解释吧: 17 | > Class 类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象。如果您想借由“修改Java标准库源码”来观察Class 对象的实际生成时机(例如在Class的constructor内添加一个println()),这样是行不通的!因为Class并没有public constructor。 18 | 19 | 20 | 下面我们便可以开始手动实现一个注解了: 21 | 22 | 23 | 24 | ```` 25 | package annotation; 26 | 27 | import java.lang.annotation.ElementType; 28 | import java.lang.annotation.Retention; 29 | import java.lang.annotation.RetentionPolicy; 30 | import java.lang.annotation.Target; 31 | 32 | @Target(ElementType.METHOD) 33 | @Retention(RetentionPolicy.RUNTIME) 34 | public @interface Reflect { 35 | String name() default "sunguoli"; 36 | } 37 | 38 | ```` 39 | 40 | ```` 41 | package annotation; 42 | 43 | import java.lang.reflect.Method; 44 | 45 | public class ReflectProcessor { 46 | public void parseMethod(final Class clazz) throws Exception { 47 | final Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {}); 48 | final Method[] methods = clazz.getDeclaredMethods(); 49 | for (final Method method : methods) { 50 | final Reflect my = method.getAnnotation(Reflect.class); 51 | if (null != my) { 52 | method.invoke(obj, my.name()); 53 | } 54 | } 55 | } 56 | } 57 | 58 | 59 | ```` 60 | 61 | 62 | ```` 63 | package annotation; 64 | 65 | public class ReflectTest { 66 | 67 | @Reflect 68 | public static void sayHello(final String name) { 69 | System.out.println("==>> Hi, " + name + " [sayHello]"); 70 | } 71 | 72 | @Reflect(name = "AngelaBaby") 73 | public static void sayHelloToSomeone(final String name) { 74 | System.out.println("==>> Hi, " + name + " [sayHelloToSomeone]"); 75 | } 76 | 77 | public static void main(final String[] args) throws Exception { 78 | final ReflectProcessor relectProcessor = new ReflectProcessor(); 79 | relectProcessor.parseMethod(ReflectTest.class); 80 | } 81 | } 82 | 83 | 84 | ```` 85 | 86 | 以上我们的接口就写完了~下面看一下运行结果~ 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ![展示图.png](http://upload-images.jianshu.io/upload_images/2585384-a61b2ff9c871718d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 97 | 98 | 99 | 这样我们就测试了我们的注解,主要利用的是java的反射,在反射过程中调用了装载的方法就可以啦~ 100 | 101 | > 当然啊,我知道我们这里面调用的反射只能算是反射的原理和最基本的概念,我写出来的注解,都是相当的水的注解,和各种框架中的注解几乎不可能相提并论,我最近也很留意这方面的源码,有别的收货还是会分享出来的~~ 102 | -------------------------------------------------------------------------------- /JavaNote/设计模式相关/单例模式.md: -------------------------------------------------------------------------------- 1 | #单例模式 2 | 3 | > 单例模式,顾名思义,就是我们的代码中只实例化出一个对象,就是单例模式,有的人说,为什么用单例模式啊,这个很简单,因为有一些时候,不是所有的对象都可以被实例化多次的,因为,无论是内存空间的大小,和逻辑关系上面都是不允许的,举个最简单的例子,有一件事情,需要皇上去做,但是由于客观事实的要求,我们只能有且只有一个皇上,所以这个时候一定要控制,对象数量的个数,不能够超过1,以下我会为大家介绍单例模式怎么实现。 4 | 5 | 单例模式,比较重要的应用是,我们可以确保我们的线程安全,因为如果我们用多个对象来操作我们的线程池的时候是很为危险的。 6 | 7 | 单例模式,同时又会分为饿汉模式,和懒汉模式,他们有一点不同,饿汉模式是在初始化类的时候,就把对象声明好,而懒汉模式,则是在调用的时候再去声明这个对象,不过他们起到的效果是一样的,就是都能确保对象的唯一性。 8 | 9 | 饿汉模式: 10 | ```` 11 | 12 | public class Singleton { 13 | private Singleton() { 14 | } 15 | private static Singleton iSingleton = new Singleton(); 16 | public static Singleton getInstance() { 17 | return iSingleton; 18 | } 19 | } 20 | 21 | public class Test { 22 | public static void main(String[] args) { 23 | Singleton aSingleton = Singleton.getInstance(); 24 | Singleton bSingleton = Singleton.getInstance(); 25 | if (aSingleton==bSingleton) { 26 | System.out.println(true); 27 | }else { 28 | System.out.println(false); 29 | } 30 | } 31 | } 32 | 33 | ```` 34 | 35 | 当我们将构造方法,变成私有之后,便不能够再通过new的方法来创造对象了,但是我们有暴露一个接口,让用户来获取我们已经写好了对象,这样就能够确保我们所有用到的所有的对象,都是同一个对象了,当然我还加上了测试,当然最终的结果肯定是true了。 36 | 37 | ---- 38 | 39 | 懒汉模式: 40 | > 懒汉模式和饿汉模式不同的仅仅是,当第一个人访问这个对象的时候,这个对象是不存在的,只需要新建一个这个对象就可以了。 41 | 42 | ```` 43 | 44 | public class Singleton2 { 45 | private Singleton2() { 46 | } 47 | private static Singleton2 singleton2; 48 | public static Singleton2 getInstance() { 49 | if (singleton2 == null) { 50 | singleton2 = new Singleton2(); 51 | } 52 | return singleton2; 53 | } 54 | } 55 | 56 | ```` 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /JavaNote/设计模式相关/单例模式的四种实现方式.md: -------------------------------------------------------------------------------- 1 | # 单例模式的四种实现方式 2 | 3 | > 原本单纯的我一直认为这世界上的单例模式,只有饿汉和懒汉呢,今天发现了,原来单例模式有四种实现方式。 4 | 5 | 6 | ## 饿汉模式 7 | 8 | ``` 9 | public class Singleton { 10 | /** 11 | * 饿汉式 12 | */ 13 | private Singleton() { 14 | 15 | } 16 | 17 | private static final Singleton SINGLETON = new Singleton(); 18 | 19 | public static Singleton getInstance(){ 20 | return SINGLETON; 21 | } 22 | 23 | public void system(){ 24 | System.out.println("---lin---> singleton"); 25 | } 26 | 27 | } 28 | 29 | ``` 30 | 31 | ## 懒汉模式 32 | 33 | ``` 34 | public class Singleton2 { 35 | /** 36 | * 懒汉式 37 | */ 38 | private Singleton2() { 39 | 40 | } 41 | 42 | private static Singleton2 singleton2 = null; 43 | 44 | public static Singleton2 getInstance() { 45 | if (singleton2 == null) { 46 | synchronized (Singleton.class) { 47 | if (singleton2 == null) { 48 | singleton2 = new Singleton2(); 49 | } 50 | } 51 | } 52 | return singleton2; 53 | } 54 | 55 | public void system(){ 56 | System.out.println("---lin---> singleton2"); 57 | } 58 | } 59 | 60 | ``` 61 | 62 | ## 枚举模式 63 | 64 | ``` 65 | public enum Singleton3 { 66 | INSTANCE; 67 | private Singleton3(){ 68 | 69 | } 70 | 71 | public void system(){ 72 | System.out.println("---lin---> singleton3"); 73 | } 74 | } 75 | 76 | ``` 77 | 78 | ## Holder模式 79 | 80 | ``` 81 | public class Singleton4 { 82 | /** 83 | * 带有Holder的方式 84 | * 类级内部类,也就是静态的成员内部类,该内部类的实例与外部类的实例没有绑定关系 85 | * 只有被调用的时候才会装在,从而实现了延迟加载 86 | */ 87 | private Singleton4() { 88 | 89 | } 90 | 91 | private static class SingletonHolder { 92 | /** 93 | * 静态初始化器,由JVM来保证线程安全 94 | */ 95 | public static final Singleton4 INSTANCE = new Singleton4(); 96 | } 97 | 98 | public static Singleton4 getInstance() { 99 | return SingletonHolder.INSTANCE; 100 | } 101 | 102 | public void system() { 103 | System.out.println("---lin---> singleton4"); 104 | } 105 | } 106 | ``` 107 | 108 | -------------------------------------------------------------------------------- /JavaNote/设计模式相关/设计模式概括.md: -------------------------------------------------------------------------------- 1 | #设计模式 2 | > 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。 3 | 4 | 设计模式总共有六大原则: 5 | 1、开闭原则(Open Close Principle) 6 | 7 | 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。 8 | 9 | 2、里氏代换原则(Liskov Substitution Principle) 10 | 11 | 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科 12 | 13 | 3、依赖倒转原则(Dependence Inversion Principle) 14 | 15 | 这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。 16 | 17 | 4、接口隔离原则(Interface Segregation Principle) 18 | 19 | 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。 20 | 21 | 5、迪米特法则(最少知道原则)(Demeter Principle) 22 | 23 | 为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。 24 | 25 | 6、合成复用原则(Composite Reuse Principle) 26 | 27 | 原则是尽量使用合成/聚合的方式,而不是使用继承。 28 | 29 | 我们广泛使用的设计模式,大概有23种,这些设计模式,可以使我们的代码复用变得更简单,可以使我们的代码的可读性变得更好,所有我们应该掌握这些设计模式,接下来,我的文章中,会着重介绍这些设计模式。 30 | 31 | > 参考文章:http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html 32 | -------------------------------------------------------------------------------- /MacNote/Mac平台重新设置MySQL的root密码.md: -------------------------------------------------------------------------------- 1 | ```` 2 | 3 | 4 | 1. 停止 mysql server. 通常是在 '系统偏好设置' > MySQL > 'Stop MySQL Server' 5 | 2. 打开终端,输入:sudo /usr/local/mysql/bin/mysqld_safe --skip-grant-tables 6 | 3. sudo /usr/local/mysql/bin/mysql -u root 7 | 4. UPDATE mysql.user SET authentication_string=PASSWORD('新密码') WHERE User='root'; 8 | 5. 重启mysql 9 | ```` 10 | -------------------------------------------------------------------------------- /MacNote/SSH原理与应用.md: -------------------------------------------------------------------------------- 1 | # SSH原理与应用 2 | 3 | ssh在程序员的生活中还是非常常见的,ssh具有很多种功能,也可以用在很多种场合。 4 | 5 | ## 什么是SSH 6 | 7 | SSH是一种网络协议,用于计算机之间的加密登录 8 | 9 | 当我们在一台电脑上面,运用ssh登录了另一台计算机,我们便可以认为,这种登录是安全的了,因为即使中途被截获,我们的密码也不会泄漏。 10 | 11 | 最早的时候,互联网通信都是明文通信,一旦被截获,内容就暴露无疑。1995年,芬兰学者Tatu Ylonen设计了SSH协议,将登录信息全部加密,成为互联网安全的一个基本解决方案,迅速在全世界获得推广,目前已经成为Linux系统的标准配置。 12 | 13 | 需要指出的是,SSH只是一种协议,存在多种实现,既有商业实现,也有开源实现。本文针对的实现是OpenSSH,它是自由软件,应用非常广泛。 14 | 15 | 此外,本文只讨论SSH在Linux Shell中的用法。如果要在Windows系统中使用SSH,会用到另一种软件PuTTY,这需要另文介绍。 16 | 17 | ## 用法 18 | 19 | 1. 登录远程服务器 20 | ``ssh root@host`` 21 | 22 | 2. 如果当前用户与远程用户同名 23 | ``ssh host`` 24 | 25 | 3. ssh默认的端口是22,如果我们要修改登录的默认端口 26 | ``ssh -p xx root@host`` 27 | 28 | ## 中间人攻击 29 | 30 | ssh采用的是非对称加密,也就是要采用公钥和私钥的方式进行加密。 31 | 32 | 整个通信的过程是这样的: 33 | 1. 远程主机收到用户的登录请求,将公钥发送给用户 34 | 2. 用户使用这个公钥,将登录的密码进行加密,发送给后台 35 | 3. 远程主机,用自己的私钥进行解密,判断用户名密码是否正确 36 | 37 | 整个过程看起来是很完美的,但是容易产生一种中间人攻击的现象: 38 | 39 | 我们发送出去的登录的信息,被中途截获了,一个中间人,将他的公钥发送过来,这样用户加密之后,他便可以用自己的私钥解密了,这样他就拥有了我们的密码,并且可以一直在中间监听我们的通话。 40 | 41 | 当然,这是基于口令的通信方式,我们也可以采用基于密钥的加密方式: 42 | 43 | 第二种级别(基于密匙的安全验证)需要依靠密匙,也就是你必须为自己创建一对密匙,并把公用密匙放在需要访问的服务器上。 如果你要连接到SSH服务器上,客户端软件就会向服务器发出请求,请求用你的密匙进行安全验证。服务器收到请求之后,先在你在该服务器的家目录下寻找你的公用密匙,然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致,服务器就用公用密匙加密“质询”(challenge)并把它发送给客户端软件。客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。 44 | 45 | 这样我们便可以防止中间人攻击的现象了。 46 | 47 | 48 | ## 口令登录 49 | 50 | ``` 51 | $ ssh user@host 52 | The authenticity of host 'host (12.18.429.21)' can't be established. 53 | RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d. 54 | Are you sure you want to continue connecting (yes/no)? 55 | ``` 56 | 这段话的意思是,无法确认host主机的真实性,只知道它的公钥指纹,问你还想继续连接吗? 57 | 所谓"公钥指纹",是指公钥长度较长(这里采用RSA算法,长达1024位),很难比对,所以对其进行MD5计算,将它变成一个128位的指纹。上例中是98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d,再进行比较,就容易多了。 58 | 很自然的一个问题就是,用户怎么知道远程主机的公钥指纹应该是多少?回答是没有好办法,远程主机必须在自己的网站上贴出公钥指纹,以便用户自行核对。 59 | 假定经过风险衡量以后,用户决定接受这个远程主机的公钥。 60 | 61 | ``` 62 | Are you sure you want to continue connecting (yes/no)? yes 63 | ``` 64 | 系统会出现一句提示,表示host主机已经得到认可。 65 | ``` 66 | Warning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts. 67 | ``` 68 | 然后,会要求输入密码。 69 | ``` 70 | Password: (enter password) 71 | ``` 72 | 如果密码正确,就可以登录了。 73 | 当远程主机的公钥被接受以后,它就会被保存在文件$HOME/.ssh/known_hosts之中。下次再连接这台主机,系统就会认出它的公钥已经保存在本地了,从而跳过警告部分,直接提示输入密码。 74 | 每个SSH用户都有自己的known_hosts文件,此外系统也有一个这样的文件,通常是/etc/ssh/ssh_known_hosts,保存一些对所有用户都可信赖的远程主机的公钥。 75 | 76 | 77 | 78 | ## 公钥登录 79 | 80 | 使用密码登录,每次都必须输入密码,非常麻烦。好在SSH还提供了公钥登录,可以省去输入密码的步骤。 81 | 82 | 所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。 83 | 这种方法要求用户必须提供自己的公钥。如果没有现成的,可以直接用ssh-keygen生成一个: 84 | 85 | ``` 86 | $ ssh-keygen 87 | ``` 88 | 89 | 运行上面的命令以后,系统会出现一系列提示,可以一路回车。其中有一个问题是,要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。 90 | 运行结束以后,在$HOME/.ssh/目录下,会新生成两个文件:id_rsa.pub和id_rsa。前者是你的公钥,后者是你的私钥。 91 | 这时再输入下面的命令,将公钥传送到远程主机host上面: 92 | 93 | ``` 94 | $ ssh-copy-id user@host 95 | ``` 96 | 97 | 好了,从此你再登录,就不需要输入密码了。 98 | 如果还是不行,就打开远程主机的/etc/ssh/sshd_config这个文件,检查下面几行前面"#"注释是否取掉。 99 | 100 | ``` 101 | RSAAuthentication yes 102 | PubkeyAuthentication yes 103 | AuthorizedKeysFile .ssh/authorized_keys 104 | ``` 105 | 106 | 然后,重启远程主机的ssh服务。 107 | 108 | ``` 109 | // ubuntu系统 110 |   service ssh restart 111 |   // debian系统 112 |   /etc/init.d/ssh restart 113 | ``` 114 | 115 | ## authorized_keys文件 116 | 117 | 远程主机将用户的公钥,保存在登录后的用户主目录的$HOME/.ssh/authorized_keys文件中。公钥就是一段字符串,只要把它追加在authorized_keys文件的末尾就行了。 118 | 119 | 这里不使用上面的ssh-copy-id命令,改用下面的命令,解释公钥的保存过程: 120 | 121 | ``` 122 | $ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub 123 | ``` 124 | 125 | 这条命令由多个语句组成,依次分解开来看:(1)"$ ssh user@host",表示登录远程主机;(2)单引号中的mkdir .ssh && cat >> .ssh/authorized_keys,表示登录后在远程shell上执行的命令:(3)"$ mkdir -p .ssh"的作用是,如果用户主目录中的.ssh目录不存在,就创建一个;(4)'cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub的作用是,将本地的公钥文件~/.ssh/id_rsa.pub,重定向追加到远程文件authorized_keys的末尾。 126 | 写入authorized_keys文件后,公钥登录的设置就完成了。 127 | 128 | ## 配置ssh config 129 | 130 | ``` 131 | vi ~/.ssh/config 132 | 133 | 134 | // 文件内容如下 135 | 136 | Host js //别名, 可以直接执行 ssh js 137 | 138 | HostName 172.16.6.84 //Host别名指向的服务器 IP 139 | 140 | User zhangsan //登录所用的用户名 141 | 142 | PreferredAuthentications publickey //鉴权方式 143 | 144 | IdentityFile ~/.ssh/zhangsan.pem //认证所需的密钥 145 | ``` 146 | 这样我们便可以通过``ssh js``来代替曾经的``ssh xxx@111.11.11.11`` 147 | 并且采用公钥+私钥的加密方式,不用输入密码,非常的方便。 148 | 149 | 150 | ## 参考文献 151 | 152 | - [SSH原理与运用](http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html) 153 | 154 | - [网络安全协议比较](http://blog.csdn.net/shizhixin/article/details/42459265) 155 | -------------------------------------------------------------------------------- /MacNote/mac上常用命令.md: -------------------------------------------------------------------------------- 1 | # 记录一些mac上面常用的命令 2 | 3 | - 查看端口占用情况: ``lsof -i:5001`` 4 | 5 | - kill掉进程: `` kill -15/-9 6327`` 6 | 7 | - 查看/取消 隐藏文件命令 8 | 9 | OS X(10.6~10.8) : 10 | ``defaults write com.apple.Finder AppleShowAllFiles Yes && killall Finder``//显示隐藏文件 11 | 12 | ``defaults write com.apple.Finder AppleShowAllFiles No && killall Finder`` //不显示隐藏文件 13 | 14 | OS X 10.9+ : 15 | ``defaults write com.apple.finder AppleShowAllFiles Yes && killall Finder`` //显示隐藏文件 16 | 17 | ``defaults write com.apple.finder AppleShowAllFiles No && killall Finder`` //不显示隐藏文件 18 | 19 | - 根绝关键字查询占用情况: ``ps -ef|grep gradle`` 20 | 21 | - 根绝关键字查询占用情况2: ``netstat -an|grep 8080`` 22 | 23 | - 查看iterm2的配置: ``nano .zshrc`` 两个我比较喜欢的主题:1.jonathan 2.robbyrussell 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /MacNote/mac本地生成ssh-key.md: -------------------------------------------------------------------------------- 1 | 进入当前路径 2 | ``` 3 | /Users/mac/.ssh/ 4 | ``` 5 | 6 | 7 | ![拥有id_rsa.pub](http://upload-images.jianshu.io/upload_images/2585384-4dfd502b058245c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 8 | 9 | 如果没有这个文件的话: 10 | ``` 11 | ssh-keygen -t rsa -C "youremail@example.com" 12 | ``` 13 | 14 | 然后会有几个提示的问题,都可以不用管,直接回车就可以,完成之后这里面就会生成公钥。 15 | 16 | 如果要是github要配置的话,只需要在账户的setting里面进行设置就可以(将.pub文件用文本阅读工具打开,然后全部复制粘贴到new ssh key就好),如下图: 17 | 18 | 19 | ![成功后的效果图](http://upload-images.jianshu.io/upload_images/2585384-aed9e786307f6fc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 20 | -------------------------------------------------------------------------------- /MacNote/mac终端与服务器保持连接.md: -------------------------------------------------------------------------------- 1 | > mac的终端与远程服务器连接之后,可能由于一段时间没有与服务器进行数据上的交互便断开了连接,现在可以利用如下的方法,保持终端与远程服务器的长期连接。 2 | 3 | 编辑“ssh_config”文件: 4 | 5 | ``` 6 | sudo vi /etc/ssh/ssh_config 7 | 8 | ``` 9 | 10 | 在Host *   下面加入: 11 | ``` 12 | ServerAliveInterval 60 13 | ``` 14 | 15 | 最后用``:wq!``保存并退出即可。 16 | 17 | > 这句话的含义是,每隔60s客户端向服务器发送一个空包,这样就可以保持连接不被断开了。 18 | 19 | 20 | ![结果图](http://upload-images.jianshu.io/upload_images/2585384-9f41d4ffabedc4dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 21 | -------------------------------------------------------------------------------- /MacNote/nodejs与npm的更新.md: -------------------------------------------------------------------------------- 1 | 1 . 检查 Node的当前版本,使用命令 2 | 3 | ```` 4 | node -v 5 | ```` 6 | 7 | ---- 8 | 9 | 2 . 检查 npm的当前版本,使用命令 10 | 11 | ```` 12 | npm -v 13 | ```` 14 | ---- 15 | 16 | 3 .npm的更新 17 | 18 | ```` 19 | sudo npm install -g latest 20 | ```` 21 | ---- 22 | 23 | 4 .清除npm cache 24 | 25 | ```` 26 | sudo npm cache clean -f 27 | ```` 28 | ---- 29 | 30 | 5 .安装n模块 31 | 32 | ```` 33 | sudo npm install -g n 34 | ```` 35 | ---- 36 | 37 | 6 .升级到最新版本 38 | 39 | ```` 40 | 升级到制定版本: 41 | sudo n 6.5.11 42 | 升级到最新的稳定版: 43 | sudo n stable 44 | ```` 45 | ---- 46 | 47 | 7.成功之后,可以用第一条和第二条来检测一下版本即可。 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /MacNote/paw-for-mac.md: -------------------------------------------------------------------------------- 1 | >paw: Easily build your HTTP requests, send and inspect server responses. Setup HTTP headers, URL parameters, JSON, form URL-encoded, or multipart body. Organize your requests in collection files, and generate client code. 2 | 3 | >Paw HTTP Client 是一款Mac上的HTTP客户端模拟测试工具,可以让Web开发者设置各种请求Header和参数,模拟发送HTTP请求,测试响应数据,支持OAuth, HTTP Basic Auth, Cookies等,这对于开发Web服务的应用很有帮助,非常实用的一款Web开发辅助工具。 4 | 5 | >下载链接:http://www.pc6.com/mac/182304.html 6 | 7 | ![软件实用截图1](http://upload-images.jianshu.io/upload_images/2585384-d4fb2aa69af1f7c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 8 | 9 | 10 | ![软件使用截图2](http://upload-images.jianshu.io/upload_images/2585384-81b67d7146326ef8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | 12 | 13 | 在本软件中我们可以建立不同的项目,然后每个项目下面建立不同的请求,该软件支持所有的请求方式,也可以添加cookie等非常实用的功能,本软件非常适合web开发者,和一些经常测试网络接口的人。 14 | -------------------------------------------------------------------------------- /MacNote/一些mac上面安装环境的指令.md: -------------------------------------------------------------------------------- 1 | > 以前,自己总是喜欢在配置服务器的过程中,用到什么命令上网找什么命令,感觉这样也挺方便的,但是随着做的东西的深入,和难度的增加,感觉总这样有点力不从心了,而且不知道为什么感觉Google的速度越来越慢了,所以打算写一篇文章,记住这些命令。 2 | 3 | 1. 在mac上面安装brew 4 | **brew** 全称Homebrew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件。 5 | 6 | ```` 7 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 8 | 9 | ```` 10 | -------------------------------------------------------------------------------- /MacNote/如何在mac上安装java1-8.md: -------------------------------------------------------------------------------- 1 | 1.首先应该检测一下目前电脑上拥有的java的版本: 2 | ```` 3 | /usr/libexec/java_home -V 4 | ```` 5 | ---- 6 | 7 | 2.如果已经有java8了,就可以直接跳转到第四步: 8 | 9 | ![拥有java1.8](http://upload-images.jianshu.io/upload_images/2585384-067a0e7b037c14dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 10 | 11 | ---- 12 | 13 | 3.没有java1.8,需要上网下载java1.8: 14 | 下载链接:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 15 | 16 | ---- 17 | 18 | 4.配置当前环境: 19 | ```` 20 | vi .bash_profile #打开配置的文件 21 | source .bash_profile #当配置完成后运行,让配置生效 22 | ```` 23 | 配置语句,可以参考我的这个: 24 | ```` 25 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home 26 | export PATH=$COCOS_CONSOLE_ROOT:$JAVA_HOME/bin:$PATH:. 27 | export CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:. 28 | ```` 29 | 30 | 给大家截个图看一下吧: 31 | 32 | ![配置截图](http://upload-images.jianshu.io/upload_images/2585384-a010212ee14ef7e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 33 | 34 | 当配置完千万要记得,要运行上面那条语句让整个文件生效。 35 | 36 | 5.检查当前java版本: 37 | ```` 38 | java -version 39 | ```` 40 | 就能看到我们配置的版本啦~ 41 | ```` 42 | java version "1.8.0_121" 43 | Java(TM) SE Runtime Environment (build 1.8.0_121-b13) 44 | Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode) 45 | ```` 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /MacNote/项目中遇到的单词.md: -------------------------------------------------------------------------------- 1 | # 项目中遇到的单词 2 | 3 | 4 | | 单词 | 含义 | 5 | | --- | --- | 6 | | Compensate | 报酬 | 7 | |force|强迫 8 | |gas|汽油 气体 9 | |economy|节约 经济 理财 10 | |reform|n.改正; 改革,改良 --- vt.重新组成; 改革,革新 11 | |wedding|n.结合; 结婚 --- v. 与…结婚 12 | |western|adj.有西方特征的; 西方的,西部的 --- n. 西方人;西部片,西部小说 13 | |bride|n. 新娘;[英俚]姑娘,女朋友 14 | |ceremony|n. 典礼,仪式;礼节,礼仪;客套,虚礼 15 | |traditional|传统 16 | |funeral|n. 葬礼;[口]麻烦事 --- adj. 丧葬的,出殡的 17 | |envelope|n. 信封,封皮;[生]包膜;[天]包层;[数]包迹 18 | |distribute|vt. 分配, 分给 --- 散发; 散播; 分布 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ReactNative相关/ReactNative入门.md: -------------------------------------------------------------------------------- 1 | # ReactNative入门 2 | 3 | ## 简介 4 | 5 | ReactNative旨在开发过程的高效,它具有一些原生App所不具备的优势,它的跨平台性非常强,代码的复用程度非常的高,并且可以具有热更新的能力,其次就是它的社区也在日趋的强大起来。 6 | 7 | ## 为什么选择ReactNative 8 | 9 | 纵观国内的比较大的厂商并没有完全的转移到React Native,但也没有完全的排斥说不搞React Native,所以学习这样一个新的技能对我们有百利而无一害,在学习React Native的过程中,我们也会接触到React这样一个新的UI方案,同时对一些ES6,ES7新的特性也都会有接触,总体感觉还不错~ 10 | 11 | ## 入门 12 | 13 | 学习React Native最好还是要有一些React JavaScript 以及原生应用的开发经验,当然没有也无妨,慢慢一步一步的来嘛。 14 | 15 | **目前我介绍的是针对Android端介绍的,后续也会把Ios相关的内容补上。** 16 | 17 | 需要有的软件: 18 | 19 | - Node.js 20 | - Android studio 21 | - Watchman 22 | 23 | 运行: 24 | 25 | ``` 26 | react-native init AwesomeProject 27 | cd AwesomeProject 28 | react-native run-android 29 | ``` 30 | 31 | ## 知识点 32 | 33 | **项目的入口:** index.android.js / index.ios.js 34 | 35 | **视图呈现:** 36 | 37 | ``` 38 | render() { 39 | return ( 40 | 41 | 42 | AAA 43 | 44 | BBB/Text> 45 | CCC 46 | 47 | 48 | { 49 | }}/> 50 | 51 | 52 | ); 53 | } 54 | ``` 55 | 56 | 我们的视图需要写在return()下面,并且这个下面只能有一个根View 57 | 58 | **抽出公共控件:** 59 | 60 | 在界面中,可能我们有很多控件是可以复用的,如果我们不把它们抽出来的话,可能就会导致代码的臃肿,并且不是很利于维护,所以我们应该将公共部分抽出来。 61 | 62 | **将公用的style抽象出来** 63 | 64 | 同上 65 | 66 | ## 推荐编译器 67 | 68 | - 需要测试性能的时候需要使用Android studio 69 | - 与性能无关时,编写代码时还是推荐使用WebStorm编写代码 70 | 71 | 72 | 好了,就先介绍到这里吧,在等到我有时间的时候,可以介绍一下,手写一个tab,或者实现登录注册的全部逻辑(包含网络框架)。 73 | -------------------------------------------------------------------------------- /ReactNative相关/ReactNative利用CodePush实现热更新.md: -------------------------------------------------------------------------------- 1 | # ReactNative利用CodePush实现热更新 2 | 3 | 4 | ReactNative的优势之一就是它可以进行热更新,需要热更新的应用场景有很多种,例如: 5 | 6 | - 各大应用市场审核时间过长 7 | - 一些重业务逻辑,经常需要更新的页面 8 | - 一个较为庞大的App,可能每一块都由一个部门负责,不能每个部门一有变动就推一个新的版本 9 | - 如果app走自己平台的cdn的时候,需要考虑流量的成本 10 | 11 | 下面我们说一下,在ReactNative项目中如何接入CodePush,如果英语还ok的小伙伴,可以直接照着英文文档撸一遍[文档地址](https://github.com/Microsoft/react-native-code-push/blob/master/docs/setup-android.md)。 12 | 13 | ## CodePush 14 | 15 | - 直接对用户部署代码更新 16 | - 管理 Alpha,Beta 和生产环境应用 17 | - 支持 React Native 和 Cordova 18 | - 支持JavaScript 文件与图片资源的更新 19 | 20 | ### 安装CodePush CLI 21 | 22 | 在终端输入命令:``npm install -g code-push-cli`` 23 | 24 | ### 创建CodePush账号 25 | 26 | 在终端输入命令:``code-push register`` 27 | 28 | 命令输入完之后,会弹出网页,我们可以在上面注册账号登录,也可以直接用GitHub登录,登录之后,网页会返回给我们一个key,我们把这个key再粘贴回命令行就可以了。 29 | 30 | ### 登录 31 | 32 | 登录:``code-push login`` 33 | 34 | 其它相关命令:``code-push logout`` 35 | 36 | 列出登录的token:``code-push access-key ls`` 37 | 38 | 删除某个key:``code-push access-key rm `` 39 | 40 | ### 向CodePush注册app 41 | 42 | Android与ios需要注册两次: 43 | 44 | ``` 45 | code-push app add MyApp-Android 46 | code-push app add MyApp-ios 47 | ``` 48 | 49 | 注册之后会返回key,这个key我们需要保存一下 50 | 51 | ### 在项目中集成CodePush SDk 52 | 53 | 在项目的根目录运行:``npm install --save react-native-code-push`` 54 | 55 | 在Android目录运行:``npm i -g rnpm`` //一般来说我们的React里面都会有,在V0.27以后 56 | 57 | 在Android中添加配置文件:``rnpm link react-native-code-push`` 58 | 59 | 运行这条命令的过程中,它会让我们输入key,就是刚刚保存的那个,当然不输入也可以 60 | 61 | ### 配置Android工程 62 | 63 | 需要在``android/app/build.gradle``检查是否有如下内容,如果没有则添加: 64 | 65 | ``` 66 | apply from: "../../node_modules/react-native/react.gradle" 67 | apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" 68 | 69 | 70 | ... 71 | dependencies { 72 | ... 73 | compile project(':react-native-code-push') 74 | } 75 | ``` 76 | 77 | 需要在``android/settings.gradle``检查是否有如下内容,如果没有则添加: 78 | 79 | ``` 80 | include ':app', ':react-native-code-push' 81 | project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') 82 | ``` 83 | 84 | ### 获取部署的秘钥 85 | 86 | 运行:``code-push deployment ls Daily -k`` 87 | 88 | ### 进行配置 89 | 90 | 在``MainApplication.java``中添加如下代码: 91 | 92 | ``` 93 | ... 94 | // 1. Import the plugin class. 95 | import com.microsoft.codepush.react.CodePush; 96 | 97 | public class MainApplication extends Application implements ReactApplication { 98 | 99 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 100 | ... 101 | // 2. Override the getJSBundleFile method in order to let 102 | // the CodePush runtime determine where to get the JS 103 | // bundle location from on each app start 104 | @Override 105 | protected String getJSBundleFile() { 106 | return CodePush.getJSBundleFile(); 107 | } 108 | 109 | @Override 110 | protected List getPackages() { 111 | // 3. Instantiate an instance of the CodePush runtime and add it to the list of 112 | // existing packages, specifying the right deployment key. If you don't already 113 | // have it, you can run "code-push deployment ls -k" to retrieve your key. 114 | return Arrays.asList( 115 | new MainReactPackage(), 116 | new CodePush("deployment-key-here", MainApplication.this, BuildConfig.DEBUG) 117 | ); 118 | } 119 | }; 120 | } 121 | ``` 122 | 123 | ``` 124 | ... 125 | // 1. Declare your ReactNativeHost to extend ReactInstanceHolder. ReactInstanceHolder is a subset of ReactNativeHost, so no additional implementation is needed. 126 | import com.microsoft.codepush.react.ReactInstanceHolder; 127 | 128 | public class MyReactNativeHost extends ReactNativeHost implements ReactInstanceHolder { 129 | // ... usual overrides 130 | } 131 | 132 | // 2. Provide your ReactNativeHost to CodePush. 133 | 134 | public class MainApplication extends Application implements ReactApplication { 135 | 136 | private final MyReactNativeHost mReactNativeHost = new MyReactNativeHost(this); 137 | 138 | @Override 139 | public void onCreate() { 140 | CodePush.setReactInstanceHolder(mReactNativeHost); 141 | super.onCreate(); 142 | } 143 | } 144 | ``` 145 | 146 | 这样就完事啦~ 147 | 148 | 如果您遇到什么问题,欢迎给我提issue~ 149 | -------------------------------------------------------------------------------- /ReactNative相关/ReactNative报错记录.md: -------------------------------------------------------------------------------- 1 | # React-Native 报错记录 2 | 3 | - ``xxx is not defined`` 4 | 5 | 例如:``Component is not defined`` 6 | 错误原因:在当前的js文件中没有定义或者没有引入这个组建,并且有调用的地方 7 | 8 | - ``JSX attributes must only assigned a non-empty expression(29:28)`` 9 | 10 | 错误原因:这个报错就非常简单了,已经告诉我们在哪一行了,错误原因就是,定义了之后不能为空 11 | 12 | - ``Cannot read property 'Aspect' of undefined`` 13 | 14 | 错误原因:没有在当前文件夹下运行``react-native start``,导致一些关键的东西引入不进来 15 | 16 | - ``undefined is not an object(evaluating 'Daily_Service.props.navigation')`` 17 | 18 | 错误原因:一般来说,我们这样写了代码,就是因为我们认为,可以用this,或者用到某个对象的时候,但是它本身是调用不了的。 19 | 20 | 21 | - ``java.lang.illegalStateException:WebSocket connection no longer valid`` 22 | 23 | 错误原因,``react-native start``命令执行之后意外中断了 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | - ``在View添加了onPress,但是在点击的时候,不会产生任何效果`` 32 | 33 | 错误原因:View没有这样的方法,当遇到需要这样操作的方法的时候可以加入`` `` 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ReactNative相关/短信验证码倒计时控件.md: -------------------------------------------------------------------------------- 1 | # ReactNative短信验证码倒计时控件 2 | 3 | ## 功能 4 | 5 | 根据项目的需要,需要写一个自定义的控件,实现如下功能: 6 | 7 | - 默认文字为**点击获取验证码** 8 | - 点击后出现60秒的倒计时 9 | - 颜色,字号可调 10 | - 倒计时过程中,再次点击需要忽略掉 11 | - 倒计时完成后文本恢复成**点击获取验证码** 12 | - 再几次点击同之前 13 | 14 | 15 | 其实说了这么多,就是个最普通的验证码的功能。。。 16 | 17 | ## 效果 18 | 19 | 效果图如下:(录的图片比较一般,对付着看吧) 20 | 21 | ![](https://ws2.sinaimg.cn/large/006tNc79ly1fh1x98k8h0g306w01oglo.gif) 22 | 23 | ## 实现原理 24 | 25 | 自己封装了个控件,它内部含有一个Text控件,然后我们又写了一个timer,然后负责倒计时,然后每次都需要判断一下是否继续,然后加了一个flag字段,判断是否接受下次点击事件,当倒计时结束之后还需要将初始状态重置回去即可。 26 | 27 | ## 代码 28 | 29 | ### 控件代码 30 | 31 | ``` 32 | import React, {Component } from 'react'; 33 | import { 34 | StyleSheet, 35 | Text, 36 | View, 37 | Image, 38 | TextInput, 39 | TouchableHighlight, 40 | StatusBar, 41 | Alert, 42 | AppRegistry 43 | } from 'react-native'; 44 | import LinkRow from '../components/LinkRow'; 45 | import cStyles from '../styles/CommonStyle'; 46 | 47 | import axios from 'axios'; 48 | 49 | class MyCountTime extends Component { 50 | constructor(props) { 51 | super(props); 52 | let timeLeft = this.props.timeLeft > 0 ? this.props.timeLeft : 5; 53 | let width = this.props.width || 100; 54 | let height = this.props.height || 50; 55 | let color = this.props.color || '#42A5F5'; 56 | let fontSize = this.props.fontSize || 30; 57 | let fontWeight = this.props.fontWeight || '600'; 58 | let borderColor = this.props.borderColor || '#42A5F5'; 59 | let borderWidth = this.props.borderWidth || 1; 60 | let borderRadius = this.props.borderRadius || 4; 61 | let backgroundColor = this.props.backgroundColor || '#42A5F5'; 62 | let begin = 0; 63 | let press = this.props.press; 64 | 65 | this.afterEnd = this.props.afterEnd || this._afterEnd; 66 | this.style = this.props.style; 67 | 68 | this.state = { 69 | timeLeft: timeLeft, 70 | begin: begin 71 | }; 72 | this.countTextStyle = { 73 | textAlign: 'center', 74 | color: '#42A5F5', 75 | fontSize: fontSize, 76 | fontWeight: fontWeight 77 | 78 | }; 79 | this.countViewStyle = { 80 | backgroundColor: backgroundColor, 81 | alignItems: 'center', 82 | borderColor: borderColor, 83 | borderWidth: borderWidth, 84 | borderRadius: borderRadius, 85 | width: width, 86 | height: height 87 | } 88 | } 89 | 90 | countdownfn(timeLeft, callback, begin) { 91 | if (timeLeft > 0) { 92 | this.state.begin = 1; 93 | console.log("===lin===>"); 94 | 95 | let that = this; 96 | let interval = setInterval(function () { 97 | if (that.state.timeLeft < 1) { 98 | clearInterval(interval); 99 | callback(that) 100 | } else { 101 | let totalTime = that.state.timeLeft; 102 | that.setState({ 103 | timeLeft: totalTime - 1 104 | }) 105 | } 106 | }, 1000) 107 | } 108 | } 109 | 110 | _beginCountDown() { 111 | if (this.state.begin === 1){ 112 | return; 113 | } 114 | let time = this.state.timeLeft; 115 | console.log("===lin===> time " + time); 116 | let afterEnd = this.afterEnd; 117 | let begin = this.state.begin; 118 | console.log("===lin===> start " + begin); 119 | this.countdownfn(time, afterEnd, begin) 120 | } 121 | 122 | _afterEnd(that) { 123 | console.log('------------time over'); 124 | that.setState({ 125 | begin : 0, 126 | timeLeft : 5, 127 | }) 128 | } 129 | 130 | componentDidMount() { 131 | 132 | } 133 | 134 | render() { 135 | return ( 136 | 137 | { this.state.begin === 0 ? '点击获取验证码' : this.state.timeLeft} 140 | 141 | 142 | ) 143 | } 144 | } 145 | 146 | ``` 147 | 148 | ### 应用代码 149 | 150 | ``` 151 | 152 | 153 | 154 | 155 | ``` 156 | 157 | 158 | 当然这只是,最简单的应用的代码,我们还提供了很多的自定义的属性,大家可以根据自己项目的需要,去调节这些参数。 159 | 160 | ---- 161 | 162 | 由于最近刚开始认真的搞RN,可能有一些封装的不是最佳实践,还是希望大家多提意见,和大家一起进步吧。 163 | 164 | 165 | -------------------------------------------------------------------------------- /ScriptNote/GitHub基础操作.md: -------------------------------------------------------------------------------- 1 | > 虽然以前也有一些git的基础,但是好久不用了,基本也都忘没了,而且以前用图形化界面的工具比较多,最近决定多用用命令行吧,感觉命令行还是挺好用的。 2 | 3 | 最近在mac上面装了iterm2感觉挺好用的,还支持各种扩展,挺好的。[iterm2下载链接](http://www.iterm2.com/) 4 | 5 | ---- 6 | 就不做什么过多的介绍了,github也这么多年了,还是不错的,而且今天打算记录的也是git的命令,等以后会用到更多的命令的时候也会更新这篇博客的。 7 | 8 | ---- 9 | 10 | | 命令 | 含义 | 11 | | ------------- |:-------------:| 12 | |git branch | 查看本地所有分支 | 13 | |git status |查看当前状态 | 14 | |git commit | 提交 | 15 | |git branch -a |查看所有的分支 | 16 | |git branch -r |查看远程所有分支 | 17 | |git commit -m "注释"| 提交并加注释| 18 | |git push origin master|将分支推送到服务器上| 19 | |git remote show origin|显示远程库里面的资源| 20 | |git remote -v|查看远程仓库| 21 | |git checkout dev|建立一个新的本地分支| 22 | |git merge origin/dev|将分支dev与当前分支进行合并| 23 | |git checkout dev|切换到本地dev分支| 24 | |git remote show|查看远程库| 25 | |git add .|将修改全部添加进去| 26 | |git rm 文件名|删除指定文件| 27 | |git pull|将本地代码与服务器同步| 28 | |git push origin master|将本地项目提交到服务器中| 29 | |git clone|将代码克隆到本地| 30 | |git checkout [name]|切换分支| 31 | |git push origin --delete [name]|删除远程分支| 32 | ---- 33 | 34 | ![github](http://upload-images.jianshu.io/upload_images/2585384-2a10c46f536c94a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 35 | 以上就是总结给自己看的吧,如果大家有什么疑问也可以留言问,我有空也会介绍一下原理和与其它版本控制工具的对比 36 | 37 | 38 | 较为简单的上传本地代码到GitHub: 39 | ```` 40 | git init 41 | git remote add [shortname] [url] 42 | git pull origin master 43 | git add . 44 | git commit -m "description" 45 | git push origin master 46 | ```` 47 | 48 | ``` 49 | 修改本地用户 50 | 51 | git config --global user.name "youname" 52 | git config --global user.email "youeamil@email.com" 53 | ``` 54 | 55 | 56 | -------------------------------------------------------------------------------- /ScriptNote/封装一些GitHub常用命令.md: -------------------------------------------------------------------------------- 1 | # 封装一些GitHub常用命令 2 | 3 | 4 | 我们在日常的开发过程中,肯定会经常要用到一些代码版本控制工具,其中较为常用的如GitHub,当然GitHub的命令已经比较精简了,不过依照我们每个人的个人习惯不同还是可以进行一些简单的封装的。 5 | 6 | ## 封装一些适用于**某个项目**的命令 7 | 8 | 比如说,我最近一直在维护一个开源的[Android笔记的项目](https://github.com/linsir6/AndroidNote),这样我每天可能都会有很多次的提交,每次提交可能输入的都是那么几个命令: 9 | 10 | ``` 11 | cd /Users/mac/WorkSpace/git_android_notes 12 | git pull 13 | git add . 14 | git commit -m "description" 15 | git push origin master 16 | ``` 17 | 18 | 虽然命令不是非常复杂,但是每次都需要手动输入,还是很麻烦的,所以如果我们能将其封装成一句命令就非常nice了,例如: 19 | 20 | ``` 21 | push description XXX XXX 22 | ``` 23 | 24 | 其实做这样一个封装是非常简单的,但是可以帮我们省很多事情。 25 | 如果您对Shell的基本命令还不是很了解,请参考[Shell脚本入门](https://github.com/linsir6/AndroidNote/blob/master/Shell%E8%84%9A%E6%9C%AC%E7%9B%B8%E5%85%B3/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%AD%A6%E6%87%82Shell%E8%84%9A%E6%9C%AC.md) 26 | 27 | 我们看一下,shell脚本的代码: 28 | 29 | ``` 30 | cd /Users/mac/WorkSpace/git_android_notes 31 | 32 | echo "begin it ..." 33 | 34 | git pull 35 | git add . 36 | 37 | git commit -m "$*" 38 | 39 | echo $* 40 | 41 | git push origin master 42 | 43 | echo "finish it ..." 44 | ``` 45 | 46 | 只要通过这样简单的封装,我们就可以实现我们,一行命令上传脚本的想法啦~ 47 | 48 | 49 | ## 封装一些具有普适性的代码 50 | 51 | - **进入到工作空间目录** 52 | 53 | 封装前: ``cd /Users/mac/WorkSpace`` 54 | 封装后: ``. me`` 55 | 56 | shell脚本代码: 57 | 58 | ``` 59 | cd ~/WorkSpace 60 | ``` 61 | 62 | ---- 63 | 64 | - **拉取远程仓库代码** 65 | 66 | 封装前: ``git pull 或 git pull origin XXX`` 67 | 封装后: ``pull 或 pull XXX`` 68 | 69 | shell脚本代码: 70 | 71 | ``` 72 | if [ "$1" = "" ] 73 | then 74 | git branch --set-upstream-to=origin/master master 75 | git pull 76 | 77 | else 78 | git pull origin $1 79 | fi 80 | ``` 81 | 82 | ---- 83 | 84 | - **提交代码到远程仓库** 85 | 86 | 封装前: 87 | 88 | ``` 89 | git pull 90 | git add . 91 | git commit -m "description" 92 | git push origin master 93 | ``` 94 | 95 | 封装后: ``push master "description"`` 96 | 97 | shell脚本代码: 98 | 99 | ``` 100 | git pull 101 | git add . 102 | temp=$1 103 | shift 104 | git commit -m "$*" 105 | git push origin $temp 106 | ``` 107 | 108 | ---- 109 | 110 | 当然,这里面只介绍了几种简单的封装,大家可以按照自己的需求,进行一些封装~ 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /ScriptNote/简单的Shell脚本.md: -------------------------------------------------------------------------------- 1 | # 封装一些简单的Shell脚本 2 | 3 | > 作者:https://github.com/linsir6 4 | > 5 | > 原文:http://www.jianshu.com/p/6f6330b0ab60 6 | 7 | 8 | 9 | 自从上次发完[一篇有关Shell的脚本的文章](http://www.jianshu.com/p/71cb62f08768)之后取得了很大程度的反响,阅读量达到了6280,喜欢达到了300+,同时被收入了特别多的专题,如下图所示,所以打算再展示几个我封装的简单的脚本。 10 | 11 | 12 | 13 | ![展示图](https://ws3.sinaimg.cn/large/006tKfTcly1fg143yrx6dj311g05kjrg.jpg) 14 | 15 | 16 | 17 | ![效果图](https://ws3.sinaimg.cn/large/006tKfTcly1fg145mbfayj310k0le75z.jpg) 18 | 19 | 20 | 21 | 因为一般热爱编程的人,大多选择GitHub作为代码管理工具,我本人更喜欢用命令行来操作GitHub,然后有一些常用的命令时经常被用到的,所以可以对他们进行简单的封装,这样即使每天提交个十几次代码,也不会很麻烦。 22 | 23 | 24 | 25 | 一些常用的操作: 26 | 27 | ```sh 28 | cd ~/WorkSpace 29 | 30 | git pull 31 | 32 | git pull origin master 33 | 34 | git status 35 | 36 | git branch 37 | 38 | git push origin master 39 | 40 | git checkout master 41 | 42 | git init 43 | git remote add origin url 44 | git pull 45 | 46 | git branch --set-upstream-to=origin/master master 47 | 48 | ``` 49 | 50 | 51 | 52 | 当然常用的命令肯定不止这些,不过我们只要掌握好,简单的封装之后,就可以很轻松的封装一个命令了。 53 | 54 | 如果你没有什么Shell方面的基础,不妨先看看我的另一篇文章 [一篇文章学懂Shell脚本](http://www.jianshu.com/p/71cb62f08768) ,再返回来看这篇文章。 55 | 56 | 57 | 58 | 以一个简单的为例: 59 | 60 | 1. 我们先新建一个脚本:``touch me`` 61 | 62 | 2. 给脚本权限:``chmod +x me`` 63 | 64 | 3. 然后编写指令 65 | 66 | ```SHell 67 | #!/bin/bash 68 | cd ~/WorkSpace 69 | ``` 70 | 71 | 4. 如果我们想这个命令在哪里都可以应用,需要将当前目录添加到系统目录下,或直接将脚本放在系统文件夹内 72 | 73 | 74 | 75 | 然后我们便可以在命令窗口里通过```. me```来进入我们的文件夹下面里,这里面需要加一个```. ```是因为我们要让效果展示出来,否则它会内部创建一个子脚本进入,然后退出的。 76 | 77 | ---- 78 | 79 | 80 | 81 | 自动push的脚本: 82 | 83 | ```shell 84 | #!/bin/bash 85 | git pull origin $2 86 | git add . 87 | git commit -m $1 88 | git push origin $2 89 | 90 | ``` 91 | 92 | 我们需要通过```push "fix" master```可以指定描述,指定执行上传到的分支。 93 | 94 | 95 | 96 | ---- 97 | 98 | 99 | 100 | 自动pull的脚本: 101 | 102 | ```shell 103 | #!/bin/bash 104 | if [ "$1" = "" ] 105 | then 106 | git branch --set-upstream-to=origin/master master 107 | git pull 108 | 109 | else 110 | git pull origin $1 111 | fi 112 | 113 | ``` 114 | 115 | 我们可以通过```pull```命令就可以执行```git pull```,通过```pull master```就可以将远程仓库中的master分支pull回来。 116 | 117 | 118 | 119 | ---- 120 | 121 | 122 | 123 | 创建git仓库的脚本: 124 | 125 | ```shell 126 | #!/bin/bash 127 | git init 128 | git remote add origin $1 129 | pull 130 | 131 | ``` 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /WebNote/MySQL相关/ERROR-1045-(28000)--Access-denied-for-user-'debian-sys-maint'@'localho.md: -------------------------------------------------------------------------------- 1 | > 原本linux(Ubuntu)上面安装mysql是非常简单的事情,但是我今天真是b了狗了,装个mysql,运行各种失败,希望大家以后遇到这个问题别这么难过啦。 2 | 3 | 4 | ```` 5 | ERROR 1045 (28000): Access denied for user 'ubuntu'@'localhost' (using password: YES) 6 | ERROR 1045 (28000): Access denied for user 'ubuntu'@'localhost' (using password: NO) 7 | ```` 8 | 9 | 10 | ![Paste_Image.png](http://upload-images.jianshu.io/upload_images/2585384-3889166b952adcb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 11 | 12 | 13 | ![Paste_Image.png](http://upload-images.jianshu.io/upload_images/2585384-7adc01e923ec74f7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 14 | 15 | 其实这里面提供了,我们可以登录的账号密码,只需要暂时用这个登录,然后再授权一个新用户就行。 16 | 17 | 以上便是这个问题的解决方案啦。 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WebNote/MySQL相关/Error--ER_TRUNCATED_WRONG_VALUE_FOR_FIELD.md: -------------------------------------------------------------------------------- 1 | ![错误截图](http://upload-images.jianshu.io/upload_images/2585384-23615f16a18323d9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 2 | 3 | > 形如上面的错误,发生在向mysql数据库中插入中文的时候,编码格式遇到了问题,上Stack Overflow看了一下,应该是编码格式的问题。 4 | 5 | 解决方法是改变数据库的编码格式: 6 | 7 | ``` 8 | mysql> alter database test character set gbk; 9 | ``` 10 | 这样我们数据库的编码格式就发生了改变,就可以插入中文了。但是原有已经有了的表还是不可以,因为已经有的表的编码格式还是默认的,所以需要将原有的表也改变一下: 11 | 12 | ``` 13 | mysql> alter table test character set gbk; 14 | ``` 15 | 16 | 好了,到这问题就解决啦~~ 17 | -------------------------------------------------------------------------------- /WebNote/MySQL相关/Mysql导出数据库、表(有无数据).md: -------------------------------------------------------------------------------- 1 | > 在命令行下导出数据库、表(有无数据)具体用法如下: 2 | 3 | ``` 4 | mysqldump -u用戶名 -p密码 -d 数据库名 表名 > 脚本名; 5 | ``` 6 | 7 | 8 | ![效果图](http://upload-images.jianshu.io/upload_images/2585384-8acc14df2492d804.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 9 | 10 | 11 | --- 12 | 13 | 导出整个数据库结构和数据: 14 | ``` 15 | mysqldump -h localhost -uroot -p123456 database > dump.sql 16 | ``` 17 | ---- 18 | 19 | 导出单个数据表结构和数据: 20 | 21 | ``` 22 | mysqldump -h localhost -uroot -p123456 database table > dump.sql 23 | ``` 24 | --- 25 | 26 | 27 | 28 | 导出整个数据库结构(不包含数据): 29 | 30 | ``` 31 | 32 | mysqldump -h localhost -uroot -p123456 -d database > dump.sql 33 | 34 | ``` 35 | ---- 36 | 37 | 导出单个数据表结构(不包含数据): 38 | 39 | ``` 40 | mysqldump -h localhost -uroot -p123456 -d database table > dump.sql 41 | 42 | ``` 43 | 44 | ---- 45 | 如果提示权限异常,请注意,是否在当前目录下具有写文件的权利,如果没有可以切换到别的目录下面,再执行这个操作。 46 | -------------------------------------------------------------------------------- /WebNote/MySQL相关/mysql基础操作.md: -------------------------------------------------------------------------------- 1 | 创建数据库: 2 | ```` 3 | create database db_biology; 4 | ```` 5 | 操作该数据库: 6 | ```` 7 | use db_biology; 8 | ```` 9 | 导入表到制定数据库 10 | ```` 11 | mysql -u用户名 -p 数据库名 < 数据库名.sql 12 | #mysql -uroot -p abc < abc.sql 13 | ```` 14 | -------------------------------------------------------------------------------- /WebNote/MySQL相关/云服务器linux下安装MySQL.md: -------------------------------------------------------------------------------- 1 | 1.linux下安装mysql: 2 | ```` 3 | sudo apt-get update 4 | sudo apt-get install mysql-client-core-5.6 5 | sudo apt-get install mysql-client-5.6 6 | sudo apt-get install mysql-server-5.6 7 | ```` 8 | 9 | 以上便是安装MySQL的全过程了。 10 | 11 | ---- 12 | 2.查看是否运行: 13 | ```` 14 | ps -ef | grep mysql 15 | ```` 16 | ---- 17 | 3.MySQL的停止: 18 | ```` 19 | sudo service mysql stop 20 | ```` 21 | ---- 22 | 4.MySQL的开启: 23 | ```` 24 | sudo service mysql start 25 | ```` 26 | ---- 27 | 5.MySQL的重启: 28 | ```` 29 | service mysql restart 30 | ```` 31 | ---- 32 | 6.MySQL的登录 33 | ```` 34 | mysql -u root -p #这里面的root是用户名 35 | ```` 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /WebNote/NodeJS相关/koa框架对post内容读取并解析.md: -------------------------------------------------------------------------------- 1 | > 最近在写一个小项目,需要涉及到前端向后端发送一个jsonArray,然后我们后端采用的是node的koa框架,所以需要用http发送,然后这个http包含着一个content即可 2 | 3 | ```` 4 | 移动端的发送代码 5 | 6 | JSONArray jsonArray = new JSONArray(); 7 | for (int i = 0; i < 5; i++) { 8 | JSONObject object = new JSONObject(); 9 | try { 10 | object.put("user_id", "11111111111"); 11 | object.put("user_name", "2222222222"); 12 | object.put("user_phone", "33333333333"); 13 | object.put("user_address", "444444444444"); 14 | object.put("product_id", "5555555555"); 15 | object.put("product_name", "666666666666"); 16 | object.put("product_price", "77777777777"); 17 | object.put("product_count", "88888888888"); 18 | } catch (JSONException e) { 19 | Log.i("lin", "---lin's log---> 进入 catch"); 20 | e.printStackTrace(); 21 | } 22 | jsonArray.put(object); 23 | } 24 | String content = jsonArray.toString(); 25 | OkHttpUtils 26 | .postString() 27 | .url("http://172.20.10.4:3008/testjson") 28 | .content(content) 29 | .build() 30 | .execute(new StringCallback() { 31 | @Override public void onError(Request request, Exception e) { 32 | Toast.makeText(TestActivity.this, "error", Toast.LENGTH_SHORT).show(); 33 | Log.i("lin", "---lin's log---> error " + e.toString()); 34 | } 35 | 36 | @Override public void onResponse(String response) { 37 | Toast.makeText(TestActivity.this, "onResponse", Toast.LENGTH_SHORT).show(); 38 | Log.i("lin", "---lin's log---> response " + response); 39 | } 40 | }); 41 | 42 | 43 | ```` 44 | 45 | 46 | 后端接收的代码: 47 | 48 | ```` 49 | var koa = require('koa'); 50 | var controller = require('koa-route'); 51 | var parse = require('co-body'); 52 | var app = koa(); 53 | 54 | app.use(controller.post('/testjson', function*() { 55 | console.log("接收到请求~"); 56 | var item = yield parse(this); 57 | var jsonList = eval(item); 58 | for (var i = 0; i < jsonList.length; i++) { 59 | console.log(jsonList[i].user_id); 60 | console.log(jsonList[i].user_name); 61 | } 62 | 63 | this.set('Cache-Control', 'no-cache'); 64 | this.body = "100"; 65 | })); 66 | 67 | 68 | 69 | 70 | ```` 71 | 72 | 73 | ![效果图](http://upload-images.jianshu.io/upload_images/2585384-de9c26d5e62ee471.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 74 | 75 | 以上便可以实现,前端向后端发送jsonArray并将其解析的过程啦~ 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /WebNote/NodeJS相关/nodejs查询数据库后将值返回前端.md: -------------------------------------------------------------------------------- 1 | > nodejs最大的优势也是大家用着最为难以理解的一点,就是它的异步功能,它几乎所有的io操作都是异步的,这也就导致很多人不理解也用不习惯。 2 | 3 | 前几天在项目中遇到这样一个问题,就是前端触发某个请求,想要查询数据库并且返回这个值,但是无论如何都返回不回来,因为没等到查询完毕,就过早的将空数据返回回来了,这个困扰了我许久,当时想到一些替代的方法,都是不治本的方法,今天打算用promise重新解决这个问题。 4 | 5 | promise的作用是让原本异步执行的代码变成类似同步执行,就是在执行完之后,会将结果返回回来。当然,我目前也只对promise只有一个浅显的理解,在之后也会深入学习的,下面说一下这个问题是怎么解决的。 6 | 7 | ```` 8 | app.use(controller.get('/aaa', function*() { 9 | this.set('Cache-Control', 'no-cache'); 10 | var data = yield service.bbb(); 11 | this.body = data; 12 | })); 13 | ```` 14 | 15 | 我们可以使用koa框架中的yield,promise可以作为它的返回参数。 16 | 17 | 18 | ```` 19 | exports.bbb = function () { 20 | var promise = new Promise(function (resolve, reject) { 21 | var mysql = require('mysql'); 22 | var connection = mysql.createConnection({ 23 | host: '127.0.0.1', 24 | user: 'root', 25 | password: 'root', 26 | port: '3306', 27 | database: 'db_biology' 28 | }); 29 | connection.connect(); 30 | connection.query( 31 | "SELECT * FROM Sheet1", 32 | function selectCb(err, results) { 33 | if (results) { 34 | console.log(results); 35 | //resolve(results); 36 | resolve(results); 37 | } 38 | if (err) { 39 | console.log(err); 40 | } 41 | connection.end(); 42 | } 43 | ); 44 | }); 45 | promise.then(function (value) { 46 | console.log(value); 47 | return value; 48 | // success 49 | }, function (value) { 50 | // failure 51 | }); 52 | return promise; 53 | }; 54 | ```` 55 | 56 | 只需要利用promise就可以实现我们以前直接return的结果了,这样就优雅的将异步代码变成了同步的了~ 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /WebNote/NodeJS相关/nodejs项目在云服务器的部署.md: -------------------------------------------------------------------------------- 1 | > 最近写了个小小的网站,折腾了好久啦,最开始使用Python的flask写的,后来感觉不是很方便于维护,而且想试试nodejs,就重新写了一遍,当然工程很小,两三天就写完了,不过中间也是吃了不少苦,掉了很多根头发的。下面为大家介绍一下nodejs怎么部署。 2 | 3 | > 良心的我,还是要给腾讯云打波广告的,原价65的云服务器,配置还可以,还有公网ip的,学生优惠只需要1元钱,全部审核过程只用了不到五分钟,总而言之非常nice。 4 | 5 | 1. 打开控制台,利用ssh命令连接上服务器 6 | 7 | ```` 8 | ssh ubuntu@139.199.177.20 9 | ```` 10 | ---- 11 | 2.看到这样的字样就代表登录成功啦 12 | 13 | ```` 14 | Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-53-generic x86_64) 15 | 16 | * Documentation: https://help.ubuntu.com 17 | * Management: https://landscape.canonical.com 18 | * Support: https://ubuntu.com/advantage 19 | 20 | ```` 21 | ---- 22 | 3.我们下载包的管理工具,并且更新一下数据源 23 | ```` 24 | sudo apt install yum 25 | ```` 26 | 以上在下载yum包管理工具 27 | 28 | ---- 29 | 30 | 4.进入/usr/src路径,下载nodejs并解压 31 | ```` 32 | cd /usr/src 33 | sudo wget http://nodejs.org/dist/v0.10.18/node-v0.10.18.tar.gz 34 | tar zxf node-v0.10.18.tar.gz 35 | ```` 36 | ---- 37 | 5.进入到解压完的文件,执行编译预处理,开始编译 38 | ```` 39 | cd node-v0.10.18 40 | ./configure 41 | sudo make 42 | ```` 43 | ---- 44 | 6.执行make install,nodejs就安装完毕了 45 | ```` 46 | sudo make install 47 | ```` 48 | 49 | ---- 50 | 7.安装forever,安装完成后,建立软连接 51 | ```` 52 | npm -g install express forever 53 | 54 | sudo ln -s /usr/local/bin/node /usr/bin/node 55 | sudo ln -s /usr/local/lib/node /usr/lib/node 56 | sudo ln -s /usr/local/bin/npm /usr/bin/npm 57 | sudo ln -s /usr/local/bin/node-waf /usr/bin/node-waf 58 | sudo ln -s /usr/local/bin/forever /usr/bin/forever 59 | ```` 60 | ---- 61 | 8.只需要上传我们的代码,然后运行就可以啦 62 | 运行: 63 | ```` 64 | sudo forever start server.js 65 | ```` 66 | 67 | 查看应用列表: 68 | ```` 69 | sudo forever list 70 | ```` 71 | 72 | 关闭应用: 73 | ```` 74 | sudo forever stop 0 75 | ```` 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /WebNote/NodeJS相关/test.html: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by linSir on 2017/7/3. 3 | */ 4 | -------------------------------------------------------------------------------- /WebNote/NodeJS相关/淘宝cnpm.md: -------------------------------------------------------------------------------- 1 | > 在国内使用npm是非常慢的,但是我们可以使用淘宝的镜像cnpm。 2 | 3 | 这个镜像,是一个完整的,同步镜像,所以我们可以完全使用cnpm来代替npm。 4 | 5 | ---- 6 | 安装npm: 7 | ```` 8 | $ npm install -g cnpm --registry=https://registry.npm.taobao.org 9 | ```` 10 | 之后就可以在使用npm的时候,用cnpm来替代了。 11 | 12 | ```` 13 | $ cnpm install [name] 14 | ```` 15 | -------------------------------------------------------------------------------- /网络协议/SSH原理与应用.md: -------------------------------------------------------------------------------- 1 | # SSH原理与应用 2 | 3 | ssh在程序员的生活中还是非常常见的,ssh具有很多种功能,也可以用在很多种场合。 4 | 5 | ## 什么是SSH 6 | 7 | SSH是一种网络协议,用于计算机之间的加密登录 8 | 9 | 当我们在一台电脑上面,运用ssh登录了另一台计算机,我们便可以认为,这种登录是安全的了,因为即使中途被截获,我们的密码也不会泄漏。 10 | 11 | 最早的时候,互联网通信都是明文通信,一旦被截获,内容就暴露无疑。1995年,芬兰学者Tatu Ylonen设计了SSH协议,将登录信息全部加密,成为互联网安全的一个基本解决方案,迅速在全世界获得推广,目前已经成为Linux系统的标准配置。 12 | 13 | 需要指出的是,SSH只是一种协议,存在多种实现,既有商业实现,也有开源实现。本文针对的实现是OpenSSH,它是自由软件,应用非常广泛。 14 | 15 | 此外,本文只讨论SSH在Linux Shell中的用法。如果要在Windows系统中使用SSH,会用到另一种软件PuTTY,这需要另文介绍。 16 | 17 | ## 用法 18 | 19 | 1. 登录远程服务器 20 | ``ssh root@host`` 21 | 22 | 2. 如果当前用户与远程用户同名 23 | ``ssh host`` 24 | 25 | 3. ssh默认的端口是22,如果我们要修改登录的默认端口 26 | ``ssh -p xx root@host`` 27 | 28 | ## 中间人攻击 29 | 30 | ssh采用的是非对称加密,也就是要采用公钥和私钥的方式进行加密。 31 | 32 | 整个通信的过程是这样的: 33 | 1. 远程主机收到用户的登录请求,将公钥发送给用户 34 | 2. 用户使用这个公钥,将登录的密码进行加密,发送给后台 35 | 3. 远程主机,用自己的私钥进行解密,判断用户名密码是否正确 36 | 37 | 整个过程看起来是很完美的,但是容易产生一种中间人攻击的现象: 38 | 39 | 我们发送出去的登录的信息,被中途截获了,一个中间人,将他的公钥发送过来,这样用户加密之后,他便可以用自己的私钥解密了,这样他就拥有了我们的密码,并且可以一直在中间监听我们的通话。 40 | 41 | 当然,这是基于口令的通信方式,我们也可以采用基于密钥的加密方式: 42 | 43 | 第二种级别(基于密匙的安全验证)需要依靠密匙,也就是你必须为自己创建一对密匙,并把公用密匙放在需要访问的服务器上。 如果你要连接到SSH服务器上,客户端软件就会向服务器发出请求,请求用你的密匙进行安全验证。服务器收到请求之后,先在你在该服务器的家目录下寻找你的公用密匙,然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致,服务器就用公用密匙加密“质询”(challenge)并把它发送给客户端软件。客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。 44 | 45 | 这样我们便可以防止中间人攻击的现象了。 46 | 47 | 48 | ## 口令登录 49 | 50 | ``` 51 | $ ssh user@host 52 | The authenticity of host 'host (12.18.429.21)' can't be established. 53 | RSA key fingerprint is 98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d. 54 | Are you sure you want to continue connecting (yes/no)? 55 | ``` 56 | 这段话的意思是,无法确认host主机的真实性,只知道它的公钥指纹,问你还想继续连接吗? 57 | 所谓"公钥指纹",是指公钥长度较长(这里采用RSA算法,长达1024位),很难比对,所以对其进行MD5计算,将它变成一个128位的指纹。上例中是98:2e:d7:e0:de:9f:ac:67:28:c2:42:2d:37:16:58:4d,再进行比较,就容易多了。 58 | 很自然的一个问题就是,用户怎么知道远程主机的公钥指纹应该是多少?回答是没有好办法,远程主机必须在自己的网站上贴出公钥指纹,以便用户自行核对。 59 | 假定经过风险衡量以后,用户决定接受这个远程主机的公钥。 60 | 61 | ``` 62 | Are you sure you want to continue connecting (yes/no)? yes 63 | ``` 64 | 系统会出现一句提示,表示host主机已经得到认可。 65 | ``` 66 | Warning: Permanently added 'host,12.18.429.21' (RSA) to the list of known hosts. 67 | ``` 68 | 然后,会要求输入密码。 69 | ``` 70 | Password: (enter password) 71 | ``` 72 | 如果密码正确,就可以登录了。 73 | 当远程主机的公钥被接受以后,它就会被保存在文件$HOME/.ssh/known_hosts之中。下次再连接这台主机,系统就会认出它的公钥已经保存在本地了,从而跳过警告部分,直接提示输入密码。 74 | 每个SSH用户都有自己的known_hosts文件,此外系统也有一个这样的文件,通常是/etc/ssh/ssh_known_hosts,保存一些对所有用户都可信赖的远程主机的公钥。 75 | 76 | 77 | 78 | ## 公钥登录 79 | 80 | 使用密码登录,每次都必须输入密码,非常麻烦。好在SSH还提供了公钥登录,可以省去输入密码的步骤。 81 | 82 | 所谓"公钥登录",原理很简单,就是用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求密码。 83 | 这种方法要求用户必须提供自己的公钥。如果没有现成的,可以直接用ssh-keygen生成一个: 84 | 85 | ``` 86 | $ ssh-keygen 87 | ``` 88 | 89 | 运行上面的命令以后,系统会出现一系列提示,可以一路回车。其中有一个问题是,要不要对私钥设置口令(passphrase),如果担心私钥的安全,这里可以设置一个。 90 | 运行结束以后,在$HOME/.ssh/目录下,会新生成两个文件:id_rsa.pub和id_rsa。前者是你的公钥,后者是你的私钥。 91 | 这时再输入下面的命令,将公钥传送到远程主机host上面: 92 | 93 | ``` 94 | $ ssh-copy-id user@host 95 | ``` 96 | 97 | 好了,从此你再登录,就不需要输入密码了。 98 | 如果还是不行,就打开远程主机的/etc/ssh/sshd_config这个文件,检查下面几行前面"#"注释是否取掉。 99 | 100 | ``` 101 | RSAAuthentication yes 102 | PubkeyAuthentication yes 103 | AuthorizedKeysFile .ssh/authorized_keys 104 | ``` 105 | 106 | 然后,重启远程主机的ssh服务。 107 | 108 | ``` 109 | // ubuntu系统 110 |   service ssh restart 111 |   // debian系统 112 |   /etc/init.d/ssh restart 113 | ``` 114 | 115 | ## authorized_keys文件 116 | 117 | 远程主机将用户的公钥,保存在登录后的用户主目录的$HOME/.ssh/authorized_keys文件中。公钥就是一段字符串,只要把它追加在authorized_keys文件的末尾就行了。 118 | 119 | 这里不使用上面的ssh-copy-id命令,改用下面的命令,解释公钥的保存过程: 120 | 121 | ``` 122 | $ ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub 123 | ``` 124 | 125 | 这条命令由多个语句组成,依次分解开来看:(1)"$ ssh user@host",表示登录远程主机;(2)单引号中的mkdir .ssh && cat >> .ssh/authorized_keys,表示登录后在远程shell上执行的命令:(3)"$ mkdir -p .ssh"的作用是,如果用户主目录中的.ssh目录不存在,就创建一个;(4)'cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub的作用是,将本地的公钥文件~/.ssh/id_rsa.pub,重定向追加到远程文件authorized_keys的末尾。 126 | 写入authorized_keys文件后,公钥登录的设置就完成了。 127 | 128 | ## 配置ssh config 129 | 130 | ``` 131 | vi ~/.ssh/config 132 | 133 | 134 | // 文件内容如下 135 | 136 | Host js //别名, 可以直接执行 ssh js 137 | 138 | HostName 172.16.6.84 //Host别名指向的服务器 IP 139 | 140 | User zhangsan //登录所用的用户名 141 | 142 | PreferredAuthentications publickey //鉴权方式 143 | 144 | IdentityFile ~/.ssh/zhangsan.pem //认证所需的密钥 145 | ``` 146 | 这样我们便可以通过``ssh js``来代替曾经的``ssh xxx@111.11.11.11`` 147 | 并且采用公钥+私钥的加密方式,不用输入密码,非常的方便。 148 | 149 | 150 | ## 参考文献 151 | 152 | - [SSH原理与运用](http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html) 153 | 154 | - [网络安全协议比较](http://blog.csdn.net/shizhixin/article/details/42459265) 155 | -------------------------------------------------------------------------------- /网络协议/浅析RPC协议.md: -------------------------------------------------------------------------------- 1 | # 浅析RPC协议 2 | 3 | > RPC是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 4 | RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。 5 | 6 | 7 | 8 | 9 | 10 | ![RPC工作原理](https://raw.githubusercontent.com/astaxie/build-web-application-with-golang/master/zh/images/8.4.rpc.png) 11 | 12 | 工作流程: 13 | 14 | 1.调用客户端句柄;执行传送参数 15 | 2.调用本地系统内核发送网络消息 16 | 3.消息传送到远程主机 17 | 4.服务器句柄得到消息并取得参数 18 | 5.执行远程过程 19 | 6.执行的过程将结果返回服务器句柄 20 | 7.服务器句柄返回结果,调用远程系统内核 21 | 8.消息传回本地主机 22 | 9.客户句柄由内核接收消息 23 | 10.客户接收句柄返回的数据 24 | 25 | RPC算法中一些需要我们解决的问题: 26 | 27 | 1.通讯问题,可以是建立tcp链接,在通信成功后释放链接,也可以保持长链接 28 | 2.寻址问题,需要知道服务器的ip地址,端口,方法名等,所以需要注册服务中心 29 | 3.传输过程中,不可避免的就是序列化和反序列化的过程 30 | 31 | 以上的问题,我们都可以在rpc 的开源框架中找到解决方案。 32 | -------------------------------------------------------------------------------- /网络协议/浅析socket.md: -------------------------------------------------------------------------------- 1 | # 浅谈Socket协议 2 | 3 | Socket 是对 TCP/IP 协议族的一种封装,是应用层与TCP/IP协议族通信的中间软件抽象层。从设计模式的角度看来,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。 4 | 5 | Socket 还可以认为是一种网络间不同计算机上的进程通信的一种方法,利用三元组(ip地址,协议,端口)就可以唯一标识网络中的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。 6 | 7 | Socket 起源于 Unix ,Unix/Linux 基本哲学之一就是“一切皆文件”,都可以用“打开(open) –> 读写(write/read) –> 关闭(close)”模式来进行操作。因此 Socket 也被处理为一种特殊的文件。 8 | 9 | ![OSI模型和TCP/IP协议的区别](http://upload-images.jianshu.io/upload_images/2964446-1fd7a0f3216c0530.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 10 | 11 | ## TCP/IP 12 | 13 | 要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准, 14 | 15 | 从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中 16 | 17 | 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 18 | 19 | 传输层:TCP,UDP 20 | 21 | 网络层:IP,ICMP,OSPF,EIGRP,IGMP 22 | 23 | 数据链路层:SLIP,CSLIP,PPP,MTU 24 | 25 | 每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的 26 | 27 | ![](http://images.cnitblog.com/blog/349217/201312/05230830-04807bb739954461a8bfc7513707f253.jpg) 28 | 29 | ![](http://images.cnitblog.com/blog/349217/201312/05230857-f49d5855f1e14a23a186737e0bec8a0f.gif) 30 | 31 | ## Socket 32 | 33 | 我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。 34 | 35 | 能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。 36 | 37 | ![](http://images.cnitblog.com/blog/349217/201312/05225723-2ffa89aad91f46099afa530ef8660b20.jpg) 38 | 39 | 40 | ## TCP(传输控制协议) 41 | 42 | 传输控制协议(Transmission Control Protocol,简写TCP)是一种面向连接,可靠的基于字节流的传输层协议。 43 | 44 | 建立连接的过程需要三次握手,释放链接需要四次挥手。 45 | 46 | **建立连接**: 47 | 48 | ![](http://upload-images.jianshu.io/upload_images/2964446-aa923712d5218eeb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 49 | 50 | (1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。 51 | 52 | (2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。 53 | 54 | (3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。 55 | 56 | 简单来说,就是 57 | 58 | 1、建立连接时,客户端发送SYN包(SYN=i)到服务器,并进入到SYN-SEND状态,等待服务器确认 59 | 60 | 2、服务器收到SYN包,必须确认客户的SYN(ack=i+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN-RECV状态 61 | 62 | 3、客户端收到服务器的SYN+ACK包,向服务器发送确认报ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手,客户端与服务器开始传送数据。 63 | 64 | 65 | **释放链接** 66 | 67 | ![](http://upload-images.jianshu.io/upload_images/2964446-2b9562b3a8b72fb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 68 | 69 | 由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。 70 | 71 | (1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。 72 | 73 | (2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。 74 | 75 | (3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。 76 | 77 | (4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。 78 | 79 | 为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 80 | 81 | 这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。 82 | 83 | 84 | ## UDP 85 | 86 | 用户数据包协议(英语:User Datagram Protocol,缩写为UDP),又称用户数据报文协议,是一个简单的面向数据报的传输层协议,正式规范为RFC 768。 87 | 88 | 在TCP/IP模型中,UDP为网络层以上和应用层以下提供了一个简单的接口。UDP只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份(所以UDP有时候也被认为是不可靠的数据报协议)。UDP在IP数据报的头部仅仅加入了复用和数据校验(字段)。 89 | UDP首部字段由4个部分组成,其中两个是可选的。各16bit的来源端口和目的端口用来标记发送和接受的应用进程。因为UDP不需要应答,所以来源端口是可选的,如果来源端口不用,那么置为零。在目的端口后面是长度固定的以字节为单位的长度域,用来指定UDP数据报包括数据部分的长度,长度最小值为8byte。首部剩下地16bit是用来对首部和数据部分一起做校验和(Checksum)的,这部分是可选的,但在实际应用中一般都使用这一功能。 90 | 由于缺乏可靠性且属于非连接导向协议,UDP应用一般必须允许一定量的丢包、出错和复制粘贴。但有些应用,比如TFTP,如果需要则必须在应用层增加根本的可靠机制。但是绝大多数UDP应用都不需要可靠机制,甚至可能因为引入可靠机制而降低性能。流媒体(流技术)、即时多媒体游戏和IP电话(VoIP)一定就是典型的UDP应用。如果某个应用需要很高的可靠性,那么可以用传输控制协议(TCP协议)来代替UDP。 91 | 由于缺乏拥塞控制(congestion control),需要基于网络的机制来减少因失控和高速UDP流量负荷而导致的拥塞崩溃效应。换句话说,因为UDP发送者不能够检测拥塞,所以像使用包队列和丢弃技术的路由器这样的网络基本设备往往就成为降低UDP过大通信量的有效工具。数据报拥塞控制协议(DCCP)设计成通过在诸如流媒体类型的高速率UDP流中,增加主机拥塞控制,来减小这个潜在的问题。 92 | 典型网络上的众多使用UDP协议的关键应用一定程度上是相似的。这些应用包括域名系统(DNS)、简单网络管理协议(SNMP)、动态主机配置协议(DHCP)、路由信息协议(RIP)和某些影音流服务等等。 93 | 94 | 95 | UDP 是一个简单的传输层协议。和 TCP 相比,UDP 有下面几个显著特性: 96 | 97 | - UDP 缺乏可靠性。UDP 本身不提供确认,序列号,超时重传等机制。UDP 数据报可能在网络中被复制,被重新排序。即 UDP 不保证数据报会到达其最终目的地,也不保证各个数据报的先后顺序,也不保证每个数据报只到达一次 98 | - UDP 数据报是有长度的。每个 UDP 数据报都有长度,如果一个数据报正确地到达目的地,那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议,没有任何(协议上的)记录边界。 99 | - UDP 是无连接的。UDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。 100 | - UDP 支持多播和广播。 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | --------------------------------------------------------------------------------