Git Product home page Git Product logo

blog's People

Contributors

zgq105 avatar

Stargazers

 avatar  avatar

blog's Issues

Android布局总结

image

1. 布局种类

1.1 线性布局(LinearLayout)

线性布局

1.2 相对布局(RelativeLayout)

相对布局

1.3 帧布局(FrameLayout)

帧布局

1.4 约束布局(ConstraintLayout)

约束布局

1.5 表格布局(TableLayout)

表格布局

1.6 坐标布局(CoordinatorLayout)

坐标布局

2. setContentView和LayoutInflater说明

1. setContentView的作用
setContentView的作用是加载布局文件到Activity的窗口对象。详细见:View的绘制流程

2. LayoutInflater的作用
LayoutInflater的作用主要是将xml布局文件解析转成View对象。
主要源码如下:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
       synchronized (mConstructorArgs) {
           Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

           final Context inflaterContext = mContext;
           final AttributeSet attrs = Xml.asAttributeSet(parser);
           Context lastContext = (Context) mConstructorArgs[0];
           mConstructorArgs[0] = inflaterContext;
           View result = root;

           try {
               // Look for the root node.
               int type;
               while ((type = parser.next()) != XmlPullParser.START_TAG &&
                       type != XmlPullParser.END_DOCUMENT) {
                   // Empty
               }

               if (type != XmlPullParser.START_TAG) {
                   throw new InflateException(parser.getPositionDescription()
                           + ": No start tag found!");
               }
               final String name = parser.getName();         
               if (TAG_MERGE.equals(name)) {
                   if (root == null || !attachToRoot) {
                       throw new InflateException("<merge /> can be used only with a valid "
                               + "ViewGroup root and attachToRoot=true");
                   }
                   rInflate(parser, root, inflaterContext, attrs, false);
               } else {
                   // Temp is the root view that was found in the xml
                   final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                   ViewGroup.LayoutParams params = null;
                   if (root != null) {                    
                       // Create layout params that match root, if supplied
                       params = root.generateLayoutParams(attrs);
                       if (!attachToRoot) {
                           // Set the layout params for temp if we are not
                           // attaching. (If we are, we use addView, below)
                           temp.setLayoutParams(params);
                       }
                   }
                   // Inflate all children under temp against its context.
                   rInflateChildren(parser, temp, attrs, true);
                   // We are supposed to attach all the views we found (int temp)
                   // to root. Do that now.
                   if (root != null && attachToRoot) {
                       root.addView(temp, params);
                   }
                   // Decide whether to return the root that was passed in or the
                   // top view found in xml.
                   if (root == null || !attachToRoot) {
                       result = temp;
                   }
               }

           } catch (XmlPullParserException e) {
               final InflateException ie = new InflateException(e.getMessage(), e);
               ie.setStackTrace(EMPTY_STACK_TRACE);
               throw ie;
           } catch (Exception e) {
               final InflateException ie = new InflateException(parser.getPositionDescription()
                       + ": " + e.getMessage(), e);
               ie.setStackTrace(EMPTY_STACK_TRACE);
               throw ie;
           } finally {
               // Don't retain static reference on context.
               mConstructorArgs[0] = lastContext;
               mConstructorArgs[1] = null;
               Trace.traceEnd(Trace.TRACE_TAG_VIEW);
           }
           return result;
       }
   }

设计模式之责任链模式

image

1. 什么是责任链模式?有什么作用?

责任链模式指为请求者创建多个处理对象的链,对请求的发送者和接收者进行解耦,属于行为型设计模式。它的作用就是通过职责链的方式将请求的发送者和请求的处理者解耦。

2. 如何实现?

以公司技术部部门的信息反馈为例实现简单的责任链设计模式,假设部门有部门经理、项目经理、普通员工三个角色。
//定义接收者抽象和实现

public abstract class Handler {

    private Handler mNextHandler;//上一级
    private int mLevel;//等级

    public Handler(int level) {
        this.mLevel = level;
    }

    /**
     * 处理传递
     */
    public final void handleMessage(Message message) {
          if(message.getLevel()==mLevel){
              handle(message);
          }else {
              if (this.mNextHandler != null) {
                  System.out.println("消息太重要,需报告上一级");
                  this.mNextHandler.handleMessage(message);
              } else {
                  System.out.println("我是部门经理,没有上头了,自己处理了");
              }

          }
    }

    /**
     * 指定上一级
     *
     * @param handler
     */
    public void setNextHandler(Handler handler) {
        this.mNextHandler = handler;
    }

    /**
     * 处理消息
     * @param message
     */
    protected abstract void handle(Message message);
}
/**
 * Created by zgq on 2019/5/29 22:12
 * 部门经理
 */
public class DepartmentManager extends Handler {
    public DepartmentManager() {
        super(1);
    }

    @Override
    protected void handle(Message message) {
        System.out.println("部门经理"+message.getMsgContent());
    }
}
/**
 * Created by zgq on 2019/5/29 22:12
 * 项目经理
 */
public class ProjectManager extends Handler {
    public ProjectManager() {
        super(2);
    }

    @Override
    protected void handle(Message message) {
        System.out.println("项目经理"+message.getMsgContent());
    }
}
/**
 * Created by zgq on 2019/5/29 22:12
 * 普通员工
 */
public class Staff extends Handler {
    public Staff() {
        super(3);
    }

    @Override
    protected void handle(Message message) {
        System.out.println("普通员工"+message.getMsgContent());
    }
}

//消息对象

public class Message {
    private int level;
    private String msgContent;

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getMsgContent() {
        return msgContent;
    }

    public void setMsgContent(String msgContent) {
        this.msgContent = msgContent;
    }
}

//消息发送者

        Staff staff = new Staff();
        ProjectManager projectManager = new ProjectManager();
        DepartmentManager departmentManager = new DepartmentManager();
        staff.setNextHandler(projectManager);
        projectManager.setNextHandler(departmentManager);
        Message message = new Message();
        message.setMsgContent("hello");
        message.setLevel(2);
        staff.handleMessage(message);

3. JDK或Android的应用举例

在JDK中,Servlet过滤器机制使用的就是责任链模式;在Android SDK中,安卓事件分发机制使用的也是责任链模式。

4. 小结

责任链模式的核心是将请求方和接收方解耦,同时接收方是链式结构。
优点:

  • 符合开闭原则,新增时,只需要在链条中添加一个类即可。
  • 请求方和接收方解耦,请求方不需要关注接收处理的流程和细节。

缺点:

  • 一个请求可能因职责链没有被正确配置而得不到处理。
  • 当接收方链过长时,效率会变慢。

关于hashCode和equals的总结

hashCode是散列码,利用键值对存储数据,方便快速查找所需的对象。而equals用于判断两个对象的值是否相等,效率就比较低;简单的理解就是hashCode用于快速过滤刷选,只有hashCode存在相同的时候,才会调用equals方法进行下一步的比较。(hashCode主要缩小查找成本

  1. 如果两个对象相等,则hashcode一定也是相同的
  2. 两个对象相等,对两个对象分别调用equals方法都返回true
  3. 两个对象有相同的hashcode值,它们也不一定是相等的
  4. 因此,equals方法被覆盖过,则hashCode方法也必须被覆盖
  5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

Android中View的绘制过程

image

1. 什么是View,作用是什么?

Android中View直译为视图或则界面的意思,可以简单理解为APP中界面呈现出来的一个个元素就是View。它负责了Android中展示的部分。

2. View和ViewGroup区别

View是所有视图组件的基类,ViewGroup也是View的子类;在View中定义了展示组件的通用规则。ViewGroup是抽象类表示带有容器类性质的View,通常指Android中的布局组件,像RelativeLayout、FrameLayout、LinearLayout等都是继承ViewGroup。而一般非容器类的组件,像TextView,Button、ImageView等都是直接继承View实现的。所以,通俗点理解就是View代表非容器类视图组件,ViewGroup代表容器类视图组件。

3. Activity、Window和View关系

借用一张大神的图
image
在Android中,每个Activity都包含一个根窗口Window对象PhoneWindow;Window是一个抽象类,PhoneWindow继承Window,Activity中setContentView方法实际调用的就是PhoneWindow中的setContentView,如下图所示:

//Activity.java中setContentView
 public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

PhoneWindow里面包含了一个DecorView,它是FrameLayout,setContentView其实就是将layout资源设置到了这个容器类型的DecorView上。
**总结:**Activity包含PhoneWindow,PhoneWindow包含DecorView,DecorView包含ActionBar和我们自定义布局。

4. View的显示经历了哪些流程

4.1 xml转View过程(预处理阶段)

xml布局资源转View过程就是通过Activity中setContentView,本质是将xml文件通过布局填充机制转成View对象,然后再添加到DecorView中。可以理解Activity的setContentView方法是完成内容视图的创建与设置工作,接下来就是进入View的绘制流程。

4.2 测量阶段(measure)

在Activity的onCreate中完成了布局资源转成View并加入到DecorView中,在onResume中,就会完成整个View的绘制流程,首先建立DecorView和ViewRootImpl关联,ViewRootImpl是负责整个绘制的流程,具体关联过程如下:ActivityThread#handleResumeActivity->WindowManagerImpl#addView->WindowManagerGlobal#addView->ViewRootImpl#setView,到这个方法为止,DecorView就和ViewRootImpl关联上了。
image

接着上面的过程ViewRootImpl#setView -> ViewRootImpl#requestLayout -> ViewRootImpl#scheduleTraversals -> ViewRootImpl中内部类TraversalRunnable#doTraversal -> ViewRootImpl#performTraversals -> ViewRootImpl#measureHierarchy -> ViewRootImpl#performMeasure,在performMeasure中调用View类提供的measure方法进行测量工作,可以理解performMeasure方法是整个测量measure的入口,接着上面流程,ViewRootImpl#performMeasure
-> View#measure(中转方法) -> DecorView#onMeasure(实际测量工作),接下来就进入到View树的测量流程,如果是ViewGroup就计算自身的大小和递归循环计算子元素的大小,如果是普通的View就计算自身大小即可。
image
测量三种模式:

  1. MeasureSpec.UNSPECIFIED表示不限制大小模式。
  2. MeasureSpec.EXACTLY表示明确的指定大小模式,比如match_parent或则设置固定dp或px值。
  3. MeasureSpec.AT_MOST表示不超过固定大小模式,比如wrap_content。

注:一般自定义View需要重写onMeasure方法,实现自己的测量规则。

4.3 布局阶段(layout)

ViewRootImpl#performTraversals ->ViewRootImpl#performLayout -> DockView#layout(实际调用的父类View的方法) -> DockView#onLayout(实际布局工作),接下来就进入到View树的布局流程,如果是ViewGroup就计算自身的大小和递归循环布局子元素,如果是普通的View控制好自身布局即可。所谓布局就是如何将View摆放在某个位置的问题。

4.4 绘制阶段(draw)

ViewRootImpl#performTraversals -> ViewRootImpl#performDraw -> ViewRootImpl#draw -> ViewRootImpl#drawSoftware -> View#draw,在View的draw中完成所有绘制工作。主要绘制内容如下:

  1. 绘制背景。
  2. 绘制内容。
  3. 绘制子元素。
  4. 绘制装饰。

设计模式之工厂方法

image

1. 什么是工厂方法?有什么作用

工厂方法是属于创建型模式,是一种创建对象的最佳实践。创建对象的过程,向用户屏蔽创建的细节,通过公共的接口来创建对象。它的作用主要是提供简洁、统一的方式来创建对象。

2. 如何实现工厂方法

//定义共同接口

public interface Ball {
    void play();
}

//定义实现类

public class Basketball implements Ball {
    @Override
    public void play() {
        Log.d("ball","Basketball");
    }
}

//定义实现类

public class Football implements Ball {
    @Override
    public void play() {
        Log.d("ball","Football");
    }
}

//定义工厂类

public class BallFactory {

    public static Ball create(String type) {
        if (type.equalsIgnoreCase("Basketball")) {
            return new Basketball();
        } else if (type.equalsIgnoreCase("Football")) {
            return new Football();
        }
        return null;
    }
}

//使用工厂方法

public static void test() {
        Ball ball = BallFactory.create("football");
        ball.play();
        Ball ball2 = BallFactory.create("basketball");
        ball2.play();
    }

3. JDK或Android的应用举例

在Android中创建Bitmap的过程,使用的BitmapFactory类就是工厂方法模式。

4. 小结

工厂方法模式作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。

java设计模式专题

image

什么是设计模式?

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

设计模式六大原则

1. 开闭原则
开闭原则是指对扩展是开发的,对修改是关闭的。

2. 里氏代换原则
里氏代换原则指任何基类可以出现的地方,子类一定可以出现。它是指对实现抽象化的具体步骤的规范。

3. 依赖倒转原则
这个是开闭原则的基础,即面向接口编程。在软件开发中,依赖于抽象而不依赖于实现。

4. 接口隔离原则
接口隔离原则是指使用多个隔离的接口,比使用单个接口要好;在软件开发中,注意高内聚,降低代码耦合度。

5. 迪米特法则,又称最少知道原则
迪米特法则指一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立;其实就是面向对象的封装性。

6. 合成复用原则
合成复用原则指尽量使用合成/聚合的方式,而不是使用继承。

1.创建型

1.1 工厂方法

工厂方法模式

1.2 抽象工厂模式

抽象工厂模式

1.3 建造者模式

建造者模式

1.4 原型模式

原型模式

1.5 单例模式

单例模式

2.结构型

2.1 适配器模式

适配器模式

2.2 桥接模式

桥接模式

2.3 组合模式

组合模式

2.4 外观模式

外观模式

2.5 装饰者模式

装饰者模式

2.6 享元模式

享元模式

2.7 代理模式

代理模式

3.行为型

3.1 责任链模式

责任链模式

3.2 命令模式

命令模式

3.3 解释器模式

3.4 迭代模式

迭代器模式

3.5 中介者模式

中介者模式

3.6 备忘录模式

备忘录模式

3.7 观察者模式

观察者模式

3.8 状态模式

状态模式

3.9 策略模式

策略模式

3.10 模板方法模式

3.11 访问者模式

Android序列化总结

image

1. 什么是序列化?

序列化指的是将对象转成二进制流的形式的过程,序列化之后便于数据的传输和持久化;而反序列化就是将二进制流还原对象的过程。

2. 序列化分类

2.1 Serializable

Serializable是JDK提供的接口,它是一个标识接口。只要实现该接口的类,便可进行序列化操作。
示范代码:

public class Person2 implements Serializable {
    private int age;
    private String name;
    private String sex;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

//使用序列化对象

 Intent intent=new Intent();
 Person2 person2=new Person2();
 person2.setAge(20);
 person2.setName("zgq");
 intent.putExtra("p",person2);

2.2 Parcelable

Parcelable是Android SDK提供的序列化操作的接口,需要序列化操作的对象需要实现接口Parcelable和实现内部序列化和反序列化的方法。具体实现代码如下:

public class Person implements Parcelable {

    private int age;
    private String name;
    private String sex;

    public Person(Parcel in) {
        age = in.readInt();
        name = in.readString();
        sex = in.readString();
    }

    public  Person(){

    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(name);
        dest.writeString(sex);
    }
}

说明:在writeToParcel中方法中实现序列化操作;在静态内部类CREATOR中实现反序列化操作。注意序列化和反序列化操作赋值过程中顺序的要一致。Parcel是数据序列化和反序列化操作中转的桥梁,内部实现了各种数据序列化和反序列化操作的方法。

//使用序列化对象

Intent intent = new Intent();
Person person = new Person();
person.setAge(20);
person.setName("zgq");
intent.putExtra("p", person);

3. 小结

Parcelable和Serializable都是实现序列化操作的接口。Parcelable的效率要比Serializable快,一般内存序列化操作优先考虑Parcelable;而涉及到持久化操作的时候优先考虑使用Serializable。Parcelable使用的是Binder机制传输数据,而Serializable用到了大量的临时变量(原因是使用了反射机制),所以内存方面的表现也是Parcelable比较好。

Android事件分发机制

Android事件分发机制

1. 事件分发机制是什么?作用?

事件分发机制是当用户触摸屏幕时,将产生点击触摸事件;事件分发机制是指事件经过逐层传递并最终得到处理的过程。事件分发处理机制是各种交互效果的基础,是用户和屏幕交互的过程中并得到各种回馈的基础。

2. 事件分发机制API

2.1 dispatchTouchEvent

dispatchTouchEvent是在基类View中定义,ViewGroup中有重写该方法并加上容器类组件的处理逻辑;dispatchTouchEvent主要是起到事件传递的作用,在事件由外向里传递过程中起作用。(事件捕获阶段)

2.2 onTouchEvent

onTouchEvent是在基类View中定义,主要在事件由里向外传递过程中起作用(事件冒泡阶段);只要触点有命中组件即会触发该组件的onTouchEvent方法。

2.3 onInterceptTouchEvent

onInterceptTouchEvent是在ViewGroup定义,主要作用是判断事件是否被拦截,返回true表示终止事件传递,反之继续传递。

3. 事件消费

事件消费是指上dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent中返回true表示该事件被当前对象消费了,即事件不会继续传递;反之事件继续传递,直到结束为止。

4. 事件分发流程

借用一张大神的图
image

4.1 事件捕获

事件捕获过程是指事件从Activity窗口有外向里遍历View树的过程,大致过程如下:
Activity#dispatchTouchEvent -> ViewGroup#dispatchTouchEvent -> ViewGroup#onInterceptTouchEvent -> View#dispatchTouchEvent 。

4.2 事件冒泡

事件冒泡过程是指事件从里向外遍历View树的过程,大致过程如下:
View#onTouchEvent-> ViewGroup#onTouchEvent->Activity#onTouchEvent。

5. onTouch和onTouchEvent区别

  1. onTouch先于onTouchEvent执行。
  2. 如果onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
  3. onTouch需要mOnTouchListener的值不能为空和当前点击的控件必须是enable的。

设计模式之抽象工厂模式

image

1. 什么是抽象工厂模式?有什么作用?

抽象工厂模式是在工厂方法模式基础上进一步对工厂进行抽象,由抽象工厂产生具体工厂,再有具体工厂产生具体产品。在抽象工厂模式中,接口是负责创建一个具体对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供具体对象。

2. 如何实现?

//创建抽象工厂

public interface AbstractFactory {
    Ball creteBall(String type);
}

//创建具体工厂

public class BallFactory implements AbstractFactory {
    @Override
    public Ball creteBall(String type) {
        if (type.equalsIgnoreCase("Basketball")) {
            return new Basketball();
        } else if (type.equalsIgnoreCase("Football")) {
            return new Football();
        }
        return null;
    }
}

//产品抽象

public interface Ball {
    void play();
}

//产品实现

public class Basketball implements Ball {
    @Override
    public void play() {
        Log.d("ball","Basketball");
    }
}

public class Football implements Ball {
    @Override
    public void play() {
        Log.d("ball","Football");
    }
}

//使用抽象工厂模式

AbstractFactory ballFactory = new BallFactory();
Ball ball1 = ballFactory.creteBall("football");
ball1.play();
Ball ball2 = ballFactory.creteBall("basketball");
ball2.play();

3. JDK或Android的应用举例

在JDK中DriverManager#getConnection中使用的就是抽象工厂模式。

4. 小结

抽象工厂模式对工厂进行了抽象,使得每个生成的工厂都能按照工厂模式提供对象。

Android模块化、组件化、插件化专题

image

1. 模块化

1.1 什么是模块化

模块化是一种软件开发**,把一个项目工程按作用和业务特性等划分为若干模块。比如,基础库模块、登录模块、分享模块、图片处理模块。模块化的作用就是将项目的结构划分清楚,使得项目结构清晰、易于维护和重用。模块化的具体实施方法分为插件化和组件化。

2. 组件化

2.1 什么是组件化

组件化是指根据当前模块所处阶段可以动态切换成library模式或则application模式,即组件化是一个可以单独运行的APP。在开发阶段时,组件是一个application工程;当集成发布时,组件是一个library工程。
(通过gradle脚本动态切换,网上资料一大堆,不在阐述)

2.2 组件化利弊

组件化优点:

  1. 组件化更注重高内聚、低耦合。
    2.组件化可以动态切换模式,便于团队分工同步开发;每个人负责自己组件的开发,互不影响。
  2. 组件化便于调试、加快编译运行速度。

组件化不足:
1.组件化开发模式动态能力相对较弱,对于线上版本的组件不能动态增加和修改。

2.3 适用场景

组件化开发合适大型项目的迭代开发,便于项目的管理和维护。

2.4 解决方案

关于组件化开发的解决方案网上有各种大同小异的解决方案。主要的**是要解决一下几个问题:

  1. 组件模块的划分问题,解决代码耦合问题。
  2. 组件之间的通信问题。
  3. 组件的重用问题。
  4. 解决团队成员的协作问题和调试编译速度问题。
    注:组件通信方式主要有通知机制和接口加路由的方式。

下面以项目架构示意图为例说明:

image

3. 插件化

3.1 什么是插件化

插件化是指每个模块就是一个单独的apk,主项目在使用的时候动态加载。

3.2 插件化利弊

插件化优点:

  1. 可以动态增加和修改线上的模块。
  2. 插件化开发可以非常方便的适合团队开发,每个人只要负责自己apk对应的模块开发即可。

插件化不足:

  1. 随着项目的不断迭代,APK数量会不断增加,因此apk的管理也是个问题。

3.3 适用场景

组件化开发合适大型项目的迭代开发,适用于经常需要热更新的的应用。

3.4 解决方案

成熟的解决方案有DynamicAPK、DroidPlugin、Android-Plugin-Framework等等。

4. 小结

组件化和插件化的思路都是类似的,主要解决单独调试、集成编译、数据传输、UI 跳转、生命周期和代码边界等问题。关键点在于要把项目架构的模块划分清楚、组件之间的依赖关系梳理清楚和实施的过程中注意代码的解耦和重用问题。

Java内存区域划分总结

image

1. 什么是java内存区域

java内存区域是指给java虚拟机在执行java程序所需要分配的各种数据区域。

2. 内存分类

2.1 程序计数器(线程私有)

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。每个线程有自己单独的程序计数器,互不影响。它是唯一不会OutOfMemoryError的内存区域。它的生命周期和线程保持一致。

2.2 java虚拟机栈(线程私有)

Java虚拟机栈(也称java方法栈)也是线程私有的,它的生命周期和线程一致,描述的是 Java方法执行的内存模型。Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表存放了基本数据类型变量和对象的引用(指针)。
超过Java虚拟机栈的最大深度限制 -> StackOverFlowError
超过Java虚拟机栈的最大内存限制 -> OutOfMemoryError

2.3 本地方法栈(线程私有)

本地方法栈和虚拟机栈的机制一样,唯一不同的是本地方法栈是为Native方法服务的。

2.4 堆(线程共享)

堆是java虚拟机中最大的一块内存区域,是线程共享的;主要用于存放对象实例,几乎所有new的对象和数组都在堆中分配内存。
超过堆的最大内存限制 -> OutOfMemoryError

2.5 方法区(线程共享)

方法区是java线程共享的一块内存区域;它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池是方法区的一部分,主要用于存放编译期生成的各种字面量和符号引用。
超过方法区的最大内存限制 -> OutOfMemoryError

分享小知识点:
内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。(通俗点理解就是占着茅坑不拉屎)

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况(OOM)。

Android四大组件之Activity

image

1. 什么是Activity?

Activity是安卓里面的负责面向用户的一种机制,主要向用户提供视图层展示和交互;Activity可以说充当了View层和Controller层,在安卓APP中我们看到的界面都是Activity机制提供的。

2. Activity生命周期

2.1 正常情况下生命周期

这里提供谷歌的一张图已经很好的诠释了生命周期的整个过程

image

第一次进入Activity时,会依次执行onCreate->onStart->onResume,onCreate主要是Activity创建阶段,在这里主要是加载布局资源和初始化数据的工作,onStart是Activity进入就绪可见阶段,直到onResume被执行之后Activity就会在前台而且用户可以交互了。
情况1:如果此时用户点击了返回键,那么依次执行onPause->onStop->onDestroy,这样一个完整的生命周期就执行完了,只要activity变成不在是前台activity时就会执行onPause,onStop执行之后activity变成不可见,onDestory主要执行一些释放资源的工作,此时activity也会被销毁。(会立刻回收该Activity吗?埋个伏笔)
情况2:如果此时用户点击了home键,那么依次执行onPause->onStop,此时Activity进入后台状态,对用户不可见。
情况3:如果此时用户点击进入对话框类型的activity或者透明的activity时,那么依次执行onPause,此时activity不在获得焦点,但是可见的。
以上几种情况,如果是从onStop状态过度的,依次执行onRestart->onStart->onResume;如果是从onPause状态过度的,依次执行onResume。

2.2 非正常情况下生命周期

一般情况下当activity不在前台运行时都有可能被系统回收,所以就有了onSaveInstanceState和onRestoreInstanceState用于数据的保存和恢复,防止因系统回收而导致数据的丢失。
onSaveInstanceState会执行的情形:

  1. 用户点击home键。
  2. 用户锁屏操作。
  3. 从当前activity启动一个新的activity时。
  4. 横竖屏切换操作。
    onRestoreInstanceState执行的情形:
    只有当activity被killed之后重新创建的时候才会执行,并且在onStart之后调用。
    因此两者的工作模式是在killed之前调用onSaveInstanceState保存数据,在activity重新创建的时候通过调用onRestoreInstanceState恢复数据,数据存储在参数Bundle对象中。

2.3 特殊情况下生命周期分析

2.3.1 横竖屏切换分析

生命周期过程:onCreate->onStart->onResume->onPause->onStop->onDestroy->onCreate->onStart->onResume

screenOrientation参数说明:
unspecified : 默认的,由系统控制显示方向。
landscape: 总是横屏显示。
portrait: 总是竖屏显示。
user: 默认当前用户首选方向,但是进去之后activity是还可以旋转切换横竖屏的。
behind: 默认保持方向和背后的activity方向一致,但是进去之后activity是还可以旋转切换横竖屏的。
nosensor: 忽略传感器的作用,不会随着用户旋转屏幕而切换横竖屏。
sensor: 根据物理传感器决定屏幕旋转方向。

configChanges参数说明: 这个参数的作用主要用于捕获手机状态的改变。
mcc: 国家代码。
mnc: 手机网络代码。
mnc: 地区发生改变。
touchscreen: 触摸屏发生改变,一般不会发生。
keyboard: 键盘发生改变。
orientation: 屏幕方向改变。
screenSize: 屏幕大小发生改变。
对android:configChanges属性,一般有以下几点比较常用:

  1. 没有设置configChanges这个属性的时候,横竖屏切换的生命周期分析,竖屏->横屏,生命周期会被销毁并重新创建;再横屏->竖屏时,生命周期再一次被销毁并重新创建。
  2. 设置Activity的android:configChanges="orientation"时,横竖屏切换的生命周期分析,竖屏->横屏,生命周期会被销毁并重新创建;再横屏->竖屏时,生命周期不会被销毁,只会回调这个方法onConfigurationChanged。
  3. 设置Activity的android:configChanges="orientation|screenSize"时,横竖屏切换的生命周期分析,竖屏->横屏,生命周期不会被销,只回调方法onConfigurationChanged;再横屏->竖屏时,生命周期不会被销毁,只会回调方法onConfigurationChanged。

2.3.2 横竖屏布局

横竖屏布局是指在不设置android:configChanges属性去改变生命周期,每次切换都销毁重建的情况下,动态加载res目录下layout-land和layout-port中的布局文件的过程。

image

2.3.3 什么情况只走onPause不走onStop

当Activity启动一个对话框类型的Activity时,此时Activity不在获得焦点,但是依旧可见,这种情形Activity只走只走onPause不走onStop。

2.3.4 什么情况不执行onDestory

当后台强杀应用时,如果当前任务栈中只有一个Activity时,此时Activity中onDestory会执行;如果当前任务栈中有多个Activity时,比如A->B->C,此时只有A中的onDestory会执行,B和C中的onDestroy就不会执行了。

3. 启动模式

3.1 什么是Activity栈?

Activity栈是安卓用于管理Activity页面的一种机制,是任务栈模型,默认一个Android应用会有一个任务栈,所有的Activity都在这个任务栈中管理,如果没有指定名称,默认是包名。如果想在应用中指定Activity所在的任务栈,需要通过android:taskAffinity指定,同时需要指定android:launchMode为singleTask或者singleInstance才会生效。

3.2 Activity四种launchMode

standard: 标准模式也是默认Activity启动模式,即每次启动都会创建一个新的实例对象,大部分的应用场景都是采用这种模式。
singleTop: 栈顶模式,即待启动的Activity如果已经存在任务栈顶部时,直接复用该实例,并且回调onNewIntent方法;如果不存在,则重新创建实例。主要的应用场景适合做搜索页面,每次打开,搜索一些结果,点击详情页面,然后返回继续搜索。
singleTask: 栈内单例模式,即待启动的Activity如果已经存在任务栈中,那么在该Activity前面的Activity将全部出栈,并且该Activity置顶复用该实例,并且回调onNewIntent方法。主要的应用场景适合做入口类页面,比如应用的主界面MainActivity。
singleInstance: 全局单例模式,即整个应用只存在一个实例,而且该Activity独占一个单独的任务栈;如果待启动Activity已经存在,则直接复用实例且执行onNewIntent;否则,重新创建实例。主要的应用场景适合做与程序分离的页面,比如闹钟页面或则拨号页面。

4. 各种状态分析

活动状态(actived): 指Activity已经在前台,用户可以和页面进行交互的状态。
暂停状态(paused): 指Activity被其他透明的Activity或则对话框类型的Activity遮住,此时Activity是可见的。
停止状态(stoped): 指Activity被其他Activity覆盖,此时Activity对用户不可见的状态,即已经进入后台状态。
杀死状态(killed): 指Activity被销毁的状态。

5. Activity组件通信

5.1 Activity与Activity通信

5.1.1 Intent机制

Intent机制是Activity之间跳转通信的桥梁,主要提供startActivity和startActivityForResult进行启动,如果需要传递参数可以通过Intent和Bundle类进行赋值传递,Intent中传递参数的机制也是通过Bundle去实现的,只不过通过外观模式的设计向用户屏蔽了内部的实现细节。

5.1.2 静态变量

所谓的静态变量的方式就是在Activity中声明public类型的静态变量,因此只要改类没有被回收的情况,其他类都是可以访问的。

5.1.3 全局变量

全局变量方式主要指通过Application类实现共享或者声明一个全局类的方式共享数据,本质是内存方式的共享。

5.1.4 持久化的方式

持久化的方式是指将数据通过SharedSreference、文件或数据库持久化,这样便可实现多个Activity之间的共享和传递。

5.2 Activity与Service通信

Activity和Service之间的通信也是通过Intent去激活,主要提供启动服务startService和绑定服务bindService,startService的方式其生命周期不会依赖启动它的Activity;而bindService的方式,其生命周期会依赖启动的Activity对象,需要一个绑定对象。
数据传递传递方向:
Activity->Service,通过Intent传递。
Service->Activity,通过Binder获取Service对象,进而获取Service中数据。

5.3 Activity与BroadCast通信

Activity与BroadCast通信是通过IntentFilter和Intent的方式通信的,主要提供多个重载的registerReceiver来注册广播接收者,通过sendBroadcast来发送广播。

5.4 Activity与Fragment通信

Fragment是Activity进一步细分模块的一种机制,数据传递方向分析:
Activity->Fragment:通过构造函数传递或者获取Fragment的实例对象
Fragment->Activity:在 Fragment 中 getActivity(),即可获取Activity对象,便可获取Activity中数据。
Fragment<->Fragment:通过Activity中转或者接口回调方式实现。

5.5 线程之间通信

5.5.1 Handler机制

安卓之间线程之间的通信主要分为主线程和子线程之间的通信,子线程和子线程之间的通信。通信的过程中主要用到Handler、Looper、MessageQueue和Message。Handler主要用于处理消息handleMessage和发送消息sendMessage,一个Handler对象只属于某一个线程,一个线程可以有多个Handler对象,处理多个消息。Looper指消息循环,启动中转消息的作用,通过Looper.prepare()创建一个线程的消息循环,再通过Looper.loop()启动消息循环(无限循环);一个线程只能有一个Looper对象。MessageQueue指的是消息队列,存放着Message的队列,按FIFO机制处理消息。Message指的是消息对象,主要是通信的数据载体。(注意: 一个线程只有一个Looper,一个MessageQueue,可以有多个Handler对象)
image

5.5.2 AsyncTask

AsyncTask是Android封装好的异步处理类,主要用于子线程和主线程之间的通信,比如实现下载一个文件的操作;AsyncTask内部实现机制还是通过线程池和Handler来实现的。

5.5.3 runOnUiThread

runOnUiThread是Activity类提供的方法,容许在子线程中直接操作主线程的UI;其内部实现方式还是通过Handler机制实现的。

5.5.4 Handler中post(Runnable)

Handler中的post方法可以用于线程之间的通信,Runnable运行的线程环境取决于Handler对象所属的线程,比如Handler对象属于主线程,则Runnable中run方法在主线程回调;如果Handler对象属于某个子线程,则Runnable中run方法在子线程回调。其内部机制是Handler。

5.5.5 View中post(Runnable)

View中post方法允许在子线程中直接调用,Runnable中run方法在主线程回调。其内部机制是Handler。

5.6 进程之间通信

5.6.1 文件

5.6.2 AIDL

5.6.3 Messenger

5.6.4 ContentProvider

5.6.5 Socket

5.6 多个App之间通信

主要通过构建隐式意图的方式来跨应用通信。

6. URL Scheme

URL Scheme是android中跨应用页面跳转的机制,通过自定义路由的方式可以实现android应用中的页面跳转,H5页面中打开原生应用页面。

1. URL Scheme协议格式
scheme://host:port/path?query,其中scheme表示自定义协议名,host表示主机,port表示端口,path表示路径,query表示查询参数。

2. URL Scheme协议的定义
Activity中的Scheme定义主要是通过意图过滤器intent-filter中定义,如下:

<intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <!--允许浏览器启动-->
                <category android:name="android.intent.category.BROWSABLE" />
                <!--允许隐式启动-->
                <category android:name="android.intent.category.DEFAULT" />
                <!--协议部分-->
                <data
                    android:host="192.168.0.1"
                    android:scheme="zgq" />
</intent-filter>

3.其他APP访问
在APP中访问Scheme类型的Activity,主要通过隐式意图的方式指定需要访问页面的路由即可,如下:

Uri data = Uri.parse("zgq://192.168.0.1");
Intent intent = new Intent(Intent.ACTION_VIEW, data);
startActivity(intent);

4. 浏览器页面中访问Scheme类型Activity
在浏览器的h5页面中需要访问原生的Activity页面,也是非常的方便,只需要指定路由的路径即可,如下:
<a href="zgq://192.168.0.1">打开Android应用</a>
这样一行代码就可以启动原生Activity页面了,就像打开网页的方式一样简单直观。

5. URL Scheme总结
URL Scheme可以降低系统之间的耦合度,使得模块之间的调用方便、简洁。使用路由的方式启动页面使得操作更安全,不用像显示意图那样打开页面。Scheme机制的便利性使得它有更多的应用场景。

设计模式之代理模式

image

1. 什么是代理模式?有什么作用?

代理模式是指一个类代表另一个类的功能,是结构型设计模式。比如我们想买个鞋子就去淘宝下单,那么淘宝就是代理的角色。主要作用就是委托代理对象实现自己的需求。(核心**:增加中间层)

2. 如何实现?

2.1 静态代理

静态代理是指代理对象是固定的,明确的,编译阶段就已经确定。以购物为例实现一个代理模式,由消费者、商家、电商平台组成。其中消费者可以理解为调用方;商家为委托对象;而电商平台就是代理对象。代码实现如下:
//定义公共接口,委托对象和代理对象需要实现

/**
 * 购物(抽象接口)
 * Created by zgq on 2019/5/26 16:16
 */
public interface Shopping {

    void buy(String name);
}

//定义委托对象

/**
 * 商家(委托对象)
 * Created by zgq on 2019/5/26 16:21
 */
public class MerchantShoppingImpl implements Shopping {
    @Override
    public void buy(String name) {
        System.out.println("购买:" + name);
    }
}

//定义代理对象

/**
 * 淘宝(代理对象)
 * Created by zgq on 2019/5/26 16:22
 */
public class TaoBaoProxyShopping implements Shopping {

    private Shopping mShopping;

    public TaoBaoProxyShopping() {
        if (mShopping == null) {
            mShopping = new MerchantShoppingImpl();
        }
    }

    @Override
    public void buy(String name) {
        mShopping.buy(name);
    }
}

//使用代理模式示例

//静态代理
TaoBaoProxyShopping taoBaoProxyShopping =new TaoBaoProxyShopping();
taoBaoProxyShopping.buy("鞋子");

2.2 动态代理

动态代理是指代理对象是不确定的,只有在运行阶段动态加载的。在上一个例子的基础,使用动态代理实现如下:
//定义公共接口

/**
 * 购物(抽象接口)
 * Created by zgq on 2019/5/26 16:16
 */
public interface Shopping {

    void buy(String name);
}

//定义委托类

/**
 * 耐克商家(委托对象)
 * Created by zgq on 2019/5/26 16:21
 */
public class NikeShopImpl implements Shopping {
    @Override
    public void buy(String name) {
        System.out.println("购买:" + name);
    }
}

//定义代理类

/**
 * 京东(代理对象)
 * Created by zgq on 2019/5/26 16:22
 */
public class JinDongProxy implements Shopping {

    private Shopping mShopping;

    public JinDongProxy() {
        if (mShopping == null) {
            mShopping = new NikeShopImpl();
        }
    }

    @Override
    public void buy(String name) {
        System.out.println("京东");
        mShopping.buy(name);
    }
}
/**
 * 淘宝(代理对象)
 * Created by zgq on 2019/5/26 16:22
 */
public class TaoBaoProxy implements Shopping {

    private Shopping mShopping;

    public TaoBaoProxy() {
        if (mShopping == null) {
            mShopping = new NikeShopImpl();
        }
    }

    @Override
    public void buy(String name) {
        mShopping.buy(name);
    }
}

//定义动态代理处理类

/**
 * 动态代理处理类
 * Created by zgq on 2019/5/26 17:01
 */
public class ShppingInvocationHandler implements InvocationHandler {

    private Shopping mShopping;

    /**
     * @param shopping 实际代理对象
     */
    public ShppingInvocationHandler(Shopping shopping) {
        this.mShopping = shopping;
    }

    public ShppingInvocationHandler(){

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("buy")) {
            method.invoke(this.mShopping, args);
        }
        return null;
    }
}

//使用动态代理
//动态代理

  ShppingInvocationHandler handler = new ShppingInvocationHandler(new TaoBaoProxyShopping());
        Shopping shopping = (Shopping) Proxy.newProxyInstance(Shopping.class.getClassLoader(), new Class[]{Shopping.class}, handler);
        shopping.buy("鞋子");

        ShppingInvocationHandler handler2 = new ShppingInvocationHandler(new JinDongProxy());
        Shopping shopping2 = (Shopping) Proxy.newProxyInstance(Shopping.class.getClassLoader(), new Class[]{Shopping.class}, handler2);
        shopping2.buy("鞋子2");

**注:**使用动态代理主要利用java反射技术,使用的API是Proxy和InvocationHandler;一般使用动态代理的步骤是:

  1. 定义好委托类和代理类共同的接口。
  2. 定义好委托类和代理类。
  3. 定义动态代理处理类,实现方式是实现InvocationHandler接口,重写invoke方法,实现具体的方法调用。(运行时动态调用)。
  4. 使用Proxy类,将具体代理类对象注入进去。
  5. 调用。

3. JDK或Android的应用举例

在Java Spring框架中,Spring实现AOP使用的就是动态代理;在Android Retrofit2中,Retrofit#create使用的就是动态代理模式,部分代码如下:

 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

4. 小结

代理模式需要了解清楚需要哪些角色,主要包括公共接口、代理类、委托类、调用方。在实际的使用过程中,主要明白了这几个角色的作用,就可以清楚的使用代理模式了。

Android 系统各版本新特性总结

image

1. Android 4.4 KitKat

1.1 通过主机卡模拟实现新的 NFC 功能

Android 4.4新增NFC功能。

1.2 打印框架

打印机制造商可以使用新的 API 开发自己的打印服务。

1.3 存储访问框架

新的存储访问框架让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。

1.4 低功耗传感器

优化传感器耗电问题,节省电量。

1.5 短信提供程序

新的短信提供程序和 API 定义了一个适用于所有短信或彩信处理应用的标准交互模式。(Telephony)

1.6 开发漂亮应用的新方式

1. 全屏沉浸模式
指可以全屏浏览视频、照片、地图等,隐藏所有系统UI。

2. 用于动画场景的转场框架
Android 4.4 引入新的转场框架。利用此转场框架,您可以定义场景、典型视图层次和转场,它们描述如何在用户进入或退出场景时制作场景动画或转换场景。(android.transition)

3. 透明系统UI样式

4.Chromium WebView
Android 4.4 包含基于Chromium内核的WebView 的全新实现。新的Chromium WebView为创建和显示基于 Web的内容提供最新的标准支持、性能和兼容性。

1.7 新的媒体功能

1. 屏幕录制

2. DASH 通用加密

3. HTTP 直播流式传输

4. DSP 音频隧道

5. 音频监控

1.8 RenderScript Compute

主要指持续性能提升、GPU加速等。

1.9 图形绘制优化

Android 4.4 将 SurfaceFlinger 从 OpenGL ES 1.0 升级至 OpenGL ES 2.0。

1.10 新的连接类型

1. 新的蓝牙配置文件
新增低功耗蓝牙支持等。

2. 红外发射器
Android 4.4 引入了对内置红外线增强器的支持,以及一项新的 API 和系统服务,可以创建使用它们的应用。
3. Wi-Fi TDLS 支持
在相同WLAN 网络中的不同设备之间可以更快地流式传输媒体数据及其他数据。

1.11 无障碍功能

1. 系统范围内的隐藏式字幕设置

2. 增强的无障碍功能 API

1.12 支持全球用户

1. RTL 语言区域的可绘制对象镜像

2. 强制实施 RTL 布局

1.13 安全增强功能

1. SELinux(强制模式)

2. 改进加密算法

1.14 内存使用率分析工具

1. Procstats
procstats是分析查看APP应用使用内存资源的情况。

2. 设备内存状态和分析

2. Android 5.0 Lollipop

2.1 材料设计(Material Design)

Android5.0提出材料设计的UI体验。

2.2 以性能为中心

Android 5.0 带来了更加快速、流畅和强大的计算体验。很大程度上提高了性能流畅度。

2.3 通知

Android 5.0 中的通知更醒目、更便于访问并且可配置程度更高。
1. 锁定屏幕
用户可以选择在安全的锁定屏幕上显示部分、全部或者不显示任何通知内容。

2. 新的元数据
Android 5.0 使用与您的应用通知关联的元数据,以更智能的方式对通知排序。(Notification.Builder)

2.4 大屏幕应用支持

Android 5.0新增Android TV模块。

2.5 以文档为中心的应用

Android 5.0 引入了重新设计的“概览”空间。更便捷预览最近打开的应用。

2.6 高级连接

1. 多个网络连接
Android 5.0 提供了新的多网络 API,允许您的应用动态扫描具有特定能力的可用网络,并与它们建立连接。(ConnectivityManager)

2. 蓝牙低功耗
Android 5.0 新增了允许应用利用蓝牙低能耗 (BLE) 执行并发操作的 API,可同时实现扫描(中心模式)和广播(外设模式)。

2.7 高性能图形绘制

1.对 OpenGL ES 3.1 的支持
新版本OpenGL的支持。

2. Android 扩展包

2.8 更强大的音频功能

音频功能的优化和新增新功能。

2.9 增强的相机和视频功能

Android 5.0 引入了全新的 Camera API,允许您采集 YUV 和 Bayer RAW 等原始格式,以及控制曝光时间、感光度和每一帧的帧持续时间等参数。

2.10 工作场所中的 Android

Android 5.0 引入了部署设备所有者应用的功能。(DevicePolicyManager)

2.11 屏幕采集和共享

2.12 新的传感器类型

1. 倾斜检测器

2. 心率传感器

3. 互动复合传感器

2.13 Chromium WebView(新版本)

Android 5.0 的初始版本提供了一版基于 Chromium M37 版本的WebView,添加了对 WebRTC、WebAudio 和 WebGL 的支持。

2.14 辅助工具与输入

2.15 用于开发省电应用的工具

新增的 Job Scheduling API 允许您通过将作业推迟到稍后或指定条件下(如设备充电或连入 WLAN 时)运行来优化电池寿命

3. Android 6.0 Marshmallow

3.1 运行时权限

Android6.0引入动态权限,让用户了解APP用到了哪些权限。让用户亲自授权和取消授权。

3.2 低电耗模式和应用待机模式

主要是节省电量的优化。
1. 低电耗模式

2. 应用待机模式

3.3 取消支持 Apache HTTP 客户端

Android 6.0 版移除了对 Apache HTTP 客户端的支持;推荐使用JDK中API HttpURLConnection。

3.4 BoringSSL

Android 正在从使用OpenSSL库转向使用BoringSSL库。

3.5 硬件标识符访问权

3.6 通知

此版本移除了Notification.setLatestEventInfo()方法;改用Notification.Builder类来构建通知。(建造在模式)

3.7 音频管理器变更

setStreamSolo()弃用,改为requestAudioFocus();setStreamMute()弃用,改为adjustStreamVolume()。

3.8 文本选择

在Android6.0中,当用户在应用中选择文本时,可以在一个浮动工具栏中显示“剪切”、“复制”和“粘贴”等文本选择操作。

3.9 浏览器书签变更

此版本移除了对全局书签的支持。

3.10 Android 密钥库变更

此版本Android密钥库提供程序不再支持 DSA。

3.11 WLAN 和网络连接变更

该版本应用只能创建和维护自己WifiConfiguration对象的状态。系统不允许您修改或删除由用户或其他应用创建的WifiConfiguration对象。

3.12 相机服务变更

相机服务**享资源的访问模式已从之前的“先到先得”访问模式更改为高优先级进程优先的访问模式

3.13 运行时

ART运行时环境现在可正确实现 newInstance()方法的访问规则。

3.14 APK验证

该平台现在执行的APK验证更为严格。如果在清单中声明的文件在APK中并不存在,该APK将被视为已损坏。移除任何内容后必须重新签署APK。

3.15 USB连接

默认情况下,现在通过 USB 端口进行的设备连接设置为仅充电模式。要通过USB连接访问设备及其内容,用户必须明确地为此类交互授予权限。(由用户控制和切换USB的连接类型)

3.16 Android for Work变更

  1. 个人上下文中的工作联系人。

  2. WLAN 配置删除。

  3. WLAN 配置锁定。

  4. 通过添加Google帐户下载设备规范控制器。

  5. 对特定 DevicePolicyManager API 行为的变更。

  6. 对其他 API 的变更。
    流量消耗android.app.usage.NetworkUsageStats类已重命名为NetworkStats。

  7. 对全局设置的变更。

4. Android 7.0 Nougat

4.1 电池和内存

  1. 低电耗模式。
    进一步在Android6.0的基础上进一步优化低电量模式,延长电池寿命。

  2. 后台优化。
    Android 7.0移除了三项隐式广播,以帮助优化内存使用和电量消耗。(移除CONNECTIVITY_ACTION、ACTION_NEW_PICTURE、ACTION_NEW_VIDEO)

4.2 权限更改

  1. 系统权限更改。
    Android7.0私有目录访问限制。分享私有文件内容的推荐方法是使用FileProvider类。

4.3 在应用间共享文件

使用FileProvider类。

4.4 无障碍改进

Android提供了对视力不佳或视力受损用户做了改进和优化体验。

  1. 屏幕缩放。

  2. 设置向导中的视觉设置。

4.5 NDK 应用链接至平台库

从Android7.0开始,应用只能使用原生代码,则只能使用公开 NDK API。

4.6 Android for Work

Android 7.0 包含一些针对面向 Android for Work 的应用的变更,包括对证书安装、密码重置、二级用户管理、设备标识符访问权限的变更。

4.7 注解保留

VISIBILITY_BUILD->仅应编译时可见。
VISIBILITY_SYSTEM->运行时应可见,但仅限底层系统。

5. Android 8.0 Oreo

5.1 后台执行限制

  1. 提高电池续航的优化。

  2. 取消大部分隐式广播。

  3. 取消Service中startService()方法,改用startForegroundService()。

5.2 Android 后台位置限制

Android8.0降低了后台应用接收位置更新的频率。

5.3 应用快捷键

Android使用 ShortcutManager 类中的 requestPinShortcut() 函数创建应用快捷方式。

5.4 提醒窗口

5.5 输入和导航

5.6 网页表单自动填充

Android8.0自动填充框架提供对自动填充功能的内置支持。

5.7 无障碍功能

5.8 网络连接和 HTTP(S) 连接

对安全网络协议的优化

5.9 蓝牙

对蓝牙设备的优化,支持蓝牙5.0标准的支持。

5.10 无缝连接

Android8.0优化了WLAN的体验。

5.11 安全性

5.12 记录未捕获的异常

Thread.UncaughtExceptionHandler

5.13 集合的处理

AbstractCollection空指针问题的修复。

6. Android 9.0 Pie

6.1 电源管理

Android9.0进一步优化电源的管理,为了省电和延长电池寿命。
1. 应用待机群组。
系统将根据用户的使用模式限制应用对 CPU 或电池等设备资源的访问。

  • 活跃
  • 工作集
  • 常用
  • 极少使用
  • 从未使用

2. 省电模式改进。
开启省电模式后,系统会对所有应用施加限制。

6.2 隐私权变更

为了增强用户隐私,Android9.0限制后台应用访问设备传感器、限制通过 Wi-Fi 扫描检索到的信息,以及与通话、手机状态和 Wi-Fi 扫描相关的新权限规则和权限组。

6.3 安全行为变更

  1. 设备安全性变更。

  2. 加密变更。
    Android 9 在 Conscrypt 中实现了更多的算法参数。 这些参数包括: AES、DESEDE、OAEP 和 EC。

6.4 ICU 库更新

6.5 Android Test 变更

Android 9 引入了多项针对 Android Test 框架库和类结构的更改。

6.6 Java UTF 解码器

6.7 使用证书的主机名验证

6.8 套接字标记

6.9 更详尽的 VPN 网络功能报告

从 Android 9 及更高版本开始,当 VPN 调用 setUnderlyingNetworks() 函数时,Android 系统将会合并任何底层网络的传输和能力并返回 VPN 网络的有效网络能力作为结果。

6.10

在 Android 9中,您不能从非 Activity 环境中启动 Activity,除非您传递 Intent 标志 FLAG_ACTIVITY_NEW_TASK。

6.11 屏幕旋转变更

6.12 Apache HTTP 客户端弃用影响采用非标准 ClassLoader 的应用

6.13 枚举相机

在 Android 9 设备上运行的应用可以通过调用 getCameraIdList() 发现每个可用的摄像头。(可实现前后置摄像头的切换等需求)

设计模式之迭代模式

image

1. 什么是迭代模式?有什么作用?

迭代模式指按顺序访问集合的元素的过程,属于行为型设计模式。主要作用是对外暴露一个遍历集合的方法,无须暴露该对象的内部表示。

2. 如何实现?

//定义迭代器

/**
 * Created by zgq on 2019/6/2 15:07
 */
public interface Iterator<T> {

    boolean hasNext();

    T next();
}

//定义集合抽象,并持有对迭代器的关联

/**
 * Created by zgq on 2019/6/2 15:08
 */
public interface BaseCollection<T> {
    Iterator<T> iterator();
}

//定义具体集合实现类

/**
 * Created by zgq on 2019/6/2 15:19
 */
public class NameCollection<T> implements BaseCollection<T> {

    private List<T> mList = new ArrayList<>();

    public NameCollection(List<T> list) {
        if (list != null && !list.isEmpty()) {
            this.mList = list;
        }
    }

    public void add(T t) {
        if (t != null) {
            this.mList.add(t);
        }
    }

    @Override
    public Iterator<T> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator {

        private int mIndex = 0;

        @Override
        public boolean hasNext() {
            if (mIndex < mList.size()) {
                return true;
            }
            return false;
        }

        @Override
        public Object next() {
            if (hasNext()) {
                return mList.get(mIndex++);
            }
            return null;
        }
    }
}

//调用

       List<String> list = new ArrayList<>();
       list.add("张三");
       list.add("李四");
       list.add("王五");
       NameCollection<String> names = new NameCollection<>(list);
       Iterator iterator = names.iterator();
       while (iterator.hasNext()) {
           System.out.println(iterator.next());
       }

3. JDK或Android的应用举例

在JDK中,Iterator使用的就是迭代器模式;在Iterator接口中定义迭代器,在接口集合Collection或者List中建立Iterator关联,在ArrayList实现类中实现具体的迭代过程。在Android Sdk中,sqlite数据中的Cursor使用的就是迭代器模式。

4. 小结

迭代器主要是对集合提供统一的按顺序访问的迭代器,实现了集合和遍历操作的分离。
优点:

  • 符合开闭原则,将容器对象和迭代操作分离。
  • 支持多种方式遍历容器对象。

缺点:

  • 会产生多余的对象,消耗内存。
  • 会增多类文件个数。

Android四大组件之Service

image

1. 什么是Service?用途是什么

Service是Android四大组件之一,它是一种运行在后台的、无界面的一种服务机制。它的作用主要是用于实现长期在后台运作的服务,比如像实现播放器,后台监听服务等等。

2. Service和Thread的区别

Service是运行在主线程的一种后台服务机制,而Thread是JDK提供的线程机制;一般情况下,Service作为后台服务载体机制,就不容易被Android机制杀死;而Thread一般处理耗时任务,依附Service一起使用。

3. Service生命周期

Service生命周期根据启动的方式不同,会有些不同。

3.1 startService -> stopService,其生命周期如下:

image

3.2 bindService -> unbindService,其生命周期如下:

image

3.3 startService -> bindService -> unbindService -> stopService,其生命周期如下:

image

3.4 bindService -> startService -> unbindService -> stopService,其生命周期如下:

image

3.5 bindService -> startService -> stopService -> unbindService,其生命周期如下:

image
:这里调用stopService,不会执行生命周期方法。

4. Service分类

4.1 按启动方式分类

4.1.1 启动服务(startService)

启动服务方式一般是应用全局服务,不会关联某个组件。使用的时候startService,使用完stopService注销服务。

//启动服务
Intent intent = new Intent(MainActivity.this, Service01.class);
this.startService(intent);
//停止服务
Intent intent = new Intent(MainActivity.this, Service01.class);
stopService(intent);

4.1.2 绑定服务(bindService)

绑定服务是一种关联绑定机制,一般会关联某个Activity组件,使用的时候建立绑定bindService,使用完就需要取消绑定unbindService。

//绑定服务
Intent intent = new Intent(MainActivity.this, Service01.class);
bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
//取消绑定
unbindService(serviceConnection);
//服务连接对象
 ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
              //在这里获取服务对象
            Service01.Service01Binder binder=(Service01.Service01Binder)iBinder;
            Service01 service01= binder.getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
  
        }
    };

4.2 按类型分类

4.2.1 前台Service

前台服务是指优先级更高,一般在通知栏有提示,告知用户有服务在使用;因为优先级高,所以不容易在内存不足时被系统杀死。启动方式只需要调用startForeground设置即可。

4.2.2 后台Service

一般和前台服务对应的服务就叫后台服务,默认创建的服务就是后台服务。

4.3 按Api层面分类

4.3.1 IntentService

IntentService是继承Service的一个子类,是一个抽象类,主要的不同就是可以在IntentService#onHandleIntent处理耗时任务,不需要单独创建线程;还有就是不需要手动调用stopService去关闭服务,IntentService会在执行完成操作之后自动停止。

@Override
  protected void onHandleIntent(Intent intent) {
      //单独的工作线程,可以处理耗时操作

  }

4.3.2 JobService

JobService是继承Service的一个子类,是一个抽象类,是API21新增的一个类;主要适合用在定期执行任务的情形,通常结合JobScheduler一起使用。

5. Service保活

5.1 手机厂商白名单

手机厂商白名单的APP像微信、QQ那样,不会被系统killed掉。

5.2 监听广播

监听广播主要是监听一些开机启动、解锁屏、网络状态等,目前高版本需要运行过一次才有效果,可以用来启动保活的服务。

5.3 双Service守护

高版本已失效。

5.4 提高Service优先级

可以一定程度上缓解Service被立马回收。

5.5 双进程保活

使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。

Android中Drawable总结

image

1. 什么是Drawable?作用?

Drawable是抽象类,表示Android可以绘制的对象。通过各种绘制对象将Drawable资源绘制到屏幕上。

2. Drawable分类

2.1 BitmapDrawable

BitmapDrawable可以来源于文件路径、输入流、XML或者Bitmap对象,以xml为例,实现代码如下:
//定义bitmap资源

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:alpha="0.5"
    android:gravity="fill"
    android:src="@drawable/close" />

//使用bitmap资源
imageView.setImageDrawable(getResources().getDrawable(R.drawable.bitmap01));

2.2 ColorDrawable

ColorDrawable主要来源XML中使用定义的信息 ,示例代码如下:
//定义资源

<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>

//使用资源

TextView textView = new TextView(this);
textView.setTextColor(getResources().getColor(R.color.colorAccent));

2.3 NinePatchDrawable

NinePatchDrawable是一种可伸缩的图片资源,具体参考:链接

2.4 VectorDrawable

VectorDrawable是Android 5.0引入的API,指矢量绘制资源,即矢量图资源。

**可缩放矢量图形(SVG):**是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。

//定义VectorDrawable资源:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="32dp"
    android:viewportWidth="1024"
    android:viewportHeight="1024">

    <path
        android:fillColor="#1296db"
        android:pathData="M714.112 464H424.512l121.728-121.792a48.64 48.64 0 0 0 0.96-68.736 48.64 48.64 0 0 0-68.8 0.832L275.84 476.928c-0.448 0.384-1.024 0.512-1.472 0.96a47.36 47.36 0 0 0-13.696 34.624c0 12.224 4.48 24.448 13.76 33.664 0.384 0.384 0.896 0.512 1.28 0.832l202.688 202.688c19.264 19.2 50.048 19.712 68.8 0.96a48.64 48.64 0 0 0-0.96-68.736L424.448 560h289.664c27.2 0 49.28-21.504 49.28-48s-22.08-48-49.28-48z"></path>
    <path
        android:fillColor="#1296db"
        android:pathData="M512 32A479.936 479.936 0 0 0 32 512c0 265.152 214.848 480 480 480s480-214.848 480-480S777.152 32 512 32zM512 896A384 384 0 1 1 512 128a384 384 0 0 1 0 768z"></path>

</vector>

//使用VectorDrawable资源:

<ImageView
       android:layout_width="100dp"
       android:layout_height="100dp"
       android:src="@drawable/vector1"
       android:id="@+id/iv"
       />

//创建SVG图片资源的方式:

  • 使用阿里巴巴矢量图标库。链接
    image

  • 使用Android Studio自带的矢量图标库,具体操作:drawable目录右键 -> New -> Vector Asset -> 点击Clip Art ->选择图标 -> 保存即可。
    image
    image

  • svg转VectorDrawable资源。drawable目录右键 -> New -> Vector Asset -> Local file(SVG,PSD) ->转成对应xml文件即可。
    image

2.5 ShapeDrawable

ShapeDrawable表示形状可绘制对象,比如绘制矩形(rectangle)、椭圆(oval)、水平线(line)、环形(ring)。以绘制矩形为例:
//定义资源:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <gradient
        android:angle="45"
        android:endColor="#80FF00FF"
        android:startColor="#FFFF0000" />
    <padding
        android:bottom="7dp"
        android:left="7dp"
        android:right="7dp"
        android:top="7dp" />
    <corners android:radius="8dp" />
</shape>

//使用资源

<ImageView
        android:id="@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/shape1" />

2.6 LayerDrawable

LayerDrawable是管理其他可绘制对象阵列的可绘制对象。列表中的每个可绘制对象按照列表的顺序绘制,列表中的最后一个可绘制对象绘于顶部。(逐层绘制,类似前端z-index)
//定义资源:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <bitmap android:src="@drawable/d6"
            android:gravity="center" />
    </item>
    <item android:top="50dp" android:left="50dp">
        <bitmap android:src="@drawable/d6"
            android:gravity="center" />
    </item>
    <item android:top="100dp" android:left="100dp">
        <bitmap android:src="@drawable/d6"
            android:gravity="center" />
    </item>
</layer-list>

//使用资源

<ImageView
        android:id="@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/layerdraw1" />

//效果
image

2.7 GradientDrawable

GradientDrawable是一种颜色渐变资源,可以用于设置按钮、背景等渐变效果。可以在xml中定义渐变效果。
//定义资源

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:angle="45"
        android:centerX="50%"
        android:centerY="30%"
        android:endColor="#0C73DA"
        android:startColor="#aabbcc" />
</shape>

//使用资源

<ImageView
        android:id="@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/gradient1" />

//效果
image

2.8 AdaptiveIconDrawable

AdaptiveIconDrawable是Android API 26新增的类,表示可伸缩的绘制资源。可以创建在xml的中创建。
//定义资源

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <foreground android:drawable="@drawable/ic_launcher_foreground">
    </foreground>
    <background android:drawable="@drawable/ic_launcher_foreground">
    </background>
</adaptive-icon>

//使用资源

<ImageView
       android:id="@+id/iv"
       android:layout_width="100dp"
       android:layout_height="100dp"
       android:src="@drawable/adaptive1" />

2.9 AnimatedImageDrawable

AnimatedImageDrawable表示动图绘制资源,比如gif图资源的加载。示例代码如下:

 ImageDecoder.Source source = ImageDecoder.createSource(getResources(), R.drawable.gif1);
                try {
                    AnimatedImageDrawable drawable = (AnimatedImageDrawable) ImageDecoder.decodeDrawable(source);
                    iv.setImageDrawable(drawable);
                    drawable.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }

2.10 AnimatedVectorDrawable

AnimatedVectorDrawable是矢量绘制动画资源,其动画属性可以使用ObjectAnimator或者AnimatorSet定义。
//示例1:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="64dp"
    android:width="64dp"
    android:viewportHeight="600"
    android:viewportWidth="600" >
    <group
        android:name="rotationGroup"
        android:pivotX="300.0"
        android:pivotY="300.0"
        android:rotation="45.0" >
        <path
            android:name="v"
            android:fillColor="#000000"
            android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
    </group>
</vector>

//示例2:

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vectordrawable" >
    <target
        android:name="rotationGroup"
        android:animation="@animator/rotation" />
    <target
        android:name="v"
        android:animation="@animator/path_morph" />
</animated-vector>

//示例3:

<set xmlns:android="http://schemas.android.com/apk/res/android">
     <objectAnimator
         android:duration="3000"
         android:propertyName="pathData"
         android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
         android:valueTo="M300,70 l 0,-70 70,0  0,140 -70,0 z"
         android:valueType="pathType"/>
 </set>
 

2.11 ColorStateListDrawable

ColorStateListDrawable表示颜色状态列表绘制对象,主要用于设置View不同状态的显示颜色。
//定义xml资源

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorPrimary" android:state_pressed="true" />
    <item android:drawable="@color/colorAccent" android:state_pressed="false" />
</selector>

**注意:**android:drawable="@color/colorPrimary"不能写成android:drawable=“#aabbcc”,必须@color方式指定颜色。

//使用资源

<ImageView
        android:id="@+id/iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/s1" />

2.12 DrawableContainer

DrawableContainer是一个绘制对象Drawable的帮助类,可以选择哪个Drawable对象用于绘制。

2.13 DrawableWrapper

DrawableWrapper是包装类,是一个抽象类。其子类包括ClipDrawable, InsetDrawable, RotateDrawable, ScaleDrawable。

2.14 PictureDrawable

PictureDrawable表示图片绘制对象。

3. 小结

Drawable是安卓中的可绘制资源的抽象,在实际开发中根据需求选择具体的绘制对象,应用在View上。一般在开发过程中,将drawable资源定义在res/drawable/的xml中。

设计模式之单例模式

image

1. 什么是单例模式?有什么作用?

单例模式是指保证整个引用只有一个对象实例。它的作用是保证全局唯一性。

2. 如何实现?

2.1 饿汉式实现

public class SingleInstance1 {
    private static final SingleInstance1 ourInstance = new SingleInstance1();

    public static SingleInstance1 getInstance() {
        return ourInstance;
    }

    private SingleInstance1() {
    }
}

2.2 懒汉式实现

public class SingleInstance2 {
    private static volatile SingleInstance2 mInstance;

    private SingleInstance2() {
    }

    public static SingleInstance2 getInstance() {
        if (mInstance == null) {
            synchronized (SingleInstance2.class) {
                if (mInstance == null) {
                    mInstance = new SingleInstance2();
                }
            }
        }
        return mInstance;
    }
}

2.3 静态内部类实现

public class SingleInstance3 {

    private SingleInstance3() {
    }

    private static class InnerClass {
        static final SingleInstance3 mInstance = new SingleInstance3();
    }

    public static SingleInstance3 getInstance() {
        return InnerClass.mInstance;
    }

}

3. JDK或Android的应用举例

在JDK中,NumberFormat#getInstance、Runtime#getRuntime使用的都是单例。在Android SDK中, LayoutInflater#from使用的是单例。

4. 小结

单例模式保证全局只有一个对象实例,避免频繁创建对象使用的开销,可以全局访问,方便调用。

设计模式之适配器模式

image

1. 什么是适配器模式?有什么作用?

适配器模式是指两个互不相关的类或者接口通过适配器进行关联的模式,属于结构型设计模式。
其作用就是让两个互不相关的类或者接口能够互相通信,启到桥梁的作用。主要有类适配器模式、类对象适配器模式和接口对象适配器模式。
image

2. 如何实现?

2.1 类适配器模式

//待适配的类

public class Source {
    public void f1(){
        System.out.println("Source-f1");
    }
}

//目标接口

public interface Target {
    void f1(); //注意这里的方法签名和待适配类中方法一致
    void f2();
}

//适配器(Adapter)类

public class Adapter extends Source implements Target {

    @Override
    public void f2() {
        System.out.println("Adapter-f2");
    }
}

//调用

Target target = new Adapter();
target.f1();
target.f2();

关键点:

  • 适配器类(Adapter)继承待适配类(Source )同时实现目标接口(Target ),待适配类(Source )中的需要暴露给外部调用的方法和目标接口(Target )中的方法名一致,本质就是待适配类实现了目标接口中的方法;从而实现了在Target中访问Source中的方法。

2.2 对象适配器模式

//待适配接口

public interface Source3 {

    void f1();
    void f2();
}

//待适配接口实现类

public class Source3Impl implements Source3 {
    @Override
    public void f1() {
        System.out.println("f1");
    }

    @Override
    public void f2() {
        System.out.println("f2");
    }
}

//目标接口

public interface Target3 {
    void f1();
    void f2();
}

//适配器(Adapter)类


//调用

Source2 source2 = new Source2();
Target2 target2 = new Adapter2(source2);
target2.f1();
target2.f2();

关键点:

  • 适配器类(Adapter2)实现目标接口(Target2 ),同时持有待适配类对象的引用。

2.3 接口对象适配器模式

//待适配的类

public class Source2 {

    public void f1(){
        System.out.println("Source2-f1");
    }
}

//目标接口

public interface Target2 {
    void f1();
    void f2();
}

//适配器(Adapter)类

public class Adapter3 implements Target3 {

    private Source3 mSource3;

    public Adapter3(Source3 source3) {
        this.mSource3 = source3;
    }

    @Override
    public void f1() {
        mSource3.f1();
    }

    @Override
    public void f2() {
        mSource3.f2();
    }
}

//调用

Source3 source3 = new Source3Impl();
Target3 target3 = new Adapter3(source3);
target3.f1();
target3.f2();

关键点:

  • 适配器类(Adapter3)实现目标接口(Target3 ),同时持有待适配接口(Source3 )对象的引用。

3. JDK或Android的应用举例

在java JDK中InputStreamReader和OutputStreamWriter;在Android SDK中ArrayAdapter、BaseAdapter、ListAdapter等都使用了大量的适配器模式。

4. 小结

适配器模式使得两个互不相干的类或接口能进行通信;适配器模式主要有三个要素,确定待适配的对象、目标对象、适配器对象(关联桥梁类)。适配器模式只适用一方访问另一方的情形,即是单向的。

Dalvik虚拟机

1. 安卓java执行过程

image

2. 安卓生成APK运行的过程

  1. 把Java源文件编译成class文件
  2. 使用DX工具把class文件转换成dex文件
  3. 使用aapt工具把dex文件、资源文件以及AndroidManifest.xml文件(二进制格式)组合成APK
  4. 将APK安装到Android设备运行

3. Dalvik虚拟机和Java虚拟机的区别

Java虚拟机都是基于栈的结构,而Dalvik虚拟机则是基于寄存器。Java虚拟机运行的是Java字节码,而Dalvik虚拟机运行的是专有文件格式dex。Android应用虽然也使用Java语言,但是在编译成class文件后,还会通过DEX工具将所有的class文件转换成一个dex文件,Dalvik虚拟机再从中读取指令和数据。dex文件除了减少整体的文件尺寸和I/O操作次数,也提高了类的查找速度。

关于在AS中Android中源码查看和调试的总结

1. 源码查看

源码查看步骤如下:Tools->Android->SDK Manager->System Settings->Android SDK,下载对应的sdk和源码。
image
然后在AS中跳转即可。

关于源码中标红的方法就是Android中不希望被开发人员看到的会使用@hide标记,解决方法是在(https://github.com/anggrayudi/android-hidden-api) 中下载对应API版本的Android.jar替换对应....\platforms\android-28的android.jar文件。
image

2. 源码调试

完成好了以上操作之后,就可以查看所有源码了;然后需要注意的是调试的源码API版本和调试设备版本一致才能完成断点调试。

js中this指向总结

1. 普通函数形式,这里面的this指的是window对象;如:

 function test() 

{
       alert('test');
}
//普通函数调用形式

test();

2. js类(对象原型)的调用形式 ,这里面的this指的是对象本身。如:

function TestClass(name) {
        this.name=name;
}

//类调用模式

var testClass=new TestClass('zs');

3. 匿名函数形式,这里面的this指的是window对象;如:

 (function () {
            alert('匿名函数');
 })();

 var f2 = function () {
        //匿名函数中的this指window对象
        alert('f2');
  };

4. 事件驱动模式,这里面的this指的是dom元素本身;如:

 document.getElementById("btnOk").onclick = function () {
        //这里面的this指按钮对象本身
    };

设计模式之命令模式

image

1. 什么是命令模式?有什么作用?

命令模式是指将指令或者操作封装到对象中的一种设计模式,属于行为型设计模式;主要有客户端角色、命令角色、接收者角色三部分组成。其主要作用是通过不同的命令将客户端参数化。

2. 如何实现?

以windows的cmd的指令模式为例实现一个简单的命令模式,实现代码如下:
//创建命令角色的抽象

/**
 * Created by zgq on 2019/5/30 21:37
 * 命令抽象角色
 */
public interface Command {
    void doWork();
}

//创建具体的命令类

/**
 * Created by zgq on 2019/5/30 21:41
 * 查看ip的具体命令
 */
public class IpconfigCommand implements Command {
    private CmdReceiver mCmdReceiver;

    public IpconfigCommand(CmdReceiver cmdReceiver){
        this.mCmdReceiver=cmdReceiver;
    }

    @Override
    public void doWork() {
        mCmdReceiver.ipconfig();
    }
}
/**
 * Created by zgq on 2019/5/30 21:41
 * 检查Windows版本的具体命令
 */
public class WinverCommand implements Command {
    private CmdReceiver mCmdReceiver;

    public WinverCommand(CmdReceiver cmdReceiver){
        this.mCmdReceiver=cmdReceiver;
    }

    @Override
    public void doWork() {
        mCmdReceiver.winver();
    }
}
/**
 * Created by zgq on 2019/5/30 21:41
 * 打开写字板的具体命令
 */
public class WriteCommand implements Command {
    private CmdReceiver mCmdReceiver;

    public WriteCommand(CmdReceiver cmdReceiver){
        this.mCmdReceiver=cmdReceiver;
    }

    @Override
    public void doWork() {
        mCmdReceiver.write();
    }
}

//创建接收者

/**
 * Created by zgq on 2019/5/30 21:39
 * 接收者角色
 */
public class CmdReceiver {

    public void winver(){
        System.out.println("检查Windows版本");
    }

    public void ipconfig(){
        System.out.println("查看Ip信息");
    }

    public void write(){
        System.out.println("打开写字板");
    }
}

//客户端调用

/**
 * Created by zgq on 2019/5/30 21:46
 */
public class CommandModeTest {

    public static void test(){
        //创建接收者
        CmdReceiver cmdReceiver=new CmdReceiver();
        //创建具体命令
        Command ipconfigCommand=new IpconfigCommand(cmdReceiver);
        Command winverCommand=new WinverCommand(cmdReceiver);
        Command writeCommand=new WriteCommand(cmdReceiver);
        //执行命令
        ipconfigCommand.doWork();
        winverCommand.doWork();
        writeCommand.doWork();
    }
}

3. JDK或Android的应用举例

在JDK中,Runable和线程池(ExecutorService)机制使用的就是命令模式;Runable是命令的抽象,线程池(ExecutorService)是接收者,通过调用ExecutorService#execute执行命令。而在Android SDK中,Handler机制使用的就是命令模式,Handler充当接收者角色、Message充当命令角色、Looper充当客户端角色。

4. 小结

命令模式主要是要理解命令角色、接收者角色、客户端角色代表的含义。命令角色就是指一个个的指令,也对应接收者里面的一个反馈;而接收者是指接收和处理不同的命令;而客户端就是调用方,也可以理解为用户。
优点:

  • 客户端和接收者完全解耦,便于扩展;使得新添加命令变得简单。
  • 客户端请求参数化,使得系统更灵活。

缺点:

  • 随着系统的越来越复杂,具体的命令类会很多。

设计模式之桥接模式

image

1. 什么是桥接模式?有什么作用?

桥接模式是在抽象部分和实现部分进行分离,使它们可以在不同的维度中独立变化。桥接模式将各维度抽象出来,各维度独立变化,之后可通过聚合,将各维度组合起来,减少了各维度间的耦合。

2. 如何实现?

以支付过程为例,按终端类型分为手机、电脑、POS机等;按支付方式分为支付宝、微信支付、银联支付等。实现代码如下:
//支付方式抽象

public interface PayStyle {
    void pay();
}

//支付方式实现类

public class Wxzf implements PayStyle {
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}

public class Ylzf implements PayStyle {
    @Override
    public void pay() {
        System.out.println("银联支付");
    }
}

public class Zfb implements PayStyle {
    @Override
    public void pay() {
        System.out.println("支付宝");
    }
}

//支付终端抽象类

public abstract class Terminal {

    protected PayStyle mPayStyle;

    public Terminal(PayStyle payStyle) {
        this.mPayStyle = payStyle;
    }

    public abstract void pay();
}

//支付实现类

public class PC extends Terminal {
    public PC(PayStyle payStyle) {
        super(payStyle);
    }

    @Override
    public void pay() {
        mPayStyle.pay();
        System.out.println("电脑支付");
    }
}

public class Phone extends Terminal {
    public Phone(PayStyle payStyle) {
        super(payStyle);
    }

    @Override
    public void pay() {
        mPayStyle.pay();
        System.out.println("手机支付");
    }
}

public class POS extends Terminal {
    public POS(PayStyle payStyle) {
        super(payStyle);
    }

    @Override
    public void pay() {
        mPayStyle.pay();
        System.out.println("POS机支付");
    }
}

//组合使用

       //在手机上用支付宝支付
        PayStyle payStyle = new Zfb();
        Terminal terminal = new Phone(payStyle);
        terminal.pay();
        //在手机上用微信支付
        PayStyle payStyle2 = new Wxzf();
        Terminal terminal2 = new Phone(payStyle2);
        terminal2.pay();

        //在PC上用微信支付
        PayStyle payStyle3 = new Wxzf();
        Terminal terminal3 = new PC(payStyle3);
        terminal3.pay();

3. JDK或Android的应用举例

在JDK中JDBC使用的就是桥接模式,JDBC中提供了统一的接口,不同的数据库有不同的实现;数据库驱动的程序起到桥接的作用。在Android中,Adapter跟AdapterView使用的就是桥接模式,源码如下:

public interface Adapter {
      void registerDataSetObserver(DataSetObserver observer);
       .......
}

public abstract class AdapterView<T extends Adapter> extends ViewGroup {
   //通过桥梁T建立桥接作用
    Adapter public abstract T getAdapter();
    public abstract void setAdapter(T adapter);
 }

4. 小结

桥接模式关键点:

  • 接口部分和实现部分进行分离,两者独立变化。
  • 桥接模式将多个维度抽象出来,各维度独立变化。
  • 使用过程中通过聚合的方式组合使用,降低系统耦合度。

git使用总结

Git 是一个开源的分布式版本控制系统,git关联的每台计算机都存有完整的版本信息。

1. git创建版本库(创建一个仓库)

创建版本库首先创建好文件夹,然后在这个定位到这个目录通过命令git init,就创建好了一个仓库,比如需要创建git仓库prj01,只需要定位到prj01这个目录,然后执行 git init prj01指令 ,prj01仓库就创建好了。

2. 添加文件到git仓库

2.1 使用git add 文件名,一次性添加多个文件的情形,使用空格隔开文件即可(如:单个git add aa.txt,多个文件git add aa.txt  bb.txt  cc.txt)

2.2 使用git commit提交到仓库,如果需要添加本次提交的描述信息需要在后面加上 -m,如:git commit -m "msg info"。

3. 仓库的状态

使用git status 即可查看当前仓库状态情况;使用git diff 文件名(如:git diff aa.txt)可以查看文件的修改内容。

4. 版本回退

git log用于查看历史提交信息,git reflog查看命令历史,git reset --hard commit_id回退到某个版本。

5. 工作区和暂存区

工作区就是客户机仓库目录文件夹,.git隐藏的文件夹就不算工作区,是git版本库;git add提交到暂存区,git commit 就是把暂存区的所有文件提交到当前分支上。

6. 修改管理

每次修改工作区中的文件,需要执行git add操作添加到暂存区,git commit 才会将修改的结果提交到当前版本;。当想直接丢弃工作区的修改时,用命令git checkout -- file。如果工作区文件修改了,而且添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,再执行git checkout -- file。命令git rm file用于删除文件。

7. 远程仓库

远程仓库的作用就是方便团队协同工作,GitHub上可以免费托管Git仓库,使用ssh key的方式管理远程仓库;关联一个远程库(情形:现有本地仓库,后又远程仓库),使用命令git remote add origin git@server-name:path/repo-name.git;关联后,使用命令git push origin master推送master分支的所有内容到远程仓库。当我们先创建远程仓库时,需要克隆远程仓库到本地仓库时,使用git clone命令即可。

8. git分支管理

当初始化一个仓库时,默认会有一个master分支,创建分支使用git branch 分支名(比如:git branch dev就创建了一个名称为dev的分支);git branch用于查看所有分支;切换分支:git checkout(比如:git checkout dev就切换到了dev这个分支上);创建+切换分支:git checkout -b;合并某分支到当前分支:git merge;删除分支:git branch -d。

     分支合并过程中如果发生了冲突,需要手动解决冲突在执行提交。合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而 fast forward 合并就看不出来曾经做过合并。

     修复 bug 时,我们会通过创建新的 bug 分支进行修复,然后合并,最后删除;当手头工作没有完成时,先把工作现场git stash一下,然后去修复 bug,修复后,再git stash pop,回到工作现场。

     开发一个新 feature,最好新建一个feature分支;如果要丢弃一个没有被合并过的分支,可以通过git branch -D 强行删除。

       查看远程库信息,使用git remote -v;本地新建的分支如果不推送到远程,对其他人就是不可见的;从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name;从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

9. git标签管理

git tag name创建一个标签,git tag查看所有标签。命令git push origin 可以推送一个本地标签;命令git push origin --tags可以推送全部未推送过的本地标签;命令git tag -d 可以删除一个本地标签;命令git push origin :refs/tags/可以删除一个远程标签。

10. 使用GitHub

在 GitHub 上,可以任意 Fork 开源仓库;自己拥有 Fork 后的仓库的读写权限;可以推送 pull request 给官方仓库来贡献代码。

11. 自定义git

在使用git的过程中如果需要忽略某些文件是,比如.class文件不需要提交管理,需要在仓库目录中创建.gitignore文件,.gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理。但需要配置别名时,使用git config指令。

常见排序算法

1. 冒泡排序(BubbleSort)

冒泡排序的原理是相邻两个元素两两比较,如果左侧数据小于右侧数据,则两者交换位置;否则不动。(以升序为例)

时间复杂度:最好时间复杂度: O(n);最差时间复杂度:O(n²);平均时间复杂度:O(n²)

辅助空间:O(1)

稳定性:稳定

冒泡过程如下:
image

说明:冒泡排序的过程是,逐个相邻元素两两比较,如果满足条件就交换位置,每一轮比较,都会确定一个最大值,而且每一轮都可以少比较一对数。

代码如下:

/**
    * 冒泡排序
    * @param arr
    */
   public static void bubbleSort(int[] arr) {
       int tmp = -1;
       for (int i = 0; i < arr.length - 1; i++) {
           for (int j = 0; j < arr.length - 1 - i; j++) {
               if (arr[j + 1] < arr[j]) {
                   tmp = arr[j];
                   arr[j] = arr[j + 1];
                   arr[j + 1] = tmp;
               }
           }
       }
   }

从上面的冒泡流程来看,以这组数据2,3,1,7,5为例,其实在执行完第二轮之后,数据已经排好序了,但是此时程序是不知道的,接下看下优化后的冒泡排序如下:

 /**
     * 优化冒泡排序
     * @param arr
     */
    public static void bubbleSort2(int[] arr) {
        int tmp = -1;
        boolean isChanged = false;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j + 1] < arr[j]) {
                    tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                    isChanged = true; //如果整轮都没有执行交换,说明这组数据已经整体有序的
                }
            }
            if (isChanged == false) {
                break;
            }
        }
    }

2. 快速排序(QuickSort)

快速排序的原理是先在待排序的数中选择一个数作为基准值(可以是第一个元素或最后一个元素或者中位数或者随机选取一个数据),把一组数据分为两部分,一部分数据比基准值小,另一部分数据比基准值大的;然后再对这两部分数据按同样的方式进行排序,直到整体有序为止。(是一种分而治之的**)

时间复杂度:最好时间复杂度: O();最差时间复杂度:O(n²);平均时间复杂度:O()

辅助空间:O()~O()

稳定性:不稳定

3. 选择排序(Selection Sort)

选择排序的原理是首先在一组待排序的数中选择最小的数和第一位数交换位置,然后再从剩下的数中找出最小的数和第二位数交换位置,以此类推到最后位置。

时间复杂度:最好时间复杂度:O(n²);最差时间复杂度:O(n²);平均时间复杂度:O(n²)

辅助空间:O(1)

稳定性:不稳定

选择排序的过程
image

代码实现

 /**
     * 选择排序
     * @param arr
     */
    public static void selectSort(int[] arr) {
        int size = arr.length;
        for (int i = 0; i < size - 1; i++) {
            int minIndex=i;
            for (int j = i; j < size-1; j++) {
                if (arr[j+1] < arr[minIndex]) {
                    int tmp = arr[j+1];
                    arr[j+1] = arr[minIndex];
                    arr[minIndex] = tmp;
                }
            }
        }
    }

4. 插入排序(Insertion Sort)

插入排序是指从第二个元素开始不断和前面的元素比较大小,然后插入到合适的位置,直到全部插入排序为止。

时间复杂度:最好时间复杂度:O(n²);最差时间复杂度:O(n²);平均时间复杂度:O(n²)

辅助空间:O(1)

稳定性:稳定

插入排序的流程
image

代码实现

public static void insertSort(int[] arr) {
        int size = arr.length;
        for (int i = 1; i < size; i++) {
            int key = arr[i];//待比较的关键字
            int j = i - 1;//key的前一个元素
            while (j >= 0 && key < arr[j]) {
                arr[j + 1] = arr[j];//元素值指针右移一位
                j = j - 1;//索引继续向前
            }
            arr[j + 1] = key;//重新调整关键字的位置
        }
    }

5. 希尔排序

希尔排序算法实质是分组插入排序,将一组待排序的数据按步长逐级划分多组数据,然后比较分组中的数据,直到步长为1为止.(步长的确定:第一次是n/2,然后逐次再减半,直到步长为1为止)

时间复杂度:最好时间复杂度:O(n);最差时间复杂度:O();平均时间复杂度:O()

辅助空间:O(1)

稳定性:不稳定

排序过程
image

代码实现

/**
     * 希尔排序
     * @param arr
     */
    public static void shellSort(int[] arr) {
        int n = arr.length;
        for (int gap = n / 2; gap > 0; gap /= 2) {//计算增量
            //内部就是一个插入排序
            for (int i = gap; i < n; i += 1) {
                int temp = arr[i];//待比较的关键字
                int j;
                for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                    arr[j] = arr[j - gap];
                }
                arr[j] = temp;
            }
        }
    }

6. 归并排序(Merge sort)

归并排序算法就是将一组数据采用二分的机制将数据分组,直到不能继续分为止(小于或等于两个),然后两两比较大小;然后再进行合并的过程。(分而治之的**)

时间复杂度:最好时间复杂度:O();最差时间复杂度:O();平均时间复杂度:O()

辅助空间:O(n)

稳定性:稳定

归并排序的过程,包括分和治的过程
image

代码实现

public class MergeSort {

    /**
     *
     * @param arr
     * @param l 左边索引
     * @param r 右边索引
     */
    public static void mergeSort(int arr[],int l,int r){
        if(l<r){
            int m = (l+r)/2;
            mergeSort(arr,l,m);
            mergeSort(arr,m+1,r);

            //合并
            merge(arr, l, m, r);
        }
    }

    private static void merge(int[] arr, int l, int m, int r) {
       
        int n1 = m - l + 1;
        int n2 = r - m;

        //创建临时数组
        int L[] = new int [n1];
        int R[] = new int [n2];

       //拷贝数据到临时数组
        for (int i=0; i<n1; ++i)
            L[i] = arr[l + i];
        for (int j=0; j<n2; ++j)
            R[j] = arr[m + 1+ j];


       //合并临时数组
        int i = 0, j = 0;
        int k = l;
        while (i < n1 && j < n2)
        {
            if (L[i] <= R[j])
            {
                arr[k] = L[i];
                i++;
            }
            else
            {
                arr[k] = R[j];
                j++;
            }
            k++;
        }
        
        while (i < n1)
        {
            arr[k] = L[i];
            i++;
            k++;
        }
        
        while (j < n2)
        {
            arr[k] = R[j];
            j++;
            k++;
        }
    }
}

7. 堆排序

堆排序是一种树形选择排序。堆排序主要有以下几个步骤:

  • 1.首先将一组输入的数据构建成一个堆,然后根据降序或者升序的需求,构建最大堆或者最小堆。
  • 2.以最大堆为例,将根节点元素和最后的元素交换位置,然后调整结构,继续交换位置,使得大的元素一直向后面转移。
  • 3.反复调整堆和交换,直到整体有序为止。

排序的过程
image
image

代码实现

public class HeapSort {

    /**
     * 构建最大堆
     *
     * @param arr 数组
     * @param n   数组长度
     * @param i   数组的索引
     */
    private void heapify(int arr[], int n, int i) {
        int max = i;
        int l = 2 * i + 1;//左子树
        int r = 2 * i + 2;//右子树

        if (l < n && arr[l] > arr[max]) {
            max = l;
        }

        if (r < n && arr[r] > arr[max]) {
            max = r;
        }

        if (max != i) {//如果父节点不是最大的值
            int temp = arr[i];
            arr[i] = arr[max];
            arr[max] = temp;
            heapify(arr, n, max);
        }
    }


    /**
     * 堆排序
     * @param arr
     */
    public void sort(int arr[]) {
        int n = arr.length;
        // 构建堆
        for (int i = n / 2 - 1; i >= 0; i--) {//(n/2-1)的含义是获取完全二叉树中最后一个非叶子节点
            heapify(arr, n, i);
        }

        for (int i = n - 1; i >= 0; i--) {
            // 交换根节点和最后一个元素
            int temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            // 剩下的元素构建最大堆
            heapify(arr, i, 0);
        }
    }
}

堆排序的平均时间复杂度为:O(nLogn)
辅助空间:O(1)

稳定性:不稳定

8. 基数排序

常见查找算法

1. 顺序查找

顺序查找指在通过遍历表中的每个元素,直到找到关键字为止。

时间复杂度:O(n)
查找条件:有序或者无序队列
示例代码:

private static int find(int[] aar, int target) {
        for (int i = 0; i < aar.length; i++) {
            if (target == aar[i]) {
                return target;
            }
        }
        return -1;
    }

2. 择半查找(二分查找)

择半查找指在顺序表(有序数组)的前提条件下,假设数组s长度为n,开始索引为sIndex=0,结束索引为eIndex=n-1,中间索引为mIndex,查找关键字为k;第一次查找,mIndex=(sIndex+eIndex)/2和查找关键字k比较,若k>s[mIndex],即说明待查找的关键字k在数组的右侧,此时sIndex=mIndex+1,eIndex=n-1;若k<s[mIndex],则说明待查找的关键字k在数组的左侧,此时,sIndex=0,eIndex=mIndex-1;再重复上面步骤继续查找,直到k=s[mIndex]为止。主要的**就是每一次比较搜索元素都减少一半。

时间复杂度:O(logn)
查找条件:有序数组
示例代码:

private static int halfFind(int[] arr, int target) {
       int startIndex = 0;
       int endIndex = arr.length - 1;
       int middleIndex = (startIndex + endIndex) / 2;
       while (startIndex <= endIndex) {
           if (arr[middleIndex] == target) {
               return target;
           } else if (target < arr[middleIndex]) {
               endIndex = middleIndex - 1;
           } else {
               startIndex = middleIndex + 1;
           }
           middleIndex = (startIndex + endIndex) / 2;
       }
       return -1;
   }

3.  斐波那契查找

斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。

4. 分块查找

分块查找是介于顺序查找和二分查找之间而产生的算法,性能也是介于两者之间,主要是将一组数据n进行分块,假设分为m块(m<=n),即第1块,第2块....第m块;块与块之间是有序的,块内元素可以无序;而且第2块中的最小元素大于第1块中的所有元素,第3块中的最小元素大于第2块中的所有元素,以此类推,分块是按每块中最大的值作为索引构成一个有序的数组。所以查找的过程主要分为以下两个步骤:

1、先按二分查找在索引表中确定属于哪一个块。

2、在确定哪一块的基础上用顺序查找找到对应元素。

时间复杂度:小于O(n),大于O(logn)
查找条件:块与块之间需要有序,块内元素不需有序
image
示例代码:

/**
 * Created by [email protected]
 * Time:2022/06/20
 **/
public class BlockSearch {
    private int[] index;
    private ArrayList[] list;

    public BlockSearch() {
        index = new int[]{10, 20, 30};
        list = new ArrayList[index.length];
        addTestData();
    }

    private void addTestData() {
        int[] arr1 = new int[]{1, 2, 5, 3, 7, 9};
        addTest(arr1);
        int[] arr2 = new int[]{11, 12, 15, 13, 17, 19};
        addTest(arr2);
        int[] arr3 = new int[]{21, 22, 25, 23, 27, 29};
        addTest(arr3);
    }

    private void addTest(int[] arr) {
        for (int item : arr) {
            int index = halfFind(item);
            if (list[index] == null) {
                list[index] = new ArrayList();
            }
            list[index].add(item);
        }
    }

    private int halfFind(int target) {
        int startIndex = 0;
        int endIndex = index.length - 1;
        int middleIndex = (startIndex + endIndex) / 2;
        while (startIndex <= endIndex) {
            if (target < index[middleIndex]) {
                endIndex = middleIndex - 1;
            } else {
                startIndex = middleIndex + 1;
            }
            middleIndex = (startIndex + endIndex) / 2;
        }
        return startIndex;
    }

    public int blockSearch(int target) {
        int index = halfFind(target);
        ArrayList dataList = list[index];
        Iterator iterator = dataList.iterator();
        while (iterator.hasNext()) {
            if ((int) iterator.next() == target) {
                return target;
            }
        }
        return -1;
    }

}

调用:

BlockSearch blockSearch = new BlockSearch();
System.out.println(blockSearch.blockSearch(23));

5. 二叉排序树(二叉查找树)

条件:先创建二叉排序树:

1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

3. 它的左、右子树也分别为二叉排序树。

原理:

在二叉查找树b中查找x的过程为:

1. 若b是空树,则搜索失败,否则:

2. 若x等于b的根节点的数据域之值,则查找成功;否则:

  1. 若x小于b的根节点的数据域之值,则搜索左子树;否则:

  2. 查找右子树。

时间复杂度:O()

6. 哈希表法(散列表)

条件:先创建哈希表(散列表)

原理:根据键值方式(Key value)进行查找,通过散列函数,定位数据元素。

时间复杂度:几乎是O(1),取决于产生冲突的多少。
示例代码:

private static int hashFind(int[] arr, int target) {
        Map map = new HashMap<Integer, Integer>();
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i], arr[i]);
        }
        if (map.containsKey(target)) {
            return (int) map.get(target);
        }
        return -1;
    }

设计模式之装饰者模式

image

1. 什么是装饰者模式?有什么作用?

装饰者模式是指通过包装的形式且不修改原有类结构的基础上添加新的功能,属于结构型设计模式。它的主要作用是不增加很多子类的情况下扩展类的功能。

2. 如何实现?

//定义原始接口

public interface Programmer {
    void code();
}

//定义原始接口实现类

public class JavaProgrammer implements Programmer{

    @Override
    public void code() {
        System.out.println("写java代码");
    }
}
public class PythonProgrammer implements Programmer{

    @Override
    public void code() {
        System.out.println("写Python代码");
    }
}

//定义装饰者抽象类,同时实现原始接口

public abstract class DecorateProgrammer implements Programmer{
    protected Programmer mProgrammer;

    public DecorateProgrammer(Programmer programmer){
        this.mProgrammer=programmer;
    }

    public void code(){
        mProgrammer.code();
    }
}

//定义装饰者实现类

public class DecorateJavaProgrammer extends DecorateProgrammer {
    public DecorateJavaProgrammer(Programmer programmer) {
        super(programmer);
    }

    @Override
    public void code() {
        mProgrammer.code();
        System.out.println("java程序员还会设计模式");
    }
}
public class DecoratePythonProgrammer extends DecorateProgrammer {
    public DecoratePythonProgrammer(Programmer programmer) {
        super(programmer);
    }

    @Override
    public void code() {
        mProgrammer.code();
        System.out.println("Python程序员还会设计模式");
    }
}

//使用装饰者模式

DecorateJavaProgrammer decorateJavaProgrammer = new DecorateJavaProgrammer(new JavaProgrammer());
decorateJavaProgrammer.code();
DecoratePythonProgrammer decoratePythonProgrammer = new DecoratePythonProgrammer(new PythonProgrammer());
decoratePythonProgrammer.code();

注:关键点是确定好装饰者和被装饰者。

3. JDK或Android的应用举例

在JDK中BufferedReader使用的就是装饰者模式;在Android中,ContextWrapper的设计就是使用了装饰者模式。

4. 小结

装饰者模式的优缺点:
优点:

  • 符合开闭原则。
  • 减少大量的子类创建,易于扩展。
  • 包装类和原始构件可以在各自维度变化,减少耦合。

缺点:

  • 多层装饰比较复杂。

Java中常用加密算法总结

image

1. 什么是加密算法?作用?

加密算法指通过算法的手段保证数据的安全性和完整性。可以防止数据在网络传输过程中或者在存储介质中被恶意篡改和窃取。

2. 加密算法分类

2.1 单向散列加密

2.1.1 定义?特征?

单向加密又称为不可逆加密算法,其密钥是由加密散列函数生成的。单向散列函数一般用于产生消息摘要,密钥加密等。
算法特征:

  • 输入相同,输出必然相同。
  • 雪崩效应,输入微小不同,输出会产生很大变化。
  • 定长输出,无论输入数据多大,结果大小都是相同的。
  • 不可逆,无法还原原始数据。

2.1.2 MD5

MD5是RSA数据安全公司开发的一种单向散列算法,非可逆,相同的明文产生相同的密文。是一种信息摘要算法。

代码实现:

public static String md5(String str) {
       try {
           MessageDigest digest = MessageDigest.getInstance("MD5");
           return new String(digest.digest(str.getBytes()));
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
       }
       return null;
   }

2.1.3 SHA

SHA是Secure Hash Algorithm的缩写,叫安全散列算法。SHA家族的五个算法,分别是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512。以SHA-1为例,代码实现如下:

public static String encrypt(String str)  {
       try {
           MessageDigest md = MessageDigest.getInstance("SHA-1");
           md.update(str.getBytes());
           byte[] digest = md.digest();

           StringBuffer hexstr = new StringBuffer();
           String shaHex = "";
           for (int i = 0; i < digest.length; i++) {
               shaHex = Integer.toHexString(digest[i] & 0xFF);
               if (shaHex.length() < 2) {
                   hexstr.append(0);
               }
               hexstr.append(shaHex);
           }
           return hexstr.toString();
       } catch (Exception e) {
           e.printStackTrace();
       }
       return null;
   }

2.1.4 CRC-32

确切的说CRC不算是信息摘要算法。它的原理也是散列函数。CRC-32主要用于提供校验功能。代码实现如下:

 public static String encrypt(String str) {
        CRC32 c32 = new CRC32();
        c32.update(str.getBytes());
        return Long.toHexString(c32.getValue());
    }

注意:该算法输入不一致,输出长度不定。

2.1.5 应用场景

单向散列加密一般用于产生消息摘要,密钥加密等。

  • **消息摘要:**又称为数字摘要(Digital Digest)。它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。如果消息在途中改变了,则接收者通过对收到消息的新产生的摘要与原摘要比较,就可知道消息是否被改变了。因此消息摘要保证了消息的完整性。

2.2 对称加密

2.2.1 定义?特征?

对称加密也加单秘钥加密,即加密和解密使用的都是同一个秘钥。
算法特征:

  • 加密方和解密方使用的都是同一个秘钥。
  • 加密解密的速度比较快,适合数据比较大的情形使用。
  • 密钥传输的过程不安全,且容易被破解,密钥管理也比较麻烦。

加密工具:

  • openssl
  • gpg

2.2.2 DES

DES是Data Encryption Standard的缩写,即数据加密标准;速度较快,适用于加密大量数据的场合。代码实现如下:

public class DesUtil {

    private static final String algorithm="DES";
    /**
     * 生成秘钥
     *
     * @return
     */
    public static byte[] greateKey() {
        KeyGenerator keyGen = null;//密钥生成器
        try {
            keyGen = KeyGenerator.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyGen.init(new SecureRandom());
        SecretKey secretKey = keyGen.generateKey();//生成密钥
        byte[] key = secretKey.getEncoded();//密钥字节数组
        return key;
    }


    /**
     * 加密
     *
     * @param key  秘钥
     * @param data 需要加密的数据
     * @return
     */
    public static String encrypt(byte[] key, String data) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, algorithm);//恢复密钥
            Cipher cipher = Cipher.getInstance("DES");//Cipher完成加密或解密工作类
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);//对Cipher初始化,加密模式

            byte[] decode = Base64.decode(data);

            byte[] cipherByte = cipher.doFinal(decode);//加密data
            return Base64.encodeBytes(cipherByte);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 解密
     *
     * @param key
     * @param data
     * @return
     */
    public static String decrypt(byte[] key, String data) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, algorithm);//恢复密钥
            Cipher cipher = Cipher.getInstance("DES");//Cipher完成加密或解密工作类
            cipher.init(Cipher.DECRYPT_MODE, secretKey);//对Cipher初始化,解密模式

            byte[] decode = Base64.decode(data);

            byte[] cipherByte = cipher.doFinal(decode);//解密data
            return Base64.encodeBytes(cipherByte);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

注意事项:

  • javax.crypto.IllegalBlockSizeException异常处理,可以使用Base64进行编码处理。
  • 加密数据必须为8的倍数。

2.2.3 3DES

是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。实现方式和DES一致,不在叙述。

2.2.4 AES

AES是Advanced Encryption Standard的缩写,即高级加密标准;是下一代的加密算法标准,速度快,安全级别高,支持128、192、256、512位密钥的加密。代码实现如下:

 /**
     * 生成秘钥
     *
     * @return
     */
    public static byte[] greateKey() {
        KeyGenerator keyGen = null;
        try {
            keyGen = KeyGenerator.getInstance("AES");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyGen.init(128,new SecureRandom());
        SecretKey secretKey = keyGen.generateKey();
        byte[] key = secretKey.getEncoded();
        return key;
    }


    /**
     * 加密
     *
     * @param key  秘钥
     * @param data 需要加密的数据
     * @return
     */
    public static String encrypt(byte[] key, String data) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            byte[] decode = Base64.decode(data);

            byte[] cipherByte = cipher.doFinal(decode);
            return Base64.encodeBytes(cipherByte);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 解密
     *
     * @param key
     * @param data
     * @return
     */
    public static String decrypt(byte[] key, String data) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey);

            byte[] decode = Base64.decode(data);

            byte[] cipherByte = cipher.doFinal(decode);
            return Base64.encodeBytes(cipherByte);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

2.2.5 Blowfish

Blowfish是一个64位分组及可变密钥长度的分组密码算法,可用来加密64Bit长度的字符串。代码实现如下:

 /**
     * @return
     * @throws Exception
     */
    public static Key keyGenerator() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("Blowfish");
        keyGenerator.init(128);
        return keyGenerator.generateKey();
    }

    /**
     * 加密
     *
     * @param key
     * @param data
     * @return
     * @throws Exception
     */
    public static String encrypt(Key key, String data) throws Exception {
        Cipher cipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] decode = Base64.decode(data);
        byte[] bytes = cipher.doFinal(decode);
        return Base64.encodeBytes(bytes);
    }

    /**
     * 解密
     *
     * @param key
     * @param data
     * @return
     * @throws Exception
     */
    public static String decrypt(Key key, String data) throws Exception {
        Cipher cipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decode = Base64.decode(data);
        byte[] bytes = cipher.doFinal(decode);
        return Base64.encodeBytes(bytes);
    }

2.2.6 应用场景

对称加密加解密效率高,系统开销小,适合进行大数据量的加解密。

2.3 非对称加密

2.3.1 定义?特征?

非对称加密也称为公钥加密,由一对公钥和私钥组成。公钥是从私钥提取出来的。可以用公钥加密,再用私钥解密,这种情形一般用于公钥加密;也可以用私钥加密,用公钥解密,常用于数字签名,因此非对称加密的主要功能就是加密和数字签名。
算法特征:

  • 秘钥对,公钥(public key)和私钥(secret key)。
  • 主要功能:加密和签名
    • 发送方用对方的公钥加密,可以保证数据的机密性(公钥加密)。
    • 发送方用自己的私钥加密,可以实现身份验证(数字签名);类似身份证的作用。

公钥加密(注意这里John使用Mary的公钥进行加密)
image

私钥加密(注意这里John使用自己的私钥进行加密)
image

注:非对称加密算法很少用来加密数据,速度太慢,通常用来实现身份验证;事实上,非对称加密的主要作用也就是身份验证;

如何确认通信方证书的合法性?

主要是通过第三方CA(Certificate Authority)。CA为每个使用公开密钥的用户签发一个含CA签名的证书。CA本身也有一个证书和私钥。假设以CA向用户A签发证书流程为例:

  1. CA首先生成一对公钥和私钥,并自签署一个CA证书certificate。
  2. 用户A向CA提供自己的基本信息和自己的公钥。
  3. CA先对用户A的基本信息和公钥计算一个特征码,然后CA再使用自己的私钥对特征码进行加密,加密生成的字符串(数字签名)、A的公钥、A的基本信息共同组成了CA签发的数字证书。

通过CA实现了身份验证,那如何保证数据的机密性呢?

实际数据通信过程中,身份确认完毕之后,通常使用对称加密方式来加密数据。那么对称加密的秘钥如何确定呢?

  1. 秘钥交换(Internet Key Exchange, IKE)算法。
  2. 公钥加密的方式协商秘钥。

2.3.2 RSA

由 RSA公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的;既可以实现加密,又可以实现签名。

2.3.3 DSA

DSA是Digital Signature Algorithm的缩写,即数字签名算法,是一种标准的 DSS(数字签名标准)

2.3.4 应用场景

主要适用于身份认证场景。

3. 小结

通常单向散列加密一般用于消息摘要;对称加密主要适用于大数据量的加密(加密速度快);而非对称加密适用于做身份认证,由于加密数据慢,不太合适用于数据加密。通常,非对称加密和对称加密、散列函数、秘钥交换等结合使用,共同完成整个网络加密的过程。

寄存器、缓存、内存、硬盘、存储器的理解

存储器是指能存储数据的器件,包括寄存器、缓存、内存、硬盘,其对应CPU访问的速度由快到慢分别是寄存器>缓存>内存>硬盘。

寄存器是**处理器的组成部分,是一种直接整合到cpu中的有限的高速访问速度的存储器,它是有一些与非门组合组成的,分为通用寄存器和特殊寄存器。(容量小,主要存储指令和CPU频繁访问的数据)

缓存其实是内存中高速缓存(cache),它之所以存在,是因为当cpu要频繁访问内存中的一些数据时,如果每次都从内存中去读,花费的时间会更多,因此在寄存器和内存之间有了缓存,把cpu要频繁访问的一些数据存储在缓冲中,这样效率就会更高,但需要注意的是,缓冲的大小也是很小的,不能存放大量的数据,并且缓存中存放的数据会因为cpu的访问而被替代,必须某个数据开始被cpu频繁访问,但后来不再频繁,那这个数据的空间会被其他访问频繁的数据所占据(那些数据会被暂时存储在缓存中是算法问题)。缓存又可以分为一级和二级缓存,一级的速度大一二级的速度。因此cpu在访问数据时,先到缓存中看有没有,没有的话再到内存中读取。

内存分为只读(ROM)和随机存储器(RAM)一级最强悍的高速缓存存储器(cache)。其中RAM应用非常广泛,例如在平常用的开发板中的内存指的就是RAM,还有我们电脑上的内存条指的就是RAM。

硬盘、U盘等存储器都归入外存储器,它们的访问速度是最慢的。

设计模式之组合模式

image

1. 什么是组合模式?有什么作用?

组合模式又叫部分整体模式,是一种结构型设计模式。组合模式使用树形结构来组合对象,用于表示部分以及整体层次。

2. 如何实现?

以公司架构为例实现组合模式,实现代码如下:
//定义部门

public class Department {

    private String name;
    private String remark;
    private List<Department> list;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public List<Department> getList() {
        return list;
    }

    public void setList(List<Department> list) {
        this.list = list;
    }
}

//创建组合

        Department js = new Department();
        js.setName("技术部");
        Department hr = new Department();
        hr.setName("人力资源部");
        Department cw = new Department();
        cw.setName("财务部");

        Department department2 = new Department();
        department2.setName("总经理");
        List<Department> list2 = new ArrayList<>();
        list2.add(js);
        list2.add(hr);
        list2.add(cw);
        department2.setList(list2);

        Department department1 = new Department();
        department1.setName("董事会");
        List<Department> list1 = new ArrayList<>();
        list1.add(department2);
        department1.setList(list1);

//递归输出部门信息

public static void print(Department department) {
        System.out.println(department.getName());
        if (department.getList() != null) {
            for (Department d : department.getList()) {
                print(d);
            }
        }
    }

3. JDK或Android的应用举例

在JDK中AWT和Swing包的设计使用的就是组合模式,File对象的管理也是组合模式。在Android SDK中,View的设计就是组合模式。

4.小结

组合模式的使用场景是表示一个对象整体-部分层次结构,即树形结构。比如文件文件夹的管理,公司部门架构等。

设计模式之解释器模式

image

1. 什么是解释器模式?有什么作用?

解释器模式提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

2. 如何实现?

3. JDK或Android的应用举例

4. 小结

Arouter框架依赖注入需要注意的问题

1.使用流程

//定义接口

public interface TestService extends IProvider {

     void test();
}

//定义实现类

@Route(path = "/service/test")
public class TestServiceImpl implements TestService {
    private Context mContext;


    public TestServiceImpl() {
        super();
    }

    @Override
    public void init(Context context) {
        this.mContext = context;
    }

    @Override
    public void test() {
        Toast.makeText(mContext, "阿里路由框架测试", Toast.LENGTH_SHORT).show();
    }
}

//使用接口

public class MainService {

    @Autowired
    TestService mTestService;

    public MainService() {
        ARouter.getInstance().inject(this);
    }

    public void test() {
        mTestService.test();
    }
}

2. 注意事项

  • @Autowired注入的变量不能声明为private的,否者编译不通过。
  • 需要调用ARouter.getInstance().inject(this);设置注入。
  • 组件化过程中,各个模块需要添加如下的配置:
defaultConfig {
       ......
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
dependencies{
    .....
    annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
}

Android四大组件之ContentProvider

ContentProvider
image

1. 什么是ContentProvider?作用是什么?

ContentProvider是安卓四大组件之一,它是一种Android应用之间数据共享的机制。它主要是通过一种协议规则进行应用之间的数据共享,比如,我们调用手机通讯录的时候使用的就是一种内容提供者的实现。

2. ContentProvider通信基础

ContentProvider通信的基础是基于Uri(统一资源标识符)的机制,外界应用程序就是通过Uri来获取到对应的内容提供者,从起对其进行增删改查的操作。内容提供者的URI格式如下:
content://com.zgq.provider/User/1,其中content表示的是内容提供者的协议格式;com.zgq.provider表示的是授权信息authority,为了增强识别度,命名通常是包名+xxProvider;/User/1是路径部分,其中User表示某个实体,可以理解某个表,而1表示某个表的某条记录。

URI两个常用通配符*和#
表示匹配任意长度的任何有效字符的字符串,比如content://com.zgq.provider/,表示匹配这个内容提供者的所有信息。
#表示匹配任意长度的数字字符的字符串,比如content://com.zgq.provider/User/#,表示匹配这个内容提供者User表中的所有记录。

MIME类型有2种形式:

  1. 单条记录:vnd.android.cursor.item/自定义
  2. 多条记录:vnd.android.cursor.dir/自定义

3. 构建ContentProvider

自定义内容提供者主要是通过实现ContentProvider抽象类,并重写实现内部增删改查的方法。
//示例代码

public class ContentProvider1 extends ContentProvider {

    private static final UriMatcher mMatcher;
    private static final String AUTHORITY = "com.zgq.android_demo.cp_demo.contentProvider1";
    private static final int TB1_CODE = 1;//表1标识码
    private static final int TB2_CODE = 2;//表2标识码

    private SQLiteDatabase mSQLiteDatabase;
    private DbHelper mDbHelper;

    static {
        //初始化UriMatcher对象
        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        mMatcher.addURI(AUTHORITY, "tb1", TB1_CODE);
        mMatcher.addURI(AUTHORITY, "tb2", TB2_CODE);
        // 若URI资源路径 = content://com.zgq.android_demo.cp_demo.contentProvider1/tb1 ,则返回注册码TB1_CODE
        //若URI资源路径 = content://com.zgq.android_demo.cp_demo.contentProvider1/tb2 ,则返回注册码TB2_CODE

    }

    @Override
    public boolean onCreate() {
        mDbHelper = new DbHelper(getContext());
        mSQLiteDatabase = mDbHelper.getWritableDatabase();
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return mSQLiteDatabase.query(getTableName(uri), projection, selection, selectionArgs, null, null, sortOrder);
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        mSQLiteDatabase.insert(getTableName(uri), null, values);
        return uri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }


    private String getTableName(Uri uri) {
        String tbName = null;
        switch (mMatcher.match(uri)) {
            case TB1_CODE: {
                tbName = "tb1";
                break;
            }
            case TB2_CODE: {
                tbName = "tb2";
                break;
            }
            default:
                break;
        }

        return tbName;
    }
}

关键API: UriMatcher,Uri;其中UriMatcher主要建立Uri和表的映射关系,根据调用方传入的Uri,便可以通过UriMatcher找到对应的表,进而进行操作。

4. 使用ContentProvider

使用ContentProvider主要用到Uri和ContentResolver类,其中Uri标识要操作的是哪个内容提供者,而ContentResolver封装好了和ContentProvider 对应的API用于操作对应的数据。
//示例代码:

//插入
ContentValues values = new ContentValues();
values.put("id", 1);
values.put("name", "zgq");
this.getContentResolver().insert(uri, values);

//查询
this.getContentResolver().query(uri, null, "id=?", new String[]{"1"}, null);

5. 小结

关于内容提供者的几点小结:
1. 内容提供者是跨进程共享数据的一种机制,通过统一的API操作方式对外提供数据。
2. 数据提供方主要继承ContentProvider类,实现好对外共享的数据资源;而调用方主要通过ContentResolver和Uri来操作对应的数据资源。
3. 内容提供者是通过Uri协议格式交换对应数据的。
4. 内容提供者内部的通信机制是通过Binder机制实现的。

Android中9patch图片使用总结

image

1. 什么是9patch图片?作用?

9patch图Android开发中特有的一种图片格式,其后缀为.9.png;9patch图片的作用就是在图片拉伸的时候保证其不会失真。
绘制之前先介绍下四条黑边的作用:

  • 顶部:在水平拉伸的时候,保持其他位置不动,只在这个点的区域做无限的延伸。
  • 左边:在竖直拉伸的时候,保持其他位置不动,只在这个点的区域做无限的延伸。
  • 底部:在水平拉伸的时候,指定图片里的内容显示的区域。
  • 右边:在竖直拉伸的时候,指定图片里的内容显示的区域。

2. AS中如何创建9patch图片

首先将原始图片拷贝到res/drawable目录 -> 右键选择Create 9-Patch file... ->然后在编辑器中拖动绘制四周的黑色边框。(删除绘制按住shift键删除)->保存在drawable目录即可。
image

3. 如何使用9patch图片

使用9patch图片和使用普通的图片一致,只需要在android:src="@drawable/xx"指定即可。

java线程之synchronized关键字

1. synchronized同步代码块

synchronized (this) {
System.out.println("synchronized 代码块");
}
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

2. synchronized同步方法

public synchronized void method() {
System.out.println("synchronized 方法");
}
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

3. JDK1.6之后synchronized的优化

JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

4. 谈谈 synchronized和ReenTrantLock 的区别

4.1 两者都是可重入锁。

4.2 synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API

4.3  ReenTrantLock 比 synchronized 增加了一些高级功能

主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)

设计模式之享元模式

image

1. 什么是享元模式?有什么作用?

享元模式是指通过减少对象的创建,以减少内存占用和提升性能,属于结构性设计模式。其主要作用是通过缓存和复用的对象来减少内存占用和提升性能。(共享元素)

2. 如何实现?

以某一款手机的生产过程为例实现一个享元模式,代码如下:
//创建手机类

public class M20Phone {
    private String id;
    private String color;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

//创建手机创建工厂类

public class M20PhoneProduce{

    private static Map<String, M20Phone> mColor2PhoneMap = new HashMap<>();

    public static M20Phone createPhone(String color) {
        M20Phone m20Phone = mColor2PhoneMap.get(color);
        if (m20Phone == null) {
            m20Phone = new M20Phone();
            m20Phone.setColor(color);
            mColor2PhoneMap.put(color, m20Phone);
        }
        m20Phone.setId(UUID.randomUUID().toString());
        return m20Phone;
    }
}

//使用工厂方法

for (int i = 0; i < 100; i++) {
            M20Phone phone = null;
            if (i % 2 == 0) {
                phone = M20PhoneProduce.createPhone("red");
            } else if (i % 5 == 0) {
                phone = M20PhoneProduce.createPhone("black");
            } else {
                phone = M20PhoneProduce.createPhone("white");
            }
            System.out.println("序列号:" + phone.getId() + ",颜色:" + phone.getColor());

        }

3. JDK或Android的应用举例

在JDK中,String的创建过程使用的就是享元模式,当String常量池中如果存在,则直接返回;否者,创建新的对象。数据库连接池使用的也是享元模式。在Android中,当我们自定义适配器的时候,在重写getView方法中使用的ViewHolder就是使用的享元模式。

4. 小结

享元模式的核心**是共享对象,以达到减少对象的创建、节省内存、提升性能。

Android动画专题

image

1. 什么是安卓动画?作用是什么?

安卓动画是指在Android开发过程中实现各种炫酷效果的机制,主要用于实现各种UI特效;比如APP启动页面的过渡效果、雷达效果等等。

2. 动画分类

2.1 视图动画/补间动画

视图动画也叫补间动画,是一种只能作用于View对象的动画机制;像移动、旋转、缩放、透明度等。视图动画相关API主要位于android.view.animation包下,首先介绍下Animation类,它是View动画的基类,是一个抽象类,对外提供一些公共的接口用于操作基本View动画。AnimationSet表示View动画集合,表示一组动画的意思,它也是Animation的子类,一般通过设置AnimationSet对象属性,它集合中的所有动画都会同时生效。View动画可以在xml中定义,同时也可以在后端代码中设置,建议使用xml方式定义。
示例代码如下:
//定义视图动画,定义在res/anim/中

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000">
    <!--透明度-->
    <alpha
        android:fromAlpha="0.5"
        android:toAlpha="0.7" />
    <!--旋转-->
    <rotate
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="360" />
    <!--缩放-->
    <scale
        android:fromXScale="1"
        android:fromYScale="1"
        android:toXScale="2"
        android:toYScale="2" />
    <!--平移-->
    <translate
        android:fromXDelta="100"
        android:fromYDelta="100"
        android:toXDelta="50"
        android:toYDelta="50" />
</set>

java类和xml中标签对应关系:

AlphaAnimation <-> alpha
RotateAnimation <-> rotate
ScaleAnimation <-> scale
TranslateAnimation <-> translate
AnimationSet <-> set

View动画启动方式:

//示例代码
 Animation animation=AnimationUtils.loadAnimation(AnimActivity.this, R.anim.view_anim);
 view.startAnimation(animation);

注意事项:补间动画执行之后并未改变View的真实布局属性值。切记这一点,譬如我们在Activity中有一个Button在屏幕上方,我们设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方,这时如果点击屏幕下方动画执行之后的Button是没有任何反应的,而点击原来屏幕上方没有Button的地方却响应的是点击Button的事件。

2.2 帧动画

帧动画(AnimationDrawable)是一种图片资源动画,本质上是一种Drawable资源;通过按时间轴的方式逐帧加载显示图片,从而形成动画效果。主要由xml方式和后台代码方式。
以xml方式为例:

//定义帧动画,定义在res/drawable/中:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/d1"
        android:duration="500" />
    <item
        android:drawable="@drawable/d2"
        android:duration="500" />
    <item
        android:drawable="@drawable/d3"
        android:duration="500" />
    <item
        android:drawable="@drawable/d4"
        android:duration="500" />
    <item
        android:drawable="@drawable/d5"
        android:duration="500" />
</animation-list>

//使用帧动画

ImageView.setBackgroundResource(R.drawable.frame1);
AnimationDrawable animationDrawable = (AnimationDrawable) iv_frame.getBackground();
animationDrawable.start();

2.3 属性动画

属性动画是Android3.0(API11)新增的动画机制,它可以作用与任何对象,这种动画是可扩展的,可以让你自定义任何类型和属性的动画。继承链关系: Animator -> ValueAnimator -> ObjectAnimator。Animator 是属性动画的基类,提供属性动画的基本操作,是一个抽象类。平时我们用的比较多的是ValueAnimator和ObjectAnimator。属性动画也是同时支持xml方式和后台代码定义的方式,以xml为例:

//定义属性动画,定义在res/animator/中

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator
        android:duration="2000"
        android:propertyName="alpha"
        android:valueFrom="0.2"
        android:valueTo="1"
        android:valueType="floatType" />

    <objectAnimator
        android:duration="2000"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="360"
        android:valueType="floatType" />

</set>

//使用属性动画

Animator animator = AnimatorInflater.loadAnimator(AnimActivity.this, R.animator.animator1);
animator.setTarget(tv_animator);
animator.start();

3. 动画插值器(interpolator)

动画插值器的作用就是修改动画的参数,可以使动画效果更加炫酷和实现更丰富的动画效果。api层次结构:TimeInterpolator -> Interpolator -> BaseInterpolator -> 插值器实现类。具体应用举例:

<alpha
        android:fromAlpha="0.5"
        android:interpolator="@android:interpolator/accelerate_cubic"
        android:toAlpha="0.7" />

4. 动画小结

动画主要包括视图动画(补间动画),属性动画、帧动画。主要心得有以下几点:

1. 视图动画是安卓最开始的动画机制,只能作用与View对象,而且动画效果也很局限,只包含透明度、旋转、缩放、平移。随着动画需求越来越复杂,视图动画已经不能满足需求,因此属性动画就在Android3.0诞生了。属性动画可以作用于任何的对象,满足更加复杂的动画需求。

2. 视图动画不会真正改变View的布局属性,而属性动画会真正改变作用对象的属性。比如一个按钮从左边平移到右边,如果是视图动画的话,点击右边不会触发点击事件,而点击原来左边区域就能触发点击事件;而如果使用属性动画的话,会真正改变对象的布局属性,因此效果刚好和视图动画相反。

3. 帧动画其实是一种drawable资源。实现帧动画其实是按时间轴轮番播放图片。

4. 视图动画定义在/res/anim/下,属性动画定义在/res/animator/下,帧动画定义在/res/drawable/下。

java中注解和反射总结

image

1. 注解

1.1 什么是注解?作用?

注解是JAVA中的一种元数据,主要起到标识、说明的作用。

1.2 如何自定义注解

//定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String name();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String name();

    boolean isId() default false;

    boolean autoGen() default false;
}

//使用注解

@Table(name = "user")
public class UserEntity {
    @Column(name = "id",isId = true)
    private String id;
    @Column(name = "name")
    private String name;
    @Column(name = "phone")
    private String phone;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

知识点:

  • 元注解:元注解是用于定义注解的注解,像@target@retention就是元注解,使用ElementType.ANNOTATION_TYPE修饰。
  • @target的作用:目标的意思,属于元注解,即自定义注解时用于描述该注解作用的元素类型是什么,由ElementType定义。
  1. ElementType.ANNOTATION_TYPE:表示用于注解类型。
  2. ElementType.CONSTRUCTOR:表示用于构造器类型。
  3. ElementType.TYPE:表示用于类、接口和枚举类型。
  4. ElementType.FIELD:表示作用于字段类型。
  5. ElementType.METHOD:表示作用于方法类型。
  6. ElementType.PARAMETER:表示作用于方法参数类型。(比如:Call<List> groupList(@path("id") int groupId);)
  7. ElementType.LOCAL_VARIABLE:表示作用于本地变量类型。
  8. ElementType.PACKAGE:表示作用于包类型。
  9. ElementType.TYPE_PARAMETER:表示作用于类型参数。(Java 8新注解类型)
  10. ElementType.TYPE_USE:表示作用于使用类型参数。(Java 8新注解类型)
  • @retention的作用:保留的意思,属于元注解,用于表示注解在代码中存活的时间长短情况,由RetentionPolicy定义,默认是RetentionPolicy.CLASS。
  1. RetentionPolicy.SOURCE:只保留到源码阶段,编译之后注解就会消失。
  2. RetentionPolicy.CLASS:保留到编译之后阶段(.class),当JVM加载运行时就会消失。(反射不可见)
  3. RetentionPolicy.RUNTIME:保留到JVM运行阶段。(反射可见)

1.3 JDK中的注解

在JDK中比较常用的注解有@OverRide@deprecated@SuppressWarnings等。具体含义只要做过java开发应该比较清楚了,这里不再阐述。

1.4 注解的分类

自定义注解的时候主要是通过@target来指定具体的类型,比如注解作用与类、接口、方法、构造器等等。如果需要设置注解支持多个类型,需要用{}来描述,例如:@target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})。

2. 反射

2.1 什么是反射?作用?

反射就是通过class对象可以获取类中所有方法、字段、构造器等信息;甚至可以通过反射调用里面的方法。(反射应用于运行阶段起作用)

2.2 反射的应用

通过反射获取字段、方法、构造器等信息,代码实现如下:

           Class cls = Class.forName("com.zgq.android_demo.annotation_reflex.UserEntity");
           //获取注解
           Annotation annotations[] = cls.getDeclaredAnnotations();
           //获取构造器
           Constructor[] constructors = cls.getDeclaredConstructors();
           //获取方法
           Method[] methods = cls.getDeclaredMethods();
           //单个方法
           Method method = cls.getDeclaredMethod("getId");
           Method method2 = cls.getDeclaredMethod("setId",String.class);
           //获取字段
           Field[]fields= cls.getDeclaredFields();

           //通过反射调用方法
           UserEntity entity= (UserEntity) cls.newInstance();
           method2.invoke(entity,"1");//invoke第一个参数需要的是类的实例对象
           String id= (String) method.invoke(entity);
           System.out.println(id);

           //访问私有方法
           Method method3=cls.getDeclaredMethod("getS");
           method3.setAccessible(true);//改变访问修饰符的权限
           String res= (String) method3.invoke(entity);
           System.out.println(res);  

2.3 反射与代理

反射就是通过class就可以调用类中内部资源;而代理是通过委托的方式来达到最终的效果,比如我们想买个鞋子就去淘宝买,淘宝就是起到代理的作用。具体实现细节请参考设计模式之代理模式篇

3. 注解和反射在框架中的应用

注解和反射在安卓框架或则java后台框架中都被大量使用到了,比如Spring框架、Xutils、ORM框架、retrofit2等等。以retrofit2中@get为例做个简单介绍。
//定义注解

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  String value() default "";
}

//动态代理Retrofit#create为入口

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });
  }

//中间方法跳转流程:Retrofit#create -> Retrofit#loadServiceMethod -> ServiceMethod#parseAnnotations -> RequestFactory#parseAnnotations -> RequestFactory#build -> RequestFactory#parseMethodAnnotation,在parseMethodAnnotation中使用了注解做逻辑判断,部分代码如下:

private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {//这里处理GET请求
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
         ......
      }
      ...........
      ...........
}

注:整个GET请求中就用到的反射、注解和代理。

Android数据存储方式总结

image

1. 什么是Android数据存储方式?

Android数据存储方式就是指将数据持久化的方式。

2. 数据存储方式分类

2.1 SharedPreferences

SharedPreferences是一种轻量级数据存储方式。存储在/data/data/应用包名/shared_prefs下的xml文件中 ;主要用于存储一些配置信息。(应用卸载时,SharedPreferences存储的数据自动删除)
image

//保存数据示例

SharedPreferences sharedPreferences = context.getSharedPreferences("name", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("loginname", "zgq");
editor.commit();

//获取数据示例

SharedPreferences sharedPreferences = context.getSharedPreferences("name", Context.MODE_PRIVATE);
sharedPreferences.getString("loginname", null);

注意知识点:

  • Editor#commit()是同步提交的;Editor#apply()是异步提交的。
  • SharedPreferences四种数据共享方式,Context.MODE_PRIVATE表示数据只能本应用读写;Context.MODE_WORLD_READABLE表示可以被其他应用读,当不能写;Context.MODE_WORLD_WRITEABLE表示数据可以被其他应用读写;Context.MODE_MULTI_PROCESS表示当多个进程读写时,会检查是否有改动。

2.2 文件

2.2.1 内部存储

内部存储是Context提供的API,数据存储在/data/data/应用包名/files下,如图所示。
image
**注意:**当应用卸载时,内部存储的数据自动删除。
//读取数据
FileInputStream fileInputStream= context.openFileInput("aa.txt");
//写入数据

FileOutputStream fileInputStream= context.openFileOutput("aa.txt",Context.MODE_APPEND);
fileInputStream.write("aa".getBytes());

2.2.2 外部存储

外部存储是指全局可读取的存储介质,即所谓SD卡存储。使用过程中需要申明访问权限。
<manifest ...>


注意:Android6.0需要动态权限。

//检查权限

public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

//创建外部存储目录
public File getAlbumStorageDir(String albumName) {
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}

2.3 数据库(Sqlite)

Android中使用的数据库是sqlite,主要提供SQLiteOpenHelper和SQLiteDatabase来操作数据库,使用方式和其他关系型数据库类似,本章节不深入阐述。

2.4 内容提供者

内容提供者是Android四大组件之一,主要作用就是提供统一的方式对外共享数据。链接

2.5 网络存储

网络存储是指将数据存储在远程服务器,使用的时候,通过网络请求的方式使用远程数据。

3. 小结

Android数据存储方式有多种方式,在开发过程中根据具体的需求选择合适的数据存储方式。

设计模式之原型模式

image

1. 什么是原型模式?有什么作用?

原型模式是创建型设计模式之一,指通过拷贝原有对象的方式实现对象的创建;从而提高了对象创建的效率。

2. 如何实现?

public class Book implements Cloneable {

    private double price;
    private String name;

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

在java中,通过实现接口Cloneable,同时重写Object#clone方法便可实现克隆操作,即可实现原型模式。

3. JDK或Android的应用举例

在JDK大量使用了原型模式,比如ArrayList#clone等;在Android SDK中,也使用大量的原型模式,比如Intent#clone等。

4. 浅拷贝、深拷贝

浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。

深拷贝:当一个类的拷贝方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。这个方式称为深拷贝。

//浅拷贝示例代码

@Override
  public Object clone() {
      try {
          return super.clone();
      } catch (CloneNotSupportedException e) {
          e.printStackTrace();
      }
      return null;
  }

//深拷贝示例代码

public class Book implements Cloneable {

    private double price;
    private String name;
    private Merchant merchant;

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Merchant getMerchant() {
        return merchant;
    }

    public void setMerchant(Merchant merchant) {
        this.merchant = merchant;
    }

    @Override
    public Object clone() {
        try {
            Book book = (Book) super.clone();
            book.setMerchant((Merchant) this.merchant.clone());
            return book;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

public class Merchant implements Cloneable {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

5. 小结

原型模式就是在原有对象的基础上,提高创建对象的效率。

设计模式之建造者模式

image

1. 什么是建造者模式?有什么作用?

建造者模式是指复杂对象创建和对象表现分离,提供简洁、统一的创建对象的方式,向使用者屏蔽内部的复杂性。可以实现相同的构建过程,实现不同的表现。

2. 如何实现?

//创建建造者模式

public class Computer {

    private ComputerParams mComputerParams;

    private Computer(ComputerParams computerParams) {
        this.mComputerParams = computerParams;
    }

    public String getCup(){
        return mComputerParams.getCup();
    }

    public String getHarddisk(){
        return mComputerParams.getHarddisk();
    }

    public String getKeyboard(){
        return mComputerParams.getKeyboard();
    }

    public static final class Builder {
        private ComputerParams mComputerParams;

        public Builder() {
            mComputerParams = new ComputerParams();
        }

        public Builder setCpu(String cpu) {
            mComputerParams.setCup(cpu);
            return this;
        }

        public Builder setKeyboard(String keyboard) {
            mComputerParams.setKeyboard(keyboard);
            return this;
        }


        public Builder setHarddisk(String harddisk) {
            mComputerParams.setHarddisk(harddisk);
            return this;
        }

        public Computer create() {
            Computer computer = new Computer(mComputerParams);
            return computer;
        }
    }
}

//参数配置类

public class ComputerParams {
   private String cup;
   private String keyboard;
   private String harddisk;

    public String getCup() {
        return cup;
    }

    public void setCup(String cup) {
        this.cup = cup;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public String getHarddisk() {
        return harddisk;
    }

    public void setHarddisk(String harddisk) {
        this.harddisk = harddisk;
    }
}

//使用建造者模式

Computer.Builder builder=new Computer.Builder();
Computer computer=builder.setCpu("cup1").setHarddisk("h1").setKeyboard("k1").create();
Log.d(TAG,computer.getCup()+" "+computer.getHarddisk()+" "+computer.getKeyboard());
Computer.Builder builder2=new Computer.Builder();
Computer computer2=builder2.setCpu("cup2").setHarddisk("h2").setKeyboard("k2").create();
Log.d(TAG,computer2.getCup()+" "+computer2.getHarddisk()+" "+computer2.getKeyboard());

3. JDK或Android的应用举例

在JDK中,StringBuilder使用的是建造者模式;在Android SDK中,AlertDialog.Builder、Notification.Builder使用的是建造者模式。

4. 小结

建造者模式的作用就是将复杂对象的创建简单化、统一化。

java线程之volatile关键字

volatile关键字主要作用:

1. 保证内存可见性

即确保多个线程能够访问到主内存中的变量是最新的状态。(保证了主内存和线程工作线程中操作过后的最新变量状态)

2. 防止指令重排

指令重排序是JVM为了优化指令,提高程序运行效率的一种机制,包括编译器重排序和运行时重排序;在单线程的情况下是不会影响执行的结果,但是在多线程的环境下执行的结果就会出现错乱。而volatile关键字便可以阻止指令的重排序。

总结:volatile是一种轻量级的多线程共享变量的同步机制,一般在两个或者更多的线程需要访问的成员变量上使用volatile关键字,注意volatile不能确保原子性。应用场景有很多,比如通过线程同步机制实现懒加载的单例模式。

 

关联概念说明:

指令重排

在计算机执行指令的顺序在经过程序编译器编译之后形成的指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

 编译器将不会对存在数据依赖性的程序指令进行重排,这里的依赖性仅仅指单线程情况下的数据依赖性;多线程并发情况下,此规则将失效。

原子性

原子是最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

可见性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。

设计模式之外观模式

image

1. 什么是外观模式?有什么作用?

外观模式就是指对外屏蔽系统的复杂性,对调用方提供简单的访问接口。主要的作用就是简化调用方的使用。

2. 如何实现?

以电脑的开机、关机为例实现一个简单的外观模式,实现代码如下:
//定义系统内部的复杂实现

public interface Hardware {

    void open();

    void close();
}
public class CircuitBoard implements Hardware {
    @Override
    public void open() {
        System.out.println("打开电路板");
    }

    @Override
    public void close() {
        System.out.println("关闭电路板");
    }
}
public class Cpu implements Hardware {
    @Override
    public void open() {
        System.out.println("打开cpu");
    }

    @Override
    public void close() {
        System.out.println("关闭cpu");
    }
}
public class Harddisk implements Hardware {
    @Override
    public void open() {
        System.out.println("打开硬盘");
    }

    @Override
    public void close() {
        System.out.println("关闭硬盘");
    }
}

//定义外观类

public class Computer {

    private Hardware mCpu=null;
    private Hardware mHarddisk=null;
    private Hardware mCircuitBoard=null;
    public Computer(){
        mCpu=new Cpu();
        mHarddisk=new Harddisk();
        mCircuitBoard=new CircuitBoard();
    }

    public void open(){
        mCpu.open();
        mCircuitBoard.open();
        mHarddisk.open();
    }

    public void close(){
        mCpu.close();
        mCircuitBoard.close();
        mHarddisk.close();
    }
}

//使用外观模式

Computer computer = new Computer();
computer.open();
computer.close();

3. JDK或Android的应用举例

在JDK中,Class#forName()使用的是外观模式;在Android SDK中,Context#startActivity()使用的就是外观模式。

4. 小结

外观模式的作用就是屏蔽内部实现细节和复杂性,给用户提供简洁、统一的调用方式。
优点:

  • 降低了子系统和调用方之间的耦合性。
  • 通过提供统一的外观类给调用方,简化使用流程。
  • 提高灵活性,子系统改变不会影响门面对象。

缺点:

  • 增加新的子系统需要修该外观类的源代码,违背了开闭原则。
  • 因为所有子系统都是通过外观类提供访问接口,外观类随着业务增加可能会变得复杂。

android版本兼容问题总结

安卓版本兼容问题出现的背景是由于安卓随着版本不断的更新,会不断的增加API和废弃一些旧版本的API,而市面上手机安装的安卓操作系统版本不一致,就会导致安卓版本的兼容问题。

平时开发中需要注意一下几点:

1、我们开发的APP尽可能先满足市面上绝大部分机器的使用。(实用性原则)。

2、了解清单文件中这两个属性minSdkVersion和targetSdkVersion的含义,minSdkVersion表示APP最低支持的安卓版本,通常这个值尽量越低越好(满足实用性原则为前提),targetSdkVersion表示最高支持版本,这个值通常设置为当前最新安卓版本的API等级。

3、安卓提供Build类可以获取当前运行环境的安卓版本,通过分支加载不同的代码块来实现版本兼容问题,比如:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
// 包含新API的代码块
}
else
{
// 包含旧的API的代码块
}
总结:
实际开发中一般把compileSdkVersion设置为android:compileSdkVersion一样,这样看来我们开发的app兼容范围就是:minSdkVersion至targetSdkVersion, 那么这三种配置理想情况应该是

minSdkVersion (lowest possible) <= targetSdkVersion == compileSdkVersion (latest SDK)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.