longtaozz / my_img

图片资源
0 stars 0 forks source link

android笔记 #3

Open longtaozz opened 5 years ago

longtaozz commented 5 years ago

android生命周期

生命周期简介

流程

  1. 启动Activity:系统会先调用onCreate方法,然后调用onStart方法,最后调用onResume,Activity进入运行状态。

  2. 当前Activity被其他Activity覆盖其上或被锁屏:系统会调用onPause方法,暂停当前Activity的执行。

  3. 当前Activity由被覆盖状态回到前台或解锁屏:系统会调用onResume方法,再次进入运行状态。

  4. 当前Activity转到新的Activity界面或按Home键回到主屏,自身退居后台:系统会先调用onPause方法,然后调用onStop方法,进入停滞状态。

  5. 用户后退回到此Activity:系统会先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,再次进入运行状态。

  6. 当前Activity处于被覆盖状态或者后台不可见状态,即第2步和第4步,系统内存不足,杀死当前Activity,而后用户退回当前Activity:再次调用onCreate方法、onStart方法、onResume方法,进入运行状态。

  7. 用户退出当前Activity:系统先调用onPause方法,然后调用onStop方法,最后调用onDestory方法,结束当前Activity。

onStart()与onResume()有什么区别?

onStart()是activity界面被显示出来的时候执行的,但不能与它交互;
onResume()是当该activity与用户能进行交互时被执行,用户可以获得activity的焦点,能够与用户交互。

Android类加载器

Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份。
Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,其中包含用来运行您的应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内可引用的方法总数限制在 65,536,其中包括 Android 框架方法、库方法以及您自己代码中的方法。--64k限制
处理办法(分包)

  1. gradle配置multiDexEnabled true//分包
  2. gradle引入multidex包
  3. androidManifest文件application配置android:name="android.support.multidex.MultiDexApplication"
  4. application重写attachBaseContext MultiDex.install(this);

    线程

    Thread直接创建线程缺点

线程池分类

1. newCachedThreadPool可缓存线程池

如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    final int index = i;
    try {
        Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    cachedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(index);
        }
    });
}

2. newFixedThreadPool定长线程池

定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block                
               e.printStackTrace();
            }
        }
    });
}

3. newScheduledThreadPool周期性定长线程池

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {

    @Override
    public void run() {
        System.out.println("delay 3 seconds");
    }
}, 3, TimeUnit.SECONDS);

每3秒执行一次

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);

延迟一秒执行,每三秒执行一次

4. newSingleThreadExecutor单线程线程池

线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block                
                e.printStackTrace();
            }
        }
    });
}

视图绘制(详细介绍

view绘制流程

每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()。

一、onMeasure()

视图测量,系统会默认使用很多流程测量出视图的大小,最后会调用setMeasuredDimension()方法来设定测量出的大小。

一般这些对我们来说都不重要,要知道的是:

在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。

二、onLayout()

视图布局过程。 首先会判断onMeasure测量的数据是否有变化,然后进行摆放布局,由于摆放复杂,通常由其子类实现自己的摆放过程。 重点: 在此方法结束之后可以调用getWidth()方法和getHeight()方法来获取视图的宽高。

getWidth()方法和getMeasureWidth()方法区别 结合上面的就很好理解这两个区别了。

  1. 这两个方法能拿到值的周期不同。
  2. getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

    三、onDraw()

    layout过程是摆放view的过程,View不需要实现,通常由ViewGroup实现,在实现onLayout时可以通过getMeasuredWidth等方法获取measure过程测量的结果进行摆放。 draw过程先是绘制背景,其次调用onDraw()方法绘制view的内容,再然后调用dispatchDraw()调用子view的draw方法,最后绘制滚动条。ViewGroup默认不会执行onDraw方法,如果复写了onDraw(Canvas)方法,需要调用 setWillNotDraw(false);清楚不需要绘制的标记。

事件分发机制(详细介绍

一般情况下,事件列是从用户按下那一刻产生的。

事件分发的本质

将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程。 事件传递的过程 = 分发过程。

基础事件

基础事件

Touch事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象

三个非常重要的方法

事件分发机制的执行顺序 Activity=>ViewGroup=>View

界面刷新

requestLayout()

重新进行测量(onMeasure())、布局(onLyout())、绘制(onDraw())

invalidate()

需要在UI线程中实现,只调用绘制(onDraw())方法,重新绘制需要绘制的区域。

postInvalidate()

与invalidate差不多,此方法为异步实现。它会先判断视图是否被添加进窗口。条件满足时会利用Handerl发消息(MSG_INVALIDATE)通知主线程更新UI(调用invalidate();)

final class ViewRootHandler extends Handler {
        @Override
        public String getMessageName(Message message) {
            ....
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
                break;
            ...
        }
    }
}

fragment懒加载

主要是利用fragment中的setUserVisibleHint(boolean isVisibleToUser)中的isVisibleToUser判断界面是否给用户显示,还有就是需要判断数据是否加载过,通过定义变量来判断。子类只需要实现数据加载的方法就行了。
我们一般都是使用viewPage+fragment实现的。viewPager会预加载下一页销毁其他页面。如果页面不多的话我们可以设置viewPager.setOffscreenPageLimit(*);加载所有页面。如果很多的话就预加载两页就够了。
另外要说的是setUserVisibleHint()只有在ViewPager+Fragment的时候才有效,单用Fragment的时候可以考虑使用onHiddenChanged(boolean hidden)方法

recycleview(基本使用

大概就是ListView与GridView的集合,LayoutManager控制recycleview的各种形状,如横向、纵向、网格之类的。

适配器 简单的Adapter示例:

public class ReAdpter extends RecyclerView.Adapter<ReAdpter.MyHolder> {
    String[] x;
    public ReAdpter(String[] s) {
        x=s;
    }

    @NonNull
    @Override
    public MyHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        //创建返回ViewHolder
        MyHolder myHolder=new MyHolder(null);
        return myHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull MyHolder myHolder, int i) {
        //itme操作
    }

    @Override
    public int getItemCount() {
        return x.length;
    }

    class MyHolder extends RecyclerView.ViewHolder{

        public MyHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}

有点像listView的BaseAdpter有点类似。

RecyclerView与ListView

缓存区别:

  1. 层级不同: ListView有两级缓存,在屏幕与非屏幕内。 RecyclerView比ListView多两级缓存,支持多个离屏ItemView缓存(匹配pos获取目标位置的缓存,如果匹配则无需再次bindView),支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。

  2. 缓存不同: ListView缓存View。 RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为: View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);

优点

RecylerView提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。 RecyclerView的扩展性更强大(LayoutManager、ItemDecoration等)。

apk打包

第一步:打包资源文件,生成R.java文件

【输入】Resource文件(就是工程中res中的文件)、Assets文件(相当于另外一种资源,这种资源Android系统并不像对res中的文件那样优化它)、AndroidManifest.xml文件(包名就是从这里读取的,因为生成R.java文件需要包名)、Android基础类库(Android.jar文件) 【输出】打包好的资源(一般在Android工程的bin目录可以看到一个叫resources.ap_的文件就是它了)、R.java文件(在gen目录中,大家应该很熟悉了) 【工具】aapt工具,它的路径在${ANDROID_SDK_HOME}/platform-tools/aapt(如果你使用的是Windows系统,按惯例路径应该这样写:%ANDROID_SDK_HOME%\platform-tools\aapt.exe,下同)。

第二步:处理AIDL文件,生成对应的.java文件(当然,有很多工程没有用到AIDL,那这个过程就可以省了)

【输入】源码文件、aidl文件、framework.aidl文件 【输出】对应的.java文件 【工具】aidl工具

第三步:编译Java文件,生成对应的.class文件

【输入】源码文件(包括R.java和AIDL生成的.java文件)、库文件(.jar文件) 【输出】.class文件 【工具】javac工具

第四步:把.class文件转化成Davik VM支持的.dex文件

【输入】 .class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),库文件(.jar文件) 【输出】.dex文件 【工具】javac工具

第五步:打包生成未签名的.apk文件

【输入】打包后的资源文件、打包后类文件(.dex文件)、libs文件(包括.so文件,当然很多工程都没有这样的文件,如果你不使用C/C++开发的话) 【输出】未签名的.apk文件 【工具】apkbuilder工具

第六步:对未签名.apk文件进行签名

【输入】未签名的.apk文件 【输出】签名的.apk文件 【工具】jarsigner

第七步:对签名后的.apk文件进行对齐处理(不进行对齐处理是不能发布到Google Market的)

【输入】签名后的.apk文件 【输出】对齐后的.apk文件 【工具】zipalign工具

apk安装流程

  1. 复制APK到/data/app目录下,解压并扫描安装包。
  2. 资源管理器解析APK里的资源文件。
  3. 解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
  4. 然后对dex文件进行优化,并保存在dalvik-cache目录下。
  5. 将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
  6. 安装完成后,发送广播。

apk瘦身

APK主要由以下几部分组成:

其中占据较大内存的是res资源、lib、class.dex,因此我们可以从下面的几个方面下手:

  1. 代码方面可以通过代码混淆,这个一般都会去做。平时也可以删除一些没有使用类。

  2. 去除无用资源。使用lint工具来检测没有使用到的资源,或者在gradle中配置shrinkResources来删除包括库中所有的无用的资源,需要配合proguard压缩代码使用。这里需要注意项目中是否存在使用getIdentifier方式获取资源,这种方式类似反射lint及shrinkResources无法检测情况。如果存在这种方式,则需要配置一个keep.xml来记录使用反射获取的资源。压缩代码和资源

  3. 去除无用国际化支持。对于一些第三库来说(如support),因为国际化的问题,它们可能会支持了几十种语言,但我们的应用可能只需要支持几种语言,可以通过配置resConfigs提出不要的语言支持。

  4. 不同尺寸的图片支持。通常情况下只需要一套xxhpi的图片就可以支持大部分分辨率的要求了,因此,我们只需要保留一套图片。

  5. 图片压缩。 png压缩或者使用webP图片,完美支持需要Android版本4.2.1+

  6. 使用矢量图形。简单的图标可以使用矢量图片。

    组件化

    • 在gradle.properties声明一个变量用于控制是否是调试模式,并在dependencies中根据是否是调试模式依赖必要组件。
    • 通过resourcePrefix规范module中资源的命名前缀。
    • 组件间通过ARouter完成界面跳转和功能调用。

      DataBinding(详细使用

      布局

      使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data

      
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
    #### 绑定 Variable
  7. 在activity的onCreate中用 DatabindingUtil.setContentView() 来替换掉 setContentView()。 DataBindingUtil.setContentView(this, R.layout.activity_main);

  8. 创建一个对象,通过binding.set...让对象与 variable 进行绑定 basebinding.setStr(new StrBean());

    特殊用法

    • 别名冲突 data中使用import导入类,绑定相同类可以用alias="..."来取别名,避免冲突
    • null值判断 android:text="@{user.displayName ?? user.lastName}"
    • 属性值 android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"
    • 资源属性 android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
    • ID查找控件
      <TextView
      android:id="@+id/firstName"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />
      //直接使用
      public final TextView firstName;

      动态控件数据绑定(如ListView, RecyclerView等)

      在构造 Holder 时把 View 与自动生成的 XXXBinding 进行绑定

      
      public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
      private static final int USER_COUNT = 10;

    @NonNull private List mUsers;

    public UserAdapter() { mUsers = new ArrayList<>(10); for (int i = 0; i < USER_COUNT; i ++) { User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName()); mUsers.add(user); } }

    public static class UserHolder extends RecyclerView.ViewHolder { private UserItemBinding mBinding;

    public UserHolder(View itemView) {
        super(itemView);
        mBinding = DataBindingUtil.bind(itemView);
    }
    
    public void bind(@NonNull User user) {
        mBinding.setUser(user);
    }

    }

    @Override public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View itemView = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.user_item, viewGroup, false); return new UserHolder(itemView); }

    @Override public void onBindViewHolder(UserHolder holder, int position) { holder.bind(mUsers.get(position)); }

    @Override public int getItemCount() { return mUsers.size(); } }

    
    ### recycler的item悬停效果
    面试中遇到,要求讲述这个功能的实现
    #### ItemDecoration介绍
    recycler不会像listVIew自带一些item间的效果,比如间隔,分割线之类的。都需要我们继承itemDecoration去实现,这也是实现悬停顶部的主要部分。
    - 主要方法
  9. getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

可以通过outRect.set(l,t,r,b)设置指定itemview的paddingLeft,paddingTop, paddingRight, paddingBottom

  1. onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)

可以通过一系列c.drawXXX()方法在绘制itemView之前绘制我们需要的内容。

  1. onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)

在绘制itemView之后绘制,具体表现形式,就是绘制的内容在itemview上层。

  1. 调用顺序 getItemOffsets->onDraw->onDrawOver

启动前黑白屏

是由于程序启动时,window打开时还没加载到视图,页面会呈现window的背景色。

解决办法

  1. 设置Them主题颜色为透明,还没加载时window背景颜色为透明,所以它会呈现前个页面,也就解决了黑白屏的问题。 缺点:如果时间较长的话,体验就不好,会给用户点击无反应的错觉
     <!--window背景色-->
    <color name="windowBackground">#ffffffff</color>
  2. 设置window的背景图片,没加载到界面的时候显示背景图片 方法都是一样的(用户体验较好,不过要设置一个通用的图片)
  3. 加载xml为背景 方法都是一样的
longtaozz commented 5 years ago

服务(service)

Service分为本地服务(LocalService)和远程服务(RemoteService) 1、本地服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。

2、远程服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。

生命周期

service生命周期

启动方式

service的启动方式分为三种,①startService()、②bindService()和前面两种都用,第一种为正常启动方式,服务启动后会无限制运行下去,除非调用外部调用stopService()或内部调用stopSelf(),第二种启动方式,服务启动后,可以利用IBinder接口得到service来进行组件交互,一个服务可以多个绑定,也可以调用onunService()进行解除绑定,当没有任何东西与此服务绑定的时候,此服务会销毁。 (两种启动方式service生命周期不同,如上图)

  1. startService() 生命周期:onCreate()>onStartCommand()>onDestroy() onCreate:不管哪种启动方式都进入此生命周期,而且多次启动只进入一次。 onStartCommand:接下来进入start,以前我记得是onStart()现在弃用了,onStartCommand也会进onStart(),且多次startService会多次进入此方法
  2. bindService() 生命周期:onCreate()>onBind()>onUnbind()>onDestroy() onBind:绑定 onUnbind:解除绑定 public boolean bindService(Intent service, ServiceConnection conn, int flags)想要与service交互,需要填入第二个参数,实现ServiceConnection接口,可在onServiceConnected拿到绑定的service。

    MService mService;
    boolean isBind=false;
    ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //绑定成功,通过此回调获得IBinder
            isBind=true;
            mService= (MService) iBinder;
        }
    
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            //解除绑定回调
            isBind=false;
        }
    };
longtaozz commented 5 years ago

okHttp3、Retrofit2、RxJava2使用

介绍三个框架的组合使用,以及各自的作用,实现原理等

okHttp3

结合当前三个框架,主要充当请求配置的作用。

使用

  1. 创建OkHttpClient 这是okhttp的核心,可以直接new使用默认配置。也可以使用OkHttpClient.Builder自定义请求配置。我们框架组合,这里主要的作用就是请求配置,所以使用自定义请求配置创建
        //手动创建一个OkHttpClient并设置超时时间
        okHttpBuilder = new OkHttpClient.Builder();
  2. 配置 okhttp配置主要使用的是拦截器(Interceptor),可以设置缓存,配置请求头,通过addInterceptor添加入okhttpclient中。还可配置请求超时等。

        /**
         * 设置缓存
         */
        File cacheFile = new File(context.getExternalCacheDir(), CACHE_NAME);
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
        Interceptor cacheInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                if (!NetConnect.isConnected(context)) {
                    request = request.newBuilder()
                            .cacheControl(CacheControl.FORCE_CACHE)
                            .build();
                }
                Response response = chain.proceed(request);
                if (!NetConnect.isConnected(context)) {
                    int maxAge = 0;
                    // 有网络时 设置缓存超时时间0个小时
                    response.newBuilder()
                            .header("Cache-Control", "public, max-age=" + maxAge)
                            .removeHeader(CACHE_NAME)// 清除头信息,因为服务器如
    果不支持,会返回一些干 扰信息,不清除下面无法生效
                            .build();
                } else {
                    // 无网络时,设置超时为4周
                    int maxStale = 60 * 60 * 24 * 28;
                    response.newBuilder()
                            .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                            .removeHeader(CACHE_NAME)
                            .build();
                }
                return response;
            }
        };
        okHttpBuilder.cache(cache).addInterceptor(cacheInterceptor);
    
        /**
         * 设置头信息
         */
        Interceptor headerInterceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();
                Request.Builder requestBuilder = originalRequest.newBuilder()
                        .addHeader("Accept-Encoding", "gzip")
                        .addHeader("Accept", "application/json")
                        .addHeader("Content-Type", "application/json; charset=utf-8")
                        .method(originalRequest.method(), originalRequest.body());
                requestBuilder.addHeader("x-token", BaseData.TOKEN);//添加请求头信息,
    服务器进行token有效性验证
                Request request = requestBuilder.build();
    
                //如果没有token获取token,按具体需求来
                if (TextUtils.isEmpty(BaseData.TOKEN)) {
                    Response response = chain.proceed(request);
                    List<String> strList = response.headers("x-token");
                    if (strList != null && strList.size() > 0) {
                        BaseData.TOKEN = strList.get(0);
                    }
                }
    
                return chain.proceed(request);
            }
        };
        okHttpBuilder.addInterceptor(headerInterceptor);

    也是使用拦截器,不过使用的是log专用的拦截器 HttpLoggingInterceptor,设置log打印等(看需要,正式版本的时候要去掉):

        //设置是否打印log
        if (AndroidUtil.isApkInDebug(context)) {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                @Override
                public void log(String message) {
                    Logger.e(message);
                }
    
            });
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            //设置 Debug Log 模式
            okHttpBuilder.addInterceptor(loggingInterceptor);
        }
  3. 发起请求
    • 创建request 可以通过Request.Builder配置请求信息,请求方式,请求地址,参数等
      RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json);
      //创建request
      Request request = new Request.Builder().header("x-token", Web.getToken())
              .post(requestBody).url(url).build();
    • 发送请求 发送请求需要用的是Call,他是一个接口,由realCall实现,下面会详细说明
  4. 整合配合Retrofit使用 通过Retrofit.Builder的client方法将自定义好的okhttp配置进去

        //3 将Request封装为Call
        Call call = mOkHttpClient.newCall(request);

    执行call请求,分为异步和同步请求。

    
        //执行Call,异步请求,由Callback接收请求回调
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                myNetCall.failed(call, e);
            }
    
            @Override
            public void onResponse(Call call, Response response) throws IOException {
               //请求完成处理
            }
        });
    
        //执行Call,同步请求,得到response
        Response response = null;
        try {
            response = call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
#### 原理
- 流程
通过OkHttpClient将构建的Request转换为Call,然后在RealCall中进行异步或同步任务,最后通过一些的拦截器interceptor发出网络请求和得到返回的response。
![okhttp请求流程](https://github.com/longtaozz/my_img/blob/master/android_img/9984264-3f4ba9f2e1dc097f.png?raw=true)
- OkHttpClient
上面说过了,OkHttpClient作为主要的组件,主要是用来做请求配置的,详细参数:
final Dispatcher dispatcher;  //重要:分发器,分发执行和关闭由request构成的Call
final Proxy proxy;  //代理
final List<Protocol> protocols; //协议
final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议
final List<Interceptor> interceptors; //重要:拦截器
final List<Interceptor> networkInterceptors; //网络拦截器
final ProxySelector proxySelector; //代理选择
final CookieJar cookieJar; //cookie
final Cache cache; //缓存
final InternalCache internalCache;  //内部缓存
final SocketFactory socketFactory;  //socket 工厂
final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS
final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。
final HostnameVerifier hostnameVerifier;    //  主机名字确认
final CertificatePinner certificatePinner;  //  证书链
final Authenticator proxyAuthenticator;     //代理身份验证
final Authenticator authenticator;      // 本地身份验证
final ConnectionPool connectionPool;    //连接池,复用连接
final Dns dns;  //域名
final boolean followSslRedirects;  //安全套接层重定向
final boolean followRedirects;  //本地重定向
final boolean retryOnConnectionFailure; //重试连接失败
final int connectTimeout;    //连接超时
final int readTimeout; //read 超时
final int writeTimeout; //write 超时

- RealCall
请求的组件,RealCall实现了Call接口,
longtaozz commented 5 years ago

eclipse项目转as

  1. 导出eclipse项目 以gradle方式导出eclipse项目
  2. AS导入项目
  3. 修改gradle版本 打开根目录的build.gradle文件,将自动配置的gradle版本改为当前AS所配置的版本
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
    }
  4. 修改下载配置 项目转换,很多sdk之类的东西需要下载,所以需要修改根目录的build.gradle
    repositories {
        google()
        jcenter()
    }
  5. 修改gradle版本依赖 修改gradle-wrapper.properties文件中的版本gradle版本依赖 如:distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
longtaozz commented 5 years ago

textView提供int值传入方法,传入int报NotFoundException

把传入的int类型值当作资源文件id使用。然而用资源文件id去查找资源的时候又找不到所对应的资源。

longtaozz commented 5 years ago

界面内容缓存

主要是指app在无网络的情况下,显示已缓存的数据,缓存方式有内存缓存和文件缓存。

  1. sq缓存页面数据 通过sql对页面进行缓存,在无网络的情况下去获取sql缓存的在有效期内的数据。缺点是在每次请求的时候都要进行数据的储存,更新。
  2. 开源框架ASimpleCache 将数据同时上存入一级缓存(内存Map)和二级缓存(文件)中 使用 ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件。 可以缓存:普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 byte数据。
longtaozz commented 5 years ago

SharedPreferences优缺点

SharedPreferences,它是一个轻量级的存储类,特别适合用于保存软件配置参数 。 优点: 1.轻量级,以键值对的方式进行存储,使用方便,易于理解 2.采用的是xml文件形式存储在本地,程序卸载后会也会一并被清除,不会残留信息 缺点: 1.有于是对文件IO读取,因此在IO上的瓶颈是个大问题,因为在每次进行get和commit时都要将数据从内存写入到文件中,或从文件中读取 2.多线程场景下效率较低,在get操作时,会锁定SharedPreferences对象,互斥其他操作,而当put,commit时,则会锁定Editor对象,使用写入锁进行互斥,在这种情况下,效率会降低 3.不支持跨进程通讯 4.由于每次都会把整个文件加载到内存中,因此,如果SharedPreferences文件过大,或者在其中的键值对是大对象的json数据则会占用大量内存,读取较慢是一方面,同时也会引发程序频繁GC,导致的界面卡顿。 使用注意点 1.建议不要存储较大数据或者较多数据到SharedPreferences中 2.频繁修改的数据修改后统一提交,而不是修改过后马上提交 3.在跨进程通讯中不去使用SharedPreferences 4.键值对不宜过多

ANR的产生的原理

应用无响应 ANR类型:

  1. KeyDispatchTimeout(5 seconds) --主要类型按键或触摸事件在特定时间内无响应
  2. BroadcastTimeout(10 seconds) --BroadcastReceiver在特定时间内无法处理完成
  3. ServiceTimeout(20 seconds) --小概率类型 Service在特定的时间内无法处理完成 其中,A key or touch event was not dispatched within the specified time(按键或触摸事件在特定时间内无响应) 具体的超时时间的定义在framework下的ActivityManagerService.java
    //How long we wait until we timeout on key dispatching.
    staticfinal int KEY_DISPATCHING_TIMEOUT = 5*1000

    所以在UI线程中如果使用一个非常耗时的操作(>5S),是否会ANR应该取决于在这个过程中是否有收到 touch event ,如果有,那必然会产生ANR 弹窗,如果没有,理论上不会。

    滚动嵌套

    事件分发是由外向内的,依靠传统的事件分发无法处理一些特殊的事件,比如滚动嵌套悬停,首先我们滑动的是下面的内容区域,而移动却是外部的ViewGroup在移动,所以按照传统的方式,肯定是外部的Parent拦截了内部的Child的事件,Parent滑动到一定程度时,Child又开始滑动了,中间整个过程是没有间断的。从正常的事件分发(不手动调用分发事件,不手动去发出事件)角度去做是不可能的,因为当Parent拦截之后,是没有办法再把事件交给Child的,事件分发,对于拦截,相当于一锤子买卖,只要拦截了,当前手势接下来的事件都会交给Parent(拦截者)来处理。 所以我们要使用滚动嵌套来实现。 CoordinatorLayout 用这个来实现嵌套悬停是相当的简单的CoordinatorLayout使用。 CoordinatorLayout是基于NestedScroll机制去实现的,NestedScroll涉及四个类:NestedScrollingChild, NestedScrollingChildHelper 和 NestedScrollingParent , NestedScrollingParentHelper NestedScroll提供了一个反向的机制,内层的view在接收到ACTION_MOVE的时候,将滚动消息先传回给外层的ViewGroup,看外层的ViewGroup是不是需要消耗一部分的移动,然后内层的View再去消耗剩下的移动.内层view可以消耗剩下的滚动的一部分,如果还没有消耗完,外层的view可以再选择把最后剩下的滚动消耗掉.

longtaozz commented 5 years ago

Parcelable序列化

Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率 实现Parcelable接口序列化 实现Parcelable接口主要实现是实现writeToParcel与describeContents接口

        @Override
        public void writeToParcel(Parcel dest, int flags) {
        }

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

describeContents:对象描述。只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以 writeToParcel:序列化。通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,所以我们可以直接调用Parcel中的write方法基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储 反序列化 饭序列化主要是实现两个方法:protected DemoParcelable(Parcel in)和public static final Creator CREATOR

    //反序列化
    protected DemoParcelable(Parcel in) {
        //普通对象读取
        count = in.readInt();
        name = in.readString();
        //读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
        user = in.readParcelable(User.class.getClassLoader());
        //集合读取
        in.createTypedArrayList(User.CREATOR);
    }

    //负责反序列化
    public static final Creator<DemoParcelable> CREATOR = new Creator<DemoParcelable>() {
        //从序列化对象中,获取原始的对象
        @Override
        public DemoParcelable createFromParcel(Parcel in) {
            return new DemoParcelable(in);
        }

        //创建指定长度的原始对象数组
        @Override
        public DemoParcelable[] newArray(int size) {
            return new DemoParcelable[size];
        }
    };

整体代码:

public class DemoParcelable implements Parcelable {
    private Integer count;
    private String name;
    private User user;
    private List<User> users;

    //反序列化
    protected DemoParcelable(Parcel in) {
        //普通对象读取
        count = in.readInt();
        name = in.readString();
        //读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
        user = in.readParcelable(User.class.getClassLoader());
        //集合读取
        in.createTypedArrayList(User.CREATOR);
    }

    //负责反序列化
    public static final Creator<DemoParcelable> CREATOR = new Creator<DemoParcelable>() {
        //从序列化对象中,获取原始的对象
        @Override
        public DemoParcelable createFromParcel(Parcel in) {
            return new DemoParcelable(in);
        }

        //创建指定长度的原始对象数组
        @Override
        public DemoParcelable[] newArray(int size) {
            return new DemoParcelable[size];
        }
    };

    /**
     * 接口1.对象描述
     * 只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
     *
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 接口2.序列化
     * 通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,
     * 所以我们可以直接调用Parcel中的write方法
     * 基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
     *
     * @param parcel
     * @param i
     */
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        //普通对象写入
        parcel.writeInt(count);
        parcel.writeString(name);
        parcel.writeParcelable(user, 0);
        parcel.writeTypedList(users);
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public String getName() {
        return name;
    }

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

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public static class User implements Parcelable {
        private String phone;

        protected User(Parcel in) {
            phone = in.readString();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
        }

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

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

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

        public String getPhone() {
            return phone;
        }

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