-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 38.1 KB
/
content.json
1
{"meta":{"title":"Hexo","subtitle":null,"description":null,"author":"John Doe","url":"http://yoursite.com"},"pages":[],"posts":[{"title":"Android自定义View(一)","slug":"custom-view-andorid","date":"2018-06-27T04:04:34.000Z","updated":"2018-06-27T04:06:27.593Z","comments":true,"path":"2018/06/27/custom-view-andorid/","link":"","permalink":"http://yoursite.com/2018/06/27/custom-view-andorid/","excerpt":"","text":"原文摘抄并整理扔物线大神的Hencoder教程 Android自定义View(一)###View的测量 EXACTLY 精确值模式 指定控件的layout_width/layout_height为固定值或指定为match_parent AT_MOST 最大值模式 指定控件的宽高属性为wrap_content时 UNSPECIFIED 不指定控件大小测量模式,View想多大就多大,通常在自定义view时使用。 ii:View默认的onMeasure()只支持EXACTLY模式。除非重写此方法。 CanvasdrawColor(int color)一般用于设置底色和半透明蒙版(“#7f000000”)类似的还有drawRGB(int r,int g,int b)、drawARGB(int a,int r,int g,int b) drawCircle(float centerX,float centerY,float radius,Paint paint)画圆 drawRect(float left,float top,float right,float bottom)画矩形,对应的是矩形四条边的坐标也可以使用drawRect(Rect rect,Paint paint)、drawRect(RectF rect,Paint paint)来绘制矩形 drawBitmap(Bitmap bitmap,float left,float top,Paint paint)画Bitmap drawText(String text,float x,float y,Paint paint)绘制文字 PaintsetColorsetStyle(Paint.Style style)设置画笔模式:Paint.Style.STROKE 勾边Paint.Style.FILL 实心Paint.Style. FILL_AND_STROKE 画线并填充 setStrokeWidth(float width)设置画笔线条宽度 setAntiAlias(boolean aa)设置是否开启抗锯齿也可以用以下方法 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); drawPoint(float x,float y,Paint paint)画点点的大小 —-> paint.setStrokeWidth(width)点的形状 —-> paint.setStrokeCap(cap),分别为ROUND(圆头),BUTT(平头),SQUARE(方头) drawPoints(float[] pts,int offset,int count,Paint paint)/drawPoints(float[] pts,Paint paint)画点 批量画多个点,pts是数组点的坐标,offset表示跳过数组的前x个数,count是要绘制多少个数(count/2为要绘制的点数 ) drawOval(float left,float top,float right,float bottom,Paint paint)画椭圆 drawLine(float startX,float startY,float stopX,float stopY,Paint paint)画线,需要起始点和终止点 drawLines(float[] pts,int offset,int count,Paint paint) / drawLines(float[] pts,Paint paint)画多条线 drawRoundRect(float left,float right,float top,float bottom,float rx,float ry,Paint paint)画圆角矩形,rx,ry分别是圆角的横向半径和纵向半径,也可以使用drawRoundRect(RectF rect,float rx,float ry,Paint paint)来绘制 drawArc(float left,float top,float right,float bottom,float startAngle,float sweepAngle,boolean userCenter,Paint paint)使用一个椭圆来描述弧形。startAngle弧形的起始角度(x轴正向–>正右的方向是0度的位置,顺时针为正,逆时针为负。),sweepAngle弧形划过的角度,userCenter是否连接到圆心(true–>扇形,flase–>弧形) drawPath(Path path,Paint paint)画自定义图形Path有两类方法,直接描述路径和辅助的设置或计算 1. 直接描述路径addXXx() --- 添加子图形 addCircle(float x,float y,float radius,Direction dir) 添加圆 其中dir为画圆的路径的方向(顺时针CW和逆时针CCW) 除了addCircle()还有类似的添加矩形,椭圆,圆角矩形等 xxxTo() ——–画线(直线或曲线)paint.setStyle(Style.STROKE) lineTo(float x,float y) 从当前位置向目标位置画一条直线(参数为**绝对坐标**) rLineTo(float x,float y) 参数为相对坐标 quadTo()/rQuadTo()画两次贝塞尔曲线######cubicTo()/rCubicTo()画三次贝塞尔曲线 moveTo(float x,float y)/rMoveTo(float x,float y)移动到目标位置 arcTo(RectF oval,float startAngle,float sweepAngle, boolean forceMoveTo) / arcTo(RectF oval,float startAngle,float sweepAngle)画弧形,其中forceMoveTo意思为是否留下移动的痕迹 addArc(…)也是画弧形,只是默认forceMoveTo为true close 封闭当前子图形辅助的设置或计算设置填充模式1Path.setFillType(Path.FillType ft) FillType的取值 EVEN_ODD WINDING(默认值) INVERSE_EVEN_ODD INVERSE_WINDING 图示:","categories":[],"tags":[{"name":"Android","slug":"Android","permalink":"http://yoursite.com/tags/Android/"}]},{"title":"来自头条的屏幕适配方案","slug":"toutiao-note","date":"2018-06-20T09:22:19.000Z","updated":"2018-06-20T09:23:14.309Z","comments":true,"path":"2018/06/20/toutiao-note/","link":"","permalink":"http://yoursite.com/2018/06/20/toutiao-note/","excerpt":"","text":"来自头条的屏幕适配方案传统的dp适配方式 android中的dp在渲染前会将dp转为px,计算如下 px = density * dp density = dpi / 160; px = dp * (dpi / 160)而dpi是根据屏幕真实的分辨率和尺寸来计算的,每个设备都可能不同 屏幕尺寸,分辨率,像素密度三者关系 梳理需求 支持以宽或者高一个维度去适配,保持 该维度上和设计图一致。 支持dp和sp单位,控制迁移成本到最小。 最终方案因为Android中很多dp转换的场合,基本都是通过DisplayMetrics来计算的,因此,达到适配,我们值需要修改DisplayMetrics中和dp转换的变量即可。 假设设计图宽度是360p,以宽的维度来适配那么适配后的density = 设备真实宽(px)/360","categories":[],"tags":[{"name":"Android 性能优化","slug":"Android-性能优化","permalink":"http://yoursite.com/tags/Android-性能优化/"}]},{"title":"RxJava随笔","slug":"RxJava-note","date":"2018-06-20T09:21:16.000Z","updated":"2018-06-20T09:21:48.775Z","comments":true,"path":"2018/06/20/RxJava-note/","link":"","permalink":"http://yoursite.com/2018/06/20/RxJava-note/","excerpt":"","text":"RxJava随笔 RxJava是一个异步处理事件的库。优势:随着程序越来越复杂,RxJava依然能够保持简洁。 扔物线大神的RxJava教程 API介绍和原理分析###概念:扩展的观察者模式 RxJava的观察者模式 Observable (被观察者) Observer (观察者) subscribe(订阅) event(事件) Observable和Observer通过subscribe()方法实现订阅关系,从而Observable可以在需要的时候发出事件通知Observer。 RxJava的回调方法 onCompleted() 意味着事件队列完结。RxJava规定,当不再有OnNext()事件产生时,需要触发此方法,作为事件队列完结的标志。 onNext() 相当于按钮中的onClick()/onEvent() onError() 意味着事件队列异常。当事件处理过程中出现异常时,事件队列会自动终止,并且不允许有事件发生。 注意:在一个正确运行的事件队列中,onCompleted()和onError()有且只有一个,并且只会是在事件队列中的最后一个。并且,这两个事件是互斥的。这意味着,当onCompleted()被调用时,onError()就不会被调用。 实现RxJava的步骤 创建Observer,即是观察者。它决定着事件会有怎么样的行为。RxJava内置了一个实现Observer的抽象类Subscriber。 12345678910111213141516 Subscriber<String> subscriber = new Subscriber<String>(){ @Override public void onNext(String s) { Log.d(tag, "Item: " + s); } @Override public void onCompleted() { Log.d(tag, "Completed!"); } @Override public void onError(Throwable e) { Log.d(tag, "Error!"); }}; Observer的使用方式与Subscriber基本相同。实际上,在RxJava的subscribe过程中,Observer也总是会转换成Subscriber再使用。 两者的区别 onStart()方法 它会在subscriber开始时调用,用于一些准备工作。例如数据的清零和重置。但是这个方法对线程有要求,例如弹出一个加载框(需要运行在UI 线程)就不行了。要在指定的线程来做准备工作可以用doOnSubscribe() unsubscribe():这是Subscriber所实现的另外一个接口Subscription的方法,用于取消订阅。当调用这个方法后,Subscriber将不再接收事件。通常在调用此方法前,用isUnscriberd()判断状态,再进行调用。注意:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。 创建Observable即为“被观察者”,它决定什么时候触发怎么样的事件。RxJava使用onCreate()来创建一个Observable,并为它定义事件触发规则。 Observable observable = Observable.create(new Observable.OnSubscribe() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello"); subscriber.onNext("World"); subscriber.onCompleted(); }});除了onCreate(),RxJava还可以使用just(T...)、from(T...)/from(Iterable<? extends T>)来快捷地创建事件序列。 1234567891011121314151617181920212223242526272829303132333435//使用just Observable observable = Observable.just("Hello", "World");//使用fromString[] words = {"Hello", "Hi", "Aloha"};Observable observable = Observable.from(words); ``` 3. Subscribe(订阅)在创建了`Observable`和`Observer`后,通过`subscriber()`将他们联结起来,这样事件序列就能运行起来。 observable.subscriber(Observer) observable.subscriber(Subscriber)### 线程控制 Scheduler背景:RxJava中在不指定线程的情况下,都遵循线程不变的原则。所以如果需要切换线程,那就需要用到`Scheduler`(调度器)1. API> - **`Scheduler.immediate(); `** 直接在当前线程运行,相当于不指定线程- **`Scheduler.newThread();` **启用新线程,并且在新线程中进行操作- **`Scheduler.io();` **I/O操作(读写操作、读写数据库、网络信息交互等)使用的`Scheduler`- **`Scheduler.computation();`**计算所用的Scheduler- **`AndroidSchedulers.mainThread();` **指定操作将在Android主线程运行。2. 方法> - ` subscribeOn();` > 指定`subscriber()`所发生的线程,即 Observable.OnSubscribe 被激活时所处的线程。或者叫做事件产生的线程。(指定的是上游发送事件的线程,并且多次指定只有第一次有效)> - `observeOn()` 指定`Subsriber`所运行的线程,也叫作事件消费的线程。(指定下游接收事件的线程,可以多次指定下游线程,也就是每次调用onserverOn(),下游的线程都会切换一次 Observable.just(1, 2, 3, 4) .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程 .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程 .subscribe(new Action1() { @Override public void call(Integer number) { Log.d(tag, “number:” + number); } });`它适用于多数的 『后台线程取数据,主线程显示』的程序策略。 变换 所谓变化就是将事件队列中的对象或整个队列进行加工处理,转换成不同的事件或事件序列。 map RxJava中最简单的一个变换操作符,它的作用是对上游发送的每一个事件应用一个函数,使得每一个事件都按照特定的函数去变化。 flatMap 将一个发送事件的上游Observable变换为多个发送事件、Observables,然后将它们发射的事件合并后放进一个单独的Observable里。不过,flatMap后事件的顺序是无序的,可以使用concatMap来严格按照上游发送的顺序来发送。 在Android中的应用场景:在注册后自动登录(将RegisterResponse转换到ObservableSource<LoginResponse>) Zip 通过一个函数将多个Observable发送的事件结合到一起,然后发送这些组合到一起的事件。它严格按照顺序应用这个函数。它只发射与发射数据项最少的那个Observable一样多的数据。 在Android中的应用场景:例如在一个界面要显示用户的信息(该信息从两个不同的接口获取),因此要当两个接口的信息都获取到的时候才能显示,这时候就可以用Zip sample 这个操作符每隔指定的时间就从上游取出一个事件发送给下游//每隔两秒去取一个事件给下游 .sample(2,TimeUnit.SECONDS) Flowable之前的上下游 Observable—> Observer不同的是:Flowable变成上游,Subscriber变成下游不过两者之间实现订阅关系的还是subscribe() Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() { @Override public void subscribe(FlowableEmitter<Integer> emitter) throws Exception { Log.d(TAG, "emit 1"); emitter.onNext(1); Log.d(TAG, "emit 2"); emitter.onNext(2); Log.d(TAG, "emit 3"); emitter.onNext(3); Log.d(TAG, "emit complete"); emitter.onComplete(); } }, BackpressureStrategy.ERROR); //增加了一个参数 Subscriber<Integer> downstream = new Subscriber<Integer>() { @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe"); s.request(Long.MAX_VALUE); //注意这句代码 } @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onError(Throwable t) { Log.w(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } }; upstream.subscribe(downstream); 选择Flowable的背压参数BackpressureStrategy.ERROR 当上下游流速不均衡的时候会抛出著名的MissingBackpressureException 另外的一个区别是在下游的onSubscribe方法中传给我们的不再是Disposable了, 而是Subscription, 它俩有什么区别呢, 首先它们都是上下游中间的一个开关, 之前我们说调用Disposable.dispose()方法可以切断水管, 同样的调用Subscription.cancel()也可以切断水管, 不同的地方在于Subscription增加了一个void request(long n)方法, 这个方法有什么用呢, 在上面的代码中也有这么一句代码:s.request(Long.MAX_VALUE); 这意味着Flowable的响应式拉取,这是下游处理事件的一种能力。 背压策略 Backpressurestrategy.BUFFER 之前默认Flowable一次性存储的地方是128个事件。现在这种策略,没有大小限制,可以存放很多很多的事件。 背压策略Backpressurestrategy.DROP 直接将存不下的事件丢弃 背压策略Backpressurestrategy.LATEST 只保留最新的事件 另外面对不是我们自己创建的Flowable我们可以使用 onBackpressureBuffer() onBackpressureDrop() onBackpressureLatest() //interval这个操作符 发送Long型事件,从0开始,每隔指定的时间就把数字加1并发送出来。在下列例子中,每隔1毫秒发送一次事件。 Flowable.interval(1, TimeUnit.MICROSECONDS) .onBackpressureDrop() //加上背压策略 .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<Long>() { @Override public void onSubscribe(Subscription s) { Log.d(TAG, "onSubscribe"); mSubscription = s; s.request(Long.MAX_VALUE); } @Override public void onNext(Long aLong) { Log.d(TAG, "onNext: " + aLong); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void onError(Throwable t) { Log.w(TAG, "onError: ", t); } @Override public void onComplete() { Log.d(TAG, "onComplete"); } });","categories":[],"tags":[{"name":"Android","slug":"Android","permalink":"http://yoursite.com/tags/Android/"}]},{"title":"Android中的MVP架构","slug":"mvp-note","date":"2018-06-20T09:11:16.000Z","updated":"2018-06-20T09:20:51.022Z","comments":true,"path":"2018/06/20/mvp-note/","link":"","permalink":"http://yoursite.com/2018/06/20/mvp-note/","excerpt":"","text":"MVP模式解析标签: Android 架构 MVP MVP模式的核心思想MVP将Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。因此,在Activity中就是响应生命周期和显示界面,其他工作,如业务逻辑等就都泡到Presenter中进行完成,Presenter其实是Model层和View的桥梁。 MVP模式需要的步骤 创建IPresenter接口,将所有业务逻辑写在里面,并且创建他的实现PresenterCompl(在这里可以方便地查看业务功能,而且也方便进行单元测试) 创建IView接口,把所有的视图逻辑的接口都放这里,其实现类是当前的Activity/Fragment。 由UML图可以看出,Activity中包含了一个IPresener,而PresenterCompl又包含了一个IView并且依赖Model。Activity中保留了对IPresener的调用,其他工作全部保留到PresenterComal中实现。 Model并不是必须有的,但一定会有View和Presenter MVP的一次简单实践 这是一个很简单的MVP实践,关于用户登录与清除的功能。 首先看最直观的LoginActivity 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class LoginActivity extends AppCompatActivity implements ILoginView{ @BindView(R.id.et_name) AppCompatEditText etName; @BindView(R.id.et_password) AppCompatEditText etPassword; @BindView(R.id.btn_login) Button btnLogin; @BindView(R.id.btn_clear) Button btnClear; ILoginPresenter loginPresenter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); ButterKnife.bind(this); loginPresenter = new LoginPresenterCompl(this); } @OnClick({R.id.btn_login, R.id.btn_clear}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.btn_login: String name = etName.getText().toString(); String password = etPassword.getText().toString(); loginPresenter.doLogin(name,password); break; case R.id.btn_clear: loginPresenter.clear(); break; } } @Override public void onClearText() { etName.setText(""); etPassword.setText(""); ToastUtils.showShort("清除成功"); } @Override public void onLoginResult(Boolean result, int code) { btnLogin.setEnabled(true); btnClear.setEnabled(true); if (result){ ToastUtils.showShort("登录成功"); } else{ ToastUtils.showShort("登录失败"); } }} 界面逻辑接口 ILoginView 1234public interface ILoginView { void onClearText(); void onLoginResult(Boolean result,int code);} 业务逻辑接口 ILoginPresenter 1234public interface ILoginPresenter { void clear(); void doLogin(String name,String password);} 业务逻辑实现类 LoginPresenterCompl 实现上面的ILoginPresenter接口 12345678910111213141516171819202122232425262728public class LoginPresenterCompl implements ILoginPresenter { private ILoginView loginView; private User user; public LoginPresenterCompl(ILoginView view){ loginView = view; user = new User("yongfeng","123123"); } @Override public void clear() { loginView.onClearText(); } @Override public void doLogin(String name, String password) { boolean result = false; int code = 0; if (TextUtils.equals(user.getName(),name) && TextUtils.equals(user.getPassword(),password)){ result = true; code = 1; }else { result = false; code = 0; } loginView.onLoginResult(result,code); }} 像以往那样的实体类 User 123456789101112131415161718192021222324252627282930313233public class User { private String name; private String password; public User(String name, String password) { this.name = name; this.password = password; } public String getName() { return name == null ? "" : name; } public void setName(String name) { this.name = name; } public String getPassword() { return password == null ? "" : password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "name='" + name + '\\'' + ", password='" + password + '\\'' + '}'; }} 总结使用了MVP架构后,只用来显示界面,具体的业务逻辑都交由ILoginPresenter去完成(注意要new 的时候,使用ILoginPresenterCompl这个实现类),识得整个Activity看上去会很清爽,在也不用在Activity中去找业务逻辑了,而界面显示逻辑则交由ILoginView这个接口去完成。 MVP的优势使Activity代码更整洁在传统中的Activity中随着项目的延展,使得在Activity中的代码越来越来,项目耦合性越来越高,使得代码难以维护。后续维护时,通常找一个业务逻辑要改的地方都难。(我就维护了一个三年前的项目,看得真是头大哦)。 但使用MVP后,Activity就能瘦身许多了,基本上只有 FindView、SetListener 以及Init的代码。其他的就是对 Presenter的调用,还有对 View接口 的实现。这种情形下阅读代码就容易多了。 而且你只要看 Presenter 的接口,就能明白这个模块都有哪些业务,很快就能定位到具体代码。Activity 变得容易看懂,容易维护,以后要调整业务、删减功能也就变得简单许多。 便于单元测试一般单元测试都是用来测试某些新加的业务逻辑有没有问题,如果采用传统的代码风格,我们可能要先在Activity里写一段测试代码,测试完了再把测试代码删掉换成正式代码,这时如果发现业务有问题又得换回测试代码,咦,测试代码已经删掉了!好吧重新写吧…… MVP 中,由于业务逻辑都在Presenter 里,我们完全可以写一个 PresenterTest 的实现类继承 Presenter 的接口,现在只要在Activity 里把 Presenter 的创建换成 PresenterTest,就能进行单元测试了,测试完再换回来即可。万一发现还得进行测试,那就再换成 PresenterTest 吧。 避免Activity内存泄漏采用传统的模式,一大堆异步任务和对UI的操作都放在 Activity里面,比如你可能从网络下载一张图片,在下载成功的回调里把图片加载到 Activity 的 ImageView 里面,所以异步任务保留着对 Activity 的引用。 这样一来,即使 Activity 已经被切换到后台(onDestroy 已经执行),这些 异步任务 仍然保留着对 Activity 实例的引用, 所以系统就无法回收这个 Activity 实例了,结果就是 Activity Leak。 Android 的组件中,Activity 对象往往是在堆(Java Heap)里占最多内存的,所以系统会优先回收 Activity 对象, 如果有 Activity Leak,APP很容易因为内存不够而 OOM。 采用 MVP模式,只要在当前的 Activity 的 onDestroy 里,分离异步任务对Activity 的引用,就能避免 Activity Leak。 参考地址","categories":[],"tags":[{"name":"Android 架构","slug":"Android-架构","permalink":"http://yoursite.com/tags/Android-架构/"}]},{"title":"Andorid中Gson的正确食用方式!","slug":"gson-note","date":"2018-06-20T09:09:48.000Z","updated":"2018-06-20T09:10:42.581Z","comments":true,"path":"2018/06/20/gson-note/","link":"","permalink":"http://yoursite.com/2018/06/20/gson-note/","excerpt":"","text":"Gson使用 项目地址:https://github.com/google/gson 实例化Gson对象 通过构造函数获取 Gson gson = new Gson(); 通过GsonBuilder获取,可以进行多项配置 Gson gson = new GsonBuilder().create(); 生成Json字符串JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("name","hyf"); jsonObject.addProperty("age",21); jsonObject.addProperty("Boolean",true); JsonElement JsonObject jsonObject = new JsonObject(); Json.addProperty("String", "leavesC"); jsonObject.addProperty("Number", 23); jsonObject.addProperty("Number", 22.9); jsonObject.addProperty("Boolean", true); jsonObject.addProperty("Char", 'c'); JsonObject jsonElement = new JsonObject(); jsonElement.addProperty("Boolean", false); jsonElement.addProperty("Double", 25.9); jsonElement.addProperty("Char", 'c'); jsonObject.add("JsonElement", jsonElement); Json和数组/List转换1. json转字符串数组Gson gson = new Gson(); String jsonArray = "[\\"3\\",\\"4\\",\\"5\\"]"; String[] strings = gson.fromJson(jsonArray,String[].class); 2. 字符串数组转jsonString jsonArray = gson.toJson(jsonArray,new TypeToken<String>(){}.getType()); 3. json转ListString jsonArray = "[....]" //此处jsonArray与(1)中相同 List<String> stringList = gson.fromJson(jsonArray,new TypeToken<List<String>>(){}.getType()); 4. List转JsonString jsonArray = gson.toJson(stringList,new TypeToken<List<String>>(){}.getType()); 序列化和反序列化#####1. 序列化 User user = new User("hyf",22,true); //假设有一个userBean里面有name,age,gender Gson gson = new Gson(); String json = gson.toJson(user); //out: {"name":"hyf","age":22,"gender":true} #####2. 反序列化 User user = gson.fromJson(json,User.class); //此处的json为上面序列化输出的json字符串 属性重命名修改User类,为name声明SerializedName。其中value设置属性名,而alternate则设置多个备选属性名 public class User{ @SerializedName(value = "userName",alternate={"user_name","Name"}) private String name; private int age; private boolean gender; } 字段过滤#####1. 基于@Expose注解Expose注解的注解值声明情况有四种 @Expose(serialize=true,deserialize=true)//序列化和反序列化都生效 @Expose(serialize=false,deserialize=false) //序列化和反序列化都不生效 @Expose(serialize=true,deserialize=false) //序列化生效,反序列化不生效 @Expose(serialize=false,deserialize=true) //序列化不生效,反序列化生效 #####2. 基于版本Gson提供了@Since和@Until两个注解基于版本对字段进行过滤,它们都包含一个Double值,用于设置版本号。 @Since 从…开始 @Until 到…为止 它们要配合GsonBuilder配合使用当版本(GsonBuilder设置的版本)大于或等于Since值或小于Until时,字段会进行序列化和反序列操作,而没有声明注解的字段都会加入序列化和反序列操作。 例子:修改User类 public class User { @Since(1.4) private String a; @Since(1.6) private String b; @Since(1.8) private String c; @Until(1.6) private String d; @Until(2.0) private String e; public User(String a, String b, String c, String d, String e) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; } @Override public String toString() { return "User{" + "a='" + a + '\\'' + ", b='" + b + '\\'' + ", c='" + c + '\\'' + ", d='" + d + '\\'' + ", e='" + e + '\\'' + '}'; } } Gson gson = new GsonBuilder().setVersion(1.6).create(); User user = new User("A", "B", "C", "D", "E"); System.out.println(); System.out.println(gson.toJson(user)); String json = "{\\"a\\":\\"A\\",\\"b\\":\\"B\\",\\"c\\":\\"C\\",\\"d\\":\\"D\\",\\"e\\":\\"E\\"}"; user = gson.fromJson(json, User.class); System.out.println(); System.out.println(user.toString()); 结果输出:只会序列化和反序列化a,b,c 序列化:{"a":"A","b":"B","e":"E"} 反序列化:User{a='A',b='B',c='null',d='null',e='E'} 3.基于访问修饰符通过GsonBuilder对象的excludeFieldsWithModifiers方法来指定不进行序列化和反序列话操作的访问修饰符字段 123456789101112131415161718192021public class ModifierSample { public String publicField = "public"; protected String protectedField = "protected"; private String privateField = "private"; String defaultField = "default"; final String finalField = "final"; static String staticField = "static";}public static void main(String[] args) { Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.PRIVATE, Modifier.STATIC).create(); ModifierSample modifierSample = new ModifierSample(); System.out.println(gson.toJson(modifierSample)); } 输出:private和static的字段将不会被序列化和反序列化 4.基于策略通过GsonBuilder的setExclusionStrategies(ExclusionStrategy... strategies)来直接排除指定字段名或者指定字段类型不进行序列化和反序列化 123456789101112Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy(){ @Override public boolean shouldSkipField(FieldAttributes fieldAttributes) { //排除指定字段名(这里指定字段名为) return fieldAttributes.getName().equals("intField"); }Overridepublic boolean shouldSkipClass(Class<?> aClass) {//排除指定字段类型(这里指定dobule类)return aClass.getName().equals(double.class.getName());}).create(); setExclusionStrategies 方法在序列化和反序列化时都会生效,如果只是想指定其中一种情况下的排除策略或分别指定排除策略,可以改为使用以下两个方法 addSerializationExclusionStrategy(ExclusionStrategy strategy); addDeserializationExclusionStrategy(ExclusionStrategy strategy); ####个性化设置 #####1. 序列化时输出null Gson gson = new GsonBuilder() .serializeNulls(); .create(); 序列化User类: 假设user类(null,24,true)输出:{“name”:null,”age”:24,”gender”:true} 2.格式化输出JsonGson默认序列化的json字符串不够直观,可以使用GsonBuilder的setPrettyPrinting进行格式化输出 Gson gson = new GsonBuilder() .serializeNulls() .setPrettyPrinting() .create(); 输出:12345{ "name":null, "age":24, "gender":true} 3.格式化时间123Gson gson = new GsonBuilder() .setDateFormat("yyyy-MM-dd HH:mm:ss:SSS") .create(); TypeAdapter TypeAdapter是一个泛型抽象类,用于接管某种类型的序列化和反序列过程,包含两个抽象方法,分别用于自定义序列化和反序列化的过程。 例子:对应还是使用User类(包含name,age,gender)定义一个类继承TypeAdapter123456789101112131415161718192021222324252627282930313233343536373839public class UserTypeAdapter extends TypeAdapter<User>{ @Override public void write(JsonWriter jsonWriter, User user) throws IOException { //流式序列化成对象开始 jsonWriter.beginObject(); //将Json的Key值都指定为大写字母开头 jsonWriter.name("Name").value(user.getName()); jsonWriter.name("Age").value(user.getAge()); jsonWriter.name("Gender").value(user.isSex()); //流式序列化结束 jsonWriter.endObject(); } @Override public User read(JsonReader jsonReader) throws IOException { User user = new User(); //流式反序列化开始 jsonReader.beginObject(); while (jsonReader.hasNext()) { switch (jsonReader.nextName()) { //首字母大小写均合法 case "name": case "Name": user.setName(jsonReader.nextString()); break; case "age": user.setAge(jsonReader.nextInt()); break; case "gender": user.setGender(jsonReader.nextBoolean()); break; } } //流式反序列化结束 jsonReader.endObject(); return user; }} 使用:123456 Gson gson = new GsonBuilder() .registerTypeAdapter(User.class,new UserTypeAdapter()) .create();User user = new User("hyf",22,true);String json = gson.toJson(user);user = gson.fromJson(json,User.class); 输出序列化后的json和反序列化后的user12{"Name":"hyf","Age":22,"Gender":true}User{name="hyf",age=22,gender=true} TypeAdapter同时接管了序列化和反序列化操作 JsonSerializer 只接管序列化过程的接口 JsonDeserializer 只接管反序列化过程的接口 TypeAdapterFactory TypeAdapterFactory 是用于创建 TypeAdapter 的工厂类,通过参数 TypeToken 来查找确定对应的 TypeAdapter , 如果没有就返回 null 并由 Gson 默认的处理方法来进行序列化和反序列化,否则就由客户预定义的 TypeAdapter 来进行处理。 Gson gson = new GsonBuilder() .registerTypeAdapterFactory(new TypeAdapterFactory() { @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { //如果 Gson 需要与 User 类相关的 TypeAdapter ,则返回我们自己定义的 TypeAdapter 对象 if (typeToken.getType().getTypeName().equals(User.class.getTypeName())) { return (TypeAdapter<T>) new UserTypeAdapter(); } //找不到则返回null return null; } }) .create(); 原文地址","categories":[],"tags":[{"name":"Android","slug":"Android","permalink":"http://yoursite.com/tags/Android/"}]},{"title":"扔物线Git掘金小册笔记","slug":"GitNote","date":"2018-06-20T03:15:59.000Z","updated":"2018-06-20T04:04:14.676Z","comments":true,"path":"2018/06/20/GitNote/","link":"","permalink":"http://yoursite.com/2018/06/20/GitNote/","excerpt":"","text":"摘录自扔物线大神的Git掘金小册笔记,详情请点击标题查看原文。 扔物线Git小册笔记版本控制系统的核心版本控制/主动提交/中央仓库 分布式版本控制系统和中央式的区别中央式的版本控制系统:保存版本信息/同步团队代码分布式:通过本地仓库来保存版本信息,中央仓库用来同步代码,不过依然会保存版本信息,但这份历史版本更多的是作为团队间的同步中转站。 分布式版本控制系统(VCS)的优缺点优点 大多数操作是在本地进行,速度更快,且不用联网。 可以分布提交代码,可以把代码提交做得更细,而不是一个提交包含很多代码。导致难以回溯(review)缺点 由于每个机器都有完整的本地仓库,所以初次获取(Git:clone)会比较耗时。 由于每个机器都有完整的本地仓库,所以本地存储会比中央式要高。 Git最基本的工作模型 从GitHub/Gitee把中央仓库clone到本地(git clone) 把写完的代码提交 git add 文件把文件添加到缓存区 git commit 提交到本地仓库 在此过程中使用git status来查看工作目录的状态 每个文件有“change/unstaged”(已修改)/“staged”(已修改并暂存)/“commited”(已提交)/“untracked”(未追踪) 提交一次或多次后,把本地提交push到中央仓库(git push) Git中的引用 HEAD:当前commit的引用 branch 分支 master默认的branch branch 创建branch(git branch branch1xx) 切换branch(git checkout branch1xx) 创建并切换到branch(git checkout -b branch1xx) 删除branch(git branch -d branch1xx) 扔物线大神的小结: HEAD 是指向当前 commit 的引用,它具有唯一性,每个仓库中只有一个 HEAD。在每次提交时它都会自动向前移动到最新的 commit 。 branch 是一类引用。HEAD 除了直接指向 commit,也可以通过指向某个 branch 来间接指向 commit。当 HEAD 指向一个 branch 时,commit 发生时,HEAD 会带着它所指向的 branch 一起移动。… master 是 Git 中的默认 branch,它和其它 branch 的区别在于 新建的仓库中的第一个 commit 会被 master 自动指向; 在 git clone 时,会自动 checkout 出 master。 branch 的创建、切换和删除: 创建 branch 的方式是 git branch 名称 或 git checkout -b 名称(创建后自动切换); 切换的方式是 git checkout 名称; 删除的方式是 git branch -d 名称。 merge: 合并commits 适用场景 合并分支 pull的内部操作 放弃解决冲突,取消merge(git merge --abort) 扔物线大神的小结 merge 的含义:从两个 commit「分叉」的位置起,把目标 commit 的内容应用到当前 commit(HEAD 所指向的 commit),并生成一个新的 commit; merge 的适用场景: 单独开发的 branch 用完了以后,合并回原先的 branch; git pull 的内部自动操作。 merge 的三种特殊情况: 冲突原因:当前分支和目标分支修改了同一部分内容,Git 无法确定应该怎样合并;应对方法:解决冲突后手动 commit。 HEAD 领先于目标 commit:Git 什么也不做,空操作; HEAD 落后于目标 commit:fast-forward。… Feature Branching:最流行的工作流 核心内容: 任何新功能或者bug修复都创建新的branch来写/ branch写完后,合并到master,然后删掉这个branch。 git add . 直接将工作目录下的所有改动全部放进暂存区。git log 查看历史记录参数配置: git log -p 查看详细历史 git log --stat查看简要统计 git show查看具体的commit git diff查看未提交的内容 git rebase 改变commit序列的基础点git rebase 目标基础点rebase是站在需要被rebase的commit上进行操作的,这点和merge不同 commit –amend 对最新的一条commit进行修正注意:commit --amend不是直接修改原commit内容,而是生成一条新的commit rebase -i 交互式rebase使用方式是 git rebase -i 目标commit;在编辑界面中指定需要操作的 commits 以及操作类型;操作完成之后用 git rebase –continue 来继续 rebase 过程。 撤销最新的提交git reset --hard 目标commit 撤销不是最新的请求 用git rebase -i在编辑界面中删除想撤销的 commits 用 git rebase --onto在 rebase 命令中直接剔除想撤销的 commits方法有两种,理念是一样的:在 rebase 的过程中去掉想撤销的 commit,让他它消失在历史中。","categories":[],"tags":[{"name":"版本控制","slug":"版本控制","permalink":"http://yoursite.com/tags/版本控制/"}]},{"title":"Hello World","slug":"hello-world","date":"2018-06-20T01:55:35.215Z","updated":"2018-06-20T01:55:35.215Z","comments":true,"path":"2018/06/20/hello-world/","link":"","permalink":"http://yoursite.com/2018/06/20/hello-world/","excerpt":"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[],"tags":[]}]}