Android 面试-基础

259 阅读9分钟

一、Activity和Fragment如何通信?

在Activity和Fragment之间进行通信,可以通过以下方式实现

  1. Activity调用Fragment方法:Activity可以通过Fragment的实例调用其公共方法来与Fragment进行通信。在Activity中,通过FragmentManager获取Fragment的实例,并调用相应的方法。

在Activity中调用Fragment的方法示例:

// 获取FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();

// 通过tag或id获取Fragment实例
YourFragment fragment = (YourFragment) fragmentManager.findFragmentByTag("your_fragment_tag");

// 调用Fragment的公共方法
if (fragment != null) {
    fragment.yourMethod();
}
  1. Fragment调用Activity方法:Fragment可以通过调用getActivity()方法获取其所属的Activity实例,并通过该实例调用Activity的公共方法。

在Fragment中调用Activity的方法示例:

// 获取所属的Activity实例
YourActivity activity = (YourActivity) getActivity();

// 调用Activity的公共方法
if (activity != null) {
    activity.yourMethod();
}

需要注意的是,在调用Activity的方法或Fragment的方法之前,需要确保Activity或Fragment已经创建并处于可交互的状态,否则可能会引发空指针异常。可以在适当的生命周期方法或回调方法中进行通信操作,例如在Activity的onCreate()或Fragment的onActivityCreated()方法中进行通信。

另外,为了更灵活和松耦合地进行通信,还可以通过接口回调的方式实现Activity和Fragment之间的通信。在Fragment中定义一个接口,Activity实现该接口并在相应方法中处理通信逻辑。然后,Fragment通过调用接口方法来通知Activity。

通过上述方式,Activity和Fragment可以方便地进行通信和交互,实现灵活的业务逻辑和界面更新。

二、Android中Handler的理解

1、handler介绍

在Android中,Handler是一种用于处理线程之间的消息传递和任务调度的机制。它可以在工作线程中发送消息给主线程,或者在主线程中处理后台任务的结果。它的主要作用是将消息和Runnable对象发送到目标线程的消息队列中,然后在目标线程中处理这些消息或执行Runnable任务。通过Handler,我们可以实现在后台线程中执行耗时操作,并在主线程中更新UI,或者在不同的线程之间进行通信和同步。

2、如何避免handler内存泄漏

为了避免Handler导致的内存泄漏,可以采取以下几种方式:

  1. 使用静态内部类:将Handler定义为Activity或Fragment的静态内部类。这样做可以避免持有外部类的引用,从而避免了内存泄漏。
public class MyActivity extends AppCompatActivity {
    private static class MyHandler extends Handler {
        private final WeakReference<MyActivity> activityRef;

        MyHandler(MyActivity activity) {
            activityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MyActivity activity = activityRef.get();
            if (activity != null) {
                // 处理消息
            }
        }
    }

    private final MyHandler myHandler = new MyHandler(this);

    // ...
}
  1. 使用弱引用(WeakReference) :在Handler中使用弱引用来持有Activity或Fragment的引用。这样在处理消息时,先通过弱引用获取实例,然后再进行相应的操作。这样当Activity或Fragment被销毁时,弱引用会自动释放,避免了内存泄漏。
public class MyActivity extends AppCompatActivity {
    private static class MyHandler extends Handler {
        private final WeakReference<MyActivity> activityRef;

        MyHandler(MyActivity activity) {
            activityRef = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MyActivity activity = activityRef.get();
            if (activity != null) {
                // 处理消息
            }
        }
    }

    private final MyHandler myHandler = new MyHandler(this);

    // ...
}
  1. 及时移除Callbacks和Messages:在Activity或Fragment销毁时,确保及时移除Handler中的Callbacks和Messages,以防止它们持续存在而导致内存泄漏。
@Override
protected void onDestroy() {
    super.onDestroy();
    myHandler.removeCallbacksAndMessages(null);
}

通过采取上述措施,可以有效地避免Handler导致的内存泄漏问题,确保应用的内存使用和性能得到良好的管理。

3、Handler常见的类的理解

Handler、ThreadHandler、Looper和MessageQueue是Android中与线程间通信和消息处理相关的核心组件。理解它们之间的关系可以帮助我们更好地使用和掌握这些概念。

  1. Handler(处理者)是用于发送和处理消息的对象。它可以与特定的线程关联,用于向目标线程发送消息或执行任务。Handler提供了一系列方法,如sendMessage()、post()等,用于发送消息或任务到目标线程的消息队列。
  2. ThreadHandler是Handler的一种具体实现,与指定的线程关联,用于在该线程中发送和处理消息。ThreadHandler可以在任何线程中创建,并与该线程的Looper关联,使得该线程可以处理消息。
  3. Looper(循环器)是一个线程内部的消息循环机制,用于循环地从该线程的消息队列中取出消息并进行处理。每个线程只能拥有一个Looper对象,它通过prepare()和loop()方法创建和启动。Looper的loop()方法会一直运行,不断地从消息队列中取出消息并传递给对应的Handler进行处理。
  4. MessageQueue(消息队列)是保存消息的容器,它与特定的线程关联。MessageQueue使用先进先出的原则存储消息,并由Looper负责从中取出消息进行处理。
  5. Message(消息)是封装要发送的数据和相关信息的对象。每个Message都包含一个标识符(what字段)和一些可选的数据字段(obj字段),用于表示要执行的操作或传递的数据。消息可以通过Handler的sendMessage()方法发送到消息队列中,并在适当的时候被Looper取出并传递给对应的Handler进行处理。

简而言之,Handler用于发送和处理消息,ThreadHandler是Handler在特定线程的具体实现,Looper负责循环地从消息队列中取出消息并传递给对应的Handler,而MessageQueue用于存储消息的容器,Message则是要发送和处理的具体消息对象。

4、ThreadHandler在什么情况下使用?
  1. 当你需要在特定线程中发送和处理消息时,可以创建一个ThreadHandler对象,并与该线程的Looper关联。这样,你就可以在该线程中使用ThreadHandler发送消息到消息队列,并在Looper循环中处理这些消息。
  2. 在多线程环境中,当你需要在一个线程中与其他线程进行通信时,可以使用ThreadHandler。例如,如果你有一个后台线程执行耗时操作,并需要将结果传递给主线程进行UI更新,你可以在主线程中创建一个ThreadHandler,并将其传递给后台线程。后台线程可以使用ThreadHandler发送消息到主线程,主线程的ThreadHandler则可以处理这些消息并更新UI。

总之,ThreadHandler适用于需要在线程间进行消息传递和通信的场景。它能够将消息发送到特定线程的消息队列,并在Looper循环中处理这些消息,从而实现线程间的同步和通信。通过使用ThreadHandler,我们可以更灵活地处理多线程编程,并实现线程间的协作和数据交换。

5、ThreadHandler例子

假设我们有一个应用程序,其中包含一个后台线程执行耗时的网络请求操作,然后将获取的数据传递给主线程进行UI更新。在这种情况下,可以使用ThreadHandler进行线程间通信。

首先,在主线程中创建一个ThreadHandler,并与主线程的Looper关联:

ThreadHandler mainThreadHandler = new ThreadHandler(Looper.getMainLooper());

然后,在后台线程中执行网络请求操作,并将获取的数据封装到消息中发送给主线程:

// 在后台线程执行网络请求
String responseData = performNetworkRequest();

// 创建消息对象
Message message = mainThreadHandler.obtainMessage();
message.what = MESSAGE_DATA_RECEIVED;
message.obj = responseData;

// 发送消息到主线程的消息队列
mainThreadHandler.sendMessage(message);

在主线程的ThreadHandler中,处理接收到的消息,并更新UI:

public class MyThreadHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        
        switch (msg.what) {
            case MESSAGE_DATA_RECEIVED:
                // 在主线程更新UI
                String responseData = (String) msg.obj;
                updateUI(responseData);
                break;
            // 处理其他消息...
        }
    }
    
    private void updateUI(String data) {
        // 更新UI显示
        textView.setText(data);
    }
}

通过这种方式,我们可以在后台线程中执行网络请求,然后使用ThreadHandler将获取的数据封装为消息发送给主线程。主线程的ThreadHandler会在Looper循环中接收到这些消息,并调用相应的处理方法进行UI更新操作。

这个例子展示了ThreadHandler在实现线程间通信和在主线程更新UI方面的应用。它使得我们可以在多线程环境中进行数据传递和协作,同时保证UI更新在主线程进行,避免了线程安全的问题。

6、Handler延迟是怎么实现的

Handler发送消息,最终都会调用sendMessageDelayed

放入在之前所有待处理的消息 (当前时间 + delayMillis) 之后,将一条消息放入消息队列sendMessageAtTime。您将在handleMessage中,在附加到该处理程序的线程中收到它。

在uptimeMillis的绝对时间 (毫秒) 之前,在所有待处理的消息之后,将一条消息放入消息队列。时基为SystemClock.uptimeMillis。在深度睡眠中花费的时间会增加执行的额外延迟。将在handleMessage中,在附加到该处理程序的线程中收到它。

三、Android 弱引用WeakReference

在Android中,WeakReference(弱引用)是一种特殊类型的引用,它不会阻止被引用对象被垃圾回收器回收。当一个对象只有被弱引用所引用时,垃圾回收器在下次回收时就会回收该对象。

使用WeakReference可以在一些场景中避免内存泄漏问题,特别是在涉及长时间持有对象引用的情况下。通过使用WeakReference,我们可以让对象在不再被强引用持有时被垃圾回收。

在Android开发中,WeakReference常见的用途有:

  1. 避免内存泄漏:当我们需要在某些地方引用一个对象,但又不希望该对象因为被引用而无法被垃圾回收时,可以使用WeakReference。比如在Handler中持有Activity的引用时,可以将Activity对象使用WeakReference包装起来,以便在Activity被销毁后能够及时释放。
public class MyHandler extends Handler {
    private WeakReference<Activity> activityRef;

    public MyHandler(Activity activity) {
        activityRef = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        Activity activity = activityRef.get();
        if (activity != null) {
            // 处理消息
        }
    }
}
  1. 缓存机制:当我们需要实现缓存功能时,使用WeakReference可以使缓存对象在内存不足时被自动回收,避免内存溢出的问题。
Map<String, WeakReference<Bitmap>> imageCache = new HashMap<>();

public void putImageToCache(String key, Bitmap image) {
    imageCache.put(key, new WeakReference<>(image));
}

public Bitmap getImageFromCache(String key) {
    WeakReference<Bitmap> weakRef = imageCache.get(key);
    if (weakRef != null) {
        return weakRef.get();
    }
    return null;
}

通过使用WeakReference作为缓存的值,当内存不足时,垃圾回收器会自动回收没有被强引用持有的缓存对象。

需要注意的是,在使用WeakReference时需要进行空值检查,以确保获取到的对象不为空。由于弱引用可能在任何时候被垃圾回收,因此在使用时需要注意空指针的处理。

四、谈谈你对application的理解

在Android应用开发中,Application是一个重要的基础类,它是全局的单例对象,代表着整个应用程序的生命周期。下面是我对Application的理解:

  1. 全局上下文:Application对象提供了一个全局的上下文,可以在应用程序的任何地方获取它。通过Application上下文,可以访问应用程序的资源、启动组件、注册广播接收器等。它是一个全局可用的环境,方便在整个应用中共享数据和状态。
  2. 生命周期与应用程序一致:Application的生命周期与整个应用程序的生命周期一致。它在应用程序启动时创建,在应用程序退出时销毁。在Application的生命周期中,可以执行一些初始化操作、注册全局的监听器、管理全局的数据等。
  3. 全局变量的存储:Application对象可以用于存储和管理全局的变量和数据。通过在Application类中定义成员变量,可以在应用程序的不同组件中共享这些数据。这样可以避免在组件之间频繁传递数据,提高代码的简洁性和可维护性。
  4. 应用级别的配置:Application对象可以用于管理应用程序的全局配置信息。例如,可以在Application中读取和设置应用程序的主题、语言设置、网络配置等。这样可以确保应用程序在不同组件中具有一致的配置。
  5. 生命周期回调:Application类提供了一些生命周期回调方法,可以在应用程序的不同阶段执行相应的操作。例如,可以在onCreate()方法中进行应用程序的初始化,或在onTerminate()方法中释放资源。

总之,Application是Android应用程序的重要组件,它提供了全局的上下文、全局变量存储、应用级别的配置和生命周期回调等功能。合理地使用Application对象可以简化应用程序的开发,提高代码的复用性和可维护性。

五、Android SharedPreferences的启动

在Android应用启动时,查询SharePreferences数据的流程如下:

  1. 应用启动时,会创建一个Context对象,通常是ApplicationActivity的实例。
  2. 通过Context对象获取SharedPreferences实例,可以使用以下方法之一:
    • getSharedPreferences():获取特定名称的SharedPreferences,如果该名称的SharedPreferences不存在,则会创建一个新的。
    • getPreferences():获取默认名称的SharedPreferences,该名称与当前活动(Activity)相关联。
  1. 通过SharedPreferences实例调用相应的读取方法(如getInt()getString()等)查询数据,可以提供默认值,以便在找不到对应数据时返回默认值。
  2. 如果在SharedPreferences中找到了对应的数据,则返回查询结果;否则,返回默认值。

整个流程可以简化为以下代码示例:

// 获取Context对象
Context context = getApplicationContext();

// 获取SharedPreferences实例
SharedPreferences sharedPreferences = context.getSharedPreferences("my_preferences", Context.MODE_PRIVATE);

// 查询数据
int intValue = sharedPreferences.getInt("my_key", 0);
String stringValue = sharedPreferences.getString("another_key", "default_value");

在应用启动时,通过获取SharedPreferences实例并调用读取方法,可以方便地查询保存在SharedPreferences中的数据。

六、Android中view的事件分发

在Android中,View的事件分发涉及三个主要的方法:dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。下面是对这三个方法的简要说明:

  1. dispatchTouchEvent(MotionEvent event):
    • 该方法是View事件分发的入口,负责将触摸事件分发给View的处理逻辑。
    • 在dispatchTouchEvent()方法中,会先判断是否拦截触摸事件(通过调用onInterceptTouchEvent()方法),如果返回true,则表示父View拦截了事件,不会将事件传递给子View;如果返回false,则继续将事件传递给子View进行处理。
    • 如果没有拦截触摸事件,dispatchTouchEvent()方法会调用onTouchEvent()方法来处理触摸事件。
  1. onInterceptTouchEvent(MotionEvent event):
    • 该方法用于判断是否拦截触摸事件,通常在父View中重写。
    • 在onInterceptTouchEvent()方法中,可以根据具体的业务逻辑来判断是否需要拦截触摸事件。
    • 如果返回true,则表示父View拦截了触摸事件,不会将事件传递给子View;如果返回false,则继续将事件传递给子View。
  1. onTouchEvent(MotionEvent event):
    • 该方法用于处理触摸事件,通常在View中重写。
    • 在onTouchEvent()方法中,可以根据触摸事件的类型(如按下、移动、抬起等)来进行相应的处理。
    • 如果事件在View内部被处理并消费(返回true),则表示该事件已经处理完毕;如果返回false,则事件会继续传递给父View或上层View进行处理。

事件分发的过程是从上至下的,即从父View传递到子View。当触摸事件发生时,系统会首先将事件传递给顶层的父View,然后依次向下传递,直到找到最底层的子View进行事件处理。在传递过程中,父View可以选择拦截事件,或者将事件传递给子View进行处理。通过重写这些方法,可以对事件的分发和处理进行自定义,以实现特定的交互行为和逻辑。

需要注意的是,在使用事件分发时,应遵循一些原则,如合理处理触摸事件的传递、避免事件冲突和混乱,以及保持良好的用户体验等。

总结:

事件分发从Action_Down开始,最初由Activity的dispatchTouchEvent()方法接收,不拦截不中断的正常分发流程:Activity的disPatchTouchEvent()方法到PhoneWindow的superDispatchTouchEvent方法,再到DecorView的superDispatchTouchEvent方法,再到ViewGroup的dispatchTouchEvent方法,在ViewGroup的dispatchTouchEvent方法中判断是否拦截,若拦截调用ViewGroup的onTouchEvent方法,该ViewGroup消费掉;若不拦截,该ViewGroup遍历子View根据点击的位置等条件判断是否为接收事件的子View,是,则分发给该子View的dispatchTouchEvent()方法,然后会调用View的onTouchEvent方法,在onTouchEvent方法中会判断该子View是否可点击,是,则事件最终传递到View的onClick方法消费;否则,事件返回向上传递,直到消费或者终止。

在dispatchTouchEvent()方法中返回true或者false,事件不向下传递,只用调用super.dispatchTouchEvent方法,事件才会向下传递。
在onTouchEvent()方法中返回true,事件在该方法中消费,不会向下或者向上传递;返回super.onTouchEvent方法,将会调用View onTouchEvent方法,判断长按事件和点击事件的执行条件存不存在,存在则会在点击事件中消费。
在onInterceptTouchEvent()方法中返回true表示拦截事件,事件可能会在该ViewGroup中消费掉;返回false表示事件继续往下传递

当某个View的onTouchEvent()返回true,那么事件不会向下或者向上传递,而Action_MOVE和Action_UP事件将会在该View的onTouchEvent方法中处理

View事件传递参考文章:
www.jianshu.com/p/e99b5e8bd…
www.jianshu.com/p/1ac8d469f…