美文网首页
Android必备知识

Android必备知识

作者: lkuo | 来源:发表于2018-09-26 19:57 被阅读27次

1.Volley优缺点(5优5缺)

  • Volley 的优点

① 非常适合进行数据量不大,但通信频繁的网络操作;
② 可直接在主线程调用服务端并处理返回结果;
③ 可以取消请求,容易扩展,面向接口编程;
④ 网络请求线程NetworkDispatcher默认开启了4个,可以优化,通过手机CPU数量;
⑤ 通过使用标准的HTTP缓存机制保持磁盘和内存响应的一致;

  • Volley 的缺点

① 使用的是httpclient、HttpURLConnection;
② Android 6.0不支持httpclient了;
③ 对大文件下载 Volley的表现非常糟糕;
④ 只支持http请求;
⑤ 图片加载性能一般;

Volley能否下载电影以及加载大图片?

Volley的request是在内存上操作数据,所以不适用大文件的操作。比如:ImageRequest去加载大图片的时候,也是在内存中读取的,这个时候就可能会出现OOM。

使用

private void getStringRequest() {
            String url="http://api.k780.com:88/?app=phone.get&phone=13800138000&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json";
            RequestQueue queue= Volley.newRequestQueue(this);
            StringRequest request=new StringRequest(url, new Response.Listener<String>() {
                @Override
                public void onResponse(String s) {
                    Log.e("success",s);
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {

                }
            });
            queue.add(request);
        }

源码解析:https://www.jianshu.com/p/33be82da8f25

2. handler工作机制

Handler、Looper、Message、MessageQueue四兄弟

关系如下:

MessageQueue:装食物的容器
Message:被装的食物
Handler(msg.target实际上就是Handler):食物的消费者
Looper:负责分发食物的人

当我们调用handler.sendMessage(msg)方法发送一个Message时,实际上这个Message是发送到与当前线程绑定的一个MessageQueue中,然后与当前线程绑定的Looper将会不断的从MessageQueue中取出新的Message,最后调用msg.target.dispathMessage(msg)(msg.target指的是当前的handler)方法将消息分发到与Message绑定的Runnablehandler.handleMessage()方法中。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg); // 调用run方法
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg); // 调用handleMessage方法
        }
    }

由此,不难发现:

  • 一个Thread可以对应多个Handler;
  • 一个Thread对应一个Looper和MessageQueue
  • Handler与Thread共享Looper和MessageQueue。
  • Message只是消息的载体,将会被发送到与线程绑定的唯一的MessageQueue中,并且被与线程绑定的唯一的Looper分发,被与其自身绑定的Handler消费。

我们都知道Handler是运行在主线程上的,那我们如何在子线程上使用Handler呢?

你可能会写下如下代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        childThread = new MyThread();
        childThread.start();

        //这样之后,childHandler和childLooper就关联起来了。
        Handler childHandler = new Handler(childThread.childLooper){
            public void handleMessage(Message msg) {
                Log.d("LK","childThread========"+Thread.currentThread());
            };
        };
        childHandler.sendMessage(1);
    }

    private class MyThread extends Thread{
        public Looper childLooper;

        @Override
        public void run() {
            Looper.prepare();//创建与当前线程相关的Looper
            childLooper = Looper.myLooper();//获取当前线程的Looper对象
            Looper.loop();//调用此方法,消息才会循环处理
        }
    }

但是运行时会发现上述代码偶尔会由于空指针错误而崩溃。这是因为当子线程start的时候,虽然子线程的run方法得到执行,但是主线程中代码依然会向下执行,当我们new Handler(childThread.childLooper)的时候,run方法中的Looper对象可能还没初始化。

其实,Android早已经为我们提供好解决上述问题的方法,即HandlerThread类。

HandlerThread

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("LK","childThread========"+Thread.currentThread());//子线程
            }
        };

        Log.d("LK","mainThread======="+Thread.currentThread());//主线程
        mHandler.sendEmptyMessage(1);
    }

创建HandlerThread对象的时候,有个参数,是指定线程名字的。上面的代码不管运行多少次都不会奔溃!!!并且这种方法创建的handlerhandleMessage方法运行在子线程中。所以我们可以在这里处理一些耗时的逻辑。到此我们完成了主线程给子线程发通知,在子线程做耗时逻辑的操作。

为什么使用HandlerThread就可以避免空指针那?

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

HandlerThread类的getLooper方法如上,我们看到当我们获取当前线程Looper对象的时候,会先判断当前线程是否存活,然后还要判断Looper对象是否为空,都满足之后才会返回给我Looper对象,否则处于等待状态!!既然有等待,那就有唤醒的时候,在那里那???我们发现HandlerThread的run方法中,有如下代码:

  @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

所以,当Looper对象初始化完毕才会唤醒之前getLooper中的等待,返回Looper对象。HandlerThread很好的避免了之前空指针的产生。

所以以后要想创建非主线程的Handler时,我们用HandlerThread类提供的Looper对象即可。

Looper.loop()为什么可以一直运行而不卡死?

先看源码:

 public static void loop() {
        final Looper me = myLooper();//获得当前线程绑定的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//获得与Looper绑定的MessageQueue

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        
        //进入死循环,不断地去取对象,分发对象到Handler中消费
        for (;;) {
            Message msg = queue.next(); // 不断的取下一个Message对象,在这里可能会造成堵塞。
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            
            //在这里,开始分发Message了
            //至于这个target是神马?什么时候被赋值的? 
            //我们一会分析Handler的时候就会讲到
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            
            //当分发完Message之后,当然要标记将该Message标记为 *正在使用* 啦
            msg.recycleUnchecked();
        }
    }

epoll+pipe,有消息就依次执行,没消息就block住,让出CPU,等有消息了,epoll会往pipe中写一个字符,把主线程从block状态唤起

3.Android运行过程

Zygote和System进程的启动过程(系统启动运行)

+------------+    +-------+   +-----------+
|Linux Kernel+--> |init.rc+-> |app_process|
+------------+    +-------+   +-----------+
               create and public          
                 server socket
  • /system/bin/app_process 是Zygote服务启动的进程名。
  • Zygote启动完成之后,会启动System进程。
  • 在System进程中会创建一个socket客户端,后续AMS(ActivityManagerService)会通过此客户端和zygote通信。
  • 在System进程中启动各种Service(例如AMS,PMS,WMS等),AMS会启动系统界面以及Home程序。。
IMG_20180510_111505.jpg

App进程启动

① AMS向Zygote发起请求(通过之前保存的socket),携带各种参数,包括“android.app.ActivityThread”。
② Zygote进程fork自己,然后在新Zygote进程中调用RuntimeInit.zygoteInit方法进行一系列的初始化(commonInit、Binder线程池初始化等)。
③ 新Zygote进程中调用ActivityThread的main函数,并启动消息循环。

  • 首先,ActivityThread(ActivityThread是应用程序的入口)从static main()函数开始,调用prepareMainLooper()为UI线程创建一个消息对列(MessageQueue)。
  • 然后,创建一个ActivityThread对象,在其初始化代码中会创建一个H(Handler)对象和一个AppplicationThread(Binder)对象,Binder负责接收远程AmS的IPC(进程间通信)调用,收到消息后,通过Handler将消息发送到消息队列,UI主线程会异步的从消息队列中取出消息并执行操作。
  • 接着,UI主线程调用Looper.loop()进入消息循环体。当ActivityThread接收到AmS发送start某个activity后,就会创建指定的Activity对象。
  • Activity又会创建PhoneWindow类--->DecroView类--->相应的View创建完成后,Activity需要把创建好的界面显示到屏幕上,于是调用WindowManager,他会创建一个ViewRoot对象,创建完ViewRoot后,WindowManager调用WindowManagerService提供的远程接口完成添加一个窗口并显示到屏幕上。
    -------------------------以上摘自柯元旦<Android内核剖析>
IMG_20180510_112350.jpg

4.Android 事件分发机制

分发机制

注意:当ViewGroup的onInterceptTouchEvent返回superfalse此处需要注意!!!!当自定义控件继承自ViewGroup时,上图是正确的;而当自定义控件继承自控件(MyView)时,返回super调用的是MyView的onInterceptTouchEvent,最后返回什么由MyView的onInterceptTouchEvent为准。

U move_up move_up2.png
  • 红色的箭头代表ACTION_DOWN 事件的流向
  • 蓝色的箭头代表ACTION_MOVE 和 ACTION_UP 事件的流向

5.RelativeLayout与LinearLayout的性能分析

  • 测试

主要是3个方法,onMeasure(),onLayout(),onDraw()
可以自己自定义控件测试。
通过测试,我们发现LinearLayout的onMeasure()耗时仅是RelativeLayout的一半左右。

  • 原因

通过查看RelativieLayout与LinearLayout的onMeasure()方法发现,RelativeLayout的对子View测量了两次。为什么要测量两次呢?

在RelativeLayout中View存在横向和纵向上的依赖。所以,源码中是对子View分别进行了横向(第一次)和纵向(第二次)的测量。
在LinearLayout中,是先判断方向,然后onMeasure()。但是如果遇到有weight属性的子View,会对其进行第二次的onMeasure()。so, try not to use “weight” as possible as you can.

调用顺序

5.EventBus源码

image.png image.png

6.MVC和MVP

image.png

(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互

视图(View):用户界面
控制器(Controller):业务逻辑
模型(Model):数据保存

  • Models--负责主要的数据或者操作数据的数据访问层。包括http数据交互,sqlite数据交互,sharePreference,contentProvider等。

  • Views--负责展示层(GUI),可以联想一下以 UI 开头的所有类。

  • Controller/Presenter/ViewModel--负责协调 Model 和 View,通常根据用户在View上的动作在Model上作出对应的更改,同时将更改的信息返回到View上。

7.TCP/IP 三次握手和四次挥手

【HTTP与TCP/IP】和其他的协议在最初OSI模型中的位置:


握手&挥手流程
image.png
握手
  • 第一次握手:客户端发送了一个带有SYN的Tcp报文到服务器,这个三次握手中的开始。表示客户端想要和服务端建立连接。
  • 第二次握手:服务端接收到客户端的请求,返回客户端报文,这个报文带有SYN和ACK标志,询问客户端是否准备好。
  • 第三次握手:客户端再次响应服务端一个ACK,表示我已经准备好。

挥手

  • 第一次挥手:TCP发送一个FIN(结束),用来关闭客户到服务端的连接。
  • 第二次挥手:服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。
  • 第三次挥手:服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。
  • 第四次挥手:客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。

Q: 为什么挥手是四次,而不是三次,即第二次和第三次FIN+ACK明明可以一起发送,就像握手是SYN和ACK一起发送一样?

A:第二次挥手时,回复客户端一个ACK,然后同时继续传输数据,当数据传输完毕后,发起第三次挥手,所以需要分成两次发送。

8.Dalvik和ART区别

Android主流的Dalvik和ART两个虚拟机,它们最大的区别就是是否支持多个dex文件的加载

ART也就是Android 5.0以上的虚拟机本身就支持多个dex文件加载,而Dalvik却不支持多个dex加载,只支持一个dex加载,如果需要支持多个dex加载则需要引入multi-dex方案。

ART上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是"空间换时间"。

ART:Ahead of Time Dalvik: Just in Time

  • 什么是Dalvik:Dalvik是Google公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一,它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。

  • 什么是ART:Android操作系统已经成熟,Google的Android团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

ART优点:

  • 系统性能的显著提升
  • 应用启动更快、运行更快、体验更流畅、触感反馈更及时
  • 更长的电池续航能力
  • 支持更低的硬件

ART缺点:
*更大的存储空间占用,可能会增加10%-20%
*更长的应用安装时间

9.热修复

https://yq.aliyun.com/articles/231111

10.AsyncTask工作原理

从构造函数开始说起,AsyncTask()中构造了一个InternalHandlerFutureTask(可以理解为有返回值的Runnable)。

    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    //这里!!!!这里!!!!
                    result = doInBackground(mParams); 
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    //取消AsyncTask时走这里
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

当执行AsyncTask的时候,execute()最终会调用executeOnExecutor(),先走 onPreExecute(),然后线程池执行FutureTask对象。

    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;
        //这里!!!这里!!!
        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

doInBackground所在的call()方法执行结束后,直接回调FutureTaskdone()方法,当取消异步线程时此处会抛出异常从而执行postResultIfNotInvoked(null)

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    ... ... 

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    //onPostExecute和onCancelled
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //这里!!!这里!!!
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

··· ···
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

Q: onProgressUpdate在源码中没有触发?
A: 只有当开发者在复写的doInBackground方法中调用publishProgress方法,通过InternalHandler实例发送一条MESSAGE_POST_PROGRESS消息更新进度,onProgressUpdate 方法才将被调用; 如果遇到异常,则发送一条MESSAGE_POST_CANCEL的消息取消任务,onCancelled()方法将被调用。

11.LeakCanary 的原理(如何确定是否发生内存泄露)

  • 监听 Activity 的生命周期
  • onDestroy的时候,创建相应的 RefrenceRefrenceQueue,并启动后台进程去检测。
  • 一段时间之后,从 RefrenceQueue 读取,若读取不到相应 activity 的 refrence,有可能发生泄露了,这个时候,再促发 gc,一段时间之后,再去读取,若在从 RefrenceQueue 还是读取不到相应 activity 的 refrence,可以断定是发生内存泄露了。
  • 发生内存泄露之后,dump,分析 hprof 文件,找到泄露路径(使用 haha 库分析)。

其中,比较重要的是如何确定是否发生内存泄露,而如何确定发生内存泄露最主要的原理是通过 RefrenceRefrenceQueue

弱引用WeakReference 和引用队列 ReferenceQueue 联合使用时,如果弱引用持有的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

12.一个线程是否只有一个Looper,如何保证一个线程只有一个Looper?

A:
第一个问题,一个线程最多只能有一个Looper。
第二个问题,源码中使用ThreadLocal来存储每个线程的Looper对象,源码如下:
Looper.class

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

13. ANR异常发生条件

    1. 5s内没有响应用户输入事件;
    1. 10s内广播接收器没有处理完毕;
    1. 20s内服务没有处理完毕;

14. Http和Https的区别?

    1. Https是ssl加密传输,Http是明文传输;
    1. Https是使用端口443,而Http使用80;
    1. Https是SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议要比Http协议安全;
    1. Https协议需要到CA申请证书;

15.常用的图片压缩方式

图片压缩方式常用的有尺寸压缩质量压缩格式变化以及通过JNI调用libjpeg库来进行压缩,下面就先分别介绍下常见的质量压缩和尺寸压缩。(尺寸压缩,质量压缩底层也是通过调用native的方法进行压缩的,而native中的则是通过Skia这个库实现的,但是,最终还是调用了libjpeg库进行压缩的。)

  1. 格式变化
    现在android支持的图片格式有三种:png、jpeg、WebP
  • png: 无损图片的压缩类型,能保存透明等图,它同时提供24位和48位真彩色图像支持以及其他诸多技术性支持。
  • Jpeg:有损图片的压缩类型,有损压缩方式去除冗余的图像和彩色数据,获取得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得到较好的图像质量。但是,bitmap quality属性越小,图片的清晰度越差。
  • WebP:WebP(发音 weppy,项目主页),是谷歌推出的一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式VP8。
  1. 质量压缩
    设置bitmap quality属性,降低图片的质量,像素不会减少(就是指bitmap所占的内存大小),第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置设置quality属性0-100,来实现压缩。(因为png是无损压缩,所以该属性对png是无效的。
/**
     * 质量压缩
     *
     * @param format  图片格式   PNG,JPEG,WEBP
     * @param quality 图片的质量 1-100
     */
    public void compress(Bitmap.CompressFormat format, int quality) {
        FileOutputStream fos = null;
        try {
            //得到一个储存路径
            File file = new File(Environment.getExternalStorageDirectory(), "test.jpg");
            //得到一个文件输入流
            fos = new FileOutputStream(file);
            //开始压缩
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_icon);
            bitmap.compress(format, quality, fos);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.尺寸压缩
尺寸压缩由于是减小了图片的像素,所以它直接对bitmap产生了影响,当然最终生成的file文件也是相对的变小了。

  • 通过缩放图片像素来减少图片占用内存大小
public static void compressBitmapToFile(Bitmap bmp, File file){
    // 尺寸压缩倍数,值越大,图片尺寸越小
    int ratio = 2;
    // 压缩Bitmap到对应尺寸
    Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
    Canvas canvas = new Canvas(result);
    Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
    canvas.drawBitmap(bmp, null, rect, null);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把压缩后的数据存放到baos中
    result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
    try {  
        FileOutputStream fos = new FileOutputStream(file);  
        fos.write(baos.toByteArray());  
        fos.flush();  
        fos.close();  
    } catch (Exception e) {  
        e.printStackTrace();  
    } 
}
  • 设置图片的采样率,降低图片像素
public static void compressBitmap(String filePath, File file){
    // 数值越高,图片像素越低
    int inSampleSize = 2;
    BitmapFactory.Options options = new BitmapFactory.Options();
    //采样率
    options.inSampleSize = inSampleSize;
    Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);  

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    // 把压缩后的数据存放到baos中
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
    try {  
        FileOutputStream fos = new FileOutputStream(file);  
        fos.write(baos.toByteArray());  
        fos.flush();  
        fos.close();  
    } catch (Exception e) {  
        e.printStackTrace();  
    } 
}

16.WebViewClient与WebChromeClient的区别

  1. WebViewClient主要帮助WebView处理各种通知、请求事件的。
//处理webView的按键事件
boolean shouldOverrideKeyEvent(WebView view, KeyEvent event)
//截取url请求,在当前视图加载,避免在跳转到自带浏览器
boolean shouldOverrideUrlLoading(WebView view, String url)
//WebView改变时调用
void onScaleChanged(WebView view, float oldScale, float newScale)
//对https的请求处理
void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
//获取返回信息授权
void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)
//处理报错信息(API23以后用第二个)
void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
//页面开始载入时调用
void onPageStarted(WebView view, String url, Bitmap favicon)
//页面载入结束时调用
void onPageFinished(WebView view, String url)
//加载资源时调用
void onLoadResource(WebView view, String url)
  1. WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等。
//webview关闭时调用
void onCloseWindow(WebView window)
//Js的弹窗
boolean onJsAlert(WebView view, String url, String message, JsResult result)
//Js提示框
boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result)
//Js确认框
boolean onJsConfirm(WebView view, String url, String message, JsResult result)
//加载进度
void onProgressChanged(WebView view, int newProgress)
//全屏模式(API18以后用第二种)
onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)
onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback)

17.ViewGroup为什么不会调用onDraw

造成这种现象的原因是继承自LinearLayout,而LinearLayout这是一个容器,它本身并没有任何可画的东西,它是一个透明的控件,因些并不会触发onDraw,但是你现在给LinearLayout设置一个背景色,其实这个背景色不管你设置成什么颜色,系统会认为,这个LinearLayout上面有东西可画了,因此会调用onDraw方法。
我们可以仔细分析View的源码,它有一个方法View#draw(Canvas)方法,这里面有两个地方调用onDraw,它的条件都是:

if (!dirtyOpaque) onDraw(canvas); 

如果dirtyOpaque是true透明的话,onDraw就不会调用。
View还提供了一个重要的方法:setWillNotDrawsetFlags(WILL_NOT_DRAW, DRAW_MASK);相于调用了setWillNotDraw(true),它就认为是透明的了。如果我们想要重写onDraw,就需要调用setWillNotDraw(false)

  public void setWillNotDraw(boolean willNotDraw) {
     setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
 
  }

所以,

  1. ViewGroup默认情况下,会被设置成WILL_NOT_DRAW,这是从性能考虑,这样一来,onDraw就不会被调用了。
  2. 如果我们要重要一个ViweGrouponDraw方法,有两种方法:
    • 在构造函数里面,给其设置一个颜色,如#00000000。
    • 在构造函数里面,调用setWillNotDraw(false),去掉WILL_NOT_DRAW的flag

18.getWidth()方法和getMeasureWidth()区别

  • 首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
  • 另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

19. Binder

1)Binder IPC 通信过程
  • 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
  • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
  • 发送方进程通过系统调用copyfromuser()将数据 copy 到内核中的数据接收缓存区,由于内核缓存区和数据接收缓存区以及接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
2)Binder通讯模型

Binder是基于C/S架构的,其中定义了4个角色:Client、Server、Binder驱动和ServiceManager。

  • Binder驱动:类似网络通信中的路由器,负责将Client的请求转发到具体的Server中执行,并将Server返回的数据传回给Client。
  • ServiceManager:类似网络通信中的DNS服务器,负责将Client请求的Binder描述符转化为具体的Server地址,以便Binder驱动能够转发给具体的Server。Server如需提供Binder服务,需要向ServiceManager注册。

具体的通讯过程

  1. Server向ServiceManager注册。Server通过Binder驱动向ServiceManager注册,声明可以对外提供服务。ServiceManager中会保留一份映射表。
  2. Client向ServiceManager请求Server的Binder引用。Client想要请求Server的数据时,需要先通过Binder驱动向ServiceManager请求Server的Binder引用(代理对象)。
  3. 向具体的Server发送请求。Client拿到这个Binder代理对象后,就可以通过Binder驱动和Server进行通信了。
  4. Server返回结果。Server响应请求后,需要再次通过Binder驱动将结果返回给Client。

Q: ServiceManager是一个单独的进程,那么Server与ServiceManager通讯是靠什么呢?

A: 当Android系统启动后,会创建一个名称为servicemanager的进程,这个进程通过一个约定的命令BINDERSETCONTEXT_MGR向Binder驱动注册,申请成为为ServiceManager,Binder驱动会自动为ServiceManager创建一个Binder实体。并且这个Binder实体的引用在所有的Client中都为0,也就说各个Client通过这个0号引用就可以和ServiceManager进行通信。Server通过0号引用向ServiceManager进行注册,Client通过0号引用就可以获取到要通信的Server的Binder引用。

20.Fragment的懒加载实现

Fragment可见状态改变时会被调用setUserVisibleHint()方法,可以通过复写该方法实现Fragment的懒加载,但需要注意该方法可能在onVIewCreated之前调用,需要确保界面已经初始化完成的情况下再去加载数据,避免空指针。
当fragment被用户可见时,setUserVisibleHint()会调用且传入true值,当fragment不被用户可见时,setUserVisibleHint()则得到false值。而在传统的fragment生命周期里也看不到这个函数。

https://www.jianshu.com/p/cf1f4104de78

21.Retrofit的实现与原理

Retrofit采用动态代理,创建声明service接口的实现对象。当我们调用service的方法时候会执行InvocationHandler的invoke方法。在这方法中:首先,通过method把它转换成ServiceMethod,该类是对声明方法的解析,可以进一步将设定参数变成Request ;然后,通过serviceMethod, args获取到okHttpCall 对象,实际调用okhttp的网络请求方法就在该类中,并且会使用serviceMethod中的responseConverter对ResponseBody转化;最后,再把okHttpCall进一步封装成声明的返回对象(默认是ExecutorCallbackCall,将原本call的回调转发至UI线程)。

Retrofit2使用详解及从源码中解析原理
Retrofit2 完全解析 探索与okhttp之间的关系

相关文章

网友评论

      本文标题:Android必备知识

      本文链接:https://www.haomeiwen.com/subject/xxkkrftx.html