19_5

文章目录

19/5-6:

请简述 LinkedHashMap 的工作原理和使用方式?

​ LinkedHashMap 是继承自 HashMap 实现Map接口,

​ LinkedHashMap 和 HashMap 的主要区别就是 LinkedHashMap 是有序的,而 HashMap 是无序的。

​ LinkedHashMap 默认为插入的顺序,他是基于HashMap 和 双向链表实现的,LinkedHashMap 是线程不安全的。

AsyncTask 启动的方式?

​ 在SDK3.0以前的版本执行 asyncTask.execute(task) 时的确是多线程并发执行的,线程池大小为5,最大128个,google 在3.0以后做了修改 ,将 asyncTask.execute(task)修改为了顺序执行,即只有当一个任务完成后才执行下一个任务

​ 那么怎么并发执行呢?很简单,3.0后新增了一个方法 executeOnExecutor(Executor exec ,Object… params),该方法接收两个参数,第一个是 Executor ,第二个是任务参数,第一个是线程池实例,google 为我们定义了两种,第一种是AsyncTask.SERIAL_EXECUTOR ,第二种是AsyncTask.THREAD_POOL_EXECUTOR,顾名思义,第一种就是3.0 以后的方法,是顺序执行的,第二种就是3.0以前的 execute 方法,是可以并发执行的,我们直接用asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, task);就可以多任务并发执行了。

​ 自定义Executor

private static ExecutorService exec = Executors.newSingleThreadExecutor();

//程序在每次调用时是使用的同一个Executor,
syncTask.executeOnExecutor(exec, task);

//当Executor类型为下面时 只有两个线程在执行任务
private static ExecutorService exec = Executors.newFixedThreadPool(2);
syncTask.executeOnExecutor(exec, task);

19/5-7

谈一谈JAVA 的垃圾回收机制?

垃圾回收机制:
当堆内存中的某块区域没有对象引用时,这个内存区域就会变成垃圾,等待垃圾回收器的回收,

怎么找到无用对象:

  • 引用计数:最简单的寻找无用对象的机制,当一个对象被引用一次,引用计数+1,当失去引用时引用计数-1,当此对象引用计数为0时可以直接回收。这种方法有一个显而易见的问题:无法回收被循环引用的对象。
  • 可达性分析:从一个根对象(GC Root)开始向下搜寻,可以认为搜寻到的所有有强引用的对象都是活跃对象,所有找不到的对象都是无用对象,无用对象可能会被即刻回收,也可能进行其他操作(比如执行对象的finalize()方法)
    这里还会引出一点问题:关于强引用,软引用,弱引用和虚引用的分别处理,具体可以看

强制垃圾回收:
程序只能控制一个对象不被任何引用变量引用,绝对不能控制它的回收。
System.gc();
Runntime.getTuntime().gc();
上面两个方法会建议系统进行垃圾回收,但是系统也有可能不进行回收。
对象的复活可以通过 finalize()方法来实现,

android ButterKnife 依赖

​ 1,如果项目只有一个 主module ,则依赖添加如下:

​ 在module 的build.gradle 中添加如下

api 'com.jakewharton:butterknife:8.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'

​ 在项目的 build.gradle 中添加:

dependencies {
    //noinspection GradleDependency
    classpath 'com.android.tools.build:gradle:3.2.0'
     //ButterKnife 依赖
    classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
}

​ 2,如果在library中使用,则需要给library的 build.gradle 中添加如下:

apply plugin: 'com.android.library'
    
api 'com.jakewharton:butterknife:8.4.0'
    //noinspection GradleDependency
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'

​ 在项目的 build.gradle 中添加:

dependencies {
    //noinspection GradleDependency
    classpath 'com.android.tools.build:gradle:3.2.0'
     //ButterKnife 依赖
    classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
}

使用注意:

​ 第一步为 绑定ButterKnife,

​ 如果是一个 主module 中使用,则可以直接使用R来调用资源文件,但是如果在 library 中使用,则需要使用R2 来调用资源文件.

设置一个单选对话框:

private String[] mGenders = new String[]{"男","女","保密"};
getGenderDialog(new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        final AppCompatTextView textView = view.findViewById(R.id.tv_arrow_value);
                        textView.setText(mGenders[which]);
                    }
                });
 //设置一个单选对话框
 private void getGenderDialog(DialogInterface.OnClickListener listener){
        final AlertDialog.Builder builder = new AlertDialog.Builder(DELEGATE.getContext());
        builder.setSingleChoiceItems(mGenders,0,listener);
        builder.show();
    }

19/5-9

类加载过程

​ 类加载的过程分为三个部分:

​ 1,加载 类加载是将.class 文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后在堆内存中创建一个对象,又来封装类在方法区内的数据接口。类加载的最终产品是为与堆中的Class 对象。

​ 2,类的连接 类加载完成后,就会进入连接阶段,连接阶段负责把类的二进制数据合并到jre中,可分为三个阶段

​ 1,验证。

​ 2,准备。

​ 3,解析。

​ 3,类的初始化 在类的初始化阶段,虚拟机负责对类进行初始化,主要是对类变量进行初始化。JVM初始化一个类包含如下几个步骤

​ 1,假如这个类没有被加载和连接,则程序加载并连接该类。

​ 2,假如该类的直接父类还没有被初始化,则先初始化其直接父类。

​ 3,假如类中有初始化语句,则系统一次执行这些初始化语句。

谈谈如果优化ListView

​ 1,在适配器中尽量少使用逻辑

​ 不要在getView()中写过多的逻辑代码。

​ 2,GC垃圾回收器

​ 当创建了大量的对象时,GC就会频繁的运行,所以在getView() 方法中不要创建非常多的对象。假设你的 log 里 面发现“GC has freed dome memory”频繁出现,那么程序肯定有问题了。

​ 你可以检查一下:

​ 1,item 布局的层级是否太深。

​ 2,getView()方法是否有大量对象存在。

​ 3,ListView 的布局属性。

​ 3,载入图片

​ 如果你的ListView 必须要从网络上下载图片,我们不要在 ListView 滑动的时候加载图片,那样会让ListView 变得卡顿,所以我们要监听ListView 的状态,在滑动的时候 停止载入图片,没有滑动在 开始载入图片。

listView.setOnScrollListener(new OnScrollListener() {

            @Override
            public void onScrollStateChanged(AbsListView listView, int scrollState) {
                    //停止载入图片 
                    if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
                            imageLoader.stopProcessingQueue();
                    } else {
                    //開始载入图片
                            imageLoader.startProcessingQueue();
                    }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    // TODO Auto-generated method stub

            }
    });

​ 4,将 ListView 的 scrollingCache 和 animateCache 设置为false

scrollingCache: scrollingCache本质上是drawing cache,你能够让一个View将他自己的drawing保存在cache中(保存为一个bitmap),这样下次再显示View的时候就不用重画了,而是从cache中取出。默认情况下drawing cahce是禁用的。由于它太耗内存了,可是它确实比重画来的更加平滑。

​ 而在ListView中,scrollingCache是默认开启的,我们能够手动将它关闭。

​ animateCache: ListView 默认开启了animateCache,这会消耗大量的内存,因此会频繁调用GC,我们能够手动将它关闭掉。

​ 5,降低item的布局的深度

​ 5,使用ViewHolder。

​ 再次建议使用RecyclerView。

日期选择器 DatePicker 的封装

public class DateDialogUtil {

    public interface IDateListener{
        void onDateChange(String date);
    }
    private String data = null;
    private IDateListener mDataListener = null;

    public void setDateListener(IDateListener listener){
        this.mDataListener = listener;
    }
    public void showDialog(final Context context){
        final LinearLayout ll = new LinearLayout(context);
        final DatePicker picker = new DatePicker(context);
        final LinearLayout.LayoutParams lp  = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        picker.setLayoutParams(lp);
        picker.init(1990, 1, 1, new DatePicker.OnDateChangedListener() {
            @Override
            public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
                final Calendar calendar = Calendar.getInstance();
                //设置时间
                calendar.set(year,monthOfYear,dayOfMonth);
                //日期格式化,使用此Java虚拟机实例的默认区域设置的当前值
                SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日",Locale.getDefault());
                data = format.format(calendar.getTime());
            }
        });
        ll.addView(picker);
        new AlertDialog.Builder(context)
                .setView(ll)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (mDataListener != null && data != null){
                            mDataListener.onDateChange(data);
                        }
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                })
                .show();
    }
}
final DateDialogUtil dateDialogUtil = new DateDialogUtil();
dateDialogUtil.setDateListener(new DateDialogUtil.IDateListener() {
    @Override
    public void onDateChange(String date) {
        final TextView textView = view.findViewById(R.id.tv_arrow_value);
        textView.setText(date);
    }
});

19/5-10

谈谈你对 Activity.runOnUiThread 的理解?

 public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

​ 判断当前是不是 ui 线程,如果是就直接运行,否则就通过handler的post方法切换到主线程

Android 权限的封装

权限的封装

注解权限:PermissionsDispatcher 的简单使用:

​ @RuntimePermissions :这是必须使用的注解,用于标注你想要的申请权限的Activity或者Fragment,

@RuntimePermissions
public abstract class PermissionCheckerDelegate extends Activity {
}

​ @NeedsPermission(Manifest.permission.CAMERA) :

​ 这个也是必须要使用的注解, 用于标注你需要获取权限的方法,注解括号里面有个参数,传入想要申请得权限,可以传入多个。当获得了对应的权限后就会执行这个方法。

@NeedsPermission(Manifest.permission.CAMERA)
void startCamera() {
    LatteCamera.start(this);
}

​ @OnShowRationale(Manifest.permission.CAMERA) :

​ 这个不是必须的注解,用于标注申请权限 时需要执行的方法,传入想要申请的权限,还需要一个PermissionRequest对象,这个对象有两种方法 proceed()让权限继续请求,canncel()让请求中断。也就是说,这个方法会拦截你发出的请求,这个方法* 用于告诉你接下来申请权限是干啥的,说服用户给你权限

  @OnShowRationale(Manifest.permission.CAMERA)
    void onCameraReational(PermissionRequest request) {
        showRetionaeDialog(request);
    }

    private void showRetionaeDialog(final PermissionRequest request) {
        new AlertDialog.Builder(getContext())
                .setPositiveButton("同意使用", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();
                    }
                })
                .setNegativeButton("拒绝使用", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("权限管理")
                .show();
    }

​ @OnPermissionDenied注解:

​ 这个也不是必须的注解,用于标注如果权限请求失败, 但是用户没有勾选不再询问的时候执行的方法,注解括号里面有参数,传入想要申请的权限。 也就是说,我们可以在这个方法做申请权限失败之后的处理,如像用户解释为什么要申请,或者重新申请操作等。

​ onRequestPermissionsResult()方法

​ Rebuild 之后会生成一个辅助类,用来调用被注解的Activity的方法,所以,第一次使用的话* 注解添加完后 要Rebuild 一次。否则不能生成辅助类。

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] data) {
    super.onRequestPermissionsResult(requestCode, permissions, data);
    PermissionCheckerDelegatePermissionsDispatcher
            .onRequestPermissionsResult(this, requestCode, data);
}

​ 使用如下

//调用该方法后就会申请权限,成功之后就会执行相应的代码
public void startCameraWithCheck() {
    PermissionCheckerDelegatePermissionsDispatcher.startCameraWithPermissionCheck(this);
}

封装一个全局的回调接口:

public interface IGlobalCallback<T> {
    void executeCallBack(T args);
}
public enum CallBackType {
    /**
     * 剪裁后的回调
     */
    ON_CROP
}
public class CallbackManager {
    private static final WeakHashMap<Object,IGlobalCallback> CALLBACKS = new WeakHashMap<>();

    private static class Holder{
        private static final CallbackManager IINSTANCE = new CallbackManager();
    }
    public static CallbackManager getInstance(){
        return Holder.IINSTANCE;
    }

    public CallbackManager addCallback(Object tag ,IGlobalCallback callback ){
        CALLBACKS.put(tag,callback);
        return this;
    }
    public IGlobalCallback getCallBack(Object tag){
        return CALLBACKS.get(tag);
    }
}

//使用如下:

	//实现
	CallbackManager.getInstance()
        .addCallback(CallBackType.ON_CROP, new IGlobalCallback<Uri>(){
            @Override
            public void executeCallBack(Uri args) {
                Log.e(TAG, "executeCallBack: "+args );
            }
        });

	//调用
	final IGlobalCallback<Uri> callback = CallbackManager
                            .getInstance()
                            .getCallBack(CallBackType.ON_CROP);
    if (callback != null){
            callback.executeCallBack(cropUri);
    }

调用手机相册,照相机,裁剪图片

点击进行查看

19/5-11

请列出几种常见的工厂模式,并说明他的用法?

工厂模式

问题:打开项目后文件名字都是J 开头,全部报红,xml 文件打开后乱码。解决办法:

​ 经过查看,使用notepad 打开后发现一切正常,最后将项目换个位置。打开后一切正常

19/5-12

请说一下HashMap 和 HashTable 的区别?

​ 1,HashMap 支持 null 键 和null值,而HashMap 在遇到null 是,会抛出 空指针异常。

​ 2,我们说HashTable是同步的,HashMap不是,也就是说HashTable在多线程使用的情况下,不需要做额外的同步

​ 3, HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。

​ 4,HashTable 继承子Dictionary 类,二HashMap 继承AbstractMap 类,但是二者都实现了Map 接口

​ 5, Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。

​ 6,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

​ 7,HashTable已经被淘汰了,不要在代码中再使用它。

针对RecyclerView 做了哪些优化

​ 1,onBindViewHolder

​ 这个方法的含义 就是绑定数据,并且是在UI线程。所以尽量在这个方法中少做一些业务的处理 。

​ 2,数据优化采用

​ android 支持包晓得DiffUtil 集合工具类结合RV分页加载会更友好,节省性能

​ 3,item 优化

​ 减少项目的视图层级,如果项目的高度可以固定的话 可以设置setHasFixedSize(真),避免requestLayout 浪费资

​ 4,使用RecyclerViewPool

​ RecyclerViewPoll 是对项目进行缓存的。项

​ 5,刷新时 可以使用局部刷新

​ mAdapter.notifyItemRangeChanged()

19/5-13

“equals” 与 “==” ,“hashCode”的区别和使用

​ 一般情况下 equals比较的是 对象的地址,和 == 一样,但是有很多类重写了equals 方法。比如String 等。

​ 而 hashcode( ) 方法返回一个 int 数,在object 类中的默认实现是 “将该对象的内存地址 转换成一个整数返回”

hashCode 的 常规协定

​ 在java 程序执行期间,在同一对象上调用多次 hashCode 返回的 int 数 必相同。前提是对象上的equals 方法中所用的信息没有被修改。

​ 如果 equals 比较的两个对象是相等的。那么hashCode 返回的 int 数必定是相等的

​ 当equals 方法被重写时 ,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定。

1,若重写了 equals 方法,则有必要重写hashCode 方法

2,若 equals 比较的结果相等,那么 hashCOde 的结果一定相等。

3,若equals 比较的结果不相等,那么 hashCode 的结果 也有可能相等

4,若 hashCode 返回相同的 int 数,那么equals 不一定相等。

5,若 hashCode 返回不同的 int 数,那么 equals 一定不相等。

6,同一对象 在执行期将 若已经存储在集合中。则不能修改影响hashCode 值得相关信息。否则会导致 内存泄露的问题。

CharSequence

​ charSeqiemce 是一个接口,表示 char 值的一个可读序列。此接口对许多不同种类的 char 序列提供统一的 自读访问。此接口不修改 equals 和 hashCode 的常协规定,因此通常未定义比较实现 charSequence 的两个对象的结果。他有几个实现类:charBuffer 、String 、StringBuffer、StringBuilder。

​ CharSequence 和 String 都能用于定义字符串,但是 charSequence 的值是可读可写序列,而String 是 只读序列。

​ 对一个抽象类或者是接口 ,不能使用new 来进行赋值。但是可以通过有一些的实例进行创建。

​ CharSequence cs = “hello”;

将资源文件 转换为字符串

Latte.getApplication().getResources().getString(R.string.clear_history)

Latte.getApplication()为 Context

19/5-14

谈一谈startService 和 bindService 的区别,生命周期以及使用场景?

生命周期:

​ startService :onCreate -> onStartCommand 。然后服务会一直保持在运行状态,知道 stopService() 或者 stopSelf()方法被调用。注意:虽然每次 调用 startService() 方法,都会使 onStartCommand()方法执行一次。但是实际上只会存在一个实例。

​ bindService:onCreate() -> onBind(), 这个使用调用者和 Service 就会绑定在一起。第一次执行bindService 时,onCreate() 和 onBind()都会得到调用。但是多次调用时 onCreate()和onBind()方法并不会多次调用.

调用者如果获取绑定后的Service 的方法:

​ onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。

既使用startService 又使用bindService 的情况:

​ 如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。

​ 那么,什么情况下既使用startService,又使用bindService呢?如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

Handler Thread 的使用场景和用法?

​ 1,创建Handler Thread 实例对象

HandlerThread mHandlerThread = new HandlerThread("mHandlerThread");

​ 2,启动线程

mHandlerThread.start();

​ 3,创建Handler对象,重写handlerMessage方法

mHandler = new Handler(mHandlerThread.getLooper()){
    @Override
    public void handleMessage(Message msg) {
        //这个方法是运行在mHandlerThread 线程 中的,可以执行耗时操作
    }
};

​ 4,使用mHandler 向工作线程发送消息

mHandler.sendEmptyMessage(1);
new Thread(new Runnable() {
    @Override
    public void run() {
        //在子线程给handler 发消息
        mHandler.sendEmptyMessage(2);
    }
});

​ 5,结束线程,即停止线程的消息循环

     mHandlerThread.quit();

Handler Thread的特点:

​ HandlerThread 将 loop 转到子线程中处理,说白了就是分担MainLooper 的工作量,降低了主线程的压力,使界面更流畅

​ 开启一个线程起到多个线程的作用,如果任务是串行执行,按消息的发送顺序处理。HandlerThread 本质是一个线程,在线程内部,代码是串行处理的。

​ 但是由于每一个任务都将以队列的方式举个被执行到,一旦列表中有某个任务执行时间过长。那么后续的会后被延时处理。

​ HandlerThrad 拥有自己的消息队列。他不会干扰或阻塞UI 线程

​ 对于网络 IO 操作,HandlerThread 并不很合适,因为她只有一个线程,还得排队一个一个等着。

19/5-15

常用的颜色

<resources>
    <!--浅灰色-->
    <color name="app_background">#1111</color>
    <!--微信黑-->
    <color name="we_char_black">#323232</color>
    <!--item 的日常颜色-->
    <color name="item_background">#F1F1F1</color>
    <!--橘黄色-->
    <color name="app_main">@android:color/holo_orange_dark</color>
</resources>

谈一谈ArrayMap 和 HashMap 的区别.

​ hashMap : 内部采用 数组和链表的结构。当链表中的长度大于8是,会转为红黑树。在查找的时候,因为是根据hashCode 来进行索引,所以查询速度会大幅度的提高。HashMap 的长度默认为 16 ,负载因子为 0.75 ,也就是说当HashMap 中的长度达到12的时候,就会自动扩容。扩容的大小为原来长度的 2 倍,但是如果 扩容。那么HashMap 中的所有元素 都会重新计算 HashCode值,并存放到扩容后的HashMap中,这是一个非常耗性能的操作。所以在使用HashMap 的时候,尽量在创建对象的时候给定 HashMap 的大小。HashMap 是不安全的。

​ ArrayMap:采用的是数组和数组的形式,一个保存哈希值,一个保存数据。在查找的时候 会根据二分查找法来查找元素。所以当数组中没扩容一次。就会导致查找的时候多一次判断。就是因为在这个原因,所以ArrayMap 不适合处理比较大的数据。在扩容的时候,如果长度大于8时申请大小 *1.5长度。大于4小于6时 申请 8 个长度。小于4时申请4个。这个比较其实是 ArrayMap 申请了更少的内存空间。但同时申请得频率就会变高。他不仅有扩容的功能,在删除时,如果集合中的元素少于一定的阈值。还会有收缩的功能,减少空间占用。所以在数据量小的时候,使用ArrayMap 会更好一些。ArrayMap 是不安全的。

ConvenientBanner 的使用(首页轮播图)

​ 通用的广告栏控件,轻松实现广告头效果,可以设置自动翻页 和时间 ,非常智能,触碰暂停翻页,离开自动翻页。

简单的封装一下:

1,添加依赖,记得要加RecyclerView 的依赖。

api 'com.android.support:recyclerview-v7:28.0.0'
api 'com.bigkoo:ConvenientBanner:2.1.4'

2,封装一个管理类:

public class BnnersCreator {
    public static void setDefault(ConvenientBanner<Integer> convenientBanner,
                                  ArrayList<Integer> banners,
                                  OnItemClickListener clickListener) {
        convenientBanner
                .setPages(new HolderCreate(),banners)
                //设置底部的小圆圈
                .setPageIndicator(new int[]{R.drawable.dot_focus,R.drawable.dot_normal})
                //设置小圆圈的位置
                .setPageIndicatorAlign(ConvenientBanner.PageIndicatorAlign.CENTER_HORIZONTAL)
                //设置监听
                .setOnItemClickListener(clickListener)
                //设置自动翻页的时间
                .startTurning(3000)
                .setCanLoop(true);
    }
}

3,创建 HolderCreate,并设置一个要显示item 的布局

public class HolderCreate implements CBViewHolderCreator {
    @Override
    public Holder createHolder(View itemView) {
        return new ImageHolder(itemView);
    }

    @Override
    public int getLayoutId() {
        return R.layout.index_photo;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/index_photo"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@android:color/white" />

</android.support.v7.widget.LinearLayoutCompat>

4,创建Holder

public class ImageHolder extends Holder<Integer> {

    AppCompatImageView mImageview ;

    public ImageHolder(View itemView) {
        super(itemView);
    }

    @Override
    protected void initView(View itemView) {
        mImageview = itemView.findViewById(R.id.index_photo);
    }

    @Override
    public void updateUI(Integer data) {
        mImageview.setImageResource(data);
    }
}

5,使用:

<com.bigkoo.convenientbanner.ConvenientBanner xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/banner_recycler_item"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:orientation="vertical"/>
AppCompatImageView image = helper.getView(R.id.index_sort_1);
image.setImageResource((Integer) item.getField(MultipleFields.IMAGE_URL));
 protected void onPause() {
        super.onPause();
        //停止轮播
        cbTest1.stopTurning();
        cbTest2.stopTurning();
    }

上面只是加载本地的图片。如果要加载网络中的图片只需要将 泛型改为String 类型,使用Giilde 加载即可,如下所示:

public class ImageHolder extends Holder<String> {

    AppCompatImageView mImageview ;

    public ImageHolder(View itemView) {
        super(itemView);
    }

    @Override
    protected void initView(View itemView) {
        mImageview = itemView.findViewById(R.id.index_photo);
    }

    @Override
    public void updateUI(String data) {
        Glide.with(Coke.getAppContext())
                .load(data)
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .dontAnimate()
                .centerCrop()
                .fitCenter()
                .into(mImageview);
    }
}

Gilde 的使用

​ Glide是Google官方推荐的一个图片加载和缓存的开源库,它不仅能实现平滑的图片列表滚动效果,还支持远程图片的获取、大小调整和展示,并且可以加载GIF图片。Glide相比与UIF、Volley、Picasso、Fresco等其他框架的优点是轻量和稳定。

常用方法如下:

.with() 图片加载的环境:1,Context对象。2,Activity对象。3,FragmentActivity对象。4,Fragment对象
 .load() 加载资源:1,drawable资源。2,本地File文件。3,uri。4,网络图片url。5,byte数组(可以直接加载GIF图片)
 .placeholder() 图片占位符
 .error() 图片加载失败时显示
 .crossFade() 显示图片时执行淡入淡出的动画默认300ms
 .dontAnimate() 不执行显示图片时的动画
 .override() 设置图片的大小
 .centerCrop()fitCenter() 图片的显示方式
 .animate() view动画 2个重构方法
 .transform() bitmap转换
 .bitmapTransform() bitmap转换。比如旋转,放大缩小,高斯模糊等(当用了转换后你就不能使用.centerCrop().fitCenter()了。)
 .priority(Priority.HIGH) 当前线程的优先级
 .signature(new StringSignature(“ssss”))
 .thumbnail(0.1f) 缩略图,3个重构方法:优先显示原始图片的百分比(10%)
 .listener() 异常监听
 .into() 图片加载完成后进行的处理:1,ImageView对象。2,宽高值。3,Target对象

1,添加依赖和网络权限.

<uses-permission android:name="android.permission.INTERNET"/>
api 'com.github.bumptech.glide:glide:4.9.0'
api 'com.squareup.okhttp3:okhttp:3.12.0'

2,使用:

//基本用法
Glide.with(mContext)
        .load(imageUrl)
        /*
         * 图片的缓存:
         * DiskCacheStrategy.NONE 什么都不缓存
         * DiskCacheStrategy.SOURCE 只缓存全尺寸图
         * DiskCacheStrategy.RESULT 只缓存最终的加载图
         * DiskCacheStrategy.ALL 缓存所有版本图(默认行为)
         */
        .diskCacheStrategy(DiskCacheStrategy.ALL)
    	// 在图片没有加载出来或加载失败时显示ic_launcher图片
    	.placeholder(R.mipmap.ic_launcher) 
        .dontAnimate()
        //将他图片按比例缩放到足以填充imageView 的尺寸,但是图片可能显示不完整
        .centerCrop()
        //将图片缩放到小于等于imageView的尺寸,这样图片会完整显示但是 imageView 就可能填不满了
        .fitCenter()
        .into((ImageView) holder.getView(R.id.img_single));

//设置加载动画
Glide.with(this).load(imageUrl).animate(R.anim.item_alpha_in).into(imageView);
//设置加载中 以及加载失败的图片
Glide
 .with(this)
  .load(imageUrl)
 .placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).into(imageView);

//设置要加载的内容 项目中有很多需要先下载图片然后再做一些合成的功能,比如项目中出现的图文混排,该如何实现目标下
Glide.with(this).load(imageUrl).centerCrop().into(new SimpleTarget<GlideDrawable>() {
            @Override
            public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
                imageView.setImageDrawable(resource);
            }
        });
//多样式的媒体加载
 Glide
        .with(context)
        .load(imageUrl).thumbnail(0.1f)//设置缩略图支持:先加载缩略图 然后在加载全图
                           //传了一个 0.1f 作为参数,Glide 将会显示原始图像的10%的大小。
                          //如果原始图像有 1000x1000 像素,那么缩略图将会有 100x100 像素。
        .asBitmap()//显示gif静态图片 
        .asGif();//显示gif动态图片
        .into(imageView)//设置跳过内存缓存
Glide
  .with(this)
.load(imageUrl)
.skipMemoryCache(true)
.into(imageView);
//设置跳过内存缓存
//这意味着 Glide 将不会把这张图片放到内存缓存中去
//这里需要明白的是,这只是会影响内存缓存!Glide 将会仍然利用磁盘缓存来避免重复的网络请求

Glide.get(this).clearDiskCache();//清理磁盘缓存 需要在子线程中执行 
Glide.get(this).clearMemory();//清理内存缓存 可以在UI主线程中进行


19/5-16

synchronized 和 volatitle 关键字的区别

​ synchronized 可以保证原子性。他可以保证 在同一时刻,只有一个线程可以访问被 synchronized 修饰的方法,或者代码块。

​ volatile 不能保证原子性。当时在使用这个关键字后。当被Volatitle 修饰字段的值发生改变后,其他线程会立刻知道这个值已经发生变化了。volatitle 可以保证可见性和有序性

1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的 volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

3.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

4.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

加载 loading

AvLoadingIndicatorView 的封装

19/5-17

Default Activity not fount ,所有的项目都找不到Activity

今天碰到一个问题。不知道为什么项目打开时候,找不到Activity。程序没办法运行。所有的项目都是这个问题。

就在准备重装 AndroidStudio 的时候,找到了问题的所在。解决如下

C:\Users\Lv_345\ .AndroidStudio3.2\system\caches

首先关闭 as 。在用户目录下找到 .AndroidStudio3.2 ,然后找到 caches 文件夹,并且删除这个文件夹重启as即可。

造成这个的原因应该是异常关闭。

什么是冒泡排序,如何进行优化:

​ 冒泡排序: 首先计算长度。

​ 有两层循环。第一层循环控制比较的趟数,第二层则进行比较。

​ 比较规则如下: 第一个元素和后面的元素挨个比较。完成之后,就是第二个元素和后面元素依次进行比较。知道倒着第一个和倒着的第二元素比较完后, 排序结束

int[] ints = {52,7,2,9,1,7,653,1,47,55,5};
for (int i = 0; i < ints.length; i++) {
    for (int j = i; j < ints.length; j++) {
        if (ints[i] > ints[j]){
            int temp = ints[i];
            ints[i] = ints[j];
            ints[j] = temp;
        }
    }
}

19/5-18

第三方数据库:greenrobot 的使用:

1,首先 添加依赖:

api 'org.greenrobot:greendao-generator:3.2.2'
api 'org.greenrobot:greendao:3.2.2'

在项目的build.gradle 中添加:

//数据库
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'

在需要使用数据库的module 中添加:

/**
 * 数据库的插件
 */
apply plugin: 'org.greenrobot.greendao'

2,进行一个小封装,并设置表名。

public class ReleaseOpenHelper extends DaoMaster.OpenHelper {

    public ReleaseOpenHelper(Context context, String name) {
        super(context, name);
    }
}
public class DatabaseManager {

    private DaoSession mDaoSession = null;
    private DatabaseManager(){}

    public DatabaseManager init(Context context){
        initDao(context);
        return this;
    }
    /**
     * 内部类 的单例模式
     */
    private static final class Holder{
        private static final DatabaseManager INSTANCE = new DatabaseManager();
    }

    /**
     * @return 返回实例
     */
    public static DatabaseManager getInstance(){
        return Holder.INSTANCE;
    }
    /**
     * 初始化,并创建表
     */
    private void initDao(Context context){
        //自定义的类:ReleaseOpenHelper 继承自 DaoMaster.OpenHelper
        //通过 DaoMaster的内部类 可以得到 SQLiteOpenHelper 的对象
        // 第二个参数为 数据库的名称
        final ReleaseOpenHelper helper = new ReleaseOpenHelper(context,"task.db");
        //获取可读写 数据库
        final Database db =helper.getWritableDb();
        //拿到 DaoSession 对象
        mDaoSession = new DaoMaster(db).newSession();
    }
    public final DaoSession getDao(){
        return mDaoSession;
    }
}

3,初始化

DatabaseManager.getInstance().init(this);

4,创建表

@Entity
public class UserProfile {
    @Id
    private String number;
    private String password;
    
    ......
}

@Entity 注解表示这是一个表。@Id 表示这是主键key ,创建完字段后 Rebuild 一下就会生成相应的代码。

5,插入数据:

UserProfile userProfile = new UserProfile("张三""123456");
DaoSession dao = DatabaseManager.getInstance().getDao();
dao.getUserProfileDao().insertOrReplace(userProfile)

6,修改数据:

//修改数据
DaoSession dao = DatabaseManager.getInstance().getDao();
//根据对应的主句 获取对应列的对象
UserProfile load = dao.getUserProfileDao().load("主键");
//修改数据
load.setPassword("我是被修改的密码");
//更新数据
 dao.update(load);

修改 TextInputEditText 下划线 的颜色:

TextInputEditText 是design 风格的数据框,非常好看.

使用如下:

<android.support.design.widget.TextInputEditText
    android:id="@+id/edit_sign_in_password"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="请输入密码"
    android:inputType="textPassword" />

修改下划线的颜色。

<style name="MyEditText" parent="Theme.AppCompat.Light">
    <item name="colorControlNormal">@color/bule</item>
</style>

最后在 xml 文件中加入 即可

android:theme="@style/MyEditText"

做教学助手,快完工了。

19/5-19

在使用数据库的时候发现问题。

数据库中的主键id 不能被修改。因为数据库的id 要和RecyclerView 中的 条目相同。所以删除某个条目后数据库中也跟着删除,过就是和数据库中的条目对应不上。

解决办法:将数据库中的所有数据拿出来,然后清空数据库。删除拿出来的数据库中对应的条目。将删除后的数据存入数据库。

虽然有点麻烦,但是目前没有想到更好的办法。将就着用吧

教学助手,马上完工了。

19/5-20

日期和时间的对话框,从底部弹出的。使用如下

//日期选择
api 'com.contrarywind:Android-PickerView:4.1.8'

日期选择

TimePickerView pvTime = new TimePickerBuilder(getContext(), new OnTimeSelectListener() {
    @Override
    public void onTimeSelect(Date date, View v) {

    }
}).build();
pvTime.show();

时间选择

Date curDate = new Date(System.currentTimeMillis());//获取当前时间
SimpleDateFormat formatter_year = new SimpleDateFormat("yyyy ");
String year_str = formatter_year.format(curDate);
int year_int = (int) Double.parseDouble(year_str);


SimpleDateFormat formatter_mouth = new SimpleDateFormat("MM ");
String mouth_str = formatter_mouth.format(curDate);
int mouth_int = (int) Double.parseDouble(mouth_str);

SimpleDateFormat formatter_day = new SimpleDateFormat("dd ");
String day_str = formatter_day.format(curDate);
int day_int = (int) Double.parseDouble(day_str);

Calendar selectedDate = Calendar.getInstance();//系统当前时间
Calendar startDate = Calendar.getInstance();
startDate.set(1900, 0, 1);
Calendar endDate = Calendar.getInstance();
endDate.set(year_int, mouth_int - 1, day_int);


 TimePickerView pvTime = new TimePickerBuilder(getContext(), new OnTimeSelectListener() {
                            @Override
                            public void onTimeSelect(Date date, View v) {
                                Log.e("---------", "onTimeSelect: "+getTime(date) );
                            }
                        })
                                //年月日时分秒 的显示与否,不设置则默认全部显示
                                .setType(new boolean[]{true, true, true, true, true, true})
                                //默认设置为年月日时分秒
                                .setLabel("年", "月", "日", "时", "分", "秒")
                                .isCenterLabel(false)
                                //设置选中项的颜色
                                .setTextColorCenter(Color.RED)
                                //设置没有被选中项的颜色
                                .setTextColorOut(Color.BLUE)
                                .setDate(selectedDate)
                                .setContentTextSize(20)
                                .setLineSpacingMultiplier(1.2f)
                                //设置X轴倾斜角度[ -90 , 90°]
                                .setTextXOffset(-10, 0, 10, 0, 0, 0)
                                .setRangDate(startDate, endDate)
                                .setDecorView(null)
                                .build();
                        pvTime.show();

  private String getTime(Date date) {//可根据需要自行截取数据显示
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(date);
    }

19/5-21

分别介绍一下你所知道 Android 的几种存储方式

​ 本地文件存储

​ 将一些不敏感的数据保存在本地。

​ 网络存储

​ 以版就是 http get或者 http post 从服务器,读取数据。业务数据获取的常用方法。

​ 数据库存储

​ SQLite 是轻量级嵌入式数据库引擎,他支持SQL 语言,并且利用很少的内存就有很好的性能。一般在开发程序是 ,就会用到 SQLite 来存储大量的数据。

​ sharepreference

​ 不支持数据频繁的读写。频繁的读写会造成数据的错乱。适用范围,保存少量的数据。且这些数据的格式非常简单。

​ ContentProvider :

​ 四大组件之一,一般配合源码使用,SharePreference,文件存储使用,支持并发读取。

CoordinatorLayout

​ CoordinatorLayout 可以说是一个加强版的FrameLayout ,这个布局也是由Design Support 库提供的,他在普通情况下的作用和FrameLayout 基本一致。

​ 事实上,CoordinatorLayout 可以监听其所有子控件的各种事件,然后帮我们作出更合理的响应。

AppBarLayout

​ AppBarLayout继承自LinearLayout,布局方向为垂直方向。所以你可以把它当成垂直布局的LinearLayout来使用。AppBarLayout是在LinearLayou上加了一些材料设计的概念,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作。

​ 请注意:上面提到的某个可滚动View,可以理解为某个ScrollView。怎么理解上面的话呢?就是说,当某个ScrollView发生滚动时,你可以定制你的“顶部栏”应该执行哪些动作(如跟着一起滚动、保持不动等等)。那某个可移动的View到底是哪个可移动的View呢?这是由你自己指定的!如何指定,我们后面说

AppBarLayout 子View 的动作

​ 内部的子View通过在布局中加app:layout_scrollFlags设置执行的动作,那么app:layout_scrollFlags可以设置哪些动作呢?分别如下:

​ (1) scroll:值设为scroll的View会跟随滚动事件一起发生移动

​ (2) enterAlways:值设为enterAlways的View,当ScrollView往下滚动时,该View会直接往下滚动。而不用考虑ScrollView是否在滚动。

​ 高度设置为: android:attr/actionBarSize,app:layout_scrollFlags="scroll|enterAlways"

​ (3) exitUntilCollapsed:值设为exitUntilCollapsed的View,当这个View要往上逐渐“消逝”时,会一直往上滑动,直到剩下的的高度达到它的最小高度后,再响应ScrollView的内部滑动事件。

​ ?简单解释:在ScrollView往上滑动时,首先是View把滑动事件“夺走”,由View去执行滑动,直到滑动最小高度后,把这个滑动事件“还”回去,让ScrollView内部去上滑 ,设置最小高度为 并将最小高度设置为?android:attr/actionBarSize,app:layout_scrollFlags="scroll|exitUntilCollapsed"

​ (4) enterAlwaysCollapsed:是enterAlways的附加选项,一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是enterAlways效果,当View的高度达到最小高度时,View就暂时不去往下滚动,直到ScrollView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束。

​ 最小高度设置android:attr/actionBarSize,app:layout_scrollFlags="scroll|enerAlways|enterAlwaysCollapsed"

CollapsingTollbarLayout 可折叠式标题栏:

​ CollaspingToolbarLayout 是一个作用于Toolbar 基础之上的布局,他可以让Toolbar 效果变得更加丰富。不仅仅只是展示一个标题栏。

​ CollaspingToolbarLayout 是 不能独立存在的。他在设计的时候就被限定只能作为AppBarLayout 的直接子布局来使用。而AppBarLayout 又必须是 CoordinatorLayout 的子布局。

19/5-22

谈谈你对Android 性能优化方面的了解

启动优化:application 中不要做大量的耗时操作,如果必须的话,建议异步做耗时操作

布局优化:使用合理的控件选择,少嵌套。(合理使用 include,merge,view Stub 等使用)

apk 优化:资源文件优化,代码优化 ,lit检查,.9png ,合理使用shape 代替图片,webp 等。

性能优化:网络优化,电量优化。

​ 避免轮询,尽量使用推送

​ 应用处于后台时,禁用某些数据传输

​ 限制访问频率,失败后不要无限重连

​ 选用合适的 定位服务(GPS , 网络定位,被动定位)

​ 使用缓存

​ startActivityForResult 代替发送广播

内存优化:

​ 循环尽量不适用局部变量

​ 避免在onDraw中创建对象。onDraw 会被频繁调用,容易造成内存抖动。循环中创建大量的对象也是如此。

​ 不用的对象及时释放。

​ 数据库 及时关闭

​ adapter 使用缓存

​ 注册广播后,在生命周期结束时 反注册

​ 及时关闭流操作

​ 图片尽量使用软引用,较大的图片可以通过bitmapFactory缩放后再使用,并及时recycler。另外加载巨图时不要 使用setImageBitmap或setImageResourse 或 BitmapFactory.decodeResource ,这些方法拿到的都是bitmap的对象,占用内存较大。可以用 BitmapFactory.decodeStream 方法配合 BitmapFactory.Options 进行缩放

​ 避免static 成员变量 引用资源耗费过多实例

​ 避免静态内部类的引用

回答一下什么是 强,软,弱,虚引用,以及区别

​ 强:最常用的最普遍的引用,垃圾回收机制绝不会回收他,当内存空间不足,java虚拟机 宁愿抛出 OutOfMemoryError 错误,让程序异常终止,也不会随意回收具有强引用的对象。

​ 软:如果内存足够,垃圾回收器就不会回收他。如果内存不足了,就会回收软引用的对象。软引用可以和一个引用队列 ReferenceQueue 来一起使用,如果软引用所引用的对象被垃圾回收器回收,java 虚拟机就会自动把这个软引用加入到与之关联的引用队列中。

private final LinkedHashMap<Object, Object> MULTIPLE_FIELDS = new LinkedHashMap<>();
private final ReferenceQueue<LinkedHashMap<Object, Object>> ITEM_QUENE = new ReferenceQueue<>();
/**
 * 软引用:SoftReference
 *       如果内存空间足够,垃圾回收器绝不会回收他,如果内存不足,则会回收这些对象的内存。
 */
private final SoftReference<LinkedHashMap<Object, Object>> FIELDS_RETERENCE
        = new SoftReference<>(MULTIPLE_FIELDS, ITEM_QUENE);

​ 弱:垃圾回收器如果发现了弱引用,不管当前内存是否充足,都会回收他的内存。弱引用可以和一个引用队列 ReferenceQueue 联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

​ 虚:虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

内存泄露:

一般可以这样理解,没有用的对象 无法进行回收,这个现象就是内存泄露。

如果内存泄露,会造成下面这些问题

​ 应用的可用内存减少,增加了堆内存的压力。

​ 降低了应用的性能,比如会频繁地GC

​ 严重的时候可能会导致内存溢出错误,即OOM Error

OOM 发生在当我们尝试创建对象,但是堆内存无法通过GC 释放足够的空间,堆内存也无法继续增长,从而完成对象创建的时候。OOM发生很有可能是 内存泄露 引起的,内存泄露也并不一定是 OOM 。

Java 的内存管理

java 内存管理

19/5-23

分别讲讲 final ,static,synchronized 可以修饰什么,以及修饰后的作用:

final :可以修饰 类,方法,字段。

​ 修饰类:该类不会被继承。

​ 修饰方法:该方法不能被重写。

​ 修饰字段:被修饰的 字段必须 赋初始值,并且不能被改变。如果字段是引用类型的。那么他将不能引用别 的对象,但是当前的对象内的 属性值是可以改变的。

static :可以修饰内部类,方法,字段。

​ 被static 修饰的 变量或者方法等,都会在类加载的时候 进行加载,知道程序死亡,他们才会被销毁。

​ 修饰内部类:被static修饰 的内部类可以直接作为一个 普通的类来使用,而不需先实例一个外部列。

​ 修饰方法:调用该方法的时候只需要类名 . 方法就可以直接调用,不需要创建对象。

​ 修饰字段:通过类名 . 的方式可以直接 获取 或者 赋值。

synchronized 可以修饰 方法,代码块

​ 修饰方法:被 synchronized 修饰方法方法在同一时刻只能被一个线程访问。其他线程将会被阻塞,直到当前线程释放锁。

​ 修饰代码块:其实和修饰方法差不多,只不过 修饰代码块可以 使用 类锁。方法如果要使用类锁,只能设置为静态的方法。

19/5-24

Android 7.0 新特性

​ 1,多窗口模式

​ 2,Data Saver :流量保护机制

​ 3,改进的java 8 语言支持

​ 4,自定义壁纸

​ 5,快随回复

​ 6,Daydream VR 支持

​ 7,后台省电

​ 8,快速设置

​ 9,Unicode 9 支持 和全新的 emoji 表情符号

​ 还有很多…

禁用多窗口模式

//在manifest 中添加如下属性。
android:resizeableActivity="false"

需要注意 当 targetSdkVersion 设置的值小于24时,这个属性不会起作用。面对这个情况,解决方案就是应用不支持横竖屏切换,比如将 MainActivity 设置为只支持竖屏,如下。

<activity
    android:name=".ExampleActivity"
    android:screenOrientation="portrait">

SnackBar 的使用

Snackbar.make(layout, "要删除吗?", BaseTransientBottomBar.LENGTH_LONG)
        .setAction("确定", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "成功", Toast.LENGTH_SHORT).show();
            }
        }).show();

XML 属性 Selector(选择器)

通过 选择器 selector 可以使控件 在不同操作下 显示不同样式

Xml属性说明
android:drawable放一个drawable资源
android:state_pressed按下状态,如一个按钮触摸或者点击。
android:state_focused取得焦点状态,比如用户选择了一个文本框。
android:state_hovered光标悬停状态,通常与focused state相同,它是4.0的新特性
android:state_selected选中状态
android:state_enabled能够接受触摸或者点击事件
android:state_checked被checked了,如:一个RadioButton可以被check了。
android:state_enabled能够接受触摸或者点击事件

注:上述所有属性得 取值 = boolean 属性 true ,false

示例:

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

 < !-- 指定按钮按下时的图片 -->
 <item android:state_pressed="true"  
       android:drawable="@drawable/start_down"
 />

 < !-- 指定按钮松开时的图片 --> 
 <item android:state_pressed="false"
       android:drawable="@drawable/start"
 />

< /selector>

使用:

<Button
  android:id="@+id/startButton"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/button_selector" 
/>

复习Activity 的生命周期

Activity 的生命周期

19/5-25——19/5-31

谈谈线程阻塞的原因

​ 1,I O 操作

​ 2,同步操作

​ 3,sleep / join 方法

​ 4,wait 方法。直接进入阻塞状态

Array List 扩容

​ 初始容量为10,当添加第11个元素的时候,会扩容1.5倍,当添加到16个元素的时候扩容为15 * 1.5 = 22,以此类推

什么是反射机制,应用场景有哪些?

​ 对于任何一个类,我们都能知道他有哪些方法。对于任何一个对象我们都可以调用他的任意一个方法和属性,这种动态的获取信息以及动态的调用对象的方法就称为java的反射机制。形象一点说,任何一个类或者对象,对我们来说都是透明的,想要啥直接拿就可以。

​ 应用场景:

  1. 逆向代码,例如反编译
  2. 与注解相结合的框架,如 Retrofit
  3. 单纯的反射机制应用框架,例如 EventBus(事件总线)
  4. 动态生成类框架 例如Gson

Java 中使用线程的方式有哪些

常用的方式:

​ 1,继承 Thread

​ 2,实现Runnable 接口

​ 3,通过线程池池 创建线程

private static final ThreadFactory FACTORY = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger();
    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r,"text ---- #"+mCount.getAndIncrement());
    }
};
public static void main(String[] args) {
    ThreadPoolExecutor textPool = new ThreadPoolExecutor(3,
            5, 0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingDeque<Runnable>(),FACTORY);
    for (int i = 0; i < 10; i++) {
        textPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }
}

结果:

text ---- #1
text ---- #2
text ---- #0
text ---- #2
text ---- #1
text ---- #2
text ---- #0
text ---- #2
text ---- #1
text ---- #0

View 动画

​ 在Android 中的 View 动画框架中,一共提供了 Alpha (透明动画),Rotate(旋转动画),Scale(缩放动画),Translate(平移动画) 四种类型的补间动画。

标签表示 补间动画的集合,对应 AnimationSet 类,所以 set 标签中 可以包含多个补间动画标签,并且还可以包含补间动画中的 集合。

Alpha (透明度 动画)
android:fromAlpha
Float. 设置透明度的初始值,其中0.0是透明,1.0是不透明的。

android:toAlpha
Float. 设置透明度的结束值,其中0.0是透明,1.0是不透明的

duration : 时间
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 透明动画 -->
    <alpha
        android:duration="2000"
        android:fromAlpha="1.0"
        android:toAlpha="0" />

</set>

调用:

Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.alpha);
animation.setFillAfter(true);
group.startAnimation(animation);
Scale(缩放动画)
android:fromXScale
Float. 水平方向缩放比例的初始值,其中1.0是没有任何变化。
android:toXScale
Float. 水平方向缩放比例的结束值,其中1.0是没有任何变化。
android:fromYScale
Float. 竖直方向缩放比例的初始值,其中1.0是没有任何变化。
android:toYScale
Float. 竖直方向缩放比例的结束值,其中1.0是没有任何变化。
android:pivotX
Float. 缩放中心点的x坐标
android:pivotY
Float. 缩放中心点的y坐标
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!--缩放动画-->
    <scale
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="0.2"
        android:toYScale="0.2" />
</set>

使用如上

Translate (平移动画)
android:fromXDelta
Float or percentage. 移动起始点的x坐标. 表示形式有三种:
1 相对于自己的左边界的距离,单位像素值。(例如 "5"2 相对于自己的左边界的距离与自身宽度的百分比。(例如  "5%"3 相对于父View的左边界的距离与父View宽度的百分比。(例如 "5%p")
android:toXDelta
Float or percentage. 移动结束点的x坐标. 表现形式同上
android:fromYDelta
Float or percentage. 移动起始点的y坐标. 表示形式有三种:
1 相对于自己的上边界的距离,单位像素值。(例如 "5"2 相对于自己的上边界的距离与自身高度的百分比。(例如  "5%"3 相对于父View的上边界的距离与父View高度的百分比。(例如 "5%p")
android:toYDelta
Float or percentage. 移动结束点的y坐标. 表现形式同上
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="2000"
        android:fromXDelta="20"
        android:fromYDelta="20"
        android:toXDelta="100"
        android:toYDelta="100" />
</set>

使用如上

Rotate (旋转动画)

android:fromDegrees
Float. 旋转初始的角度。
android:toDegrees
Float. 旋转结束的角度。
android:pivotX
Float or percentage. 旋转中心点x坐标,表示形式有三种:
1 相对于自己的左边界的距离,单位像素值。(例如 "5"2 相对于自己的左边界的距离与自身宽度的百分比。(例如 "5%"3 相对于父View的左边界的距离与父View宽度的百分比。(例如 "5%p")
android:pivotY
Float or percentage. 旋转中心点y坐标,表示形式有三种:
1 相对于自己的上边界的距离,单位像素值。(例如 "5"2 相对于自己的上边界的距离与自身宽度的百分比。(例如 "5%"3 相对于父View的上边界的距离与父View高度的百分比。(例如 "5%p"
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
        android:fromDegrees="0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="1000"
        android:toDegrees="+360" />
</set>
通过补间动画 为 Activity 自动以切换动画

​ 通过调用Activity 类的 overridePendingTransition(int enterAnim, int exitAnim) 方法可以实现自定义Activity 的切换动画,注意这个方方必须在 startActivity 和 finish 之后调用,否则无效

逐帧 动画

​ 逐帧动画 是用来 逐帧 显示预先定义好的 一组图片,类似于电影播放,对应于 AnimationDrawable 类。

​ 动画的资源文件一般放在 Drawable 文件夹下。

通过 xml 定义一个逐帧动画。

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true" >
    <item
        android:drawable="@drawable/first_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/second_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/third_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/fourth_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/fifth_pic"
        android:duration="1000"/>
    <item
        android:drawable="@drawable/sixth_pic"
        android:duration="1000"/>
</animation-list>

然后将面定义的AnimationDrawable 作为 View 的背景 并且通过 AnimationDrawable 来播放动画。

image.setImageResource(R.drawable.anim_list);
AnimationDrawable animationDrawable = (AnimationDrawable) image.getDrawable();
animationDrawable.start();
//animationDrawable.stop(); //如果oneshot为false,必要时要停止动画

通过代码实现:

//代码定义、创建、执行动画
AnimationDrawable animationDrawable = new AnimationDrawable();
animationDrawable.addFrame(getResources().getDrawable(R.drawable.first_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.second_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.third_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.fourth_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.fifth_pic), 1000);
animationDrawable.addFrame(getResources().getDrawable(R.drawable.sixth_pic), 1000);
animationDrawable.setOneShot(true);
image.setImageDrawable(animationDrawable);
animationDrawable.start();

属性动画

ViewPropertyAnimator

​ 使用方式:View.animate() 后面根 translationX()等方法

view.animate().translationX(500);  

​ 具体可以跟的方法以及方法所对应的 View 中的实际操作的方法如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TSNjJAcW-1569745892498)(F:\笔记\android\assets\1558856697517.png)]

​ 从图中可以看到, View 的每个方法都对应了 ViewPropertyAnimator 的两个方法,其中一个是带有 -By 后缀的,例如,View.setTranslationX() 对应了 ViewPropertyAnimator.translationX()ViewPropertyAnimator.translationXBy() 这两个方法。其中带有 -By() 后缀的是增量版本的方法,例如,translationX(100) 表示用动画把 ViewtranslationX 值渐变为 100,而 translationXBy(100) 则表示用动画把 ViewtranslationX 值渐变地增加 100

常用的方法:

ViewPropertyAnimator.withStartAction/EndAction()

​ 这两个方法是 ViewPropertyAnimator 的独有方法。它们和 set/addListener() 中回调的 onAnimationStart() / onAnimationEnd() 相比起来的不同主要有两点:

1,`withStartAction()` / `withEndAction()` 是一次性的,在动画执行结束后就自动弃掉了,就算之后再重用 `ViewPropertyAnimator` 来做别的动画,用它们设置的回调也不会再被调用。而 `set/addListener()` 所设置的 `AnimatorListener` 是持续有效的,当动画重复执行时,回调总会被调用 

​ 2,withEndAction() 设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和 AnimatorListener.onAnimationEnd() 的行为是不一致的。

常用的监听
 			   ViewPropertyAnimator animate = imageView.animate();
				//当动画的属性更新时(不严谨的说,即每过 10 毫秒,动画的完成度更新时),这个方法被调用。
                animate.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                    }
                });
                animate.setListener(new AnimatorListenerAdapter() {
                    //当动画被cancel()方法取消时,这个方法会被调用。
                    //需要说明一下的是,就算动画被取消,onAnimationEnd() 也会被调用。所以当动画被取消时,如果设置了  AnimatorListener,那么 onAnimationCancel() 和 onAnimationEnd() 都会被调用。onAnimationCancel() 会先于 onAnimationEnd() 被调用。
                    @Override
                    public void onAnimationCancel(Animator animation) {
                        super.onAnimationCancel(animation);
                    }

                    //当动画结束时调用
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                    }
                    //当动画通过 setRepeatMode() / setRepeatCount() 或 repeat() 方法重复执行时,这个方法被调用。
                    @Override
                    public void onAnimationRepeat(Animator animation) {
                        super.onAnimationRepeat(animation);
                    }
                    //当动画开始时调用
                    @Override
                    public void onAnimationStart(Animator animation) {
                        super.onAnimationStart(animation);
                    }
                });
ObjectAnimator

​ 使用方式 :

​ 1,如果是自定义控件,需要添加 setter/getter 方法;

​ 2,使用 ObjectAnimatior.ofXXX(),创建 ObjectAnimator 对象,

​ 3,使用start 方法执行动画

​ 与属性动画相比,View 动画存在一个缺陷,View 动画只是改变 View 的显示,并没有改变 View 的响应区域,并且View 动画只能对View 做四种类型的补间动画。因此 在 3.0 以后 添加了 属性动画框架

实现View 的四种动画
  			   //利用 ObjectAnimator 实现透明动画。group 代表的是 视图
                ObjectAnimator
                        .ofFloat(group, "alpha", 1, 0, 1)
                        .setDuration(2000)
                        .start();

                //利用AnimatorSet 和 ObjectAnimation 实现缩放动画
                final AnimatorSet animatorSet = new AnimatorSet();
                group.setPivotX(group.getWidth()/2);
                group.setPivotY(group.getHeight()/2);
                animatorSet.playTogether(
                        ObjectAnimator.ofFloat(group,"scaleX",1,0.2f).setDuration(5000),
                        ObjectAnimator.ofFloat(group,"scaleY",1,0.2f).setDuration(5000)
                );
                animatorSet.start();

                //使用AnimatorSet 和 ObjectAnimation 实现平移动画
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playTogether(
                        ObjectAnimator.ofFloat(group,"translationX",20,100).setDuration(2000),
                        ObjectAnimator.ofFloat(group,"translationY",20,100).setDuration(2000)
                );
                animatorSet.start();

                //利用ObjectAnimation 实现旋转动画
                group.setPivotX(group.getWidth() / 2);
                group.setPivotY(group.getHeight() / 2);
                ObjectAnimator
                        .ofFloat(group, "rotation", 0, 180)
                        .setDuration(2000)
                        .start();

​ 上面是通过代码形式实现的 属性动画,对用通过xml 定义升序工会的方式不是很常用,因为属性的起始值 和 结束值 大多是程序运行时候动态获取的。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4jVJQGcu-1569745892500)(C:\Users\Lv_345\Desktop\ObjectAnimator.gif)]

给自定义的View 设置属性动画
public class Text extends View {

    int TextX = 0;

    public int getTextX() {
        return TextX;
    }

    public void setTextX(int textX) {
        TextX = textX;
        invalidate();
    }

    public Text(Context context) {
        super(context);
    }

    public Text(Context context,AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);//描边
        paint.setStrokeCap(Paint.Cap.BUTT); //线帽:默认
        paint.setStrokeJoin(Paint.Join.BEVEL); //直线

        paint.setTextSize(50);
        paint.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));//字体类型
        //对齐方式
        paint.setTextAlign(Paint.Align.LEFT);

        //文本
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        //获取基线 的坐标
        float TextY = getHeight()/2 +((fontMetrics.top - fontMetrics.bottom)/2 - fontMetrics.bottom);

        canvas.drawText("我是自定义文本",TextX,TextY,paint);
    }
}

以上是自定义的 文本。只不过 给他的 起始位置 的 x 值添加了一个 get/set 方法。默认是0

<com.admin.view_core.viewGroup.Text
    android:id="@+id/text"
    android:layout_width="200dp"
    android:layout_height="100dp"
    android:background="#999999"
    />

下面给 其实位置的 x 设置属性动画

Text text = findViewById(R.id.text);
ObjectAnimator animator = ObjectAnimator.ofInt(text,"TextX",0,100);
animator.start();

效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gMnF22RU-1569745892503)(F:\笔记\android\assets/ObjectAnimator.gif)]

属性动画常用的方法和监听:

1, 设置动画时长

				 ObjectAnimator
                        .ofFloat(group, "alpha", 1, 0, 1)
                        .setDuration(2000)
                        .start();

2,设置 速度控制器

			  Text text = findViewById(R.id.text);
                ObjectAnimator animator = ObjectAnimator.ofInt(text,"TextX",0,900);
                //速度设置器。设置不同的 Interpolator ,动画就会以不同的速度来执行
//                animator.setInterpolator(new LinearInterpolator());//匀速
//                animator.setInterpolator(new AccelerateDecelerateInterpolator());//先加速,在减速,这个是默认的
//                animator.setInterpolator(new AccelerateInterpolator());//持续加速
//                animator.setInterpolator(new DecelerateInterpolator());//持续 减速直到0
                // 先回拉一下在进行正常的 动画轨迹,如果是 放大,就先缩小一下在放大,其他同理
//                animator.setInterpolator(new DecelerateInterpolator());
                //动画会超过目标值一些,然后 弹回来
//                animator.setInterpolator(new OvershootInterpolator());
                //上面两个的结合,开始前会拉,最后 超过一些在回弹
//                animator.setInterpolator(new AnticipateOvershootInterpolator());
                //在目标出处弹跳
//                animator.setInterpolator(new BounceInterpolator());
                //这是一个 正弦./余弦曲线。他可以自定义 曲线的生命周期,所以动画可以不到终点就结束
                // 也可以 到达终点后 反弹,回弹的次数由 曲线的周期确定,曲线的周期由 CycleInterpolator 构造方法的参数决定
//                animator.setInterpolator(new CycleInterpolator(0.5f));
				animator.start();

3,常用的监听

ObjectAnimator animator = ObjectAnimator.ofInt(text,"TextX",50,200);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationCancel(Animator animation) {
        super.onAnimationCancel(animation);
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        super.onAnimationRepeat(animation);
    }

    @Override
    public void onAnimationStart(Animator animation) {
        super.onAnimationStart(animation);
    }

    //由于ObjectAnimation 支持使用 onPause 暂停,
    // 所以增加了下面两个 监听
    @Override
    public void onAnimationPause(Animator animation) {
        super.onAnimationPause(animation);
    }
    @Override
    public void onAnimationResume(Animator animation) {
        super.onAnimationResume(animation);
    }
});

animator.start();

​ 效果和 ViewPropertyAnimator 的监听一样。

渐变 ArgbEvaluator
ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
// 在这里使用 ObjectAnimator.setEvaluator() 来设置 ArgbEvaluator,修复闪烁问题
animator.setEvaluator(new ArgbEvaluator());
animator.setInterpolator(new LinearInterpolator());
animator.setDuration(2000);
animator.start();
在同一个动画中改变多个属性值
// 使用 PropertyValuesHolder.ofFloat() 来创建不同属性的动画值方案
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 0,1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 0,1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 0,1);

// 然后,用 ObjectAnimator.ofPropertyValuesHolder() 把三个属性合并,创建 Animator 然后执行
ObjectAnimator
        .ofPropertyValuesHolder(view, holder1, holder2, holder3)
        .start();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QpD3D19X-1569745892507)(F:\笔记\日记\assets/ObjectAnimator.gif)]

AnimatorSet 多个动画配合执行

​ 说白了,就是按照指定的顺序执行

view.setTranslationX(-200f);
ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "alpha", 0, 1);
animator1.setDuration(1000);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "translationX", -200, 200);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(view, "rotation", 0, 1080);
animator3.setDuration(1000);

AnimatorSet animatorSet = new AnimatorSet();
// 用 AnimatorSet 的方法来让三个动画协作执行
// 要求 1: animator1 先执行,animator2 在 animator1 完成后立即开始
// 要求 2: animator2 和 animator3 同时开始

//先执行 1,在执行2
animatorSet.playSequentially(animator1,animator2);
//2 和 3 一起执行
animatorSet.playTogether(animator2,animator3);
animatorSet.start();

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htRBxC0X-1569745892509)(F:\笔记\日记\assets/ObjectAnimator-1559532557047.gif)]

PropertyValuesHolders.ofKeyframe() 把同一个属性拆分

除了合并 多个属性和调配多个动画,你还可以在 PropertyValueHodler 的基础上更进一步,例如,你可以让一个进度增加到 100% 后在 反弹回来。

// 使用 Keyframe.ofFloat() 来为 view 的 progress 属性创建关键帧
// 初始帧:progress 为 0
Keyframe keyframe1 = Keyframe.ofFloat(0,0);
// 时间进行到一半:progress 为 100
Keyframe keyframe2 = Keyframe.ofFloat(0.5f,100);
// 结束帧:progress 回落到 80
Keyframe keyframe3 = Keyframe.ofFloat(1,80);
// 使用 PropertyValuesHolder.ofKeyframe() 来把关键帧拼接成一个完整的属性动画方案
PropertyValuesHolder holder = PropertyValuesHolder
        .ofKeyframe("progress",keyframe1,keyframe2,keyframe3);
// 使用 ObjectAnimator.ofPropertyValuesHolder() 来创建动画
ObjectAnimator animator  = ObjectAnimator.ofPropertyValuesHolder(view,holder);
animator.setDuration(1000);
animator.start();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bAoxYXJn-1569745892510)(F:\笔记\日记\assets/ObjectAnimator-1559533128364.gif)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tʀᴜsᴛ³⁴⁵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值