https://blog.csdn.net/qq873044564/article/list/3

第一周:

四大组件(Activity、Service、BroadcastReceiver、 ContentProvider)

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

https://blog.csdn.net/qq_42418169/article/details/115090626

1、activity

activity栈:

系统采用Activity栈的方式来管理Activity。

activity的四种形态:

  • Activity/Running:这个时候,Activity处于Activity栈的最顶层,可见,并与用户进行交互。
  • Paused:Activity失去焦点,被一个新的非全屏的Activity或者一个透明的Activity放置在栈顶时,Activity就转换成了qaused形态,他失去了与用户交互的能力,所有状态信息,成员变量都还保留着,只有在系统内存极地的情况下,才会被系统回收。
  • Stopped:如果一个Activity被另一个Activity完全覆盖,那么Activity就会进入stop形态,此时他不在可见,但依然保留着所有的状态和成员变量。
  • Killed:当Activity被系统回收或者Activity从来没有创建过,Activity就处于killed状态。


链接:https://www.jianshu.com/p/2cce9303b933

activity 的生命周期:

image.png

Activity启动和销毁过程
  • onCreate中创建基本的UI元素。
  • onPause和onStop:清除Acvtivity的资源,避免浪费。
  • onDestroy:因为引用会在Activity销毁的时候销毁,而线程不会,所以清除开启的线程。

    Activity的暂停和恢复过程

    当栈顶的Activity部分不可见的时候,就会倒置Activity进入onPause。

  • onPause:释放系统资源。

  • onResume:需要重新初始化onPause释放的资源。
    Activity的停止过程
    栈顶的Activity部分不可见的时候,实际上后续会有两种可能,从部分不可见到可见,也就是恢复过程,从部分不可见到完全不可见,也就是停止过程,系统在当前Activity不可见的时候调用onPause。

onResume是在启动activity启动之后才能执行的,也就是恢复执行。
程序正常启动:onCreate()->onStart()->onResume();
正常退出:onPause()->onStop()->onDestory()
一个Activity启动另一个Activity: onPause()->onStop(),
再返回:onRestart()->onStart()->onResume()
程序按back 退出: onPause()->onStop()->onDestory(),
再进入:onCreate()->onStart()->onResume();
程序按home 退出: onPause()->onStop(),
再进入:onRestart()->onStart()->onResume();

(1)前台切换到后台,然后再回到前台,Activity生命周期回调方法? (我们假设从Ativity A 跳到 Activity B)

  1. A调用 onCreate() 方法 -> onStart() 方法 -> onResume() 方法,此时 A 前台可见。<br />当 A 跳转到 B 时,A 调用 onPause() 方法,然后调用新的 Activity B 中的 onCreate() 方法 -> onStart() 方法 -> onResume() 方法。最后 A 再调用onStop()方法。<br />当 A 再次回到时前台,B 调用 onPause() 方法,A 调用 onRestart() -> onStart() -> onResume() 方法,然后 B 再调用 onStop() -> onDestroy()方法。

(2)弹出 Dialog,生命周期?

  1. 其实是否弹出 Dialog,并不影响 Activity 的生命周期,所以这时和正常启动时 Activity 的生命回调方法一致: onCreate() -> onStart() -> onResume()。

(3)Activity上有Dialog的时候按Home键时的生命周期

弹出的AlertDialog对话框实际上是Activity的一个组件,我们对Activity并不是不可见而是被一个布满屏幕的组件覆盖掉了其他组件,所以我们无法对其他内容进行操作,也就是AlertDialog实际上是一个布满全屏的组件。有 Dialog 和 无 Dialog 按 Home 键效果一样

https://blog.csdn.net/weixin_38576356/article/details/89597896

(4)横竖屏切换时activity生命周期变化的

跟清单文件AndroidManifest.xml中android:configChanges属性有关,而且还跟手机系统的版本有关,因为在Android 4.0及其以上系统上,还跟程序的targetSdkVersion设置有关

清单文件中不配置android:configChanges属性或者配置了android:configChanges=”orientation”,横竖屏切换时的变化跟Android 2.3是一样的,都会先销毁当前activity然后重新创建它。
https://blog.csdn.net/xiaoli100861/article/details/50855152

AlertDialog,popupWindow,Activity区别

(1)AlertDialog builder:用来提示用户一些信息,用起来也比较简单,设置标题类容和按钮即可,如果是加载的自定义的view ,调用 dialog.setView(layout);加载布局即可

创建AlertDialog,用AlertDialog.Builder中的create()方法。

setItems(CharSequence[] items, final OnClickListener listener)方法来实现类似ListView的AlertDialog
第一个参数是要显示的数据的数组,第二个参数是点击某个item的触发事件

  1. **setSingleChoiceItems**(CharSequence[] items, int checkedItem, final OnClickListener listener)方法来实现类似RadioButtonAlertDialog

setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final OnMultiChoiceClickListener listener)方法来实现类似CheckBox的AlertDialog
第一个参数是要显示的数据的数组,第二个参数是选中状态的数组,第三个参数是点击某个item的触发事件

  1. 自定义ViewAlertDialog

原文链接:https://blog.csdn.net/android_cmos/article/details/51223776

(2)popupWindow:就是一个悬浮在Activity之上的窗口,可以用展示任意布局文件。

可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。

(3)activity:Activity是Android系统中的四大组件之一,可以用于显示View。Activity是一个与用记交互的系统模块,几乎所有的Activity都是和用户进行交互的。

Activity的启动模式:

  • standard

默认的启动模式,每次点击standard模式创建Activity之后,都会创建新的MainActivity覆盖在原有的Activity上。

链接:https://www.jianshu.com/p/2cce9303b933

  • singleTop

如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。

  • singleTask

只有一个实例。在同一个应用程序中启动它的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉,并调用它的onNewIntent方法。

链接:https://www.jianshu.com/p/3eaa521ff330

  • singleInstance

只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。

设置Activity的启动模式,只需要在AndroidManifest.xml里对应的标签设置Android:launchMode属性,例如:
android:name=”.A1”
android:launchMode=”standard”/>

链接:https://www.jianshu.com/p/3eaa521ff330

singleTask 启动模式:

假设在任务栈中存在该Activity的实例,再次启动的时候,也就不会重新去创建它的实例,onCreate方法并没有执行,也就获取不到Bundle传递过来的值。此时,我们需要重写 onNewIntent()方法,系统会回调其onNewIntent方法,并将 onNewIntent 接收的 intent设置给 Activity。之后,我们可以在 onStart()方法中接收Bundle传递过来的值。

如果Activity B的启动模式为singleTask,B在任务栈中已经存在,A启动B,
1. 使用startActivity方法,这时B调用的方法为onNewIntent->onRestart->onStart->onResume
2. 使用startActivityForResult方法,这时B的启动模式就退化为standard模式,这是B调用的方法为onCreate->onStart->onResume

activity之间的通信模式

(1) 在Intent跳转时携带数据

  1. //创建Intent对象
  2. Intent intent = new Intent(MainActivity.this, SecondActivity.class);
  3. //程序自动创建Bundle,然后将对Intent添加的数据装载在Bundle中,对用户透明
  4. intent.putExtra("name", "WangJie");
  5. intent.putExtra("age", 23);
  6. startActivity(intent);

(2)借助类的静态变量来实现

由于类的静态成员可以通过“className.fileName”来访问,故而可以供两个Activity访问从而实现Activity之间的数据通信:

(3)借助全局变量来实现/Application

和类的静态变量类似,但是这个类作为单独第三个类(最好是写一个Application类):

https://blog.csdn.net/cyanchen666/article/details/81982562

(4)借助外部工具

– 借助SharedPreference
– 使用Android数据库SQLite
– 赤裸裸的使用File
– Android剪切板

(5)借助Service

(6)bundle

可以通过将数据封装在Bundle对象中 ,然后在Intent跳转的时候携带Bundle对象
bundle 本质上是使用 arrayMap实现的
可以传递基本数据类型和String类型的数据,如果传递的是对象就需要进行序列化。

(7) Serializable 和 Parcelable

  1. 如果使用BundleIntent之间传递对象需要先进行序列化。<br /> <br />Parcelable是由Android提供的序列化接口,google做了大量的优化<br /> <br />(8) EventBus

EventBus 使用的是发布 订阅者模型,发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。

https://www.cnblogs.com/chenjy1225/p/9662510.html

Application context和Activity context的区别

https://blog.csdn.net/xiaodongvtion/article/details/8443772

1、不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的
2. 对于生命周期长的对象,可以使用application context

android中context可以作很多操作,但是最主要的功能是加载和访问资源。
通常我们在各种类和方法间传递的是activity context。
比如一个activity的onCreate:
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = newTextView(this); //传递context给view
control label.setText(“Leaks are bad”);
setContentView(label); }

把activity context传递给view,意味着view拥有一个指向activity的引用,进而引用activity占有的资源: 这样如果context发生内存泄露的话,就会泄露很多内存。

()setContentView底层原理

Activity调用setContentView的方法是将xml布局文件加载到Activity中。

https://www.jianshu.com/p/9acdf27aae06

()Android Activity 的详细启动过程

需要的知识前提:

  1. Android 中的消息机制
  2. Binder 的原理和用法
  3. zygote 进程 和 system_server 进程的启动过程
  4. Android 消息机制之同步屏障


https://blog.csdn.net/yanzhenjie1003/article/details/99656633

()ActivityThread的理解

ActivityThread就是我们常说的主线程或UI线程,ActivityThread的main方法是整个APP的入口。

2、fragment

Activity与Fragment之间生命周期比较

片段fragment 必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。 例如,当 Activity 暂停时,其中的所有片段也会暂停;当 Activity 被销毁时,所有片段也会被销毁。

image.png
image.png

  1. onAttach(Context context):在Fragment和Activity关联时的时候调用,且仅调用一次。
  2. onCreate:在最初创建Fragment的时候会调用,和Activity的onCreate类似。
  3. onCreateView在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,并不是一定会被调用,当添加的是没有界面的Fragment就不会调用。
  4. onActivityCreated :在Activity的onCreated执行完时会调用。
  5. onStart() :Fragment对用户可见的时候调用,前提是Activity已经started。
  6. onResume():Fragment和用户之前可交互时会调用,前提是Activity已经resumed。
  7. onPause():Fragment和用户之前不可交互时会调用。
  8. onStop():Fragment不可见时会调用。
  9. onDestroyView():在移除Fragment相关视图层级时调用。
  10. onDestroy():最终清楚Fragment状态时会调用。
  11. onDetach():Fragment和Activity解除关联时调用。


链接:https://www.jianshu.com/p/70d7bfae18f3

Fragment 在各种情况下的生命周期

Activity中调用replace()方法时的生命周期:
新替换的Fragment:onAttach > onCreate > onCreateView > onViewCreated > onActivityCreated > onStart > onResume
被替换的Fragment:onPause > onStop > onDestroyView > onDestroy > onDetach

https://blog.csdn.net/ya1139569539/article/details/78192112

如何实现Fragment的滑动

和viewpager绑定一下,就可以滑动了。因为viewpager里面处理了touch事件,会进行move事件的滑动处理。

https://www.yuque.com/ambitious-lu4gu/vtl3p9/nempqp/edit#XajCR

Fragment 之间传递数据

https://blog.csdn.net/xcjean/article/details/78815964

(1)如需将数据从 Fragment B 传回到 Fragment A,请先在接收结果的 Fragment A 上设置结果监听器。在 Fragment A 的 FragmentManager 上调用 setFragmentResultListener() API

(2)如需将结果从子级 Fragment 传递到父级 Fragment,父级 Fragment 在调用 setFragmentResultListener() 时应使用 getChildFragmentManager() 而不是 getParentFragmentManager()。

(3)在MainFragment中设置一个setData()方法,在方法中设置更改按钮名称。在MenuFragment中的ListView条目点击事件中通过标签获取到MainFragment,并调用对应的setData()方法,将数据设置进去,从而达到数据传递的目的。

(4)采取接口回调的方式进行数据传递。

(5)用EventBus来实现以下Fragment之间的数据传递

EventBus是一款针对Android优化的发布/订阅(publish/subscribe)事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息。
简化了应用程序内各组件间、组件与后台线程间的通信。优点是开销小,代码更优雅,以及将发送者和接收者解耦。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现。

fragment 懒加载

https://www.jianshu.com/p/2201a107d5b5?utm_campaign=hugo

为什么Fragment需要懒加载呢,一般我们都会在onCreate()或者onCreateView()里去启动一些数据加载操作,比如从本地加载或者从服务器加载。大部分情况下,这样并不会出现什么问题,但是当你使用ViewPager + Fragment的时候,问题就来了,这时就应该考虑是否需要实现懒加载了。
ViewPager为了让滑动的时候可以有很好的用户的体验,也就是防止出现卡顿现象,因此它有一个缓存机制。默认情况下,ViewPager会提前创建好当前Fragment旁的两个Fragment,举个例子说也就是如果你当前显示的是编号3的Fragment,那么其实编号2和4的Fragment也已经创建好了,也就是说这3个Fragment都已经执行完 onAttach() -> onResume() 这之间的生命周期函数了。

https://www.cnblogs.com/dasusu/p/5926731.html

https://blog.csdn.net/Ashurol/article/details/50564182

3、Service

https://www.jb51.net/article/98871.htm
【耗时的较长的工作最好放在服务中完成】

Service生命周期

image.png
onStartCommand()
当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。
一旦执行此方法,服务即会启动并可在后台无限期运行。 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf() 或 stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)

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

https://www.cnblogs.com/huihuizhang/p/7623760.html


怎么启动server

1、startService 启动的服务:主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService;
2、bindService 启动的服务:该方法启动的服务可以进行通信。停止服务使用unbindService;
3、startService 同时也 bindService 启动的服务:停止服务应同时使用stepService与unbindService

https://blog.csdn.net/imxiangzi/article/details/76039978

如果先是bind了,那么start的时候就直接运行Service的onStart方法,
如果先是start,那么bind的时候就直接运行onBind方法。如果你先bind上了,就stop不掉了,
只能先UnbindService, 再StopService,所以是先start还是先bind行为是有区别的。
https://blog.csdn.net/baidu_32731497/article/details/51347298

Activity与Service怎么进行数据交互:

(1)使用bindService
https://www.cnblogs.com/l2rf/p/5953214.html

https://www.jianshu.com/p/0dfc6e9d82b3
(2)Activity和Service的交互方式主要有以下几种

  • 通过广播进行交互
  • 通过共享文件
  • Messenger
  • AIDL

https://blog.csdn.net/zh175578809/article/details/72856795

为什么 bindService 能和 Activity 的生命周期联动

bindService 方法执行时,LoadedApk 会记录 ServiceConnection 信息
Activity 执行 finish 方法时,会通过 LoadedApk 检查 Activity 是否存在未注销/解绑的 BroadcastReceiver 和 ServiceConnection,如果有,那么会通知 AMS 注销/解绑对应的 BroadcastReceiver 和 Service,并打印异常信息,告诉用户应该主动执行注销/解绑的操作

https://blog.csdn.net/u011330638/article/details/85010858

4、ContentProvider

作为Android四大组件之一的ContentProvider,其设计的目的是为了为了提供跨应用的数据共享解决方案。

  • ContentProvider的底层原理 = Android中的Binder机制

ContentProvider为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题

采用ContentProvider方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效

ContentResolver 类提供了与ContentProvider类相同名字 & 作用的4个方法

// 外部进程向 ContentProvider 中添加数据
public Uri insert(Uri uri, ContentValues values) 

// 外部进程 删除 ContentProvider 中的数据
public int delete(Uri uri, String selection, String[] selectionArgs)

// 外部进程更新 ContentProvider 中的数据
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 

/ 外部应用 获取 ContentProvider 中的数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

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


5、Android广播(Broadcast)

Broadcast是android中的四大组件之一,是在组件之间传播数据(Intent)的一种机制。

实现原理:Android中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型

广播的使用场景

a.同一app内有多个进程的不同组件之间的消息通信。
b.不同app之间的组件之间消息的通信。


Broadcast的分类

标准广播:context.sendBroadcast(Intent)方法发送的广播,不可被拦截
有序广播:context.sendOrderBroadcast(Intent)方法发送的广播,可被拦截
本地广播:localBroadcastManager.sendBroadcast(Intent),只在app内传播

https://www.cnblogs.com/qikeyishu/p/9215458.html

  1. 普通广播(标准广播)
    普通广播是完全异步的,通过Context的sendBroadcast()方法来发送,通过onReceive方法接收。消息传递效率比较高,但所有receivers(接收器)的执行顺序不确定。缺点在于:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传播,直到没有匹配的接收器广播时才能停止传播。

  2. 系统广播
    Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广波,每个广播都有特定的Intent - Filter(包括具体的action)。

3.有序广播(Ordered Broadcast)

Android中的有序广播,用sendOrderedBroadcast发送。
该广播主要有以下特性:
(1)按照接收者的优先顺序来接收广播,优先级别在intent-filter中的priority中声明,-1000到1000之间,值越大优先级越高,
(2)可以终止广播的继续传播,接受者可以修改intent的内容。
(3)同级别接收顺序是随机的,级别低的后收到
(4)能截断广播的继续传播,高级别的广播接收器接收广播后能决定时候截断。能处理广播
(5)同级别动态注册高于静态注册

  1. 粘性广播(Sticky Broadcast)

粘性广播的特点是Intent会一直保留到广播事件结束,而这种广播也没有所谓的10秒限制,10秒限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以干掉的candidate,一旦系统资源不够的时候,就会干掉这个广播而让它不执行。该广播用sendStickyBroadcast发送。

  1. App应用内广播(Local Broadcast)

    因为android中的广播是可以跨域的(跨App),因此可能存在一下问题:其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息; 即会出现安全性和效率性的问题。
    App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。相比于全局广播(普通广播),App应用内广播优势体现在:安全性高效率高

原文链接:https://blog.csdn.net/weixin_34819401/article/details/109774523

BroadcastReceiver与LocalBroadcastReceiver有什么区别?

  • BroadcastReceiver 是跨应用广播,利用Binder机制实现。
  • LocalBroadcastReceiver 是应用内广播,利用Handler实现,利用了IntentFilter的match功能,提供消息的发布与接收功能,实现应用内通信,效率比较高。

https://www.kancloud.cn/aslai/interview-guide/1113686

LocalBroadcastReceiver

[

](https://www.jianshu.com/p/6f1ea19a17ed)

https://www.jianshu.com/p/6f1ea19a17ed

Binder机制原理

https://blog.csdn.net/augfun/article/details/82343249

https://blog.csdn.net/weijinqian0/article/details/52233529

动态注册和静态注册区别

静态注册

  • 注册方式:在AndroidManifest.xml里通过标签声明

    动态注册

  • 注册方式:在代码中调用Context.registerReceiver()方法

  • 动态广播最好在Activity 的 onResume()注册、onPause()注销。在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。
  • 对于动态广播,有注册就必然得有注销,否则会导致内存泄露
  • 重复注册、重复注销也不允许

image.png
https://www.jianshu.com/p/ca3d87a4cdf3

6、Android Manifest文件详解


https://www.jianshu.com/p/543c5ff531a7

Android Intent用法总结

https://www.jianshu.com/p/67d99a82509b
Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。

  • 显式Intent
  1. 显式,即直接指定需要打开的activity对应的类。<br />1)构造方法传入**Component**,最常用的方式:
  • 隐式Intent

隐式,不明确指定启动哪个Activity,而是设置Action、Data、Category,让系统来筛选出合适的Activity。筛选是根据所有的来筛选。


Android Bundle 用法总结

https://blog.csdn.net/qq_34480777/article/details/55041032
当一个 Activity 启动另一个 Activity 时,常常会有一些数据需要传过去。因为两个 Activity 之间本来就有一个 Intent,因此我们主要将需要交换的数据放入 Intent 中即可。
Intent 主要通过 Bundle 对象来携带数据,因此 Intent 提供了 putExtras()和 getExtras()两个方法。除此之外,Intent 也提供了多个重载的 putExtra(String key,Xxx value)、getXxxExtra(String key)。其实 Intent 提供的 putExtra(String key,Xxx value)、getXxxExtra(String key)方法,只是两个便捷的方法,这些方法依然是存取 Intent 所携带的 Bundle 中的数据。
Intent 的 putExtra(String key, XXxx value)方法是“智能”的,当程序调用 Intent 的 putExtra(String key,Xxx value)方法向 Intent 中存入数据时,如果该 Intent 中已经携带了 Bundle 对象,则该方法直接向 Intent 所携带的 Bundle 中存入数据;如果 Intent 还没有携带 Bundle 对象,putExtra(String key,Xxx value)方法会优先为 Intent 创建一个 Bundle,再向 Bundle 中存入数据。

7、Handler机制

https://www.jianshu.com/p/592fb6bb69fa
主要作用是消息通信。
https://blog.csdn.net/luoyingxing/article/details/86500542

Message
Message有8个静态方法可以创建Message实例
Message有两个重要的成员变量,分别为target 和callback,一个是Handler,一个是Runnable。
Message有4个公开变量what、arg1、arg2、obj 可以存储消息进行传递
Message还有一个包间成员变量next,它是Message类型,后面会使用到,知道有这个next就行

  • 创建方法,obtain()方法

Handler

Handler有且只能绑定一个线程的Looper;

Handler的消息是发送给Looper的消息队列MessageQueue,需要等待处理;
  • 无参构造(最常用的),接收两个参数的构造方法

检验当前线程是否具备开启Handler的条件,如果具备的话,就将Handler和当前线程的Looper以及MessageQueue进行关联绑定。

  • enqueueMessage方法

Handler中的这个方法最终内部就是调用了MessageQueue中的enqueueMessage方法来将消息插入队列的

  • Handler的sendMessage方法、sendMessageDelayed方法或者是post方法,最终都会调用到这个enqueueMessage方法

  • dispatchMessage方法

这个方法是Handler分发消息去处理的方法,在前面讲Looper的loop方法中提到过。
当消息被传入到此方法后,方法内部会根据消息对象的属性以及Handler的属性来决定如何分发这个消息。

  • post方法

其本质也是发送一条消息给消息队列,最终再由Looper发送给Handler,只不过这个方法发送的消息的callback属性不为空。而在前面讲dispatchMessage方法时说过,如果当前分发的消息对象的callback不为空,那么会执行到handleCallback方法中。而handleCallback方法内部其实就是调用了消息对象的callback的run方法,所以run方法是运行在Handler所创建的线程中的

作者:孟校长
链接:https://www.jianshu.com/p/7cc1c8b4adb1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

MessageQueue
MessageQueue的enqueueMessage()方法
通过单向链表的数据结构来存储消息。既然有了插入消息的方法供Handler插入消息,那么应该有对应的取出消息的方法,供Looper调用取出消息处理,它就是Message.next这个变量(结点)

【几个重要的变量】

  • mQuitAllowed变量

含义是当前的MessageQueue是否允许被退出。它在MessageQueue的构造方法中被赋值

  • mQuitting变量

含义是当前的MessageQueue是否可以退出了

只有当mQuitAllowed变量为true的前提下,quit方法才得以正常的执行下去,mQuitting变量也才可以有机会被置为true;

  • mBlocked变量

含义是作为MessageQueue中next方法是否以一个非零的超时时长被阻塞在pollOnce()方法中的一个标志,简单的可以理解为当前消息队列是否被阻塞的标志。

  • mMessages变量,

它是一个Message类型的变量。next的变量,也是一个Message类型的变量。

  1. ----------<br />** enqueueMessage方法**<br />第一步,判断消息是否满足入队的条件并且检测队列是否能够插入消息;<br />第二步,根据队列和消息的状态来决定消息插入的方式以及确定是否需要唤醒队列;<br />第三步就是根据值来决定是否需要唤醒队列。
  2. **next方法**<br />取出消息的过程也可以简单分成三步,<br />第一步,判断队列是否已经退出以及消息是否合法;<br />第二步,根据队列中消息的顺序确定要返回的消息;<br />第三步,将返回的消息从队列中移除。


链接:https://www.jianshu.com/p/7cc1c8b4adb1

Looper

Looper在Handler机制中扮演着关键的一环,它不断的从消息队列中取出的消息,处理,然后分发处理事件。每个线程都可以且只能绑定一个Looper。主线程之所以能处理消息,也是因为在APP启动时,在ActivityThread中的main()方法中就已经启动了Looper循环。

  • prepare方法

用于在一个线程中创建一个Looper

  • loop方法

loop方法的流程也可以概括为三步,
第一步,检测Looper对象是否已经创建;
第二步,在循环中通过调用MessageQueue的next方法从消息队列中遍历出消息;
第三步,调用Handler的dispatchMessage方法将消息发送到Handler的手中然后将消息重置。

如果在一个线程中不再使用Looper了,记得调用它的quit或者quitSafely方法,这样loop方法才能结束的。

————————————————
Handler机制可以简述为:
Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。

Handler 使用场景: 执行比较耗时的I/O操作或者访问网络的操作。
image.png

关于handler,在任何地方new handler 都是什么线程下

(1)Handler的构造函数可以传入Looper,默认的使用当前线程的Looper。
如果当前线程没有Looper,则会报错。MainLooper在程序启动,ActivityThread中就创建了。
如果自己开辟线程,可以使用Looper.prepare()创建Looper,然后创建Handler就不会报错了。

(2)Handler handler = new Handler(Looper.getMainLooper());
这个不管在哪个线程中创建,handleMessage回调的都是主线程。

(3)使用默认构造函数创建,当前在哪个线程,handleMessage回调就是在哪个线程。

https://blog.csdn.net/u013072976/article/details/78261349
[

](https://blog.csdn.net/luoyingxing/article/details/86500542)
1.要刷新UI,handler要用到主线程的looper。那么在主线程 Handler handler = new Handler();
如果在其他线程,也要满足这个功能的话,要Handler handler = new Handler(Looper.getMainLooper());

2.不用刷新ui,只是处理消息。
当前线程如果是主线程的话,Handler handler = new Handler();
不是主线程的话,Looper.prepare(); Handler handler = new Handler();Looper.loop();
或者Handler handler = new Handler(Looper.getMainLooper());表示放到主UI线程去处理

https://blog.csdn.net/u013072976/article/details/78261349

handle 发消息给子线程,looper 怎么启动

该handler中引用的looper对象是属于那个线程的, 就会把消息投递给该线程, 并在该线程里执行 handleMessage方法。
https://www.zhihu.com/question/48130951?sort=created

https://zhidao.baidu.com/link?url=WI-igRa5qkwedPgkcn3Z27VU73zHRpi-u5C23FaZcX27XmQT2VlEx5DXZqVAUOhchJOu4M-JOoDY0XELuziBP-X8VnfhcfInZy8Y8yiSckK

https://blog.csdn.net/qq_27070117/article/details/81183118

Android多线程

常用方法主要有:

  1. 继承Thread类
  2. 实现Runnable接口
  3. Handler
  4. AsyncTask
  5. HandlerThread

AsyncTask异步任务

https://www.runoob.com/w3cnote/android-tutorial-ansynctask.html
官方给提供的封装好的轻量级异步类,相比起Handler,AsyncTask显得更加简单,快捷。
这只适合简单的异步操作,但是到了公司真正做项目以后,我们更多的使用第三发的框架,比如Volley,OkHttp,android-async-http,XUtils等很多。
image.png

https://www.jianshu.com/p/ee1342fcf5e7
[

](https://blog.csdn.net/luoyingxing/article/details/86500542)

AsyncTask的缺陷和问题

  • 生命周期

如果我们的Activity销毁之前,没有取消 AsyncTask,这有可能让我们的AsyncTask崩溃(crash)

  • 内存泄漏

如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。

  • 结果丢失

屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。


如何取消Asynctask

在AsyncTask中,我们没有办法直接停止掉异步任务,只能通过cancel方法来将AsyncTask标记为cancel状态,即cancel方法只是传递了一个信号量,而不是真的cancel了异步任务。
所以如果希望cancel方法能直接取消掉异步任务,就需要在doInBackground中检测当前状态:当状态是cancel状态,则立刻跳出循环。

https://blog.csdn.net/zgljl2012/article/details/47258301/

工作线程更新UI 的方法

1.Activity.runOnUiThread(Runable)

2.View.post(Runable)

3.View.postDelay(Runable)

4.handler更新

https://blog.csdn.net/qidingquan/article/details/50624792
https://www.cnblogs.com/ouyangping/p/7743863.html

为什么不能在子线程更新UI

Android的UI操作不是线程安全的invalidate()在子线程调用会导致线程不安全。

  • 子线程可以在ViewRootImpl还没有被创建之前更新UI;
  • 访问UI是没有加对象锁的,在子线程环境下更新UI,会造成不可预期的风险;
  • 开发者更新UI一定要在主线程进行操作;

ViewRootImpl对象是在onResume方法回调之后才创建,那么就说明了为什么在生命周期的onCreate方法里,甚至是onResume方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl对象,并不会进行是否为主线程的判断;

链接:https://www.jianshu.com/p/58c999d3ada7

https://blog.csdn.net/dfskhgalshgkajghljgh/article/details/106093924
———-

Android Toast使用的简单小结

1.最基本的Toast

2.自定义位置的Toast

3.自定义布局(带图片)的Toast

4.自定义带动画效果的Toast控件

原文链接:https://blog.csdn.net/fitaotao/article/details/82251750
————

一个线程能否创建多个Handler,Handler跟Looper之间的对应关系 ?

一个线程能够创建多个Handler,Handler跟Looper没有对应关系,线程才跟Looper有对应关系,一个线程对应着一个Looper

链接:https://www.jianshu.com/p/c1f8dad0dafb

【HandlerB能够获取HandlerA发送的异步消息吗?】
https://www.cnblogs.com/transmuse/archive/2011/05/16/2048073.html

lopper 是如何区分多个handler

首先说明,一个handler发送的消息只会被自己接收,所以是可以正常处理的
发送消息除了利用Handler之外还有Message,Message一般利用Obtain获取,其实obtain中还可以传递参数,可以接收Message,还可以接收Handler,message有多个属性,常用的有what,arg1,arg2,data等,其实还有一个属性叫做target,
这个target属性就是标识handler的。

原文链接:https://blog.csdn.net/u010126792/article/details/82899976

这样在处理消息的时候通过msg.target就可以区分开不同的Handler了。处理的方法在Looper.loop中.
https://blog.csdn.net/ly502541243/article/details/87475229

8、view相关

什么是View?

View是Android中所有控件的基类,不管是简单的TextView,Button还是复杂的LinearLayout和ListView,它们的共同基类都是View;
View是一种界面层的控件的一种抽象,它代表了一个控件。
在Android中,ViewGroup也继承了View,这就意味着View可以是单个控件,也可以是由多个控件组成的一组控件;

https://www.jianshu.com/p/1ba3d28781e4

image.png
https://blog.csdn.net/makyan/article/details/89106967

Android View 绘制流程详解

https://www.jianshu.com/p/9e5ebeb3ed35

traversals 遍历
整个 View 树的绘图流程在ViewRoot.java类的 performTraversals() 函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、
是否需要重新安置视图的位置(layout)、
以及是否需要重绘(draw),
流程图如下:

image.png

  • measure

measure 过程会为一个 View 及所有子节点的 mMeasuredWidth 和 mMeasuredHeight 变量赋值,
该值可以通过
getMeasuredWidth()和
getMeasuredHeight()方法获得。

  1. **measure 过程传递尺寸的两个类**
  • ViewGroup.LayoutParams (View 自身的布局参数)

    • MATCH_PARENT 表示子视图希望和父视图一样大(不包含 padding 值)
    • WRAP_CONTENT 表示视图为正好能包裹其内容大小(包含 padding 值)
  • MeasureSpecs 类(父视图对子视图的测量要求)

    • UNSPECIFIED (unspecified)
      父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到,
    • EXACTLY (exactly)
      父视图为子视图指定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体值,比如 100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。
    • AT_MOST
      父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。

      measure 核心方法

  • measure(int widthMeasureSpec, int heightMeasureSpec)
    该方法定义在View.java类中,为 final 类型,不可被复写,但 measure 调用链最终会回调 View/ViewGroup 对象的 onMeasure()方法,因此自定义视图时,只需要复写 onMeasure() 方法即可。
  • onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    该方法就是我们自定义视图中实现测量逻辑的方法,该方法的参数是父视图对子视图的 width 和 height 的测量要求。在我们自身的自定义视图中,要做的就是根据该 widthMeasureSpec 和 heightMeasureSpec 计算视图的 width 和 height,不同的模式处理方式不同。

  • setMeasuredDimension()
    测量阶段终极方法,在 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法中调用,将计算得到的尺寸,传递给该方法,测量阶段即结束。该方法也是必须要调用的方法,否则会报异常。在我们在自定义视图的时候,不需要关心系统复杂的 Measure 过程的,

只需调用setMeasuredDimension()设置根据 MeasureSpec 计算得到的尺寸即可,你可以参考 ViewPagerIndicator的 onMeasure 方法。

layout

View 的 onLayout 方法为空实现,而 ViewGroup 的 onLayout 为 abstract 的,因此,如果自定义的 View 要继承 ViewGroup 时,必须实现 onLayout 函数。
在 layout 过程中,子视图会调用
getMeasuredWidth()和
getMeasuredHeight()方法获取到 measure 过程得到的 mMeasuredWidth 和 mMeasuredHeight,作为自己的 width 和 height。
然后调用每一个子视图的layout(l, t, r, b)函数,来确定每个子视图在父视图中的位置。

draw

  • View.draw(Canvas canvas):
    由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
  • View.onDraw():
    View 的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。
  • dispatchDraw()
    发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。该方法我们不用去管,

    1. 自定义的 ViewGroup 不应该对dispatchDraw()进行复写。
    • drawChild(canvas, this, drawingTime)
      直接调用了 View 的child.draw(canvas, this,drawingTime)方法,,child.draw(canvas, this, drawingTime) 肯定是处理了和父视图相关的逻辑,

      1. View 的最终绘制,还是View.draw(Canvas)方法。
    • invalidate()
      请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些调用了invalidate()方法的 View。

    • requestLayout()
      当布局变化的时候,比如方向变化,尺寸的变化,会调用该方法,在自定义的视图中,如果某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它会触发measure()和layout()过程,但不会进行 draw。

Android 计算一个view 的嵌套层级

Android view层级:

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/23038245/1637897354282-2efea1f3-8ad7-445c-bb91-d97ed9cab4a5.png#clientId=ubd317d18-f44a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=384&id=uab71b34c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=536&originWidth=660&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18707&status=done&style=none&taskId=u1e5e6de2-8d4b-41bd-9d2b-21c77359878&title=&width=473)<br />[https://www.jianshu.com/p/4dd8eb080534](https://www.jianshu.com/p/4dd8eb080534)

https://blog.csdn.net/zx_android/article/details/79558509

Android获取View的宽度和高度

  • onWindowFocusChanged方法中获取:
    onWindowFocusChanged方法执行时代表View已经初始化完毕了,宽度和高度已经测量完毕并且最终确认好了,这时我们可以放心的在这里去获取View 的宽度和高度。

    onWindowFocusChanged会执行多次,在Activity获取焦点和失去焦点时都会被调用一次。
    即在onPause和onResume方法被执行时被反复调用。

  • View.post(new Runnable())方法中获取:
    通过View.post方法把Runnable对象放到消息队列的末尾,当执行到这个runable方法的时候,View所有的初始化测量方法说明都已经执行完毕了。因此在这里获取的时候就是测量后的真实值。

  • 通过ViewTreeObserver获取:
    当View树的状态发生改变或者View树内部View的可见性发生改变的时候,onGlobalLayout将会被回调。注意:伴随着View树的状态改变,onGlobalLayout会被调用多次

  • 手动调用View的measure方法后获取
    手动调用measure后,View会调用onMeasure方法对View发起测量,测量完后,就可以获取测量后的宽度和高度了。但是要对LayoutParams的参数分情况处理才能得到具体的参数值:
    View的LayoutParams参数为match_parent:这种情况下无法获取到具体宽高值,因为当View的测量模式为match_parent时,宽高值是取父容器的剩余空间大小作为它自己的宽高。而这时无法获取到父容器的尺寸大小,因此获取会失败。

    https://blog.csdn.net/chenbaige/article/details/77991594

自定义view需要重写哪些方法

https://www.jianshu.com/p/99878de32418

首先定义一个继承View基类的子类,然后重写View类的一个或者多个方法:

  • 1.构造器:重写构造器是定制View的最基本方式,当Java代码创建一个View实例,或根据XML布局文件加载并构建界面时将需要调用该构造器。
    2.onFinishinflate():这是一个回调方法,当应用从XML布局文件加载该组件并利用它来构建界面之后,该方法就会被回调。
    3.onMeasure(int,int):调用该方法来检测View组件及它所包含的所有子组件的大小。
    4.onLayout(boolean,int,int,int,int):当该组件需要分配其子控件的位置、大小时该方法就会被调用。
    5.onSizeChanged(int, int, int, int):当该组件的大小被改变时回调该方法。
    6.onDraw(Canvas):当该组件将要绘制它的内容时回调该方法进行绘制。
    7.onKeyDown(int, KeyEvent):当某个键被按下时触发该方法。
    8.onKeyUp(int, KeyEvent):当松开某个键时触发该方法。
    9.onTrackballEvent(MotionEvent):当发生轨迹球事件时触发该方法。
    10.onTouchEvent(MotionEvent):当发生触摸屏事件时触发该方法。
    11.onWindowFocusChanged(boolean):当该组件得到、失去焦点时触发该方法。
    12.onAttachedToWindow():当把该组件放入某个窗口时触发该方法。
    13.onDetachedFromWindow():当把该组件从某个窗口上分离时触发该方法。
    14.onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该方法。

当需要开发自定义View时,并不需要重写上面列出的所有方法,而是根据业务需要重写上面的部分方法。

——————-
Window首先加载一个超级复合View,用它包含住所有的其他View,这个超级复合View就叫做DecorView。但是这个DecorView除了包含我们的用户界面上的那些View,还包含了作为一个Window特有的View,叫做TitleBar。

链接:https://www.jianshu.com/p/70853209a182

[自定义view 计步器界面设计]

https://www.bilibili.com/video/BV1S741157Qj?p=3&spm_id_from=pageDriver

  1. // activity_main.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context=".MainActivity"
  9. android:id="@+id/refresh">
  10. <!--使用自定义的view组件 -->
  11. <com.example.myapplication_view.FIWStepView
  12. android:id="@+id/step_view"
  13. android:layout_width="match_parent"
  14. android:layout_height="match_parent"
  15. app:border_width="30dp"
  16. app:outer_color="@color/yellow"
  17. app:inner_color="@color/blueviolet"
  18. app:step_text_color="@color/blueviolet"
  19. app:step_text_size="30dp"
  20. />
  21. </androidx.constraintlayout.widget.ConstraintLayout>
  1. // MainActivity.java
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.animation.ValueAnimator;
  4. import android.os.Bundle;
  5. import android.view.animation.DecelerateInterpolator;
  6. public class MainActivity extends AppCompatActivity {
  7. private FIWStepView fiwStepView; //引用自定义组件view
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. fiwStepView = findViewById(R.id.step_view);
  13. fiwStepView.setmMaxStep(10000); // 设置最大步数
  14. // 加入动画
  15. ValueAnimator animator = ValueAnimator.ofFloat(0,8000); // 步数范围0-8000
  16. animator.setInterpolator(new DecelerateInterpolator());// 设置加速度效果
  17. animator.setDuration(1000);
  18. animator.addUpdateListener( (animation)->{
  19. float currentSteps = (float)animation.getAnimatedValue();
  20. fiwStepView.setmCurrentStep((int) currentSteps);
  21. });
  22. animator.start();
  23. }
  24. }
  1. // activity_review.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:orientation="vertical"
  8. android:id="@+id/refresh">
  9. <com.example.myapplication_view.FIWStepView
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. app:border_width="10dp"
  13. app:outer_color="@color/yellow"
  14. app:inner_color="@color/blueviolet"
  15. app:step_text_color="@color/blueviolet"
  16. app:step_text_size="30dp"
  17. />
  18. </LinearLayout>
  1. // FIWStepView.java
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Canvas;
  5. import android.graphics.Color;
  6. import android.graphics.Paint;
  7. import android.graphics.Rect;
  8. import android.graphics.RectF;
  9. import android.util.AttributeSet;
  10. import android.view.View;
  11. import androidx.annotation.Nullable;
  12. public class FIWStepView extends View {
  13. private int mOuterColor = Color.YELLOW; //外层颜色
  14. private int mInnerColor = Color.BLUE; // 内层颜色
  15. private int mBorderWidth; //
  16. private int mStepTextColor = Color.BLUE; // 步数文字颜色
  17. private int getmStepTextSize; // 步数文本的大小
  18. private int mCurrentStep; // 当前步数,// 这连个值在mainActivity.java 中设置
  19. private int mMaxStep ; // 最大步数
  20. private Paint mOuterPaint;// 外圆环 ,设置为全局
  21. private Paint mInnerPaint; // 内圆环
  22. private Paint mTextpaint; // 文字
  23. // 重写get(),为了可以读到私有的值
  24. public int getmCurrentStep() {
  25. return mCurrentStep;
  26. }
  27. public void setmCurrentStep(int mCurrentStep) {
  28. this.mCurrentStep = mCurrentStep;
  29. // 防止调用后就停止,所以要刷新
  30. invalidate();
  31. }
  32. public int getmMaxStep() {
  33. return mMaxStep;
  34. }
  35. public void setmMaxStep(int mMaxStep) {
  36. this.mMaxStep = mMaxStep;
  37. }
  38. // 构造函数
  39. public FIWStepView(Context context) {
  40. // 把super 改成 this
  41. this(context,null);
  42. }
  43. public FIWStepView(Context context, @Nullable AttributeSet attrs) {
  44. this(context, attrs,0);
  45. }
  46. public FIWStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  47. super(context, attrs, defStyleAttr);
  48. // 获取自定义属性
  49. TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.FIWStepView);
  50. mOuterColor = array.getColor(R.styleable.FIWStepView_outer_color,mOuterColor);
  51. mInnerColor = array.getColor(R.styleable.FIWStepView_inner_color,mInnerColor);
  52. mBorderWidth =(int)array.getDimension(R.styleable.FIWStepView_border_width,mBorderWidth);
  53. mStepTextColor=array.getColor(R.styleable.FIWStepView_step_text_color,mStepTextColor);
  54. getmStepTextSize=array.getDimensionPixelSize(R.styleable.FIWStepView_step_text_size,getmStepTextSize);
  55. array.recycle();
  56. //外圆环
  57. mOuterPaint = new Paint();
  58. mOuterPaint.setAntiAlias(true);
  59. mOuterPaint.setColor(mOuterColor);
  60. mOuterPaint.setStrokeWidth(mBorderWidth);
  61. mOuterPaint.setStyle(Paint.Style.STROKE);//设置圆弧内不填充
  62. mOuterPaint.setStrokeCap(Paint.Cap.ROUND);//设置断点为圆弧
  63. //内圆环
  64. mInnerPaint = new Paint();
  65. mInnerPaint.setAntiAlias(true);
  66. mInnerPaint.setColor(mInnerColor);
  67. mInnerPaint.setStrokeWidth(mBorderWidth);
  68. mInnerPaint.setStyle(Paint.Style.STROKE);
  69. mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
  70. // 文字
  71. mTextpaint = new Paint();
  72. mTextpaint.setAntiAlias(true);
  73. mTextpaint.setColor(mStepTextColor);
  74. mTextpaint.setTextSize(getmStepTextSize);
  75. }
  76. //----------------重写方法-------------
  77. @Override
  78. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  79. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  80. // 圆形由正方形截来,所以先以短边来成正方形
  81. int width = MeasureSpec.getSize(widthMeasureSpec);
  82. int height = MeasureSpec.getSize(heightMeasureSpec);
  83. setMeasuredDimension(width>height? height:width ,width>height?height:width);
  84. }
  85. @Override
  86. protected void onDraw(Canvas canvas) {
  87. super.onDraw(canvas);
  88. // 5 ---------绘制内圆弧
  89. // RectF rectF = new RectF(0,0,getWidth(),getWidth());
  90. // 新距离算法,保证圆环完整显示
  91. int center = getWidth()/2;
  92. int radius = (getWidth()-mBorderWidth)/2;
  93. RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2,center+radius,center+radius);
  94. //Paint mOuterPaint = new Paint(); 这个耗时操作不要放在这里
  95. // 系统自带画圆弧的功能,需要参数
  96. canvas.drawArc(rectF,135,270,false,mOuterPaint);
  97. // ---------绘制外圆环
  98. if(mMaxStep ==0){return ;}
  99. float radio = (float) mCurrentStep/mMaxStep;
  100. canvas.drawArc(rectF,135,270*radio,false,mInnerPaint);
  101. //-------绘制文字
  102. String mText = mCurrentStep+"";
  103. Rect rect = new Rect();
  104. mTextpaint.getTextBounds(mText,0,mText.length(),rect);
  105. int dx= getWidth()/2-rect.width()/2;
  106. Paint.FontMetricsInt fontMetricsInt = mTextpaint.getFontMetricsInt();
  107. int dy = fontMetricsInt.bottom - fontMetricsInt.top;
  108. int baseLine = getHeight()/2-dy/2;
  109. canvas.drawText(mText,dx,baseLine,mTextpaint);
  110. }
  111. }
  1. // AndroidManifest.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  4. package="com.example.myapplication_view">
  5. <application
  6. android:allowBackup="true"
  7. android:icon="@mipmap/ic_launcher"
  8. android:label="@string/app_name"
  9. android:roundIcon="@mipmap/ic_launcher_round"
  10. android:supportsRtl="true"
  11. android:theme="@style/Theme.MyApplication_View">
  12. <activity
  13. android:name=".MainActivity"
  14. android:exported="true">
  15. <intent-filter>
  16. <action android:name="android.intent.action.MAIN" />
  17. <category android:name="android.intent.category.LAUNCHER" />
  18. </intent-filter>
  19. </activity>
  20. </application>
  21. </manifest>
  1. // attrs.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <resources>
  4. <declare-styleable name="FIWStepView">
  5. <attr name="outer_color" format="color"/>
  6. <attr name="inner_color" format="color"/>
  7. <attr name="border_width" format="dimension"/>
  8. <attr name="step_text" format="dimension"/>
  9. <attr name="step_text_color" format="color"/>
  10. <attr name="step_text_size" format="dimension"/>
  11. </declare-styleable>
  12. </resources>

ViewPage使用

  • ViewPager类直接继承了ViewGroup类,是一个容器,需要在里面添加我们想要显示的内容。
  • ViewPager类需要PagerAdapter适配器类提供数据。

作用:左右切换当前的view,实现滑动切换的效果。

用相应的适配器Adapter关联上面的页卡(View/Fragment)和ViewPager:

  • PagerAdapter 数据源:List
  • FragmentPagerAdapter 数据源:List
  • FragmentStatePagerAdapter 数据源:List

利用Viewpage自带的方法setPageTransformer()可用于设置切换动画

FragmentStatePagerAdapter 和 FragmentPagerAdapter 的异同:


  • PageAdapter 是 FragmentPagerAdapter 以及
    FragmentStatePagerAdapter 的基类,可将上面的FragmentPagerAdapter 替换成FragmentStatePagerAdapter

  • FragmentPagerAdapter使用时,每一个生成的 Fragment 都将保存在内存之中,而 FragmentStatePagerAdapter 只保留了当前显示的Fragment,其他划过的Fragment离开视线后,就会被销毁;而在页面需要显示时,再生成新的实例。

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

ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化

https://www.cnblogs.com/dmrbell/p/12408207.html
(fragment 懒加载)
利用 setUserVisibleHint 和 生命周期方法

https://blog.csdn.net/wellto/article/details/52145899

https://blog.csdn.net/wuqingsen1/article/details/84544665

Android 事件分发机制

事件分发就是从上往下依次遍历,直到找到能够处理消费这次点击事件的View或ViewGroup。

事件分发中的事件指的是点击事件(Touch事件)
一次完整的点击包含四种事件:
(1)ACTION.DOWN:手指触摸到屏幕
(2)ACTION.MOVE:手指在屏幕中滑动
(3)ACTION.UP:手指离开屏幕
(4)ACTION.CANCEL:取消点击事件

事件分发涉及到的方法有3个:

dispatchTouchEvent()(分发事件)
onInterceptTouchEvent()(拦截事件)
onTouchEvent()(处理事件)

onInterceptTouchEvent()方法是ViewGroup独有的,另外两个方法是三者共有的。

https://blog.csdn.net/yuncaidaishu/article/details/100039363

点击事件被拦截,但是想传到下面的View,如何操作?

重写子类的requestDisallowInterceptTouchEvent()方法返回true,
就不会执行父类的onInterceptTouchEvent(),即可将点击事件传到下面的View

  1. https://www.jianshu.com/p/04d74729d79d

ListView

借助ListView来显示更多的内容。
ListView允许用户通过上下滑动来将屏幕外的数据滚动到屏幕内,同时屏幕内原有的数据滚动出屏幕,从而显示更多的数据内容。

重写getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。
首先通过getItem()得到当前的项的Fruit实例,
然后使用LayoutInflater来为这个子项加载我们传入的布局。

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

ListView复用和优化详解

  • 为什么会存在Item复用问题

ListView内部为了优化而建立的复用机制,在getView()方法中第二个参数就是ListView传递给你,让你进行复用的View.

  • 为什么需要ViewHolder

    1. 了在列表滚动的时候,频繁调用getView方法的时候尽量提高性能.我们可以使用一个普通类,这个类通常就起名字为ViewHolder了,当创建itemView的时候,我们也把里面要用到的控件也找到,然后放在ViewHolder类中,然后再通过itemView.setTag(Object ob)方法实现一个itemView和一个ViewHolder进行绑定.
  • 出现复用问题怎么解决

    原理:当我们复用ListView回传的View的时候,这个View是被之前使用过的,也就是说给你的这个View保存了之前用过的状态。

解决方法:对产生问题的控件进行初始化,,把出问题的控件,状态还原一下

https://blog.csdn.net/u011692041/article/details/53099584

Android ListView工作原理完全解析

https://blog.csdn.net/guolin_blog/article/details/44996879

ListView并不会直接和数据源打交道,而是会借助Adapter这个桥梁来去访问真正的数据源。
Adapter的接口都是统一的,因此ListView不用再去担心任何适配方面的问题。
而Adapter又是一个接口(interface),它可以去实现各种各样的子类,每个子类都能通过自己的逻辑来去完成特定的功能。

Adapter还有一个非常非常重要的方法也需要我们在Adapter当中去重写,就是getView()方法。

在开始分析ListView的源码之前,还有一个东西是我们提前需要了解的,就是RecycleBin机制,这个机制也是ListView能够实现成百上千条数据都不会OOM最重要的一个原因

RecycleBin最主要的几个方法:

  • fillActiveViews() 调用这个方法后就会根据传入的参数来将ListView中的指定元素存储到mActiveViews数组当中。
  • getActiveView() 这个方法和fillActiveViews()是对应的,用于从mActiveViews数组当中获取数据。

  • addScrapView() 用于将一个废弃的View进行缓存

  • getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的,

  • setViewTypeCount() 我们都知道Adapter当中可以重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法通常情况下使用的并不是很多,所以我们只要知道RecycleBin当中有这样一个功能就行了。

[

](https://blog.csdn.net/guolin_blog/article/details/44996879)
[

](https://blog.csdn.net/guolin_blog/article/details/44996879)
obtainView()方法中的代码并不多,但却包含了非常非常重要的逻辑,不夸张的说,整个ListView中最重要的内容可能就在这个方法里了

调用RecyleBin的getScrapView()方法来尝试从废弃缓存中获取一个View,那么废弃缓存有没有View呢?当然有,因为刚才在trackMotionScroll()方法中我们就已经看到了,一旦有任何子View被移出了屏幕,就会将它加入到废弃缓存中,而从obtainView()方法中的逻辑来看,一旦有新的数据需要显示到屏幕上,就会尝试从废弃缓存中获取View。所以它们之间就形成了一个生产者和消费者的模式,那么ListView神奇的地方也就在这里体现出来了,不管你有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据重新利用起来,因而不管我们加载多少数据都不会出现OOM的情况,甚至内存都不会有所增加。
————————————————

Android ListView异步加载图片乱序问题,原因分析及解决方案

https://blog.csdn.net/guolin_blog/article/details/45586553

RecyclerView

image.png

是Android一个更强大的控件,其不仅可以实现和ListView同样的效果,还有优化了ListView中的各种不足。其可以实现数据纵向滚动,也可以实现横向滚动(ListView做不到横向滚动)。接下来讲解RecyclerView的用法。

  • 定义内部类ViewHolder,并继承RecyclerView.ViewHolder。传入的View参数通常是RecyclerView子项的最外层布局。
  • FruitAdapter构造函数,用于把要展示的数据源传入,并赋予值给全局变量mFruitList。
  • FruitAdapter继承RecyclerView.Adapter。因为必须重写onCreateViewHolder(),onBindViewHolder()和getItemCount()三个方法
    • onCreateViewHolder()用于创建ViewHolder实例,并把加载的布局传入到构造函数去,再把ViewHolder实例返回。
    • onBindViewHolder()则是用于对子项的数据进行赋值,会在每个子项被滚动到屏幕内时执行。position得到当前项的Fruit实例。
    • getItemCount()返回RecyclerView的子项数目。


链接:https://www.jianshu.com/p/b4bb52cdbeb7


RecyclerViewDemo

  1. //activity_main.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context=".MainActivity">
  9. <!--在MainActivity 中 填写 要展示的recyclerview 组件id也是在这里定义-->
  10. <androidx.recyclerview.widget.RecyclerView
  11. android:id="@+id/rv_main"
  12. android:layout_width="match_parent"
  13. android:layout_height="match_parent"
  14. />
  15. </LinearLayout>
  1. //MainActivity.java
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.recyclerview.widget.LinearLayoutManager;
  4. import androidx.recyclerview.widget.RecyclerView;
  5. import android.os.Bundle;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. public class MainActivity extends AppCompatActivity {
  9. List<Student> mStudent = new ArrayList<>(); // 数据
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. initView(); //初始化View,抽取为方法
  15. initdata(); // 数据赋值
  16. }
  17. // 把数据构建好
  18. private void initdata() { // 在这里构建(读取)数据
  19. for(int i=0;i<100;i++){
  20. Student student = new Student("name:"+i);
  21. mStudent.add(student);
  22. }
  23. }
  24. // 实例化都写在这个方法上
  25. private void initView() {
  26. RecyclerView recyclerView = findViewById(R.id.rv_main);// 找,传入recyclerView
  27. // ----控制格式-----
  28. //传入 LayoutManager 的子类 LinearLayoutManager ,线性
  29. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
  30. linearLayoutManager.setOrientation(RecyclerView.VERTICAL); //设置方向
  31. recyclerView.setLayoutManager(linearLayoutManager); //需要传入一个Manager,构建
  32. // ----设置适配器----
  33. // 创建一个 Adapter Java类
  34. RecyclerViewAdater recyclerViewAdater = new RecyclerViewAdater(this,mStudent);// 把数据传过去
  35. recyclerView.setAdapter(recyclerViewAdater); // 需要传入一个自定义Adater(通过重写)
  36. }
  37. }
  1. // RecyclerViewAdater.java
  2. import android.content.Context;
  3. import android.view.View;
  4. import android.view.ViewGroup;
  5. import android.widget.TextView;
  6. import androidx.annotation.NonNull;
  7. import androidx.recyclerview.widget.RecyclerView;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. public class RecyclerViewAdater extends RecyclerView.Adapter<RecyclerViewAdater.InnerHolder> {
  11. Context mContent;
  12. List<Student> mStudent = new ArrayList<>();//传进来自定义数据
  13. public RecyclerViewAdater(Context context, List<Student> list){ // 构造方法
  14. this.mContent = context; // 把content 传进来
  15. this.mStudent = list;
  16. }
  17. @Override
  18. public RecyclerViewAdater.InnerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  19. // 绑定 item 视图
  20. View view =View.inflate(mContent,R.layout.item_listview,null);// 需要content,还有item_listview的资源文件
  21. return new InnerHolder(view);// 需要参数view --- 联系InnerHolder()函数
  22. }
  23. @Override
  24. public void onBindViewHolder(@NonNull RecyclerViewAdater.InnerHolder holder, int position) {
  25. // 对实例化后的控件进行操作,进行数据的展示,点击事件的监听
  26. Student student = mStudent.get(position);// 得到某一行的数据
  27. holder.itemText.setText(student.getStName()); //展示数据
  28. }
  29. @Override
  30. public int getItemCount() { // 返回数量
  31. return mStudent.size();
  32. }
  33. // 在这个类中对 item 进行实例化
  34. public class InnerHolder extends RecyclerView.ViewHolder {
  35. TextView itemText; //
  36. public InnerHolder(@NonNull View itemView){
  37. super(itemView); // 实例化item
  38. itemText = itemView.findViewById(R.id.item_text);// 从传进来的itemView中获取
  39. }
  40. }
  41. }
  1. // item_listview.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <!-- 在item中 尽量使用相对布局而不是 线性布局-->
  4. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  5. xmlns:app="http://schemas.android.com/apk/res-auto"
  6. android:orientation="vertical"
  7. android:layout_width="match_parent"
  8. android:layout_height="match_parent">
  9. // 使用卡片布局包裹效果
  10. <androidx.cardview.widget.CardView
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"
  13. app:cardUseCompatPadding="true"
  14. >
  15. <TextView
  16. android:layout_width="match_parent"
  17. android:layout_height="50dp"
  18. android:id="@+id/item_text"
  19. android:text="@string/app_name"
  20. android:textSize="20dp"
  21. android:textStyle="bold"
  22. android:gravity="center_vertical"
  23. />
  24. </androidx.cardview.widget.CardView>
  25. </RelativeLayout>
  1. // Student.java
  2. // 自定义数据
  3. public class Student {
  4. private String stName;
  5. public Student(String stName) {
  6. this.stName = stName;
  7. }
  8. public String getStName() {
  9. return stName;
  10. }
  11. public void setStName(String stName) {
  12. this.stName = stName;
  13. }
  14. }
  1. // AndroidMainfest.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  4. package="com.example.recyclerviewdemo">
  5. <application
  6. android:allowBackup="true"
  7. android:icon="@mipmap/ic_launcher"
  8. android:label="@string/app_name"
  9. android:roundIcon="@mipmap/ic_launcher_round"
  10. android:supportsRtl="true"
  11. android:theme="@style/Theme.RecyclerViewDemo">
  12. //注册了MainActivity
  13. <activity
  14. android:name=".MainActivity"
  15. android:exported="true">
  16. <intent-filter>
  17. <action android:name="android.intent.action.MAIN" />
  18. <category android:name="android.intent.category.LAUNCHER" />
  19. </intent-filter>
  20. </activity>
  21. </application>
  22. </manifest>
  1. // string.xml
  2. <resources>
  3. <string name="app_name">RecyclerViewDemo</string>
  4. </resources>

RecycleView四级缓存和RecycleView优化

https://www.jianshu.com/p/11081a051f31










9、okHttp

image.png
https://my.oschina.net/u/4856869/blog/4877748
okhttp3是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。
HttpURLConnection这个类相比HttpClient实在是太难用,太弱爆了。
google在Android 6.0中删除了关于Httpclient的APi,采用的则是okhttp

OkHttp是一个相对成熟的解决方案,Android4.4的源码中可以看到HttpURLConnection已经替换成OkHttp实现了。
OkHttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。
使用 OkHttp 无需重写您程序中的网络代码。OkHttp实现了几乎和java.net.HttpURLConnection一样的API。如果你用了 Apache HttpClient,则OkHttp也提供了一个对应的okhttp-apache 模块。

OkHttp 源码解析

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

  1. 加入网络权限
    在 AndroidManifest.xml 文件中加入如下:

  2. 添加 OkHttp 库的依赖
    在当前使用的 module 下的 build.gradle 中加入如下:

需要注意的是,response.body().string() 只能调用一次,否则会抛出如下异常:

  1. 同步 GET 请求的步骤:
  2. private void syncGetRequestByOkHttp() throws Exception {
  3. OkHttpClient client = new OkHttpClient();//1. 创建 OkHttpClient 对象
  4. //2. 创建 Request 对象,然后通过 Builder() 链式调用可以设置请求 url、header、method 等。
  5. Request request = new Request.Builder()
  6. .url("https://www.baidu.com")
  7. .build();
  8. //调用 OkHttpClient 对象的 newCall() 方法创建一个 Call 对象
  9. Call call = client.newCall(request);
  10. //4. 调用 Call 对象的 execute() 方法发起一个请求,并获取服务器返回的数据。
  11. Response response = call.execute();
  12. //Response 就是返回的数据
  13. if (response.isSuccessful()) {
  14. Log.i(TAG, "syncGetRequestByOkHttp data-->" + response.body().string());
  15. } else {
  16. throw new IOException("Unexpected code " + response);
  17. }
  18. }

希望返回 String,则调用 response.body().string(),适用于不超过 1 MB 的数据。
希望返回输入流,则调用 response.body().byteStream(),适用于超过 1 MB 的数据,例如下载文件。
希望返回二进制字节数组,则调用 response.body().bytes()

  1. //异步 GET 请求
  2. 异步 GET 请求与同步 GET 请求的代码差不多,区别是:
  3. 异步方法不需要在子线程中执行,因为 enqueue() 方法内部已经有一个线程池去执行。
  4. 返回的数据在 onResponse() 方法中,由于内部是通过线程池去执行的,所以该方法也在子线程。如果需要操作 UI,需要使用 handler 等切换到主线程。
  5. private void asyncGetRequestByOkHttp() {
  6. OkHttpClient client = new OkHttpClient();
  7. Request request = new Request.Builder()
  8. .url("https://www.baidu.com")
  9. .build();
  10. Call call = client.newCall(request);
  11. //将同步方法 execute() 换成异步方法 enqueue()。
  12. call.enqueue(new Callback() {
  13. @Override
  14. public void onFailure(Call call, IOException e) {
  15. }
  16. //
  17. @Override
  18. public void onResponse(Call call, Response response) throws IOException {
  19. if (response.isSuccessful()) {
  20. Log.i(TAG, "onResponse data-->" + response.body().string());
  21. } else {
  22. throw new IOException("Unexpected code " + response);
  23. }
  24. }
  25. });
  26. }
  1. // 异步post请求
  2. POST 请求与 GET 请求的区别是 POST 请求需要构建一个 RequestBody 来存放请求参数,然后在 Request.Builder 中调用 post 方法,并传入 RequestBody 对象
  3. private void asyncPostRequestByOkHttp() {
  4. OkHttpClient client = new OkHttpClient();
  5. //构建Requestbody
  6. RequestBody formBody = new FormBody.Builder()
  7. .add("username", "wildma")
  8. .add("password", "123456")
  9. .build();
  10. // 在Request.builder 中 调用post 请求,并传入 RequestBody对象
  11. Request request = new Request.Builder()
  12. .url("https://postman-echo.com/post")
  13. .post(formBody)
  14. .build();
  15. Call call = client.newCall(request);
  16. call.enqueue(new Callback() {
  17. @Override
  18. public void onFailure(Call call, IOException e) {
  19. }
  20. @Override
  21. public void onResponse(Call call, Response response) throws IOException {
  22. if (response.isSuccessful()) {
  23. Log.i(TAG, "onResponse data-->" + response.body().string());
  24. } else {
  25. throw new IOException("Unexpected code " + response);
  26. }
  27. }
  28. });
  29. }

OkHttp 是一个设计得非常优秀的框架。该框架运用了很多设计模式,例如建造者模式、责任链模式等等。
知道了 OkHttp 的核心是拦截器,这里采用的就是责任链模式,每个拦截器负责相应的功能,发起请求的时候由上往下依次执行每个拦截器,响应的数据则层层往上传递。

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

https://www.jianshu.com/p/7624b45fbdc1

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

https://www.jianshu.com/p/2663ce3da0db

Android Studio 爬虫 之 简单实现使用 jsoup/okhttp3 爬取购物商品信息的案例demo(附有详细步骤)

https://blog.csdn.net/u014361280/article/details/108124146?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-5.no_search_link&spm=1001.2101.3001.4242.4

https://blog.csdn.net/PenTablet/article/details/78273444?locationNum=2&fps=1

【商场系列】
https://www.cnblogs.com/nylcy/p/6599245.html

【OKhttp简单应用】

——-记得测试手机是否正常联网

https://www.bilibili.com/video/BV1Jb4y187C4?p=69
简单测试网址:
https://www.httpbin.org/#/Status_codes

开启网络权限

依赖:

implementation(“com.squareup.okhttp3:okhttp:4.7.2”)

  1. // MainActivity.java
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.View;
  6. import android.widget.ThemedSpinnerAdapter;
  7. import java.io.IOException;
  8. import okhttp3.Call;
  9. import okhttp3.OkHttpClient;
  10. import okhttp3.Request;
  11. import okhttp3.Response;
  12. public class MainActivity extends AppCompatActivity {
  13. private OkHttpClient okHttpClient;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. okHttpClient = new OkHttpClient();
  19. }
  20. public void getSync(View view) {
  21. // 要在子线程中开启网络请求
  22. new Thread(){
  23. @Override
  24. public void run() {
  25. Request request = new Request.Builder()
  26. .url("https://tianqiapi.com/api?unescape=1&version=v1&appid=71834655&appsecret=DIwH2ihM&city="+"北京")
  27. .build();
  28. Call call = okHttpClient.newCall(request);
  29. try {
  30. Response response=call.execute();
  31. Log.e("MainActivity","getSync"+response.body().string());
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }.start();
  37. }
  38. }
  1. //activity_main.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context=".MainActivity">
  9. <Button
  10. android:id="@+id/button0"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:onClick="getSync"
  14. android:text="get同步请求"
  15. />
  16. </androidx.constraintlayout.widget.ConstraintLayout>

访问天气API——————-

导入依赖
implementation(“com.squareup.okhttp3:okhttp:4.9.0”)
implementation ‘com.google.code.gson:gson:2.8.5’

  1. // main_activity.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. android:orientation="vertical"
  9. android:background="#74B6A7"
  10. android:gravity="center_horizontal"
  11. tools:context=".MainActivity">
  12. <!--单独实现靠左-->
  13. <RelativeLayout
  14. android:layout_width="match_parent"
  15. android:layout_height="wrap_content">
  16. <androidx.appcompat.widget.AppCompatSpinner
  17. android:id="@+id/sp_city"
  18. android:layout_width="100dp"
  19. android:layout_height="48dp"
  20. android:entries="@array/cities"
  21. android:spinnerMode="dropdown"
  22. />
  23. </RelativeLayout>
  24. <ImageView
  25. android:layout_width="65dp"
  26. android:layout_height="65dp"
  27. android:id="@+id/iv_weather"
  28. android:src="@drawable/biz_plugin_weather_yin"/>
  29. <TextView
  30. android:layout_width="wrap_content"
  31. android:layout_height="wrap_content"
  32. android:id="@+id/tv_tem"
  33. android:textSize="20dp"
  34. android:text="31"
  35. android:textColor="@color/white"
  36. />
  37. <TextView
  38. android:layout_width="wrap_content"
  39. android:layout_height="wrap_content"
  40. android:id="@+id/tv_weather"
  41. android:textSize="20dp"
  42. android:text="阴转多云(2021-07-25 星期三)"
  43. android:textColor="@color/white"
  44. android:layout_marginTop="10dp"
  45. />
  46. <TextView
  47. android:layout_width="wrap_content"
  48. android:layout_height="wrap_content"
  49. android:id="@+id/tv_tem_low_high"
  50. android:textSize="20dp"
  51. android:text="25°C - 33°C"
  52. android:textColor="@color/white"
  53. android:layout_marginTop="10dp"
  54. />
  55. <TextView
  56. android:layout_width="wrap_content"
  57. android:layout_height="wrap_content"
  58. android:id="@+id/tv_win"
  59. android:textSize="20dp"
  60. android:text="南风3-4级"
  61. android:textColor="@color/white"
  62. android:layout_marginTop="10dp"
  63. />
  64. <TextView
  65. android:layout_width="wrap_content"
  66. android:layout_height="wrap_content"
  67. android:id="@+id/tv_air"
  68. android:textSize="10dp"
  69. android:text="空气:53 良 \n空气好,适应外出"
  70. android:gravity="center"
  71. android:textColor="@color/white"
  72. android:layout_marginTop="10dp"
  73. />
  74. <!--未来天气-->
  75. <androidx.recyclerview.widget.RecyclerView
  76. android:layout_marginTop="10dp"
  77. android:layout_width="match_parent"
  78. android:layout_height="match_parent"
  79. android:id="@+id/rlv_future_weather"
  80. />
  81. </LinearLayout>
  1. // Main_activity.java
  2. import androidx.annotation.NonNull;
  3. import androidx.appcompat.app.AppCompatActivity;
  4. import androidx.appcompat.widget.AppCompatSpinner;
  5. import androidx.recyclerview.widget.LinearLayoutManager;
  6. import androidx.recyclerview.widget.RecyclerView;
  7. import android.os.Bundle;
  8. import android.os.Handler;
  9. import android.os.Looper;
  10. import android.os.Message;
  11. import android.util.Log;
  12. import android.view.View;
  13. import android.widget.AdapterView;
  14. import android.widget.ArrayAdapter;
  15. import android.widget.ImageView;
  16. import android.widget.TextView;
  17. import com.example.weather_study.Adapter.FutureWeatherAdapter;
  18. import com.example.weather_study.bean.DayWeatherBean;
  19. import com.example.weather_study.bean.WeatherBean;
  20. import com.example.weather_study.util.NetUtil;
  21. import com.google.gson.Gson;
  22. import java.util.List;
  23. public class MainActivity extends AppCompatActivity {
  24. // 1、定义
  25. private AppCompatSpinner mSpinner;
  26. // 2、适配器
  27. private ArrayAdapter<String> mSpAdater;
  28. //3 、数据资源
  29. private String[] mCities;
  30. // 添加组件进来
  31. private TextView tvWeather,tvTem,tvTemLowHigh,tvWin,tvAir;
  32. private ImageView ivWeather;
  33. private RecyclerView rlvFutureWeather;
  34. private FutureWeatherAdapter mWeatherAdapter;
  35. // 创建Handler
  36. private Handler mHandler = new Handler(Looper.myLooper()){
  37. @Override
  38. public void handleMessage(@NonNull Message msg) {
  39. super.handleMessage(msg);
  40. if(msg.what==0){
  41. String weather = (String) msg.obj;
  42. Log.e("fan","---主线程收到天气数据----weather----"+weather);
  43. // json解析
  44. Gson gson = new Gson();
  45. WeatherBean weatherBean = gson.fromJson(weather,WeatherBean.class);
  46. Log.e("fan","--解析后的数据----weather----"+weatherBean.toString());
  47. updateUIOfWeather(weatherBean);
  48. }
  49. }
  50. };
  51. private void updateUIOfWeather(WeatherBean weatherBean) {
  52. // 获取数据第一步,应该是判断是否为空
  53. if(weatherBean==null) return;
  54. //获取 天 数据
  55. List<DayWeatherBean> dayWeathers = weatherBean.getDayWeatherBeanList();
  56. DayWeatherBean todayWeather = dayWeathers.get(0);
  57. if(todayWeather==null) return;
  58. //把数据展示
  59. tvTem.setText(todayWeather.getTem());
  60. tvWeather.setText(todayWeather.getWea()+"("+todayWeather.getDate()+todayWeather.getWeek()+")");
  61. tvTem.setText(todayWeather.getTem());
  62. tvWeather.setText(todayWeather.getWea()+"("+todayWeather.getDate()+todayWeather.getWeek()+")");
  63. tvTemLowHigh.setText(todayWeather.getTem2()+"~"+ todayWeather.getTem1());
  64. tvWin.setText(todayWeather.getWin()[0]+todayWeather.getWinSpeed());
  65. tvAir.setText("空气:"+todayWeather.getAir()+ todayWeather.getAirLevel()+"\n"+todayWeather.getAirTips());
  66. //
  67. ivWeather.setImageResource(getImgResOfWeather(todayWeather.getWea_Img()));
  68. // 未来天气的展示
  69. // 先要删除当天的天气
  70. dayWeathers.remove(0);
  71. //设置数据适配器,布局方向
  72. mWeatherAdapter = new FutureWeatherAdapter(this,dayWeathers);
  73. rlvFutureWeather.setAdapter(mWeatherAdapter);
  74. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
  75. rlvFutureWeather.setLayoutManager(linearLayoutManager);
  76. }
  77. // 这个API 没有返回图片,只返回了一个 标志位,需要自己将本地图片和标志 一一对应
  78. private int getImgResOfWeather(String weaStr){
  79. int result = 0;
  80. switch (weaStr){
  81. case "qing":
  82. result = R.drawable.biz_plugin_weather_qing;
  83. break;
  84. case "yin":
  85. result = R.drawable.biz_plugin_weather_yin;
  86. break;
  87. case "yu":
  88. result = R.drawable.biz_plugin_weather_yu ;
  89. break;
  90. case"bingbao":
  91. result = R.drawable.biz_plugin_weather_bingbao;
  92. break;
  93. case "wu":
  94. result = R.drawable.biz_plugin_weather_wu;
  95. break;
  96. case "shachen":
  97. result =R.drawable.biz_plugin_weather_shachen;
  98. break;
  99. case "lei":
  100. result = R.drawable.biz_plugin_weather_lei;
  101. break;
  102. default:
  103. result=R.drawable.biz_plugin_weather_xue ;
  104. break;
  105. }
  106. return result;
  107. }
  108. @Override
  109. protected void onCreate(Bundle savedInstanceState) {
  110. super.onCreate(savedInstanceState);
  111. setContentView(R.layout.activity_main);
  112. initView(); // 刚开始因为没有用这个方法,吃了好多苦
  113. }
  114. private void initView(){
  115. mSpinner = findViewById(R.id.sp_city);
  116. mCities = getResources().getStringArray(R.array.cities);// 提前写好的城市名
  117. mSpAdater = new ArrayAdapter<>(this,R.layout.sp_item_layout,mCities);
  118. mSpinner.setAdapter(mSpAdater);
  119. // 点击事件
  120. mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
  121. @Override
  122. public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
  123. String selectedCity = mCities[position];
  124. getWeatherOfCity(selectedCity); // 调用方法
  125. }
  126. @Override
  127. public void onNothingSelected(AdapterView<?> adapterView) {
  128. }
  129. });
  130. tvWeather =findViewById(R.id.tv_weather);
  131. tvAir = findViewById(R.id.tv_air);
  132. tvTem = findViewById(R.id.tv_tem);
  133. tvTemLowHigh = findViewById(R.id.tv_tem_low_high);
  134. tvWin = findViewById(R.id.tv_win);
  135. ivWeather=findViewById(R.id.iv_weather);
  136. rlvFutureWeather = findViewById(R.id.rlv_future_weather);
  137. }
  138. private void getWeatherOfCity(String selectedCity) {
  139. // 开启子线程,请求网络
  140. new Thread(new Runnable() {
  141. @Override
  142. public void run() {
  143. // 请求网络
  144. String weatherOfCity = NetUtil.getWeatherofCity(selectedCity);
  145. //使用handler 将数据传递给主线程
  146. Message message = Message.obtain();
  147. message.what=0;
  148. message.obj = weatherOfCity;
  149. mHandler.sendMessage(message);
  150. }
  151. }).start();
  152. }
  153. }

Json 数据解析
image.png

  1. //json 数据解析
  2. //DayWeatherBean.java
  3. import com.google.gson.annotations.SerializedName;
  4. import java.util.Arrays;
  5. import java.util.List;
  6. public class DayWeatherBean {
  7. @SerializedName("day")
  8. private String day;
  9. @SerializedName("date")
  10. private String date;
  11. @SerializedName("week")
  12. private String week;
  13. @SerializedName("wea")
  14. private String wea;
  15. @SerializedName("wea_img")
  16. private String wea_Img;
  17. @SerializedName("wea_day")
  18. private String weaDay;
  19. @SerializedName("tem")
  20. private String tem;
  21. @SerializedName("tem1")
  22. private String tem1;
  23. @SerializedName("tem2")
  24. private String tem2;
  25. @SerializedName("win")
  26. private String[] win;
  27. @SerializedName("win_speed")
  28. private String winSpeed;
  29. @SerializedName("air")
  30. private String air;
  31. @SerializedName("air_level")
  32. private String airLevel;
  33. @SerializedName("air_tips")
  34. private String airTips;
  35. //
  36. @SerializedName("index")
  37. private List<OtherTipsBean> mTipsBeans;
  38. public List<OtherTipsBean> getmTipsBeans() {
  39. return mTipsBeans;
  40. }
  41. public void setmTipsBeans(List<OtherTipsBean> mTipsBeans) {
  42. this.mTipsBeans = mTipsBeans;
  43. }
  44. public String getDay() {
  45. return day;
  46. }
  47. public void setDay(String day) {
  48. this.day = day;
  49. }
  50. public String getDate() {
  51. return date;
  52. }
  53. public void setDate(String date) {
  54. this.date = date;
  55. }
  56. public String getWeek() {
  57. return week;
  58. }
  59. public void setWeek(String week) {
  60. this.week = week;
  61. }
  62. public String getWea() {
  63. return wea;
  64. }
  65. public void setWea(String wea) {
  66. this.wea = wea;
  67. }
  68. public String getWea_Img() {
  69. return wea_Img;
  70. }
  71. public void setWea_Img(String wea_Img) {
  72. this.wea_Img = wea_Img;
  73. }
  74. public String getWeaDay() {
  75. return weaDay;
  76. }
  77. public void setWeaDay(String weaDay) {
  78. this.weaDay = weaDay;
  79. }
  80. public String getTem() {
  81. return tem;
  82. }
  83. public void setTem(String tem) {
  84. this.tem = tem;
  85. }
  86. public String getTem1() {
  87. return tem1;
  88. }
  89. public void setTem1(String tem1) {
  90. this.tem1 = tem1;
  91. }
  92. public String getTem2() {
  93. return tem2;
  94. }
  95. public void setTem2(String tem2) {
  96. this.tem2 = tem2;
  97. }
  98. public String[] getWin() {
  99. return win;
  100. }
  101. public void setWin(String[] win) {
  102. this.win = win;
  103. }
  104. public String getWinSpeed() {
  105. return winSpeed;
  106. }
  107. public void setWinSpeed(String winSpeed) {
  108. this.winSpeed = winSpeed;
  109. }
  110. public String getAir() {
  111. return air;
  112. }
  113. public void setAir(String air) {
  114. this.air = air;
  115. }
  116. public String getAirLevel() {
  117. return airLevel;
  118. }
  119. public void setAirLevel(String airLevel) {
  120. this.airLevel = airLevel;
  121. }
  122. public String getAirTips() {
  123. return airTips;
  124. }
  125. public void setAirTips(String airTips) {
  126. this.airTips = airTips;
  127. }
  128. @Override
  129. public String toString() {
  130. return "DayWeatherBean{" +
  131. "day='" + day + '\'' +
  132. ", date='" + date + '\'' +
  133. ", week='" + week + '\'' +
  134. ", wea='" + wea + '\'' +
  135. ", wea_Img='" + wea_Img + '\'' +
  136. ", weaDay='" + weaDay + '\'' +
  137. ", tem='" + tem + '\'' +
  138. ", tem1='" + tem1 + '\'' +
  139. ", tem2='" + tem2 + '\'' +
  140. ", win=" + Arrays.toString(win) +
  141. ", winSpeed='" + winSpeed + '\'' +
  142. ", air='" + air + '\'' +
  143. ", airLevel='" + airLevel + '\'' +
  144. ", airTips='" + airTips + '\'' +
  145. ", mTipsBeans=" + mTipsBeans +
  146. '}';
  147. }
  148. }
  1. // WeatherBean.java
  2. import com.google.gson.annotations.SerializedName;
  3. import java.util.List;
  4. public class WeatherBean {
  5. @SerializedName("cityid")
  6. private String cityId;
  7. @SerializedName("city")
  8. private String city;
  9. @SerializedName("update_time")
  10. private String updateTime;
  11. @SerializedName("data")
  12. private List<DayWeatherBean> dayWeatherBeanList;
  13. public String getCityId() {
  14. return cityId;
  15. }
  16. public void setCityId(String cityId) {
  17. this.cityId = cityId;
  18. }
  19. public String getCity() {
  20. return city;
  21. }
  22. public void setCity(String city) {
  23. this.city = city;
  24. }
  25. public String getUpdateTime() {
  26. return updateTime;
  27. }
  28. public void setUpdateTime(String updateTime) {
  29. this.updateTime = updateTime;
  30. }
  31. public List<DayWeatherBean> getDayWeatherBeanList() {
  32. return dayWeatherBeanList;
  33. }
  34. public void setDayWeatherBeanList(List<DayWeatherBean> dayWeatherBeanList) {
  35. this.dayWeatherBeanList = dayWeatherBeanList;
  36. }
  37. @Override
  38. public String toString() {
  39. return "WeatherBean{" +
  40. "cityId='" + cityId + '\'' +
  41. ", city='" + city + '\'' +
  42. ", updateTime='" + updateTime + '\'' +
  43. ", dayWeatherBeanList=" + dayWeatherBeanList +
  44. '}';
  45. }
  46. }
  1. // OtherTipsBean.java
  2. import com.google.gson.annotations.SerializedName;
  3. public class OtherTipsBean {
  4. @SerializedName("title")
  5. private String title;
  6. @SerializedName("level")
  7. private String level;
  8. @SerializedName("desc")
  9. private String desc;
  10. public String getTitle() {
  11. return title;
  12. }
  13. public void setTitle(String title) {
  14. this.title = title;
  15. }
  16. public String getLevel() {
  17. return level;
  18. }
  19. public void setLevel(String level) {
  20. this.level = level;
  21. }
  22. public String getDesc() {
  23. return desc;
  24. }
  25. public void setDesc(String desc) {
  26. this.desc = desc;
  27. }
  28. @Override
  29. public String toString() {
  30. return "OtherTipsBean{" +
  31. "title='" + title + '\'' +
  32. ", level='" + level + '\'' +
  33. ", desc='" + desc + '\'' +
  34. '}';
  35. }
  36. }
  1. // NetUtil.java
  2. import android.util.Log;
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import java.net.HttpURLConnection;
  8. import java.net.URL;
  9. public class NetUtil {
  10. //
  11. public static final String URL_WEATEHER_WITH_FUTURE = "https://tianqiapi.com/api?unescape=1&version=v1&appid=71834655&appsecret=DIwH2ihM";
  12. public static String doGet(String urlStr){
  13. String result = "";
  14. HttpURLConnection connection = null;
  15. InputStreamReader inputStreamReader=null;
  16. BufferedReader bufferedReader=null;
  17. // 连接网络
  18. try {
  19. URL url = new URL(urlStr);
  20. connection = (HttpURLConnection) url.openConnection();
  21. connection.setRequestMethod("GET");
  22. connection.setConnectTimeout(5000);
  23. // 从连接中读取数据
  24. InputStream inputStream = connection.getInputStream();
  25. inputStreamReader = new InputStreamReader(inputStream);
  26. bufferedReader = new BufferedReader(inputStreamReader);
  27. StringBuilder stringBuilder = new StringBuilder();
  28. String line="";
  29. while((line=bufferedReader.readLine())!=null){
  30. stringBuilder.append(line);
  31. }
  32. result = stringBuilder.toString();
  33. } catch (IOException e) {
  34. e.printStackTrace();
  35. }finally {
  36. if(connection!=null){
  37. connection.disconnect();
  38. }
  39. if(inputStreamReader!=null){
  40. try {
  41. inputStreamReader.close();
  42. } catch (IOException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. if(bufferedReader!=null){
  47. try {
  48. bufferedReader.close();
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }
  54. return result;
  55. }
  56. public static String getWeatherofCity(String city){
  57. String weatherUrl = URL_WEATEHER_WITH_FUTURE+ "&city=" +city;
  58. Log.d("fan","-----weatherUrl------"+weatherUrl);
  59. String weatherResult = doGet(weatherUrl);
  60. Log.d("fan","-----weatherResult------"+weatherResult);
  61. return weatherResult;
  62. }
  63. }
  1. // FutureWeatherAdapter.java
  2. import android.content.Context;
  3. import android.view.LayoutInflater;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.ImageView;
  7. import android.widget.TextView;
  8. import androidx.annotation.NonNull;
  9. import androidx.recyclerview.widget.RecyclerView;
  10. import com.example.weather_study.R;
  11. import com.example.weather_study.bean.DayWeatherBean;
  12. import java.util.List;
  13. public class FutureWeatherAdapter extends RecyclerView.Adapter<FutureWeatherAdapter.WeatherViewHolder> {
  14. private Context mContext;
  15. private List<DayWeatherBean> mWeatherBeans;
  16. public FutureWeatherAdapter(Context context,List<DayWeatherBean> weatherBeans){
  17. mContext=context;
  18. this.mWeatherBeans = weatherBeans;
  19. }
  20. @NonNull
  21. @Override
  22. public WeatherViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  23. //首先需要布局文件
  24. View view=LayoutInflater.from(mContext).inflate(R.layout.weather_item_layout,parent,false);
  25. WeatherViewHolder weatherViewHolder = new WeatherViewHolder(view);
  26. return weatherViewHolder;
  27. }
  28. @Override
  29. public void onBindViewHolder(@NonNull WeatherViewHolder holder, int position) {
  30. DayWeatherBean weatherBean = mWeatherBeans.get(position);
  31. holder.tvWeather.setText(weatherBean.getWea()+"("+weatherBean.getDate()+weatherBean.getWeek()+")");
  32. holder.tvTem.setText(weatherBean.getTem());
  33. holder.tvTemLowHigh.setText(weatherBean.getTem2()+"~"+weatherBean.getTem1());
  34. holder.tvWin.setText(weatherBean.getWin()[0]+weatherBean.getWinSpeed());
  35. holder.tvAir.setText("空气:"+weatherBean.getAir()+weatherBean.getAirLevel());
  36. // 图片
  37. holder.ivWeather.setImageResource(getImgResOfWeather(weatherBean.getWea_Img()));
  38. }
  39. private int getImgResOfWeather(String weaStr){
  40. int result = 0;
  41. switch (weaStr){
  42. case "qing":
  43. result = R.drawable.biz_plugin_weather_qing;
  44. break;
  45. case "yin":
  46. result = R.drawable.biz_plugin_weather_yin;
  47. break;
  48. case "yu":
  49. result = R.drawable.biz_plugin_weather_yu ;
  50. break;
  51. case"bingbao":
  52. result = R.drawable.biz_plugin_weather_bingbao;
  53. break;
  54. case "wu":
  55. result = R.drawable.biz_plugin_weather_wu;
  56. break;
  57. case "shachen":
  58. result =R.drawable.biz_plugin_weather_shachen;
  59. break;
  60. case "lei":
  61. result = R.drawable.biz_plugin_weather_lei;
  62. break;
  63. default:
  64. result=R.drawable.biz_plugin_weather_xue ;
  65. break;
  66. }
  67. return result;
  68. }
  69. @Override
  70. public int getItemCount() {
  71. return mWeatherBeans==null ? 0 : mWeatherBeans.size();
  72. }
  73. class WeatherViewHolder extends RecyclerView.ViewHolder{
  74. // 引入item中的组件
  75. TextView tvWeather,tvTem,tvTemLowHigh,tvWin,tvAir;
  76. ImageView ivWeather;
  77. public WeatherViewHolder(@NonNull View itemView) {
  78. super(itemView);
  79. //根据ID 找到
  80. tvWeather = itemView.findViewById(R.id.tv_weather);
  81. tvAir = itemView.findViewById(R.id.tv_air);
  82. tvTem = itemView.findViewById(R.id.tv_tem);
  83. tvTemLowHigh = itemView.findViewById(R.id.tv_tem_low_high);
  84. tvWin = itemView.findViewById(R.id.tv_win);
  85. ivWeather=itemView.findViewById(R.id.iv_weather);
  86. }
  87. }
  88. }
  1. // weather_item_layout.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:orientation="vertical"
  6. android:layout_width="150dp"
  7. android:layout_height="250dp"
  8. android:gravity="center_horizontal"
  9. tools:background="#74B6A7">
  10. <ImageView
  11. android:layout_width="45dp"
  12. android:layout_height="45dp"
  13. android:id="@+id/iv_weather"
  14. android:src="@drawable/biz_plugin_weather_yin"/>
  15. <TextView
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. android:id="@+id/tv_tem"
  19. android:textSize="20dp"
  20. android:text="31"
  21. android:textColor="@color/white"
  22. android:layout_marginTop="10dp"
  23. />
  24. <TextView
  25. android:layout_width="wrap_content"
  26. android:layout_height="wrap_content"
  27. android:id="@+id/tv_weather"
  28. android:textSize="10dp"
  29. android:text="阴转多云(2021-07-25 星期三)"
  30. android:textColor="@color/white"
  31. android:layout_marginTop="10dp"
  32. />
  33. <TextView
  34. android:layout_width="wrap_content"
  35. android:layout_height="wrap_content"
  36. android:id="@+id/tv_tem_low_high"
  37. android:textSize="10dp"
  38. android:text="25°C - 33°C"
  39. android:textColor="@color/white"
  40. android:layout_marginTop="10dp"
  41. />
  42. <TextView
  43. android:layout_width="wrap_content"
  44. android:layout_height="wrap_content"
  45. android:id="@+id/tv_win"
  46. android:textSize="10dp"
  47. android:text="南风3-4级"
  48. android:textColor="@color/white"
  49. android:layout_marginTop="10dp"
  50. />
  51. <TextView
  52. android:layout_width="wrap_content"
  53. android:layout_height="wrap_content"
  54. android:id="@+id/tv_air"
  55. android:textSize="10dp"
  56. android:text="空气:53 良 \n空气好,适应外出"
  57. android:gravity="center"
  58. android:textColor="@color/white"
  59. android:layout_marginTop="10dp"
  60. />
  61. </LinearLayout>
  1. // sp_item_layout.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <!---改变下拉框中的文字的颜色样式-->
  4. <TextView xmlns:android="http://schemas.android.com/apk/res/android"
  5. android:layout_width="match_parent"
  6. android:layout_height="48dp"
  7. android:id="@android:id/text1"
  8. android:textColor="@color/white"
  9. android:text="广州"
  10. android:textSize="20sp"
  11. android:gravity="center_vertical"
  12. android:paddingLeft="10dp"
  13. android:background="@color/purple_200"
  14. >
  15. </TextView>
  1. // strings.xml
  2. <resources>
  3. <string name="app_name">WeatherApp</string>
  4. <string-array name="cities">
  5. <item>武汉</item>
  6. <item>北京</item>
  7. <item>深圳</item>
  8. <item>广州</item>
  9. <item>上海</item>
  10. </string-array>
  11. </resources>

  1. // colors.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <resources>
  4. <color name="purple_200">#FFBB86FC</color>
  5. <color name="purple_500">#FF6200EE</color>
  6. <color name="purple_700">#FF3700B3</color>
  7. <color name="teal_200">#FF03DAC5</color>
  8. <color name="teal_700">#FF018786</color>
  9. <color name="black">#FF000000</color>
  10. <color name="white">#FFFFFFFF</color>
  11. </resources>

————换成okhttp 的方式请求

  1. //OKHttpUtil.java
  2. import android.util.Log;
  3. import androidx.annotation.NonNull;
  4. import java.io.IOException;
  5. import java.util.concurrent.TimeUnit;
  6. import okhttp3.Call;
  7. import okhttp3.Callback;
  8. import okhttp3.OkHttpClient;
  9. import okhttp3.Request;
  10. import okhttp3.Response;
  11. public class OKHttpUtil {
  12. public static final String URL_WEATEHER_WITH_FUTURE = "https://tianqiapi.com/api?unescape=1&version=v1&appid=71834655&appsecret=DIwH2ihM";
  13. // 注意这里的接口回调
  14. public static void doGet(String urlStr,Callback callback) {
  15. OkHttpClient okHttpClient = new OkHttpClient()
  16. .newBuilder()
  17. .callTimeout(10, TimeUnit.SECONDS)
  18. .readTimeout(20,TimeUnit.SECONDS)
  19. .build();
  20. Request request = new Request.Builder()
  21. .get()
  22. .url(urlStr)
  23. .build();
  24. Call call = okHttpClient.newCall(request);
  25. call.enqueue(callback);
  26. }
  27. public static void getWeatherofCity(String city,Callback callback){
  28. String weatherUrl = URL_WEATEHER_WITH_FUTURE+ "&city=" +city;
  29. Log.e("fan","-----weatherUrl------"+weatherUrl);
  30. doGet(weatherUrl,callback);
  31. }
  32. }

——-然后要修改的地方

  1. // MainActivity.java
  2. private void getWeatherOfCity(String selectedCity) {
  3. // 开启子线程,请求网络
  4. // new Thread(new Runnable() {
  5. // @Override
  6. // public void run() {
  7. // // 请求网络
  8. // String weatherOfCity = NetUtil.getWeatherofCity(selectedCity);
  9. //
  10. // //使用handler 将数据传递给主线程
  11. // Message message = Message.obtain();
  12. // message.what=0;
  13. // message.obj = weatherOfCity;
  14. // mHandler.sendMessage(message);
  15. //
  16. //
  17. // }
  18. // }).start();
  19. // 使用okhttp ,并采用接口回调的方式
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. OKHttpUtil.getWeatherofCity(selectedCity, new Callback() {
  24. @Override
  25. public void onFailure(@NonNull Call call, @NonNull IOException e) {
  26. String error = e.getMessage();
  27. Log.e("OKHTTP",error);
  28. }
  29. @Override
  30. public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
  31. String data=response.body().string();
  32. Message message = Message.obtain();
  33. message.what=0;
  34. message.obj = data;
  35. mHandler.sendMessage(message);
  36. Log.e("OKHTTP",data);
  37. }
  38. });
  39. }
  40. }).start();
  41. }

————增加一个点击弹出tips 的实现———————-

  1. // TipsActivity.java
  2. import android.content.Intent;
  3. import android.os.Bundle;
  4. import androidx.annotation.Nullable;
  5. import androidx.appcompat.app.AppCompatActivity;
  6. import androidx.recyclerview.widget.LinearLayoutManager;
  7. import androidx.recyclerview.widget.RecyclerView;
  8. import com.example.weather_study.Adapter.TipsAdapter;
  9. import com.example.weather_study.R;
  10. import com.example.weather_study.bean.DayWeatherBean;
  11. public class TipsActivity extends AppCompatActivity {
  12. //控件
  13. private RecyclerView rlvTips;
  14. private TipsAdapter mTipsAdapter;
  15. @Override
  16. protected void onCreate(@Nullable Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_tips);
  19. rlvTips = findViewById(R.id.rlv_tips);
  20. // 数据需要通过传递
  21. Intent intent = getIntent();
  22. // 需要在JavaBean 中实现序列化
  23. // 在MainActivity.java 的tips点击数据中 拿到数据
  24. DayWeatherBean weatherBean = (DayWeatherBean) intent.getSerializableExtra("tips");
  25. // 判断数据是否为空
  26. if(weatherBean==null) return;
  27. mTipsAdapter = new TipsAdapter(this,weatherBean.getmTipsBeans());
  28. rlvTips.setAdapter(mTipsAdapter);
  29. rlvTips.setLayoutManager(new LinearLayoutManager(this));
  30. }
  31. }

记得在AndroidManifest.xml 中注册这个Activity

  1. // activity_tips.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical"
  7. xmlns:tools="http://schemas.android.com/tools"
  8. tools:context=".Activity.TipsActivity"
  9. android:background="#74B6A7"
  10. >
  11. <TextView
  12. android:layout_width="match_parent"
  13. android:layout_height="48dp"
  14. android:text="生活小提示"
  15. android:textColor="@color/white"
  16. android:textSize="25dp"
  17. android:gravity="center"
  18. android:layout_marginTop="20dp"
  19. />
  20. <androidx.recyclerview.widget.RecyclerView
  21. android:layout_width="match_parent"
  22. android:layout_height="match_parent"
  23. android:id="@+id/rlv_tips"
  24. />
  25. </LinearLayout>
  1. // tips_item_layout.xml
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content"
  6. android:background="#74B6A7"
  7. android:padding="10dp"
  8. xmlns:tools="http://schemas.android.com/tools"
  9. >
  10. <TextView
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:id="@+id/tv_title"
  14. android:textColor="@color/white"
  15. android:textSize="20dp"
  16. tools:text="紫外线强度"
  17. />
  18. <TextView
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:id="@+id/tv_level"
  22. android:textColor="@color/white"
  23. android:textSize="20dp"
  24. tools:text="一般"
  25. android:layout_alignParentRight="true"
  26. />
  27. <TextView
  28. android:layout_width="wrap_content"
  29. android:layout_height="wrap_content"
  30. android:id="@+id/tv_desc"
  31. android:textColor="@color/white"
  32. android:textSize="15dp"
  33. tools:text="擦涂SPF大于15、PA+防嗮护肤品"
  34. android:layout_marginTop="35dp"
  35. />
  36. </RelativeLayout>
  1. // TipsAdapter.java
  2. import android.content.Context;
  3. import android.view.LayoutInflater;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.TextView;
  7. import androidx.annotation.NonNull;
  8. import androidx.recyclerview.widget.RecyclerView;
  9. import com.example.weather_study.R;
  10. import com.example.weather_study.bean.OtherTipsBean;
  11. import java.util.List;
  12. public class TipsAdapter extends RecyclerView.Adapter<TipsAdapter.TipsViewHolder> {
  13. //
  14. private Context mContext ;
  15. private List<OtherTipsBean> mTipsBean;
  16. //
  17. public TipsAdapter(Context mContext, List<OtherTipsBean> mTipsBean) {
  18. this.mContext = mContext;
  19. this.mTipsBean = mTipsBean;
  20. }
  21. @NonNull
  22. @Override
  23. public TipsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  24. View view = LayoutInflater.from(mContext).inflate(R.layout.tips_item_layout,parent,false);
  25. return new TipsViewHolder(view);
  26. }
  27. @Override
  28. public void onBindViewHolder(@NonNull TipsViewHolder holder, int position) {
  29. // 拿到要显示的数据
  30. OtherTipsBean otherTipsBean = mTipsBean.get(position);
  31. //设置要显示的文字
  32. holder.tvTitle.setText(otherTipsBean.getTitle());
  33. holder.tvLevel.setText(otherTipsBean.getLevel());
  34. holder.tvDesc.setText(otherTipsBean.getDesc());
  35. }
  36. @Override
  37. public int getItemCount() {
  38. return mTipsBean==null ? 0 : mTipsBean.size();
  39. }
  40. class TipsViewHolder extends RecyclerView.ViewHolder{
  41. //引入内容要显示的组件
  42. TextView tvTitle,tvDesc,tvLevel;
  43. public TipsViewHolder(@NonNull View itemView) {
  44. super(itemView);
  45. // 绑定
  46. tvDesc =itemView.findViewById(R.id.tv_desc);
  47. tvLevel = itemView.findViewById(R.id.tv_level);
  48. tvTitle = itemView.findViewById(R.id.tv_title);
  49. }
  50. }
  51. }

javaBean实现序列化接口

  1. public class DayWeatherBean implements Serializable {
  2. public class OtherTipsBean implements Serializable {
  3. public class WeatherBean implements Serializable {
  1. // MainActivity.java
  2. // 主要改变为 加一个 tips 点击事件
  3. import androidx.annotation.NonNull;
  4. import androidx.appcompat.app.AppCompatActivity;
  5. import androidx.appcompat.widget.AppCompatSpinner;
  6. import androidx.recyclerview.widget.LinearLayoutManager;
  7. import androidx.recyclerview.widget.RecyclerView;
  8. import android.content.Intent;
  9. import android.os.Bundle;
  10. import android.os.Handler;
  11. import android.os.Looper;
  12. import android.os.Message;
  13. import android.util.Log;
  14. import android.view.View;
  15. import android.widget.AdapterView;
  16. import android.widget.ArrayAdapter;
  17. import android.widget.ImageView;
  18. import android.widget.TextView;
  19. import com.example.weather_study.Adapter.FutureWeatherAdapter;
  20. import com.example.weather_study.R;
  21. import com.example.weather_study.bean.DayWeatherBean;
  22. import com.example.weather_study.bean.WeatherBean;
  23. import com.example.weather_study.util.NetUtil;
  24. import com.example.weather_study.util.OKHttpUtil;
  25. import com.google.gson.Gson;
  26. import java.io.IOException;
  27. import java.util.List;
  28. import okhttp3.Call;
  29. import okhttp3.Callback;
  30. import okhttp3.Response;
  31. public class MainActivity extends AppCompatActivity {
  32. // 1、定义
  33. private AppCompatSpinner mSpinner;
  34. // 2、适配器
  35. private ArrayAdapter<String> mSpAdater;
  36. //3 、数据资源
  37. private String[] mCities;
  38. // 添加组件进来
  39. private TextView tvWeather,tvTem,tvTemLowHigh,tvWin,tvAir;
  40. private ImageView ivWeather;
  41. private RecyclerView rlvFutureWeather;
  42. private FutureWeatherAdapter mWeatherAdapter;
  43. // 全局变量
  44. DayWeatherBean todayWeather;
  45. // 创建Handler
  46. private Handler mHandler = new Handler(Looper.myLooper()){
  47. @Override
  48. public void handleMessage(@NonNull Message msg) {
  49. super.handleMessage(msg);
  50. if(msg.what==0){
  51. String weather = (String) msg.obj;
  52. Log.e("fan","---主线程收到天气数据----weather----"+weather);
  53. // json解析
  54. Gson gson = new Gson();
  55. WeatherBean weatherBean = gson.fromJson(weather,WeatherBean.class);
  56. Log.e("fan","--解析后的数据----weather----"+weatherBean.toString());
  57. updateUIOfWeather(weatherBean);
  58. }
  59. }
  60. };
  61. private void updateUIOfWeather(WeatherBean weatherBean) {
  62. // 获取数据第一步,应该是判断是否为空
  63. if(weatherBean==null) return;
  64. //获取 天 数据
  65. List<DayWeatherBean> dayWeathers = weatherBean.getDayWeatherBeanList();
  66. todayWeather = dayWeathers.get(0);
  67. if(todayWeather==null) return;
  68. //把数据展示
  69. tvTem.setText(todayWeather.getTem());
  70. tvWeather.setText(todayWeather.getWea()+"("+todayWeather.getDate()+todayWeather.getWeek()+")");
  71. tvTem.setText(todayWeather.getTem());
  72. tvWeather.setText(todayWeather.getWea()+"("+todayWeather.getDate()+todayWeather.getWeek()+")");
  73. tvTemLowHigh.setText(todayWeather.getTem2()+"~"+ todayWeather.getTem1());
  74. tvWin.setText(todayWeather.getWin()[0]+todayWeather.getWinSpeed());
  75. tvAir.setText("空气:"+todayWeather.getAir()+ todayWeather.getAirLevel()+"\n"+todayWeather.getAirTips());
  76. //
  77. ivWeather.setImageResource(getImgResOfWeather(todayWeather.getWea_Img()));
  78. // 未来天气的展示
  79. // 先要删除当天的天气
  80. dayWeathers.remove(0);
  81. //设置数据适配器,布局方向
  82. mWeatherAdapter = new FutureWeatherAdapter(this,dayWeathers);
  83. rlvFutureWeather.setAdapter(mWeatherAdapter);
  84. LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
  85. rlvFutureWeather.setLayoutManager(linearLayoutManager);
  86. }
  87. // 这个API 没有返回图片,只返回了一个 标志位,需要自己将本地图片和标志 一一对应
  88. private int getImgResOfWeather(String weaStr){
  89. int result = 0;
  90. switch (weaStr){
  91. case "qing":
  92. result = R.drawable.biz_plugin_weather_qing;
  93. break;
  94. case "yin":
  95. result = R.drawable.biz_plugin_weather_yin;
  96. break;
  97. case "yu":
  98. result = R.drawable.biz_plugin_weather_yu ;
  99. break;
  100. case"bingbao":
  101. result = R.drawable.biz_plugin_weather_bingbao;
  102. break;
  103. case "wu":
  104. result = R.drawable.biz_plugin_weather_wu;
  105. break;
  106. case "shachen":
  107. result =R.drawable.biz_plugin_weather_shachen;
  108. break;
  109. case "lei":
  110. result = R.drawable.biz_plugin_weather_lei;
  111. break;
  112. default:
  113. result=R.drawable.biz_plugin_weather_xue ;
  114. break;
  115. }
  116. return result;
  117. }
  118. @Override
  119. protected void onCreate(Bundle savedInstanceState) {
  120. super.onCreate(savedInstanceState);
  121. setContentView(R.layout.activity_main);
  122. initView(); // 刚开始因为没有用这个方法,吃了好多苦
  123. }
  124. private void initView(){
  125. mSpinner = findViewById(R.id.sp_city);
  126. mCities = getResources().getStringArray(R.array.cities);// 提前写好的城市名
  127. mSpAdater = new ArrayAdapter<>(this,R.layout.sp_item_layout,mCities);
  128. mSpinner.setAdapter(mSpAdater);
  129. // 点击事件
  130. mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){
  131. @Override
  132. public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
  133. String selectedCity = mCities[position];
  134. getWeatherOfCity(selectedCity); // 调用方法
  135. }
  136. @Override
  137. public void onNothingSelected(AdapterView<?> adapterView) {
  138. }
  139. });
  140. tvWeather =findViewById(R.id.tv_weather);
  141. tvAir = findViewById(R.id.tv_air);
  142. tvTem = findViewById(R.id.tv_tem);
  143. tvTemLowHigh = findViewById(R.id.tv_tem_low_high);
  144. tvWin = findViewById(R.id.tv_win);
  145. ivWeather=findViewById(R.id.iv_weather);
  146. rlvFutureWeather = findViewById(R.id.rlv_future_weather);
  147. // tips 点击事件
  148. tvAir.setOnClickListener(new View.OnClickListener(){
  149. @Override
  150. public void onClick(View v) {
  151. Intent intent = new Intent(MainActivity.this,TipsActivity.class);
  152. // 将数据传递给tipsActivity
  153. intent.putExtra("tips",todayWeather);
  154. startActivity(intent);
  155. }
  156. });
  157. }
  158. private void getWeatherOfCity(String selectedCity) {
  159. // 开启子线程,请求网络
  160. new Thread(new Runnable() {
  161. @Override
  162. public void run() {
  163. // 请求网络
  164. String weatherOfCity = NetUtil.getWeatherofCity(selectedCity);
  165. //使用handler 将数据传递给主线程
  166. Message message = Message.obtain();
  167. message.what=0;
  168. message.obj = weatherOfCity;
  169. mHandler.sendMessage(message);
  170. }
  171. }).start();
  172. }
  173. }

JAVA接口回调机制

10、Retrofit

也是 Square 公司开源的网络框架,它的底层就是基于 OkHttp 实现的,不过它比 OkHttp 使用更方便,也更适合进行 RESTful API 格式的请求。

https://www.jianshu.com/p/841e19c7f531

它相较于OkHttp主要做了三件事情:
1,线程切换。
2,数据解析。
3,请求参数可配置化。
https://www.jianshu.com/p/dc9f28f63666

11、Glide——图片加载

https://www.imooc.com/article/17662?block_id=tuijian_wz

使用Glide+Recycler加载展示网络图片精炼详解

https://www.imooc.com/article/27129

12、什么是MVVM框架

View层: xml, Activity,自定义控件等;
Model层: sqlite数据库, JavaBean,SharedPreference, sdcard,获取网络数据的api等
ViewModel层:独立的业务逻辑处理模块,部分参与业务逻辑的JavaBean

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

https://github.com/imyyq-star/MVVMArchitecture

13、Android插件化主流框架和实现原理

14、Android基础之编译打包签名流程

Android编译打包的步骤如下:
1.生成R.java文件
2.将aidl文件生成java文件
3.将java文件编译成class文件
4.将class文件打包成classes.dex文件
5.打包资源文件(res、assets、AndroidManifest.xml等)
6.生成未签名的apk文件
7.对apk进行签名

Android签名
Android签名就是用来保证文件的完整性以及有效性,防止文件被篡改,Android签名采用非对称加密算法,用KeyStore文件里面的私钥进行文件内容的加密,校验的时候用公钥进行解密。而直接对apk内容进行加密的话,apk内容太多,所以会先对apk文件里面的文件进行一个摘要处理。

原文链接:https://blog.csdn.net/zhqw_csdn/article/details/82785359

[

](https://blog.csdn.net/zhqw_csdn/article/details/82785359)

Kotlin协程和RxJava在不同业务场景下的使用体验

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

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

Android协程+Retrofit简化网络请求

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

Kotlin + 协程 + Retrofit + MVVM优雅的实现网络请求

https://www.jianshu.com/p/18f86faa0711

Kotlin协程:一个轻量级的线程框架

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

初探Android中Repository模式

https://www.jianshu.com/p/4679c384acae

RxJava学习指南

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

Android JetPack系列之——DataBinding

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

git

https://blog.csdn.net/lei_notes/article/details/53307917

Android开发-ButterKnife的导入与使用

ButterKnife,俗称黄油刀,在Android开发经常使用的一个专注于Android系统的View注入框架,简短findViewById来获取View对象的代码长度是它的功能之一,此外它还可以缩减监听事件的长度,并对其性能影响不大,但编译时间会有所加长,不过你是感受不出来的

https://blog.csdn.net/qq_41858698/article/details/105415170

安卓开发中常用的依赖(常更新)

https://blog.csdn.net/qq873044564/article/details/107167800

安卓9.0网络权限的新要求

https://blog.csdn.net/qq873044564/article/details/107175497

Android sudio 在Mac中快捷键

https://blog.csdn.net/u010818425/article/details/52266195

Android studio git 回滚文件到上一个版本的

https://blog.csdn.net/Rodulf/article/details/51243541?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase

push of current branch was rejected remote changes need to be merged before pushing

https://blog.csdn.net/weixin_44542261/article/details/119216383

https://blog.csdn.net/zhan107876/article/details/111184064