Android高工(一)——四大组件

一、Activity

Activity是App与用户交互的页面组件,主要作用是向用户呈现界面与进行交互

1、生命周期流转过程

启动一个Activity后生命周期过程如下:

 

(1)onCreate

设置布局,解析xml布局,生成ViewTree

注意:该回调在生命周期中只执行一次

(2)onStart

onCreate之后的生命周期,此时Activity可见,但是无法与用户交互,不能获得焦点

(3)onResume

执行ViewTree的测量、布局绘制,在该生命周期时,Activity处于前台,获得焦点

(4)onPause

该方法表示Activity不再处于前台

通常意味着当前Activity已经失去焦点

 

有两个特殊的场景需要注意:

1> Android 7.0开始,多窗口模式下会存在多个Activity同时显示,但是只有一个Activity处于前台,在onResume状态下,其他的Activity尽管可见,但是处于没有焦点,处于onPause

2> 半透明Activity打开后,前一个Activity仍然可见,但是没有焦点,处于onPause生命周期内

 

 

(5)onStop

Activity不可见,Activity可能继续走向onDestroy标志着销毁,或者Activity重新进入前台

通常我们在该生命周期内执行一些不可见时无用的资源,例如,暂停动画等

(6)onRestart

已处于onStop的Activity重新进入前台,即onStop-> onRestart-> onStart-> onResume

(7)onDestory

系统在销毁Activity之前回调

注意:该生命周期只调用一次

 

2、启动模式

启动模式的意义是可以根据我们的需求,指定Activity的启动栈以及对于无需重复创建的Activity可以避免创建多个实例

(1)启动模式两种使用方式

    • Intent方式指定
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    • Manifest中设置
        <activity
            android:name=".framework.ui.activity.GenuineReaderActivity"
            android:configChanges="orientation|screenSize"
            android:hardwareAccelerated="true"
            android:launchMode="singleTask"
            android:screenOrientation="portrait" />

注意:当同一个Activity的两种方式启动模式的指定方式同时存在时,以Intent Flag的方式为准

(2)standard

默认模式/标准模式,每次启动Activity时都会创建Activity的实例,即经历onCreate-> onStart->onResume等过程

例如:当前栈内为A-B-C-D,D的启动模式为standard,那么此时启动D,则栈内结构为A-B-C-D-D

(3)singleTop

栈顶复用模式

如果当前任务的栈顶是在该Activity的实例,那么系统会调用onNewIntent方法将Intent实例传递到该实例,此时复用该Activity实例,不会创建新对象

如果当前任务栈中的栈顶不是该Activity的实例,那么会创建一个该Activity的实例

 

例如,当前Task内的Activity栈结构为A-B-C-D,D在栈顶,D的模式为SingleTop

我们启动D,那么不会创建D的实例,而是调用onNewIntent传入新的Intent,之后执行onRestart-> onStart->onResume

栈内结构为A-B-C-D

 

对应Flag 为 FLAG_ACTIVITY_SINGLE_TOP

(4)singleTask

栈内复用模式

对于一个具有SingleTask启动模式的Activity,启动它时

如果当前Activity亲和的栈不存在,那么就需要创建一个这样的栈,将该Activity放入

如果当前Activity亲和的栈存在,那么就需要从栈内查找该Activity是否存在,如果存在,则,则清除该Activity上面的Activity,使他位于栈顶

如果不存在,则创建实例,入栈

 

例如,App的主页就可以采用该启动模式,当通知等外部打开该Activity时,如果栈顶存在其他页面则会清除,保证栈内只有一个Activity实例

 

对应Flag为 FLAG_ACTIVITY_NEW_TASK

(5)singleInstance

全局单例,这种启动模式的Activity会单独创建一个Task,并且Task中只会有这一个Activity

 

关于启动模式的效果我们可以使用adb shell dumpsys activity结合adb shell am start -n xxx/

3、任务栈

任务栈可以理解为Activity的一个栈的结构,它的特点上和我们认知中的栈结构很相似,但是不是严格意义的栈

 

Activity创建启动时会进入那个任务栈,这与Activity对栈的亲和度有关,这个亲和度可以通过TaskAffinity来指定

默认情况下,所有Activity的任务栈的名字都是包名,但是我们可以结合TaskAffinity来指定Activity归属的栈

注意:TaskAffinity需要和SingleTask结合才有意义

        <activity
            android:name=".ui.activity.GenuineActivity"
            android:taskAffinity="com.test.test"
            android:configChanges="keyboardHidden|orientation"
            android:hardwareAccelerated="true"
            android:launchMode="singleTask"
            android:screenOrientation="portrait" />

 

3、Activity之间的传值

(1)启动过程中Intent传值

(2)startActivityForResult+onActivityResult

A Activity启动B Activity
在A Actiivity中
startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);
B Activity中,重载onActivityResult
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case REQUEST_CODE_PICK_IMAGE:
                    if (data == null) {
                        break;
                    }

                    Uri result = data.getData();
                    if (mValueCallback != null) {
                        mValueCallback.onReceiveValue(result);
                        mValueCallback = null;
                    }
                    break;
                default:
                    break;
            }
        }

        super.onActivityResult(requestCode, resultCode, data);
    }

(3)事件总线EventBus传值

(4)持久化存储Sp、数据库等方式传值

(5)广播

 

二、Fragemnt

设计Fragment的意义需要溯源到Android 3.0开始,为了大屏UI的设计而提出的,例如,一个Activity可以有不同的Tab,点击后可以切换不同的UI,同时它又有自己的生命周期

1、Fragment的生命周期

 

onAttach:将Fragment与Activity相关联

onCeate:创建Fragment对象

onCeateView:创建Fragment视图

onActivityCreated:在Activity的onCreate完成时回调该方法

onstart :和Activity的onStart类似,此时可见但是无焦点

onResume:此时有焦点

onPause:和Activity的onPause类似,此时可见但是无焦点

onStop:和Activity的onStop类似,此时不可见

onDestoryView:销毁视图

onDestory:销毁Fragment对象

onDetach:将Fragment与Activity解绑

2、多Fragment的切换方式

(1)FragmentTransaction

FragmentTranscation作为Fragment添加、移除、替换等操作的事务管理器

获取事务管理器的方式是:

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

添加Fragment

        transaction.add(mFlFragment.getId(), mSearchHistoryFragment);
        transaction.add(mFlFragment.getId(), mSearchSuggestFragment);
        transaction.add(mFlFragment.getId(), mSearchResultFragment);

提交事务

        transaction.commitAllowingStateLoss();

隐藏和显示某个Fragment

            transaction.hide(mSearchHistoryFragment);
            transaction.hide(mSearchResultFragment);
            transaction.show(mSearchSuggestFragment);
            transaction.commitAllowingStateLoss();

提交Fragment事务的方法主要有两个:commit与commitAllowingStatLoss

区别在于:commit方法的提交必须在Activity saveInstance保存状态之前,这样才能保存这些信息,因此如果是在saveInstance之后,就会抛出异常

而commitAllowingStateLoss则不会关心在保存状态之前还是之后

Can not perform this action after onSaveInstanceState

(2)ViewPager

在ViewPager中,添加多个Fragement,由于最小的预加载数只能为1,因此左右两侧都会有一个Fragemnt被预加载,而被预加载的Fragement生命周期会都会知道onResume,从而会导致生命周期紊乱

对于非androidx的,需要根据getUserVisibleHint来获取当前Fragment是否对用户可见

androidx可以它的最大生命周期为onResume,这样生命周期调用就符合了

(2)ViewPager2

生命周期调用符合可见性的逻辑

3、Fragment之间通信方式

(1)在Activity中可以获取Fragment实例,调用实例方法即可通信

(2)事件总线

(3)广播

(4)持久化数据

4、Fragment与Activity的通信方式

(1)fragment.setArguments

(2)使用接口或者在Activity调用fragment实例的对应方法

(3)广播

(4)事件总线

5、懒加载的实现

首先来看懒加载的含义,懒加载是指在我们在多Fragment切换场景下,对于非第一个展示的Fragemement只加载布局,但是不加载数据,当首次可见时才开始加载数据

即我们需要根据一个boolean标记来表示是首次进入该页面,那么我们再开始加载数据

三、广播

1、广播的设计意义

广播的设计,简单来说是解决应用或者是组件的通信问题,更重要是可以完成多对多的通信,而不是简单的一对一

同时广播也是支持通过Intent来进行跨进程通信的

2、广播分类

按照注册时机来分类

(1)静态广播

静态广播是在应用安装时由PMS来完成注册的

        <receiver
            android:name="com.tencent.mm.plugin.base.model.ShortcutBroadCastReceiver">

            <intent-filter>

                <action
                    android:name="android.intent.action.CREATE_SHORTCUT" />
            </intent-filter>
        </receiver>

(2)动态广播

动态广播是在代码中动态完成注册的

       // 注册
		mBatteryReceiver = new BatteryReceiver();//电池电量变化产生的广播的接收器
        registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

		// 取消注册
  		 try {
            unregisterReceiver(mBatteryReceiver);
        } catch (Exception ignored) {
        }

		// 发送广播
		sendBroadCast()

动态广播中又有比较特殊的本地广播

按照有序性

(1)有序广播

有序广播发出后,高优先级的会现接收到,并且可以拦截,例如:电话、短信等

(2)无序广播

无序广播发出后,没有优先级之分,也无法被拦截

 

3、本地广播

    protected LocalBroadcastManager mLocalBroadcastManager = null;
    mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
    mLocalBroadcastManager.registerReceiver(mRefreshReadActReceiver, RefreshReadActReceiver.createIntentFilter());

     try {
            mLocalBroadcastManager.unregisterReceiver(mRefreshReadActReceiver);
        } catch (Exception ignored) {
        }

本地广播的本质上是Handler的消息机制,本地广播LocalBroadCastManager是一个单例,

它的内部通过HashMap保存着接收者的信息

  private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers
            = new HashMap<>();
    private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();

在sendBroadCast时

    public boolean sendBroadcast(@NonNull Intent intent) {
        synchronized (mReceivers) {
            final String action = intent.getAction();
            final String type = intent.resolveTypeIfNeeded(
                    mAppContext.getContentResolver());
            final Uri data = intent.getData();
            final String scheme = intent.getScheme();
            final Set<String> categories = intent.getCategories();

            final boolean debug = DEBUG ||
                    ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
            if (debug) Log.v(
                    TAG, "Resolving type " + type + " scheme " + scheme
                    + " of intent " + intent);

            ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
            if (entries != null) {
                if (debug) Log.v(TAG, "Action list: " + entries);

                ArrayList<ReceiverRecord> receivers = null;
                for (int i=0; i<entries.size(); i++) {
                    ReceiverRecord receiver = entries.get(i);
                    if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);

                    if (receiver.broadcasting) {
                        if (debug) {
                            Log.v(TAG, "  Filter's target already added");
                        }
                        continue;
                    }

                    int match = receiver.filter.match(action, type, scheme, data,
                            categories, "LocalBroadcastManager");
                    if (match >= 0) {
                        if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
                                Integer.toHexString(match));
                        if (receivers == null) {
                            receivers = new ArrayList<ReceiverRecord>();
                        }
                        receivers.add(receiver);
                        receiver.broadcasting = true;
                    } else {
                        if (debug) {
                            String reason;
                            switch (match) {
                                case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
                                case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
                                case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
                                case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
                                default: reason = "unknown reason"; break;
                            }
                            Log.v(TAG, "  Filter did not match: " + reason);
                        }
                    }
                }

                if (receivers != null) {
                    for (int i=0; i<receivers.size(); i++) {
                        receivers.get(i).broadcasting = false;
                    }
                    mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }

最终是调用 mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);

        mHandler = new Handler(context.getMainLooper()) {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };

    void executePendingBroadcasts() {
        while (true) {
            final BroadcastRecord[] brs;
            synchronized (mReceivers) {
                final int N = mPendingBroadcasts.size();
                if (N <= 0) {
                    return;
                }
                brs = new BroadcastRecord[N];
                mPendingBroadcasts.toArray(brs);
                mPendingBroadcasts.clear();
            }
            for (int i=0; i<brs.length; i++) {
                final BroadcastRecord br = brs[i];
                final int nbr = br.receivers.size();
                for (int j=0; j<nbr; j++) {
                    final ReceiverRecord rec = br.receivers.get(j);
                    if (!rec.dead) {
                        // 执行接收者的onReceive方法
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }

5、对比动态广播和静态广播

(1)生命周期

静态广播在App启动时就完成注册,在App结束之后,部分广播也可以通过静态广播唤醒

动态广播则是在于它注册和取消注册的生命周期内

从理论上来说,静态广播在App结束之后还是可以收到的,但是在版本中

(2)优先级

优先级上,动态广播优先于静态广播

 

6、广播的源码分析

(1)注册广播

广播接收注册时,会通过Binder机制,向ASM完成注册

(2)发送广播

广播发送者通过Binder机制将广播消息发送到ASM中

(3)AMS查找接收者

AMS根据发送者指定的ACTION,从已注册列表中查找接收者

(4)AMS将接收者加入到消息队列中

AMS将根据找到的接收者,通过ActivityThread发送RECEIVER消息到消息队列中

        public final void scheduleReceiver(Intent intent, ActivityInfo info,
                CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                boolean sync, int sendingUser, int processState) {
            updateProcessState(processState, false);
            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                    sync, false, mAppThread.asBinder(), sendingUser);
            r.info = info;
            r.compatInfo = compatInfo;
            sendMessage(H.RECEIVER, r);
        }

                case RECEIVER:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                    handleReceiver((ReceiverData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

(5)消息队列处理广播消息

ActivityThread中收到RECEIVER消息后,首先执行makeApplication,根据当前Receiver是否指定了多进程,如果是多进程,则创建Application对象,并执行onCreate等方法,如果不是,则不会创建新的Application

之后通过反射生成Receiver对象,然后调用对象的onReceive方法

从这里可以看出,onReceive方法是执行在主线程的,因此这里不能执行耗时操作

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private void handleReceiver(ReceiverData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        String component = data.intent.getComponent().getClassName();

        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);

        IActivityManager mgr = ActivityManager.getService();

        Application app;
        BroadcastReceiver receiver;
        ContextImpl context;
        try {
            // 如果广播指定了多进程,则这里会重新创建一个Application,如果没有,则在当前进程中执行即可
            app = packageInfo.makeApplication(false, mInstrumentation);
            context = (ContextImpl) app.getBaseContext();
            if (data.info.splitName != null) {
                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
            }
            java.lang.ClassLoader cl = context.getClassLoader();
            data.intent.setExtrasClassLoader(cl);
            data.intent.prepareToEnterProcess();
            data.setExtrasClassLoader(cl);
            // 构建receiver
            receiver = packageInfo.getAppFactory()
                    .instantiateReceiver(cl, data.info.name, data.intent);
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            throw new RuntimeException(
                "Unable to instantiate receiver " + component
                + ": " + e.toString(), e);
        }

        try {
            if (localLOGV) Slog.v(
                TAG, "Performing receive of " + data.intent
                + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + packageInfo.getPackageName()
                + ", comp=" + data.intent.getComponent().toShortString()
                + ", dir=" + packageInfo.getAppDir());

            sCurrentBroadcastIntent.set(data.intent);
            receiver.setPendingResult(data);
            
            // 执行onReceiver方法
            receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            if (!mInstrumentation.onException(receiver, e)) {
                throw new RuntimeException(
                    "Unable to start receiver " + component
                    + ": " + e.toString(), e);
            }
        } finally {
            sCurrentBroadcastIntent.set(null);
        }

        if (receiver.getPendingResult() != null) {
            data.finish();
        }
    }

7、BroadCastReceiver的ANR问题

从上面的分析可以知道,BroadCastReceiver的onReceive方法是执行在主线程的,因此不能执行耗时操作,当onReceive 10s内没有处理完就会触发ANR事件

 

广播中一般不建议执行耗时操作,如果有耗时操作,那么两个方案:

    • 开启子线程执行
    • 使用Service执行后台任务

8、在onReceive中启动Activity的问题

从源码分析来看,onReceive中传入了context对象

     receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);

	final Context getReceiverRestrictedContext() {
    if (mReceiverRestrictedContext != null) {
            return mReceiverRestrictedContext;
        }
        return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext());
    }

class ReceiverRestrictedContext extends ContextWrapper

而这个context对象是一个Application的Context对象,因此启动Activity需要使用FLAG_ACTIVITY_NEW_TASK来启动

 

四、Service

1、设计的意义

Service的设计主要是用来执行后台任务的

2、使用

(1)在Manifest中注册

    <application>
        <service android:name=".manager.UpdateService"/>
    </application>

(2)启动Service

启动的方式上分为绑定Service和非绑定Service

1> 绑定Service

mIsBind = activity.bindService(mBinderIntent, mConn, Context.BIND_AUTO_CREATE);

if (mConn != null && mIsBind) {
            try {
                activity.unbindService(mConn);
            } catch (Exception e) {
                e.printStackTrace();
            }
            mIsBind = false;
        }

 

bindService可以返回一个iBinder接口,用于与Service进行通信

(3)创建Service类

public class LocalService extends Service
{
    @Nullable
    @Override
    public IBinder onBind(Intent intent)//所有继承Service都必须实现onBind()方法
    {
        return null;
    }

    @Override
    public void onCreate()//Service创建时调用,只调用一次
    {
        super.onCreate();
    }

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

    @Override
    public void onDestroy()//Service销毁时调用
    {
        super.onDestroy();
    }

}

 

2> 非绑定Service

        activity.startService(mBinderIntent);

3、生命周期变化

 

对于非绑定Service来说:

使用startService后,会调用onCreate-> onStartCommand(),我们在onStartCommand中执行我们的后台任务,执行完成后,销毁前调用onDestory

 

对于绑定Service来说

使用bindService,会调用onCreate->onBind方法,在该方法中返回Ibinder对象,之后可以通过调用Ibinder对象来执行对应的任务,之后调用unbindService时回调用onUnbind()->onDestory

 

注意:多次启动一个Service,只会存在一个Service对象实例,onCreate只会调用一次,但是onStartCommand会调用多次

 

4、Service的源码分析

 

5、Service的ANR问题

Service的生命周期方法是在主线程执行的,因此当Service的方法在20s没有执行完时,就会导致ANR

 

在Service中执行耗时任务的方案主要有两种:

    • 启动子线程执行
    • 使用IntentService,它内部是使用了HandlerThread,启动了一个子线程和消息队列来完成的
    • 使用WorkManager

6、Service中启动Activity问题

同样类似于BroadCastReceiver

五、ContentProvider

1、设计的意义

ContentProvider是设计用于进程间数据通信的方式,底层原理是Binder机制

2、注册方式

        <provider
            android:name="com.read.statlib.provider.StatContentProvider"
            android:authorities="com.read.xsh.StatContentProvider"
            android:enabled="true"
            android:exported="false" />

3、初始化时机

在ActivityThread的handleBindApplication中,会创建Application对象,然后加载ContentProvider,之后回调Applicatoin的onCreate方法

即它的加载时机是在Application的onCreate之前,但是在attachBaseContext之后

 

面试问题

1、Activity

(1)生命周期,以及不同启动方式对生命周期影响

(2)A->B 两者的生命周期变化,B退回到A,两者生命周期变化,结合生命周期变化

 

(3)setResult与startActivityForResult

(4)在Application中启动Activity会有什么问题

会报错,,需要指定FLAG_ACTIVITY_NEW_TASK

 

2、Fragment

(1)设计Fragment的意义

(2)Fragment懒加载如何实现

(3)Fragment的切换方式有哪些

 

(4)首页包含四个底部 Tab 以及 ABCD 4 个 Fragment,A Fragment 内部包含 ABCD 4 个 Fragment,内部需要动态加载数据,如何实现?

(5)FragmentTransaction中commit和commitAllowingStateLoss的区别

 

3、Service

(1)简述作用

(2)启动方式有几种?区别是什么?

(3)项目中哪里用到?如何使用

(4)Service中执行耗时任务如何处理

(5)Service中启动Activity或者显示Dialog如何处理

4、BroadCastReceiver

(1)广播的类型和使用场景

(2)本地广播的实现原理

(3)动态广播和静态广播对比

5、ContentProvider

(1)如何实现数据共享,原理是什么,如何操作

(2)ContentProvider在应用启动的初始化过程

https://yjy239.github.io/2020/08/15/android-chong-xue-xi-lie-contentprovider-qi-dong-yuan-li/

posted @ 2022-02-21 17:37  大喵爱吃鱼1027  阅读(354)  评论(0编辑  收藏  举报