IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 个人认为重要的Android面试总结六 -> 正文阅读

[移动开发]个人认为重要的Android面试总结六

????????????????????????? Activity,Dialog,Toast的Window创建过程

上篇文章说过Dialog的创建,先来回顾下:?

1)Dialog?

//构造函数
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
        //......
        //获取了WindowManager对象,mContext一般是个Activity,获取系统服务一般是通过Binder获取
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        //创建新的Window
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        //这里也是上方mWindow.getCallback()为什么是Activity的原因,在创建新Window的时候会设置callback为自己
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        //关联WindowManager与新Window,token为null
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }



//show方法
    public void show() {
        //......
        if (!mCreated) {
            //回调Dialog的onCreate方法
            dispatchOnCreate(null);
        }
        //回调Dialog的onStart方法
        onStart();
        //获取当前新Window的DecorView对象
        mDecor = mWindow.getDecorView();
        WindowManager.LayoutParams l = mWindow.getAttributes();
        try {
            //把一个View添加到Activity共用的windowManager里面去
            mWindowManager.addView(mDecor, l);
            //......
        } finally {
        }
    }

可以看到一个Dialog从无到有经历了以下几个步骤:

  • 首先创建了一个新的Window,类型是PhoneWindow类型,与Activity创建Window过程类似,并设置setCallback回调。
  • 将这个新Window与从Activity拿到的WindowManager对象相关联,也就是dialog与Activity公用了同一个WindowManager对象。
  • show方法展示Dialog,先回调了Dialog的onCreate,onStart方法。
  • 然后获取Dialog自己的DecorView对象,并通过addView方法添加到WindowManager对象中,Dialog出现到屏幕上。

2)Activity

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        //...

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillOptions(application.getAutofillOptions());
        setContentCaptureOptions(application.getContentCaptureOptions());
    }



    public void setContentView(@LayoutRes int layoutResID) {
        // 交给Window
        getWindow().setContentView(layoutResID);
        // 创建ActionBar
        initWindowDecorActionBar();
    }    

performLauchActivity方法,然后会创建Activity的实例对象,并调用attach方法,也就是上述贴的源码。

在这个方法中,创建了新的Window对象,设置回调接口。这个回调接口主要就是用作Window在接收到外界状态改变的时候,就会回调给这个callback,比如onAttachedToWindow、dispatchTouchEvent方法等,这个上篇文章也有说过,事件分发的时候就是通过在DecorView中这个callback进行分发的。

然后view怎么显示到界面上的呢,Activity可没有show方法哦?其实就是通过setContentView方法。该方法主要做了以下几件事:

  • 创建DecorView,如果不存在的话。
  • 然后将xml中解析到的view添加到DecorView的mContentParent中,也就是布局为android.R.id.content的ContentView。
  • 回调onContentChanged方法,通知Activity视图已经发生改变。

贴张图:

到这里,一个有完整view结构的DecorView就创建出来了,但是它还没有被显示到手机界面上,也就是没有被添加到Window中。最后要调用了WMS的addView方法才会被用户真正看到:

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

3)Toast

public void show() {
    if (mNextView == null) {
        throw new RuntimeException("setView must have been called");
    }

    INotificationManager service = getService();
    String pkg = mContext.getOpPackageName();
    TN tn = mTN;
    tn.mNextView = mNextView;
    final int displayId = mContext.getDisplayId();

    try {
        service.enqueueToast(pkg, tn, mDuration, displayId);
    } catch (RemoteException e) {
        // Empty
    }
}


public void cancel() {
    mTN.cancel();
}


//class TN
public void handleShow() {
   // ......
    mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    mWM.addView(mView, mParams);
}

public void handleHide() {
    if (mView != null) {
        if (mView.getParent() != null) {
            mWM.removeView(mView);
        }
        mView = null;
    }
}    

Toast有点不同的在于,它内部维护了两个IPC通信,一个是NotificationManagerService,一个是回调TN接口。最终的实现都是走到TN.class的handleShow和handleHide方法,也就是addView和removeView。

? ? ? ? ? ? ? ? ? ? ? Android中创建多进程的方式

1) 第一种,大家熟知的,就是给四大组件再AndroidManifest中指定android:process属性。

<activity android:name="com.example.uithread.UIActivity" 
      android:process=":test"/>

<activity android:name="com.example.uithread.UIActivity2"
      android:process="com.example.test"/>   

可以看到,android:process有两种表达方式:

  • :test。“:”的含义是指要在当前的进程名前面加上当前的包名,如果当前包名为com.example.jimu。那么这个进程名就应该是com.example.jimu:test。这种冒号开头的进程属于当前应用的私有进程,其他应用的组件不可以和他跑到同一个进程中。
  • com.example.test。第二种表达方式,是完整的命名方式,它就是新进程的进程名,这种属于全局进程,其他应用可以通过shareUID的方式跑到同一个进程中。

简单说下shareUID:正常来说,Android中每个app都是一个单独的进程,与之对应的是一个唯一的linux user ID,所以就能保住该应用程序的文件或者组件只对该应用程序可见。但是也有一个办法能让不同的apk进行共享文件,那就是通过shareUID,它可以使不同的apk使用相同的 user ID。贴下用法:

//app1
<manifest package="com.test.app1"
android:sharedUserId="com.test.jimu"
>

//app2
<manifest package="com.test.app2"
android:sharedUserId="com.test.jimu"
>

//app1中获取app2的上下文:
Context mContext=this.createPackageContext("com.test.app2", Context.CONTEXT_IGNORE_SECURITY);

2)第二种创建进程的方法,就是通过JNI在native层中去fork一个进程。(本人不是太懂,但是也写下来吧) ,这种就比较复杂了,找到一个fork普通进程的:

//主要代码
long add(long x,long y)
{
   //fpid表示fork函数返回的值 
    pid_t fpid; 
    int count=0; 
    fpid=fork();  
}

//结果:
USER       PID   PPID   VSZ     RSS  STAT  NAME                 
root       152  1              S    zygote
u0_a66   17247  152   297120  44096  S  com.example.jni
u0_a66   17520  17247  0    0    Z  com.example.jni

最终的结果是可以创建出一个进程,但是没有运行,占用的内存为0,处于僵尸程序状态。

但是它这个是通过普通进程fork出来的,我们知道Android中所有的进程都是直接通过zygote进程fork出来的(fork可以理解为孵化出来的当前进程的一个副本)。所以不知道直接去操作zygote进程可不可以成功,有了解的小伙伴可以在微信讨论群里给大家说说。

对了,有的小伙伴可能会问,为什么所有进程都必须用zygote进程fork呢?

  • 这是因为fork的行为是复制整个用户的空间数据以及所有的系统对象,并且只复制当前所在的线程到新的进程中。也就是说,父进程中的其他进程在子进程中都消失了,为了防止出现各种问题(比如死锁,状态不一致)呢,就只让zygote进程,这个单线程的进程,来fork新进程。
  • 而且在zygote进程中会做好一些初始化工作,比如启动虚拟机,加载系统资源。这样子进程fork的时候也就能直接共享,提高效率,这也是这种机制的优点。

一个应用使用多进程会有什么问题吗?

上面说到创建进程的方法很简单,写个android:process属性即可,那么使用是不是也这么简单呢?很显然不是,一个应用中多进程会导致各种各样的问题,主要有如下几个:

  • 静态成员和单例模式完全失效。因为每个进程都会分配到一个独立的虚拟机,而不同的虚拟机在内存分配上有不同的地址空间,所以在不同的进程,也就是不同的虚拟机中访问同一个类的对象会产生多个副本。
  • 线程同步机制完全失效。同上面一样,不同的内存是无法保证线程同步的,因为线程锁的对象都不一样了。
  • SharedPreferences不在可靠。SharedPreferences是不支持多进程的。
  • Application会多次创建。多进程其实就对应了多应用,所以新进程创建的过程其实就是启动了一个新的应用,自然也会创建新的Application,Application和虚拟机和一个进程中的组件是一一对应的。

Android中的IPC方式(多进程间通信,简称IPC

既然多进程有很多问题,自然也就有解决的办法,虽然不能共享内存,但是可以进行数据交互啊,也就是可以进行多进程间通信,简称IPC。

下面就具体说说Android中的八大IPC方式:

1.)Bundle Android四大组件都是支持在Intent中使用Bundle来传递数据,所以四大组件直接的进程间通信就可以使用Bundle。但是Bundle有个大小限制要注意下,bundle的数据传递限制大小为1M,如果你的数据超过这个大小就要使用其他的通信方式了。

2.)文件共享 这种方式就是多个进程通过读写一个文件来交换数据,完成进程间通信。但是这种方式有个很大的弊端就是多线程读写容易出问题,也就是并发问题,如果出现并发读或者并发写都容易出问题,所以这个方法适合对数据同步要求不高的进程直接进行通信

这里可能有人就奇怪了,SharedPreference不就是读写xml文件吗?怎么就不支持进程间通信了?

  • 这是因为系统对于SharedPreference有读写缓存策略,也就是在内存中有一份SharedPreference文件的缓存,涉及到内存了,那肯定在多进程中就不那么可靠了

3.)MessengerMessenger是用来传递Message对象的,在Message中可以放入我们要传递的数据。它是一种轻量级的IPC方案,底层实现是AIDL。

4.)AIDL

Messenger虽然可以发送消息和接收消息,但是无法同时处理大量消息,并且无法跨进程方法。但是AIDL则可以做到,这里简单说下AIDL的使用流程

服务端首先建立一个Service监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中申明,最后在Service中实现这个AIDL接口。客户端需要绑定这个服务端的Service,然后将服务端返回的Binder对象转换成AIDL接口的属性,然后就可以调用AIDL中的方法了。

5.)ContentProvider

这个大家应很熟悉了,四大组件之一,专门用于不同应用间进行数据共享的。它的底层实现是通过Binder实现的。

6.)Socket

套接字,在网络通信中用的很多,比如TCP,UDP。关于Socket通信,借用网络上的一张图说明:

7.)Binder连接池?

关于Binder的介绍,这里主要讲一个Binder的实际使用的技术——Binder连接池。由于每个AIDL请求都要开启一个服务,防止太多服务被创建,就引用了Binder连接池技术。Binder连接池的主要作用就是将每个业务模块的Binder请求统一 转发到远程Service中去执行,从而避免了重复创建Service的过程。贴一下Binder连接池的工作原理:

  • 每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象.
  • 对于服务端来说,只需要一个 Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来 返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。

8.)BroadcastReceiver?

广播,不用多说了吧~ 像我们可以监听系统的开机广播,网络变动广播等等,都是体现了进程间通信的作用。

冷启动、温启动、热启动

首先了解下启动的这三个概念,也是面试常被问到的:

  • 冷启动。冷启动指的是该应用程序在此之前没有被创建,发生在应用程序首次启动或者自上次被终止后的再次启动。简单的说就是app进程还没有,需要创建app的进程启动app。

比如开机后,点击屏幕的app图标启动应用。

冷启动的过程主要分为两步:

1)系统任务。加载并启动应用程序;显示应用程序的空白启动窗口;创建APP进程?

2)APP进程任务。启动主线程;创建Activity;加载布局;屏幕布局;绘制屏幕

其实这不就是APP的启动流程嘛?所以冷启动是会完整走完一个启动流程的,从系统到进程。

  • 温启动。温启动指的是App进程存在,但Activity可能因为内存不足被回收,这时候启动App不需要重新创建进程,只需要执行APP进程中的一些任务,比如创建Activity。

比如返回主页后,又继续使用其他的APP,时间久了或者打开的应用多了,之前应用的Activity有可能被回收了,但是进程还在。

所以温启动相当于执行了冷启动的第二过程,也就是APP进程任务,需要重新启动线程,Activity等。

  • 热启动。热启动就是App进程存在,并且Activity对象仍然存在内存中没有被回收。

比如app被切到后台,再次启动app的过程。

所以热启动的开销最少,这个过程只会把Activity从后台展示到前台,无需初始化,布局绘制等工作。

启动优化我们可以介入的优化点

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:32:03  更:2021-08-10 13:34:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 21:37:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码