Android开发

本文深入讲解Android开发中的关键技术,包括OkHttp原理、Picasso图片加载库的使用与优势、View动画与属性动画详解、RecyclerView局部刷新技巧、MVP模式设计、AIDL原理及Binder机制、GC根对象分析、内存模型探讨、ListView复用机制、自定义View实践等,旨在提升开发者对Android底层机制的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.okhttp原理

(1).首先看一下流程图
在这里插入图片描述
(2).使用的是责任链的设计模式
优点:
降低耦合度:客户端不需要知道请求由哪个处理者处理,而处理者也不需要知道处理者之间的传递关系,由系统灵活的组织和分配。
良好的扩展性:增加处理者的实现很简单,只需重写处理请求业务逻辑的方法。

2.picasso解析

(1)调用流程:
用户调用into(ImageView iv)接口后,Picasso将其该下载图片的请求包装成一个ImageViewAction,接着由Picasso负责发送到Dispatcher中;Dispatcher接着会把该Action继续封装成一个可执行对象BitmapHunter,接着将其提交到线程池PicassoExecutorService中执行;如果此时发现内存中已经有图片,则直接返回,否则BitmapHunter将通过一个Downloader下载图片,下载图片完成后,BitmapHunter会将结果返回给Dispatcher,最后由Dispatcher告知Picasso图片加载完成。

(2)自带统计监控功能
支持图片缓存使用的监控,包括缓存命中率、已使用内存大小、节省的流量等。

(3)支持优先级处理 :
每次任务调度前会选择优先级高的任务,比如 App 页面中 Banner 的优先级高于 Icon 时就很适用。

(4) 支持延迟到图片尺寸计算完成加载

(5) 支持飞行模式、并发线程数根据网络类型而变:
手机切换到飞行模式或网络类型变换时会自动调整线程池最大并发数,比如 wifi 最大并发为 4, 4g 为 3,3g 为 2。
这里 Picasso 根据网络类型来决定最大并发数,而不是 CPU 核数。

(6) “无”本地缓存
无”本地缓存,不是说没有本地缓存,而是 Picasso 自己没有实现,交给了 Square 的另外一个网络库 okhttp 去实现,这样的好处是可以通过请求 Response Header 中的 Cache-Control 及 Expired 控制图片的过期时间。

3.android中的View动画

(1)、view动画有四种:
渐变Alpha、旋转Rotate、缩放Scale、平移Translate
(2)、View动画的实现有两种方式:
通过xml文件来定义,通过java代码来实现。
(3)、一些公共的属性:

android:duration     动画持续时间
android:fillAfter    为true动画结束时,View将保持动画结束时的状态
android:fillBefore   为true动画结束时,View将还原到开始开始时的状态
android:repeatCount  动画重复执行的次数
android:repeatMode   动画重复模式 ,重复播放时restart重头开始,reverse重复播放时倒叙回放,该属性需要和android:repeatCount一起使用
android:interpolator 插值器,相当于变速器,改变动画的不同阶段的执行速度

(4)、这些属性都是继承自Animation在四种动画中都可以使用
(5)、所有的xml定义的View动画都要放在anim目录下。
(6)、例子:
渐变:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
       android:duration="2000"
       android:fromAlpha="1.0"
       android:toAlpha="0">
</alpha>

旋转:

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="2000"
        android:fillAfter="true"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="360">
</rotate>

缩放:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
       android:duration="2000"
       android:fromXScale="1.0"
       android:fromYScale="1.0"
       android:pivotX="50%"
       android:pivotY="50%"
       android:toXScale="0.5"
       android:toYScale="0.5">
</scale>

平移:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="2000"
           android:fromXDelta="0"
           android:fromYDelta="0"
           android:toXDelta="100%"
           android:toYDelta="100%">
</translate>

(7)、三种单位的区别
rotate、scale动画的android:pivotX和android:pivotY属性、translate动画的android:toXDelta和android:toYDelta属性的取值都可以是都可以数值、百分数、百分数p,比如:50、50%、50%p,他们取值的代表的意义各不相同:
50表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移50px的位置;
50%表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移View宽度或高度的50%处的位置;
50%p表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移父控件宽度或高度的50%处的位置(p表示相对于ParentView的位置)。
50:
在这里插入图片描述
50%:
在这里插入图片描述
50%p:
在这里插入图片描述

4.android中的属性动画

(1).属性动画是通过反射机制来实现的,通过动态的改变属性值达到动画的效果
(2).属性动画是通过TypeEvaluator来实现计算规则的

5.动画中应该注意的事项

(1).帧动画容易造成oom的问题。
(2).View动画并没有真实的改变属性
(3).内存泄漏:使用属性动画的时候,当使用无限循环动画,需要在 Activity 退出的时候停止动画,不然可能会因为无法释放资源而导致 Activity 内存泄漏。
(4).动画兼容:当 APP 需要兼容到 API 11 以下的时候就需要注意动画的兼容问题。
(5).用 dp 而不是 px:因为 px 在不同设备上面的兼容问题,使用动画的时候尽量使用 dp 作为单位。

6.AAC架构
7.Recycle的adapter是通过观察者模式实现的。
 /**
         * Notify any registered observers that the currently reflected <code>itemCount</code>
         * items starting at <code>positionStart</code> have been newly inserted. The items
         * previously located at <code>positionStart</code> and beyond can now be found starting
         * at position <code>positionStart + itemCount</code>.
         *
         * <p>This is a structural change event. Representations of other existing items in the
         * data set are still considered up to date and will not be rebound, though their positions
         * may be altered.</p>
         *
         * @param positionStart Position of the first item that was inserted
         * @param itemCount Number of items inserted
         *
         * @see #notifyItemInserted(int)
         */
        public final void notifyItemRangeInserted(int positionStart, int itemCount) {
            mObservable.notifyItemRangeInserted(positionStart, itemCount);
        }
8.DataBindingUtil可以实现数据和view的绑定。
ListFragmentBinding  mBinding = DataBindingUtil.inflate(inflater, R.layout.list_fragment, container, false);
9.通过schema从一个app进入到其他app的界面。

(1)、只需要在activity中的配置中加入schema字段就可以实现。

<activity
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="appname" android:host="host" />
        </intent-filter>
  </activity>

(2)、原理
当应用在安装的时候packageManager会解析AndroidManifest.xml中注册的activity的信息,如果在activity中有的schema的信息,那么packageManager会将schema数据提取出来,当有其他的app通过schema打开的时候就会通过schema信息找到相应的activity。然后交给AMS启动activity。
(3)、注意事项
需要考虑到应用的冷启动和热启动。

10.RecyclerView

(1)、引入依赖
在使用RecyclerView的时候需要在app/build.gradle文件中添加依赖

   implementation 'com.android.support:recyclerview-v7:28.0.0'

(2)、recyclerView中的adapter是通过观察者模式实现的,当数据发生变化时就会通过观察者来更新数据,以下是adapter的源码。

public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
    private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
        private boolean mHasStableIds = false;

    public Adapter() {
    }

    public final void notifyDataSetChanged() {
        this.mObservable.notifyChanged();
    }

    public final void notifyItemChanged(int position) {
        this.mObservable.notifyItemRangeChanged(position, 1);
    }

    public final void notifyItemChanged(int position, @Nullable Object payload) {
        this.mObservable.notifyItemRangeChanged(position, 1, payload);
    }

    public final void notifyItemRangeChanged(int positionStart, int itemCount) {
        this.mObservable.notifyItemRangeChanged(positionStart, itemCount);
    }

    public final void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
        this.mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
    }

    public final void notifyItemInserted(int position) {
        this.mObservable.notifyItemRangeInserted(position, 1);
    }

    public final void notifyItemMoved(int fromPosition, int toPosition) {
        this.mObservable.notifyItemMoved(fromPosition, toPosition);
    }

    public final void notifyItemRangeInserted(int positionStart, int itemCount) {
        this.mObservable.notifyItemRangeInserted(positionStart, itemCount);
    }

    public final void notifyItemRemoved(int position) {
        this.mObservable.notifyItemRangeRemoved(position, 1);
    }

    public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
        this.mObservable.notifyItemRangeRemoved(positionStart, itemCount);
    }
 }
11.RecyclerView的局部刷新

(1)、listview中的局部刷新的做法
记录点击的Item的position,然后在更新过程中,不断的判断,该position是不是介于可见的Item之间,如果是,则更新,否者,不更新。按着这样的思维我们可以实现如下的recyclerview的局部刷新的方式。
首先要寻找RecyclerView中可见的item的位置范围,该方法并不在RecyclerView中,而是LinearLayoutManager中,如:

int fristPos = layoutManager.findFirstVisibleItemPosi();
int lastPos = layoutManager.findLastVisibleItemPosition();

if (position >= fristPos && position <= lastPos) {
  View view = recyclerView.getChildAt(position);
  …
}

然后查找到对应的View,进行更新(递归查找)

/**
     * 在ViewGroup中根据id进行查找
     * @param vg
     * @param id 如:R.id.tv_name
     * @return
     */
    private View findViewInViewGroupById(ViewGroup vg, int id) {
        for (int i = 0; i < vg.getChildCount(); i++) {
            View v = vg.getChildAt(i);
            if (v.getId() == id) {
                return v;
            } else {
                if (v instanceof ViewGroup) {
                    return findViewInViewGroupById((ViewGroup) v, id);
                }
            }
        }
        return null;
    }

(2)、新的思路:notifyItemChanged
RecyclerView不像ListView,只有一个更新notifyDataSetChanged,它不仅保留了ListView的更新特点,还针对“增加,删除,更新”操作专门进行更新,可以只更新一个item,也可以更新一部分item,所以,用起来效率更高。因此,RecyclerView的局部刷新,就可以通过修改数据源的方式,调用notifyItemChanged(position)即可。

12.FrameLayout布局

FrameLayout是比较简单的一个布局。在这个布局中,整个界面被当成一块空白备用区域,所有的子元素都不能被指定放置的位置,它们统统放于这块区域的左上角,并且后面的子元素直接覆盖在前面的子元素之上,将前面的子元素部分和全部遮挡。相同层级布局中 FrameLayout的效率也是最高的,占用内存相对来说也是较小的。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
  <TextView
      android:layout_width="300dp"
      android:layout_height="300dp"
      android:text="第一层"/>
  <TextView
      android:layout_width="150dp"
      android:layout_height="140dp"
      android:text="第二层"/>
</FrameLayout>
13.view 前景色
<ImageView
	android:foreground="@color/video_image_foreground_color"
/>
14.Gradle

(1)、gradle是用来构建app的工具。
(2)、Gradle会最后以task的形式执行

15.marginLeft和marginStart的区别

(1)、我们得知MarginStart指的是控件距离开头View部分的间距大小,MarginLeft则指的是控件距离左边View部分的间距大小,MarginEnd和MarginRight同理。

(2)、一般情况下,View开始部分就是左边,但是有的语言目前为止还是按照从右往左的顺序来书写的,例如阿拉伯语,在Android 4.2系统之后,Google在Android中引入了RTL布局,更好了支持了由右到左文字布局的显示,为了更好的兼容RTL布局,google推荐使用MarginStart和MarginEnd来替代MarginLeft和MarginRight,这样应用可以在正常的屏幕和由右到左显示文字的屏幕上都保持一致的用户体验。

16.TextView 省略号

(1)、不同类型的省略号:

android:ellipsize="end"     省略号在结尾
android:ellipsize="start"   省略号在开头
android:ellipsize="middle"   省略号在中间
android:ellipsize="marquee"  跑马灯
android:lines="2"
17.获取屏幕的宽度或者高度
WindowManager windowManager =(WindowManager) sContext.getSystemService(Context.WINDOW_SERVICE);
point = new Point();
windowManager.getDefaultDisplay().getRealSize(point);
point.x;//屏幕宽度
point.y;//屏幕的高度
18.android6.0以后的权限管理

(1)、Android6.0以前权限的申请非常简单,只需要在mainfest声明所需的权限即可。(2)、6.0以后,Android将权限的管理进一步严格化,它要求用户在使用某些敏感权限时,必须在mainfest中先声明之后再动态申请。在一定程度上约束了应用对权限的索取,保证了用户的数据隐私。

19.android异常情况保存

Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。

20.IntentService

它是Service的子类,用法跟Service也差不多,就是实现的方法名字不一样,耗时逻辑应放在onHandleIntent(Intent intent)的方法体里,它同样有着退出启动它的Activity后不会被系统杀死的特点,而且当任务执行完后会自动停止,无须手动去终止它。例如在APP里我们要实现一个下载功能,当退出页面后下载不会被中断,那么这时候IntentService就是一个不错的选择了。

21.SharedPreferences存储数据后无法获取到数据。

(1)、案例:

SharedPreferences sp =  Instance.getSharedPreferences("UPDATE_DATA_TIME", Activity.MODE_PRIVATE);
sp.edit().putString("data", date);
sp.edit().commit();

在这个案例中调用get函数的时候是获取不到数据的。
(2)、解决方案:

SharedPreferences sp =  Instance.getSharedPreferences("DATA", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("data", date);
editor.commit();
22.HashMap 遍历方式

(1)、键和值都遍历

HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
 
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
 
    System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
 
}

(2)、遍历值

for (Integer value : map.values()) {
 
    System.out.println("Value = " + value);
 
}

(3)、遍历键

for (Integer key : map.keySet()) {
 
    System.out.println("Key = " + key);
 
}
 
23.沙箱机制

1.每个App会被分配一个uid,互相之间数据不能随意访问。

24.service的生命周期

service有onCreate(),onBind(),onStartCommand(),onDestory()函数,大家可以用log的方式打印一下就可以体会到他的调用顺序。如下所示:

public class TestService extends Service {
    @Override
    public void onCreate() {
        Log.d("Service","onCreate");
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("Service","onBind");
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("Service","onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.d("Service","onDestory");
        super.onDestroy();
    }
}
25.startService方式的特点

1.startService第一次的时候会调用onCreate(),而且只会被调用一次,不管startService被执行多少次。
2.每次调用的时候都会执行onStartCommand()。
2.除非使用stopService(),否则service是不会被停止的。

26.判断定位是否可用
public static boolean isLocServiceEnable(Context context) {
        LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        if (gps || network) {
            return true;
        }
        return false;
}
27.java数组初始化
String[] strArr = {"str1","str2","str3"};
28.contentProvider 中的query语句用法
Cursorcursor = contentResolver.query(
	android.provider.ContactsContract.Contacts.CONTENT_URI,  
    new String[]{android.provider.ContactsContract.Contacts.DISPLAY_NAME},  
    android.provider.ContactsContract.Contacts.DISPLAY_NAME + "=?",  
    new String[]{"张三"}, 
    android.provider.ContactsContract.Contacts._ID + " DESC"
 );

(1)、第一个参数,Uri,例如android.provider.ContactsContract.Contacts.CONTENT_URI
(2)、第二个参数,这个参数告诉Provider要返回的内容(列Column)
(3)、第三个参数,selection,设置条件,相当于SQL语句中的where。null表示不进行筛选。
(4)、第四个参数,selectionArgs,这个参数是要配合第三个参数使用的,如果你在第三个参数里面有?,那么你在selectionArgs写的数据就会替换掉?
(5)、第五个参数,sortOrder,按照什么进行排序,相当于SQL语句中的Order by。如果想要结果按照ID的降序排列。

29.Date 获取时间戳
/**
     * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
     * represented by this <tt>Date</tt> object.
     *
     * @return  the number of milliseconds since January 1, 1970, 00:00:00 GMT
     *          represented by this date.
     */
    public long getTime() {
        return getTimeImpl();
    }
30.设置版本号

(1)、在gradle中设置

defaultConfig {
    applicationId "cn.wangbaiyuan.translate"
    minSdkVersion 15
    targetSdkVersion 22
    versionCode 1
    versionName "1.0"
}

(2)、在xml中设置

 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      package="appname"
      <!-- 用于版本升级,值为int类型,用于判断app是否需要升级 -->
      android:versionCode="1"
      <!-- 版本号名称,用于显示给用户看到的app版本号 -->
      android:versionName="1.0.0">
  
 </manifest>
31.SharedPreference.Editor的apply和commit方法异同

这两个方法的区别在于:
(1). apply没有返回值而commit返回boolean表明修改是否提交成功
(2). apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
(3). apply方法不会提示任何失败的提示。
由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

32.hash表的作用

(1)、hash表的作用是为了加快查找的效率。
(2)、hashcode方法的作用就是返回一个hash值,将相同hash值的对象映射一起。
(3)、每次查找的时候先通过hash值初步定位到同一个hash值的范围,最后通过equals方法遍历精确查找。

33.程序计数器的功能

(1)、程序计数器是用来表示下一条要执行的字节码指令的。
(2)、在单核的系统中线程的切换也是通过程序计数器实现的。
(3)、在执行native代码的时候JVM程序计数器是空的,因为程序计数器是表示下一条要执行的字节码指令的。native是c的指令。

34.动态代理和静态代理

(1)、代理模式让客户端只需知道代理即可,实现了客户端和业务类的解耦。
(2)、静态代理是在编译层就确定了class,所以当要代理很多功能的时候需要写很多代理类。
(3)、动态代理利用java的反射机制是在运行时确定class,解决了静态代理的弊端。

35.MVP的类图

在这里插入图片描述
(1)、两个基类接口
首先定义两个接口,这两个接口分别是所有View和Presenter的基类: IBaseView和IBasePresenter。

IBaseView中主要定义一些通用的界面方法,如显示/隐藏进度条、显示提示信息等。
IBasePresenter中也可以定义一些通用的方法,如初始化方法等。

public interface IBaseView {
    void showLoading();
    void hideLoading();
    void showMessage(String msg);
}
public interface IBasePresenter {
    ...
}

(2)、定义契约类(接口)
使用契约类来统一管理View与Presenter的所有接口,这种方式使得View与Presenter中有哪些功能,一目了然,维护起来也很方便。

public interface CookDetailContract {
    interface IView extends IBaseView {
        void updateCookDetail(CookDetail cookDetail);
    }

    interface IPresenter extends IBasePresenter {
        void getCookDetail(String apikey, String id);
    }
}

CookDetailContract中的IView接口定义了该界面(功能)中所有的UI状态情况,MainAcitivty作为View层,实现了该接口,这样MainActivity就只关注UI相关的状态更新。
IPresenter接口则定义了该界面(功能)中所有的用户操作事件,CookDetailPresenter作为Presenter层,实现了该接口,这样CookDetailPresenter就只关注业务层的相关逻辑,UI的更新只需调用IView的状态方法。
(3)、View层(Activity/Fragment)
Activity/Fragment是一个全局的控制者,负责创建View以及Presenter实例,并将二者联系起来。

在本例中,MainActivity实现了CookDetailContract.IView接口,并在onResume()回调中创建CookDetailPresenter实例,CookDetailPresenter的构造函数中实现了View和Presenter的关联。

在创建完Presenter后,调用Presenter的getCookDetail()方法获取相应的数据(如上图步骤①)。

在获取到Model层的数据后,Presenter通过IView中的updateCookDetail()方法返回数据(如上图步骤④),Activity获取数据后,将结果展示到界面上反馈给用户。

mCookDetailPresenter = new CookDetailPresenter(MainActivity.this, this);
mCookDetailPresenter.getCookDetail(Config.API_KEY, (id++) + "");

@Override
public void updateCookDetail(CookDetail cookDetail) {
    tvName.setText(cookDetail.getName());
    Picasso.with(this).load(Config.IMAGE_URL_PREFIX + cookDetail.getImg()).into(ivImage);
}

(4)、Presenter层
它实现了契约类中的IPresenter接口。

Presenter翻译过来是主持人的意思,它做为MVP架构中最关键的一层,负责连接View层和Model层。比如控制显示/隐藏进度框、显示/隐藏空布局、错误布局,调用相应的Model层方法进行数据的获取,并在Model层返回数据后,将数据适配到View中展示。这样,便可以让Model层只关注数据相关的操作、也让View层只专注于界面的展示,让各个层级各司其职,相互协作。

public class CookDetailPresenter implements CookDetailContract.IPresenter {

    private Context mContext;
    private CookDetailContract.IView mView;
    private CookDetailManager mCookDetailManager = CookDetailManager.getInstance();

    public CookDetailPresenter(Context context, CookDetailContract.IView view) {
        this.mContext = context;
        this.mView = view;
    }

    @Override
    public void getCookDetail(String apikey, String id) {
        mView.showLoading();

        mCookDetailManager.getCookDetail(apikey, id, new Callback<CookDetail>() {
            @Override
            public void onSuccess(CookDetail object) {
                mView.updateCookDetail(object);
                mView.hideLoading();
            }

            @Override
            public void onFail(int errorNo, String errorMsg) {
                ErrorUtil.processErrorMessage(mContext, errorNo, errorMsg, mView);
                mView.hideLoading();
            }
        });
    }
}

(5)、Model层
Model层不只包含实体对象,更主要的功能是处理一切与数据相关的操作,如数据的获取、存储、数据状态变化都是Model层的任务,Presenter会根据需要调用该层的数据处理逻辑(如上图步骤②),如有需要,Model层会使用回调将数据传回Presenter层(如上图步骤③)。

public class CookDetailManager {

    private volatile static CookDetailManager instance;

    private CookDetailManager() {
    }

    public static CookDetailManager getInstance() {
        if (instance == null) {
            synchronized (CookDetailManager.class) {
                if (instance == null) {
                    instance = new CookDetailManager();
                }
            }
        }
        return instance;
    }
    
    public void getCookDetail(String apikey, String id, final Callback<CookDetail> callback) {
        if (callback == null) {
            return;
        }

        Retrofit retrofit = RetrofitClient.INSTANCE.getRetrofit();
        ApiService apiService = retrofit.create(ApiService.class);

        Observable<CookDetail> observable = apiService.getCookDetail(apikey, id);
        observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<CookDetail>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                        callback.onFail(Constants.ErrorNo.ServerError, "");
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(CookDetail respEntity) {
                        callback.onSuccess(respEntity);
                    }
                });
    }
}
36.迭代器模式

(1)、为了方便的处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator).
(2)、对 Collection 进行迭代的类,称其为迭代器。还是面向对象的思想,专业对象做专业的事情,迭代器就是专门取出集合元素的对象。但是该对象比较特殊,不能直接创建对象(通过new),该对象是以内部类的形式存在于每个集合类的内部。
(3)、如何获取迭代器?Collection接口中定义了获取集合类迭代器的方法(iterator()),所以所有的Collection体系集合都可以获取自身的迭代器。

37.Glide的图片复用机制的解码

(1)、默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
活动资源 (Active Resources)
内存缓存 (Memory Cache)
资源类型(Resource Disk Cache)
原始数据 (Data Disk Cache)
(2)、四级缓存的解释
活动资源:如果当前对应的图片资源正在使用,则这个图片会被Glide放入活动缓存。
内存缓存:如果图片最近被加载过,并且当前没有使用这个图片,则会被放入内存中
资源类型: 被解码后的图片写入磁盘文件中,解码的过程可能修改了图片的参数(如:inSampleSize、inPreferredConfig)
原始数据: 图片原始数据在磁盘中的缓存(从网络、文件中直接获得的原始数据)
在这里插入图片描述
(3)、复用机制
BitmapPool是Glide中的Bitmap复用池,同样适用LRU来进行管理。
当一个Bitmap从内存缓存 被动 的被移除(内存紧张、达到maxSize)的时候并不会被recycle。而是加入这个BitmapPool,只有从这个BitmapPool 被动
被移除的时候,Bitmap的内存才会真正被recycle释放
(4)、复用解决的问题
在未使用复用的情况下,每张图片都需要一块内存。而使用复用的时候,如果存在能被复用的图片会重复使用该图片的内存。
所以复用并不能减少程序正在使用的内存大小。Bitmap复用,解决的是减少频繁申请内存带来的性能(抖动、碎片)问题。

38.android 获取地理位置有时通过gps获取不到

(1)、解决方案
通过network方式获取。

39.Java中基础数据合占几个字节

char:2字节(jvm是双字节编码)
int:4字节
dobule:8字节
long:8字节
short:2字节

40.面向对象的多态的理解

(1)、多态的核心一
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
(2)、多态的核心二
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)
(3)、多态的作用
消除类型之间的耦合关系。
(4)、多态存在的三个必要条件
一、要有继承;
二、要有重写;
三、父类引用指向子类对象

41.内部类的作用

(1).内部类可以很好的实现隐藏

一般的非内部类,是不允许有 private 与protected权限的,但内部类可以

(2).内部类拥有外围类的所有元素的访问权限

(3).可是实现多重继承

(4).可以避免修改接口而实现同一个类中两种同名方法的调用。

42.抽象类和接口的区别

(1)、语法层面上的区别
抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

一个类只能继承一个抽象类,而一个类却可以实现多个接口。
(2)、设计层面上的区别
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。

设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。

新加一个公共方法的时候接口会影响其他类(实现接口的全部需要改动),而抽象类不会。

43.ThreadLocal理解

(1)、ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
(2)、ThreadLocal 的作用和同步机制有些相反:同步机制是为了保证多线程环境下数据的一致性;而 ThreadLocal 是保证了多线程环境下数据的独立性。
(3)、threadLocal变量是在每个线程都用一个hashmap(key是threadlocal)来保存了这个值,所以一个线程改变了这个值不会影响其他线程的。

44.接口的作用和意义

(1)、重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的 面向对象能力。

(2)、简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。

(3)、维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类,可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦,如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。

(4)、安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。
(5)、接口将实现和设计分离

45.AIDL原理

(1)、AIDL是利用service将实现的。
(2)、通信机制是binder机制。
(3)、AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。

46.可以作为GC root的对象

(1)、虚拟机栈(栈帧中的本地变量表)中引用的对象
(2)、本地方法栈中JNI(即一般说的Native方法)引用的对象
(3)、方法区中类静态属性引用的对象
(4)、方法区中常量引用的对象

47.栈帧的结构

(1)、栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的虚拟机栈的栈元素。

(2)、栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。

(3)、每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。
在这里插入图片描述

48.硬件架构

在这里插入图片描述
(1)、现代计算机一般都有2个以上CPU,而且每个CPU还有可能包含多个核心。因此,如果我们的应用是多线程的话,这些线程可能会在各个CPU核心中并行运行。

(2)、在CPU内部有一组CPU寄存器,也就是CPU的储存器。CPU操作寄存器的速度要比操作计算机主存快的多。在主存和CPU寄存器之间还存在一个CPU缓存,CPU操作CPU缓存的速度快于主存但慢于CPU寄存器。某些CPU可能有多个缓存层(一级缓存和二级缓存)。计算机的主存也称作RAM,所有的CPU都能够访问主存,而且主存比上面提到的缓存和寄存器大很多。

(3)、当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某些节点把缓存数据flush到主存。

49.java内存模型

在这里插入图片描述

50.listview 复用机制

(1)、填充第一屏数据的时候,第一次onLayout()尝试获取一个ActiveView,无缓存返回null,再去调用obtain()方法,ScrapView也返回null,继而到getView中去inflate view。

(2)、第二次onLayout()时会获取ListView元素不为0,此时会将ListView中的子View放入ActiveView数组中,并detach所有View又从数组里取出缓存(缓存取出后会被删除),此时第一屏数据显示完毕。

(3)、接下来向下滑,onTouchEvent()中获取Y轴偏移量后一方面使子View都跟着滑动,另一方面会判断滑动方向并且detach掉移除屏幕的View,同时将其放入ScrapView数组。发现最后一个子View的bottom要进入屏幕时,尝试获取一个ActiveView,显然返回null,从而继续走obtain()方法,幸运的是ScrapView返回了view,继而将其传入到getView中的convertView中。
(4)、在实现Adapter的时候,我们一般会加上ViewHolder这个东西,ViewHolder和复用机制和原理是无关的,其主要作用是持有Item中控件的引用,从而减少findViewById()的次数,因为findViewById()方法也是会影响效率的。因此ViewHolder起到了提高效率的作用。但是显然和ListView的复用机制不是一码事。

51.泛型的作用

(1)、类型的参数化,就是可以把类型像方法的参数那样传递。这一点意义非凡。

(2)、泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。

52.onNewIntent的调用时机

在这里插入图片描述
(1)、当 activity (假设为 A) 的 launchMode 为 singleTop 且 A 的实例已经在 task 栈顶,或者 launchMode 为 singleTask 且 A 的实例已在 task 栈里 (无论是栈顶还是栈中),再次启动 activity A 时,便不会调用 onCreate() 去产生新的实例,而是调用 onNewIntent() 并重用 task 栈里的 A 实例。

(2)、如果 A 在栈顶,那么调用顺序依次是 A.onPause() –> A.onNewIntent() –> A.onResume()。A 的 launchMode 可以是 singleTop 或者是 singlTask.

(3)、如果 A 不在栈顶,此时它处于 A.onStop() 状态,当再次启动时,调用顺序依次是 [A.onStop()] –> A.onNewIntent() –> A.onRestart() –> A.onStart() –> A.onResume()。A 的 launchMode 只能是 singleTask。

53.view的双缓存机制

(1)、问题的由来
CPU访问内存的速度要远远快于访问屏幕的速度。如果需要绘制大量复杂的图像时,每次都一个个从内存中读取图形然后绘制到屏幕就会造成多次地访问屏幕,从而导致效率很低。这就跟CPU和内存之间还需要有三级缓存一样,需要提高效率。
(2)、第一层缓冲
在绘制图像时不用上述一个一个绘制的方案,而采用先在内存中将所有的图像都绘制到一个Bitmap对象上,然后一次性将内存中的Bitmap绘制到屏幕,从而提高绘制的效率。Android中View的onDraw()方法已经实现了这一层缓冲。onDraw()方法中不是绘制一点显示一点,而是都绘制完后一次性显示到屏幕。
(3)、第二层缓冲
onDraw()方法的Canvas对象是和屏幕关联的,而onDraw()方法是运行在UI线程中的,如果要绘制的图像过于复杂,则有可能导致应用程序卡顿,甚至ANR。因此我们可以先创建一个临时的Canvas对象,将图像都绘制到这个临时的Canvas对象中,绘制完成之后再将这个临时Canvas对象中的内容(也就是一个Bitmap),通过drawBitmap()方法绘制到onDraw()方法中的canvas对象中。这样的话就相当于是一个Bitmap的拷贝过程,比直接绘制效率要高,可以减少对UI线程的阻塞。

53.service 底层原理

(1)、ServiceManager
ServiceManager 启动后会通过 binder_loop 循环对 Binder Driver 进行监听,当发现了有新的Service服务请求后,就会调用 svcmgr_handler() 函数对检测的Service服务进行处理,通过*do_find_service()函数可以在svclist集中检测Service服务,若当前svclist服务集中未存在当前服务,就会通过do_add_service()进行注册,把当前服务及其唯一标识符加入到svclist中,这样当前的 Service 服务被绑定后就完成在ServiceManager的注册。Binder Driver 会按照规定的格式把它转化为 Binder 实体发送到内核当中,当被 Client 调用时 ServiceManager 会根据 Service 服务的标识符在 svclist 中找到该 Binder 实体,并把 Binder 实体的引用发送给Client。完成计算后 Binder Driver 会进行数据处理,把计算结果发回到Client客户端。由于Binder实体是以强类型的形式存在,所以即使被多次引用,系统都会指向同一个Binder实体,除非所有都结束链接,否则Binder实体会一直存在。
在这里插入图片描述
(2)、Binder Driver简介
Binder Driver运行于Android 内核当中,它以 “字符驱动设备” 中的 “misc设备注册” 存在于设备目录 dev/binder,由于权限问题,在一般手机中没有权限进行复制,对此有兴趣的朋友可以在google 提供的 android 源代码中查看。它提供open(),mmap(),poll(),ioctl() 等函数进行标准化文件操作,负责进程之间Binder通信的建立,Binder实体在进程之间的传递,Binder实体引用的计数管理,数据包在进程之间的传递与交互等一系列底层操作。

54、内存泄露

(1)、非静态内部类会持有外部类的引用,如果这个非静态的内部类的生命周期比它的外部类的生命周期长,那么当销毁外部类的时候,它无法被回收,就会造成内存泄漏。

55.自定义view

(1)、onLayout
从源码我们知道,在layout()方法中已经通过setOpticalFrame(l, t, r, b)或 setFrame(l, t, r, b)方法对View自身的位置进行了设置,所以onLayout(changed, l, t, r, b)方法主要是ViewGroup对子View的位置进行计算。
(2)、总结
所有的view都有一个默认的onMeasure和onLayout,子view只不过是覆盖默认的,viewGroup除了对自身的测量和绘制还会对子view进行测量和布局。

56.onTouch, onTouchEvent, OnClick顺序和区别

(1)、onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。

(2)、假如onTouch方法返回false会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。

(3)、内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
(4)、顺序为:
onTouch—–>onTouchEvent—>onclick
(5)、其中onTouchEvent是view自带的实现,onTouch是OnTouchListener接口的方法。

57.view中的onlongclick和onclick的执行顺序

(1)、在View收到ACTION_DOWN事件的时候,会将mHasPerformedLongPress赋值为false,并将执行长按回调的任务postDelay()到任务队列,delay的时间就是长按的阈值;

(2)、而执行长按回调这个任务会在执行回调时根据onLongClick的返回值将mHasPerformedLongPress设置为true;

(3)、在View收到ACTION_UP事件时,会根据mHasPerformedLongPress来判断是否执行onClick();如果从ACTION_DOWN开始已经过了长按的时间,长按任务已经执行了,并且mHasPerformedLongPress已经设置为了true,就不会执行onClick;

(4)、如果点击很短的时间就松开,View接到ACTION_UP的时候长按任务还没被执行,mHasPerformedLongPress就不可能被设置为true,将会执行onClick,并且在onClick之前,将长按任务从任务队列中移除;

58.RecyclerView中在滑动的时候停止加载数据实现

(1)、Gilde的实现

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            Glide.with(mContext).resumeRequests();
        }else {
            Glide.with(mContext).pauseRequests();
        }
    }
});
59.指令重排

(1)、指令重排只适应在单线程
(2)、指令重排是通过线程本地缓存或者寄存器提高了执行效率
(3)、如何防止指令重排
volatile关键字可以保证变量的可见性,因为对volatile的操作都在Main Memory中,而Main Memory是被所有线程所共享的,这里的代价就是牺牲了性能,无法利用寄存器或Cache,因为它们都不是全局的,无法保证可见性,可能产生脏读。
volatile还有一个作用就是局部阻止重排序的发生,对volatile变量的操作指令都不会被重排序,因为如果重排序,又可能产生可见性问题。
在保证可见性方面,锁(包括显式锁、对象锁)以及对原子变量的读写都可以确保变量的可见性。但是实现方式略有不同,例如同步锁保证得到锁时从内存里重新读入数据刷新缓存,释放锁时将数据写回内存以保数据可见,而volatile变量干脆都是读写内存。

60.RecyclerView 数据更新

(1)、Adapter中更新数据的方法
notifyItemChanged(int position) 更新列表position位置上的数据可以调用
notifyItemInserted(int position) 列表position位置添加一条数据时可以调用,伴有动画效果
notifyItemRemoved(int position) 列表position位置移除一条数据时调用,伴有动画效果
notifyItemMoved(int fromPosition, int toPosition) 列表fromPosition位置的数据移到toPosition位置时调用,伴有动画效果
notifyItemRangeChanged(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项进行数据刷新
notifyItemRangeInserted(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量添加数据时调用,伴有动画效果
notifyItemRangeRemoved(int positionStart, int itemCount) 列表从positionStart位置到itemCount数量的列表项批量删除数据时调用,伴有动画效果
(2)、局部更新
通过payload可以实现局部刷新

 public final void notifyItemChanged(int position, @Nullable Object payload) {
        mObservable.notifyItemRangeChanged(position, 1, payload);
    }
public void onBindViewHolder(@NonNull VH holder, int position,
            @NonNull List<Object> payloads) {
        onBindViewHolder(holder, position);
    }
61.TreeMap

(1)、threemap的底层是树结构和hashmap是不同的数据结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

互联网小熊猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值