当前位置:首页 » 《随便一记》 » 正文

移动应用开发_su_zui的博客

2 人参与  2022年05月12日 11:09  分类 : 《随便一记》  评论

点击全文阅读


移动应用开发笔记

参考资料:

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

https://www.cnblogs.com/lwbqqyumidi/p/3769113.html

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

https://www.cnblogs.com/atree/archive/2010/12/10/android-thread.html

基础知识架构

img

android四大组件分别为activity、service、content provider、broadcast receiver。

本次项目框架

在这里插入图片描述

Activaty的生命周期

Activity是由Activity栈进管理,当来到一个新的Activity后,此Activity将被加入到Activity栈顶,之前的Activity位于此Activity底部。Acitivity一般意义上有四种状态:

1.当Activity位于栈顶时,此时正好处于屏幕最前方,此时处于运行状态

2.当Activity失去了焦点但仍然对用于可见(如栈顶的Activity是透明的或者栈顶Activity并不是铺满整个手机屏幕),此时处于暂停状态

3.当Activity被其他Activity完全遮挡,此时此Activity对用户不可见,此时处于停止状态

4.当Activity由于人为或系统原因(如低内存等)被销毁,此时处于销毁状态;

在每个不同的状态阶段,Adnroid系统对Activity内相应的方法进行了回调。因此,我们在程序中写Activity时,一般都是继承Activity类并重写相应的回调方法。

四个状态

  1. running->当前显示在屏幕的activity(位于任务栈的顶部),用户可见状态。
  2. paused->依旧在用户可见状态,但是界面焦点已经失去,此Activity无法与用户进行交互。
  3. stopped->用户看不到当前界面,也无法与用户进行交互 完全被覆盖.
  4. killed->当前界面被销毁,等待这系统被回收

在这里插入图片描述

(1)onCreate:create表示创建,这是Activity生命周期的第一个方法,也是我们在android开发中接触的最多的生命周期方法。它本身的作用是进行Activity的一些初始化工作,比如使用setContentView加载布局,对一些控件和变量进行初始化等。但也有很多人将很多与初始化无关的代码放在这,其实这是不规范的。此时Activity还在后台,不可见。所以动画不应该在这里初始化,因为看不到……

(2)onStart:start表示启动,这是Activity生命周期的第二个方法。此时Activity已经可见了,但是还没出现在前台,我们还看不到,无法与Activity交互。其实将Activity的初始化工作放在这也没有什么问题,放在onCreate中是由于官方推荐的以及我们开发的习惯。

(3)onResume:resume表示继续、重新开始,这名字和它的职责也相同。此时Activity经过前两个阶段的初始化已经蓄势待发。Activity在这个阶段已经出现在前台并且可见了。这个阶段可以打开独占设备

(4)onPause:pause表示暂停,当Activity要跳到另一个Activity或应用正常退出时都会执行这个方法。此时Activity在前台并可见,我们可以进行一些轻量级的存储数据和去初始化的工作,不能太耗时,因为在跳转Activity时只有当一个Activity执行完了onPause方法后另一个Activity才会启动,而且android中指定如果onPause在500ms即0.5秒内没有执行完毕的话就会强制关闭Activity。从生命周期图中发现可以在这快速重启,但这种情况其实很罕见,比如用户切到下一个Activity的途中按back键快速得切回来。

(5)onStop:stop表示停止,此时Activity已经不可见了,但是Activity对象还在内存中,没有被销毁。这个阶段的主要工作也是做一些资源的回收工作。

(6)onDestroy:destroy表示毁灭,这个阶段Activity被销毁,不可见,我们可以将还没释放的资源释放,以及进行一些回收工作。

(7)onRestart:restart表示重新开始,Activity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。

在这里插入图片描述

onCreate和onStart之间有什么区别?

(1)可见与不可见的区别。前者不可见,后者可见。
(2)执行次数的区别。onCreate方法只在Activity创建时执行一次,而onStart方法在Activity的切换以及按Home键返回桌面再切回应用的过程中被多次调用。因此Bundle数据的恢复在onStart中进行比onCreate中执行更合适。
(3)onCreate能做的事onStart其实都能做,但是onstart能做的事onCreate却未必适合做。如前文所说的,setContentView和资源初始化在两者都能做,然而想动画的初始化在onStart中做比较好。

onStart方法和onResume方法有什么区别?

(1)是否在前台。onStart方法中Activity可见但不在前台,不可交互,而在onResume中在前台。
(2)职责不同,onStart方法中主要还是进行初始化工作,而onResume方法,根据官方的建议,可以做开启动画和独占设备的操作。

onPause方法和onStop方法有什么区别?

(1)是否可见。onPause时Activity可见,onStop时Activity不可见,但Activity对象还在内存中。
(2)在系统内存不足的时候可能不会执行onStop方法,因此程序状态的保存、独占设备和动画的关闭、以及一些数据的保存最好在onPause中进行,但要注意不能太耗时。

onStop方法和onDestroy方法有什么区别?

onStop阶段Activity还没有被销毁,对象还在内存中,此时可以通过切换Activity再次回到该Activity,而onDestroy阶段Acivity被销毁

为什么切换Activity时各方法的执行次序是(A)onPause→(B)onCreate→(B)onStart→(B)onResume→(A)onStop而不是(A)onPause→(A)onStop→(B)onCreate→(B)onStart→(B)onResume

(1)一个Activity或多或少会占有系统资源,而在官方的建议中,onPause方法将会释放掉很多系统资源,为切换Activity提供流畅性的保障,而不需要再等多两个阶段,这样做切换更快。
(2)按照生命周期图的表示,如果用户在切换Activity的过程中再次切回原Activity,是在onPause方法后直接调用onResume方法的,这样比onPause→onStop→onRestart→onStart→onResume要快得多。

与生命周期密切相关的onSaveInstanceState方法和onRestoreInstanceState方法在什么时候执行?

通过阅读源码会发现,当targetSdkVersion小于3时onSaveInstanceState是在onPause方法中调用的,而大于3时是在onStop方法中调用的。
而onRestoreInstanceState是在onStart之后、onResume之前调用的。

Fragment的生命周期

Fragment基本概要

Fragment表示 Activity 中的行为或用户界面部分。您可以将多个片段(片段就是指 Fragment )组合在一个 Activity 中来构建多窗格 UI,以及在多个 Activity 中重复使用某个片段。您可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除片段(有点像您可以在不同 Activity 中重复使用的“子 Activity”)。

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

当您将片段作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 ViewGroup 内部,并且片段会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明片段,将其作为 <fragment> 元素插入您的 Activity 布局中,即静态添加。或者通过将其添加到某个现有 ViewGroup,利用应用代码进行动态插入。不过,片段并非必须成为 Activity 布局的一部分;您还可以将没有自己 UI 的片段用作 Activity 的不可见工作线程。

在这里插入图片描述

  1. onAttach(Context context):在Fragment和Activity关联上的时候调用,且仅调用一次。在该回调中我们可以将context转化为Activity保存下来,从而避免后期频繁调用getAtivity()获取Activity的局面,避免了在某些情况下getAtivity()为空的异常(Activity和Fragment分离的情况下)。同时也可以在该回调中将传入的Arguments提取并解析,在这里强烈推荐通过setArguments给Fragment传参数,因为在应用被系统回收时Fragment不会保存相关属性,具体之后会讲解。
  2. onCreate:在最初创建Fragment的时候会调用,和Activity的onCreate类似。
  3. View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState):在准备绘制Fragment界面时调用,返回值为Fragment要绘制布局的根视图,当然也可以返回null。注意使用inflater构建View时一定要将attachToRoot指明false,因为Fragment会自动将视图添加到container中,attachToRoot为true会重复添加报错。onCreateView并不是一定会被调用,当添加的是没有界面的Fragment就不会调用,比如调用FragmentTransaction的 add(Fragment fragment, String tag)方法。
  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解除关联时调用。

在这里插入图片描述

在这里插入图片描述

Fragment 生命周期与 Activity 生命周期的一个关键区别就在于,Fragment 的生命周期方法是由托管Activity而不是操作系统调用的。Activity 中生命周期方法都是 protected,而 Fragment 都是 public,也能印证了这一点,因为 Activity 需要调用 Fragment 那些方法并管理它。

加载 Fragment

  1. 静态加载 在 Activity 的布局文件内声明片段
    下边代码是含有两个 Fragment 的 Activity 布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

其中 fragment 中的 android:name 属性要指定 fragment 对应的具体包名路径,当系统创建此 Activity 布局时,会实例化在布局中指定的每个 fragment,并为每个 fragment 调用 onCreateView()方法,以检索每个 fragment 的布局。系统会直接插入 fragment 返回的 View 来替代 fragment 元素。

并且在 Activity 活动里可以直接使用 findViewById() 方法获取 fragment 对应布局里的控件。同样在 fragment 里可以直接使用 getActivity()方法获得绑定的主 Activity 实例,并调用 Activity 里的方法或其他 fragment 实例。

  1. 动态加载 通过编程方式将 fragment 添加到某个activity布局里现有的 ViewGroup (例如 LinearLayout 或 FrameLayout)里。
    要想在 Avtivity 中执行 Fragment 事务 (如添加、删除或替换 Fragment),必须使用 FragmentTransaction 中的 API。可以使用下面这样从 Activity 中获取一个 FragmentTransaction。
FragmentManager  fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

然后可以使用 add()方法添加一个 fragment ,指定要添加的 fragment 和插入到哪个视图。例如

ExampleFragment  exampleFragment = new ExampleFragment();
fragmentTransaction.add(R.id.frame_layout,exampleFragment);
fragmentTransaction.commit();

add 方法中第一个参数是一个activity 对应布局文件中的 ViewGroup,即应该放置 fragment 的位置,由资源 ID 指定,第二个参数是加入的 fragment ,一旦通过 fragmentTransaction 做了更改,最后必须使用 commit 方法以使更改生效。

添加没有 UI 的 fragment

要想添加没有 UI 的片段,请使用 add(Fragment, String) 从 Activity 添加片段(为片段提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加片段,但由于它并不与 Activity 布局中的视图关联,因此不会收到对onCreateView() 的调用。因此,不需要实现该方法。

执行 Fragment 事务

在 Activity 中使用 Fragment 可以很方便的进行添加 add、替换 replace、移除 remove 等操作,这样提交给 Activity 的每组更改都可以称为事务。像上边动态添加 fragment 那样,使用 FragmentTransaction 里的 API 就可以执行一项事务。同时也可以将此事务保存到 Activity 管理的返回栈中,从而用户可以回退到 fragment 改变之前的状态(类似于 activity 回退到上一个页面)。

下边的例子演示了如何替换一个 fragment,并把替换之前的状态保存到 Activity 的返回栈中。

 Fragment newFragment = new ExampleFragment();
 FragmentTransaction mTransaction =  getFragmentManager().beginTransaction();
//用新的 fragment 替换原来fragment 所在位置的布局,并且把此事务添加到返回栈中。
 mTransaction.replace(R.id.frame_layout,newFragment);
 mTransaction.addToBackStack(null);
 mTransaction.commit();

如果在执行移除 fragment 的事务时没有调用 addToBackStack(),则事务提交时该 fragment 会被销毁,用户将无法回退到该 fragment 。 不过,如果在删除 fragment 时调用了 addToBackStack(),则系统会停止该 fragment,并在用户按返回键时将其恢复。

Fragment 与 Activity 通信

上边说过,在 fragment 中可以调用 getActivity() 获取 activity 的实例并调用 activity 里的方法和布局,同样在 activity 里也可以通过 findFragmentById()(对于在 activity 提供 fragment 布局的) 或 findFragmentByTag() (对于在 activity 提供或者不提供 fragment 布局的)方法获取 fragment 的实例,例如在 activity 中从 FragmentManager 获取对 Fragment 的引用来调用 fragment 中的方法:

Fragment fragment = getFragmentManager.findFragmentById(R.id.fragment_container);

使用 FragmentManager 还可以执行的操作包括:

  • 通过 findFragmentById 或 findFragmentByTag 获取 activity 中存在的 fragment 的实例
  • 通过 popBackStack (模拟用户点击返回按钮操作)将 fragment 从返回栈中弹出
  • 通过 addOnBackStackChangedListener() 注册一个监听返回栈改变的监听器
  • 像上边生成 fragmentTransaction 的方法,可以使用 fragmentManager 生成一个 fragmentTransaction 来执行某些事务,比如添加、替换、移除、addToBackStack()等。

需要注意Fragment中不存在onRestart()方法,该方法只存在与Activity中。

Fragment相关操作对生命周期的影响

添加Fragment可以分为静态添加和动态添加两大类。静态添加是在XML中直接添加Fragment,简单方便,缺点是添加之后不能在删除。动态添加是在代码中FragmentManger使用一系列FragmentTransaction事务操作动态控制,灵活多变。一般都是使用动态添加,下面就讲讲动态添加有关的生命周期。

  1. add:onAttach->onCreate->onCreateView->onActivityCreated->onStart->onResume
  2. remove:onPause->onStop->onDestroyView->onDestroy->onDetach
  3. show:onHiddenChanged(boolean hidden) hidden为false
  4. hide:onHiddenChanged(boolean hidden) hidden为true
  5. replace:旧Fragment的remove生命周期->新Fragment的add生命周期
  6. replace+addToBackStack:onPause->onStop->onDestroyView->新Fragment的add生命周期
    之后点击back:新Fragment的remove->onCreateView->onViewCreated->onActivityCreated->onStart->onResume 就是第一张图的线
  7. detach:onPause->onStop->onDestroyView 可以看到只是视图被移除,Fragment关联状态还是不变,还是处于FragmentManger的管理下
  8. FragmentTransaction.attach(Fragment var1):onStart->onResume->onCreateView

注意:Fragment的show和hide仅仅是将Fragment视图设置为是否可见,不会调用任何生命周期。该Fragment的生命周期还是会随着Activity的生命周期变化而变化,例如FragmentA hide、FragmentB show,点击Home A和B都会onPause->onStop

应用被系统回收对生命周期的影响

应用被回收一般都是后台应用,所以生命周期是从onDestroyView开始

  • 单独一个Fragment
    onDestroyView->onDestroy->onDetach->add生命周期
  • Fragment A hide,Fragment B show
    A.onDestroyView->A.onDestroy->A.onDetach->B.onDestroyView->B.onDestroy->B.onDetach->A.onAttach->A.onCreate->B.onAttach->B.onCreate->A.onCreateView->A.onActivityCreated->B.onCreateView->B.onActivityCreated->A.onStart->B.onStart->A.onResume->B.onResume

为了防止在系统回收应用情况下,再次进不出错,强烈建议大家
\1. 使用setArguments(Bundle bundle)方法传递参数。对于常规变量想必大家都已经十分熟练了,就不细说了。这里主要强调View变量和接口变量,View变量可以通过传入View的id,之后再通过id获取view的方法来实现。接口可以通过Activity实现,Fragment强转Activity实现。
\2. addFragment之前先通过findFragmentById判断是否添加过避免重复添加,如使用FragmentAdapter可以在onSaveInstanceState存储相应Fragment.getTag


ViewPager对Fragment生命周期的影响

ViewPager+Fragment已经是比较常见的组合,一般搭配ViewPager的FragmentPagerAdapter或FragmentStatePagerAdapter使用。不过ViewPager为了防止滑动出现卡顿,有一个缓存机制,默认情况下ViewPager会创建并缓存当前页面左右两边的页面(如Fragment)。此时左右两个Fragment都会执行从onAttach->….->onResume的生命周期,明明Fragment没有显示却已经到onResume了,在某些情况下会出现问题。比如数据的加载时机、判断Fragment是否可见等。

可以通过ViewPager的setOffscreenPageLimit(int limit)来设置ViewPager缓存个数,最小是1个即便我们设置为0,源码如下。

 public void setOffscreenPageLimit(int limit) {
        if (limit < DEFAULT_OFFSCREEN_PAGES) { 
        //     private static final int DEFAULT_OFFSCREEN_PAGES = 1;
            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
                    + DEFAULT_OFFSCREEN_PAGES);
            limit = DEFAULT_OFFSCREEN_PAGES;
        }
        if (limit != mOffscreenPageLimit) {
            mOffscreenPageLimit = limit;
            populate();
        }
    }
 123456789101112123456789101112

懒加载

如果不对数据加载时机做处理,使用ViewPager默认会将左右两个Fragment的数据也加载,当数据加载比较消耗资源会影响性能。我们可以对加载时机做调整使用懒加载,具体就是在Fragment真正可见时才加载数据。

在Fragment切换时候会调用setUserVisibleHint(boolean isVisibleToUser),isVisibleToUser表示是否对用户可见。setUserVisibleHint不单单在Fragment切换时调用,在onAttach之前都会调用一次此时isVisibleToUser为false,在onCreateView之前会调用一次此时isVisibleToUser的值为当前Fragment是否可见,之后就是在Fragment切换的时候会调用。

因为setUserVisibleHint会在onCreateView之前调用,如果数据加载涉及到view相关的操作别忘了设置一个变量来判断一下视图是否已经创建好。


判断Fragment是否可见

判断Fragment是否可见最常见就是用在埋点上了,不同情况下判断方法并不相同,主要分为两大类。

在Activity中使用

先上一段在Fragment可见的埋点代码

 @Override
    public void onResume() {
        super.onResume();
        // 注意这个判断 isHidden()是当前Fragment是否隐藏
        if (!isHidden()) {
            reportPageShow();
        }
    }

@Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (!hidden) {
            reportPageShow();
        }
    }

在添加Fragment或者从Fragment和其他Activity相互跳转的时候,都会调用onResume和onStop。如果是show或在hide Fragment的时候会调用onHiddenChanged。

不知道大家有没有发现在onResume的时候判断了一下当前Fragment是否被隐藏了,这个判断是有说法的。之前说过show和hide仅仅是将Fragment视图设置为是否可见,不会影响其生命周期。还原场景,如果Fragment A hide,Fragment B show,然后Activity从后台到前台是A和B的onResume都会被调用!所以要在onResume判断一下当前Fragment是否被隐藏,当然如果没有用到show和hide,只使用简单的onResume和onStop也没问题。

在ViewPager中使用

之前说过ViewPager为了防止滑动出现卡顿,有一个缓存机制,默认情况下ViewPager会创建并缓存当前页面左右两边的页面(如Fragment)。左右两个Fragment都会执行从onAttach->….->onResume的生命周期,此时Fragment的生命周期已经不可靠。不过在Fragment切换的时候会调用setUserVisibleHint(boolean isVisibleToUser),isVisibleToUser表示是否对用户可见。因此可以在setUserVisibleHint进行Fragment是否可见的判断。

 @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            reportPageShow();
        }
    }

Service的生命周期

Service与Activity一样,是Android的四大组件之一。Activity是看得见的,而Service则是看不见的。看得见的Activity一般承担显示界面、控制的功能,而Service则承担后台任务。而Service与线程的区别在于,Service是Android的四大组件之一,所以系统不会轻易将其杀死。服务可有其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信(IPC)。例如,服务可以处理网络事务、播放音乐、执行文件I/O或与内容提供程序交互,而所有这一切均可在后台进行。

服务基本上分为两种形式运行
- 启动
当应用组件(如Activity)通过调用startService()启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。操作完成后,服务会自行停止运行。
- 绑定
当应用组件通过bindService()绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信(IPC)跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

在这里插入图片描述

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

onBind()
当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。

onCreate()
首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果服务已在运行,则不会调用此方法。

onDestroy()
当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。

在这里插入图片描述

手动调用方法作用
startService()启动服务
stopService()关闭服务
bindService()绑定服务
unbindService()解绑服务
内部自动调用的方法作用
onCreat()创建服务
onStartCommand()开始服务
onDestroy()销毁服务
onBind()绑定服务
onUnbind()解绑服务

安卓适配器模式

适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

**定义:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。

何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

**如何解决:**继承或依赖(推荐)。

**关键代码:**适配器继承或依赖已有的对象,实现想要的目标接口。

优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

**使用场景:**有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

Android源码中的适配器模式,最典型的无非就是ListView、GridVIew、RecyclerView等等。我们来简单分析一下ListView,其他的大同小异。

我们前边在使用场景中有这么一点:你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,对象适配器可以适配它的父亲接口。

这句话怎么理解呢?其实放到ListView源码中理解起来更容易一些。首先我们的Item View可以是有各种不同布局的,我们不可能对每一个布局都去进行匹配,但是我们可以去适配它们的父类,父类是什么,就是View类。

ListView做为client,他所需要的目标接口(target interface)就是ListAdapter,包含getCount(),getItem(),getView()等几个基本的方法,为了兼容List,Cursor等数据类型作为数据源,还专门定义两个适配器来适配他们:ArrayAdapter和CursorAdapter。这两个适配器,说白了,就是针对目标接口对数据源进行兼容修饰。

接下来我们看看源码中的具体实现。我们Adapter的使用时在ListView的父类,AbsListView中,我们看下注释

/**
* Base class that can be used to implement virtualized lists of items. A list does
* not have a spatial definition here. For instance, subclases of this class can
* display the content of the list in a grid, in a carousel, as stack, etc.
*/

**AbsListView定义了集合视图的逻辑框架,比如Adapter的使用、复用Item View的逻辑、布局子视图的逻辑等,子类只需要重写特定的方法即可实现集合视图的功能。**我们看下代码

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
        ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
        ViewTreeObserver.OnTouchModeChangeListener,
        RemoteViewsAdapter.RemoteAdapterConnectionCallback {
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        final ViewTreeObserver treeObserver = getViewTreeObserver();
        treeObserver.addOnTouchModeChangeListener(this);
        if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
            treeObserver.addOnGlobalLayoutListener(this);
        }

        if (mAdapter != null && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // Data may have changed while we were detached. Refresh.
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
        }
    }

    /**
     * Subclasses should NOT override this method but
     *  {@link #layoutChildren()} instead.
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        mInLayout = true;

        final int childCount = getChildCount();
        if (changed) {
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }

        layoutChildren();
        mInLayout = false;

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

        // TODO: Move somewhere sane. This doesn't belong in onLayout().
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
    }

    /**
     * Subclasses must override this method to layout their children.
     */
    protected void layoutChildren() {
    }
}

首先在onAttachedToWindow()方法中调用mAdapter.getCount(),获取Item View的数量,然后调用onLayout(boolean changed, int l, int t, int r, int b)方法进行布局,从注释里也可以看到,子类对于Item View的布局需要重写layoutChildren()方法。我们看下ListView中的layoutChildren()方法。

@Override
protected void layoutChildren() {
    try {
        //根据不同的布局模式来布局Item View
        switch (mLayoutMode) {
            //~省略部分无关代码~
            case LAYOUT_FORCE_BOTTOM:
                sel = fillUp(mItemCount - 1, childrenBottom);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_FORCE_TOP:
                mFirstPosition = 0;
                sel = fillFromTop(childrenTop);
                adjustViewsUpOrDown();
                break;
            //~省略部分无关代码~
            defaule:
                break;
        }
    }
}

/**
 * Fills the list from pos up to the top of the list view.
 *
 */
private View fillUp(int pos, int nextBottom) {
    View selectedView = null;

    int end = 0;
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end = mListPadding.top;
    }

    while (nextBottom > end && pos >= 0) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
        nextBottom = child.getTop() - mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos--;
    }

    mFirstPosition = pos + 1;
    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

/**
 * Fills the list from top to bottom, starting with mFirstPosition
 */
private View fillFromTop(int nextTop) {
    mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    if (mFirstPosition < 0) {
        mFirstPosition = 0;
    }
    return fillDown(mFirstPosition, nextTop);
}

/**
 * Fills the list from pos down to the end of the list view.
 */
private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }

    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

从layoutChildren()方法这一串调用来看,无非就是对应ListView的不同布局模式,一种是新数据显示在最上方,一种是把新数据显示在最下方。但是无论是哪一种方式,在循环中获取View都是通过makeAndAddView(pos, nextTop, true, mListPadding.left, selected)方法来进行的。

/**
 * Obtain the view and add it to our list of children. The view can be made
 * fresh, converted from an unused view, or used as is if it was in the
 * recycle bin.
 */
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;


    if (!mDataChanged) {
        // Try to use an existing view for this position
        child = mRecycler.getActiveView(position);
        if (child != null) {
            // Found it -- we're using an existing child
            // This just needs to be positioned
            setupChild(child, position, y, flow, childrenLeft, selected, true);

            return child;
        }
    }

    // Make a new view for this position, or convert an unused view if possible
    child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

从注释了解到,这个方法是用来对我们的Item View进行缓存的。这里边又调用了obtainView(position, mIsScrap)来获取Item View,因为我们的Adapter变量是在AbsListView中,通过mAdapter来调用getView肯定也在AbsListView中。

因为缓存机制不是我们今天分析的重点,我们看下方法注释好了

/**
* Get a view and have it show the data associated with the specified
* position. This is called when we have already discovered that the view is
* not available for reuse in the recycle bin. The only choices left are
* converting an old view or making a new one.
* /

注释说的很明确:获得一个View,并且使它显示特定位置的数据。同时这里会处理View的复用或者重新生成。

总结一下就是,我们在负责展示数据的集合视图(AbsListView类族)和用户提供的布局、数据之间隔了一个类,即Adapter。Adapter会返回给集合视图用户数据的数量、布局,但是它吧所有的布局都抽象成了View。这样保证了AbsListView类族的高度可定制化。

安卓进行Http请求

为什么不支持http协议

Google表示,为保证用户数据和设备的安全,针对下一代 Android 系统(Android P(9.0)) 的应用程序,将要求默认使用加密连接,这意味着 Android P(9.0) 将禁止 App 使用所有未加密的连接,因此运行 Android P(9.0) 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。

因此在Android P(9.0) 使用HttpUrlConnection进行http请求会出现以下异常:

W/System.err: java.io.IOException: Cleartext HTTP traffic to ** not permitted
使用OKHttp请求会出现:

java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy
意思大概就是:安全策略不允许进行明文通讯

在Android P(9.0)系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,如果应用嵌套了webview,webview也只能使用https请求。无法使用http请求,有人认为 Android P(9.0)上所有的 App 都需要使用 TLS 加密会降低上网体验,个人觉得上这是一种误解,对于一些老旧非要使用http协议的服务器我们可以采取配置APP的联网策略方式进行限制突破

为什么要在子线程中进行网络请求

1、为了避免导致UI卡顿的情况:比如在OnCreate 里面先进行网络请求然后还要加载布局 。

2、在Android4.0以后,在主线程中的HTTP请求,运行时都会报错 ANRs (“Application Not Responding”),”应用没有响应“。

handle有什么用

handle、looper、MessageQueen和Message的关系。

1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。

Handler创建消息

Handler通过创建消息来完成接收和处理UI的更新。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。

Handler发送消息

UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。

Handler处理消息

UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。

安卓为什么要分子线程

我们创建的Service、Activity以及Broadcast均是一个主线程处理,这里我们可以理解为UI线程.但是在操作一些耗时操作时,比如I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们可以考虑使用Thread线程来解决.

Android中使用Thread线程会遇到哪些问题?

对于从事过J2ME开发的程序员来说Thread比较简单,直接匿名创建重写run方法,调用start方法执行即可.或者从Runnable接口继承,但对于Android平台来说UI控件都没有设计成为线程安全类型,所以需要引入一些同步的机制来使其刷新,这点Google在设计Android时倒是参考了下Win32的消息处理机制.

1. 对于线程中的刷新一个View为基类的界面,可以使用postInvalidate()方法在线程中来处理,其中还提供了一些重写方法比如postInvalidate(int left,int top,int right,int bottom) 来刷新一个矩形区域,以及延时执行,比如postInvalidateDelayed(long delayMilliseconds)或postInvalidateDelayed(long delayMilliseconds,int left,int top,int right,int bottom) 方法,其中第一个参数为毫秒,如下:

2. 当然推荐的方法是通过一个Handler来处理这些,可以在一个线程的run方法中调用handler对象的 postMessage或sendMessage方法来实现,Android程序内部维护着一个消息队列,会轮训处理这些,如果你是Win32程序员可以很好理解这些消息处理,不过相对于Android来说没有提供 PreTranslateMessage这些干涉内部的方法.

3. Looper又是什么呢? ,其实Android中每一个Thread都跟着一个Looper,Looper可以帮助Thread维护一个消息队列,昨天的问题 Can’t create handler inside thread 错误 一文中提到这一概念,但是Looper和Handler没有什么关系,我们从开源的代码可以看到Android还提供了一个Thread继承类HanderThread可以帮助我们处理,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将其这个Looper对象映射到一个Handler中去来实现一个线程同步机制,Looper对象的执行需要初始化Looper.prepare方法就是昨天我们看到的问题,同时推出时还要释放资源,使用Looper.release方法.

4.Message 在Android是什么呢? 对于Android中Handler可以传递一些内容,通过Bundle对象可以封装String、Integer以及Blob二进制对象,我们通过在线程中使用Handler对象的 sendEmptyMessage或sendMessage方法来传递一个Bundle对象到Handler处理器.对于Handler类提供了重写方法handleMessage(Message msg) 来判断,通过msg.what来区分每条信息.将Bundle解包来实现Handler类更新UI线程中的内容实现控件的刷新操作.相关的Handler对象有关消息发送sendXXXX相关方法如下,同时还有postXXXX相关方法,这些和Win32中的道理基本一致,一个为发送后直接返回,一个为处理后才返回 :

5. java.util.concurrent对象分析,对于过去从事Java开发的程序员不会对Concurrent对象感到陌生吧,他是JDK 1.5以后新增的重要特性作为掌上设备,我们不提倡使用该类,考虑到Android为我们已经设计好的Task机制,我们这里Android开发网对其不做过多的赘述,相关原因参考下面的介绍:

6. 在Android中还提供了一种有别于线程的处理方式,就是Task以及AsyncTask,从开源代码中可以看到是针对Concurrent的封装,开发人员可以方便的处理这些异步任务,具体的Android123在以前的文章中有详细解释,可以使用站内搜索来了解更多.


点击全文阅读


本文链接:http://zhangshiyu.com/post/39972.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1