去面试腾讯,万万没想到面试官拿Android多进程对着我刨根问底,救救孩纸...

去面试腾讯,万万没想到面试官拿Android多进程对着我刨根问底,救救孩纸...,第1张

概述前言今天看到一道腾讯面试题,关于Android多进程,那么今天就来聊聊吧。Android中创建多进程的方式1)第一种,大家熟知的,就是给四大组件再AndroidManifest中指定android:process属性。<activityandroid:name="com.example.uithread.UIActivity"android:process=":test"/

前言

今天看到一道腾讯面试题,关于AndroID多进程,那么今天就来聊聊吧。

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    zygoteu0_a66   17247  152   297120  44096  S  com.example.jniu0_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的文章中说过这一点,SharedPreferences是不支持多进程的。Application会多次创建。多进程其实就对应了多应用,所以新进程创建的过程其实就是启动了一个新的应用,自然也会创建新的Application,Application和虚拟机和一个进程中的组件是一一对应的。AndroID中的IPC方式

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

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

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

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

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

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

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

看看用法,客户端和服务端通信:

//客户端public class MyActivity extends Activity {    private Messenger mService;    private Messenger mGetReplyMessager=new Messenger(new MessengerHandler());    private static class MessengerHandler extends Handler{        @OverrIDe        public voID handleMessage(@NonNull Message msg) {            switch (msg.what){                case 1:                    //收到消息                    break;            }            super.handleMessage(msg);        }    }    private ServiceConnection mConnection=new ServiceConnection() {        @OverrIDe        public voID onServiceConnected(Componentname name, IBinder service) {            mService = new Messenger(service);            Message msg = Message.obtain(null,0);            Bundle bundle = new Bundle();            bundle.putString("test", "message1");            msg.setData(bundle);            msg.replyTo = mGetReplyMessager; //设置接受消息者            try {                mService.send(msg);            } catch (remoteexception e) {                e.printstacktrace();            }        }        @OverrIDe        public voID onServicedisconnected(Componentname name) {        }    };    @OverrIDe    protected voID onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_main);        Intent i=new Intent(this,ServerService.class);        bindService(i,mConnection, Context.BIND_auto_CREATE);    }    @OverrIDe    protected voID onDestroy() {        super.onDestroy();        unbindService(mConnection);    }}//服务端    public class ServerService extends Service {    Messenger messenger = new Messenger(new MessageHandler());    public ServerService() {    }    public IBinder onBind(Intent intent) {        return messenger.getBinder();    }    private class MessageHandler extends Handler {        @OverrIDe        public voID handleMessage(Message msg) {            switch (msg.what){                case 0:                    //收到消息并发送消息                    Messenger replyTo = msg.replyTo;                    Message replyMsg = Message.obtain(null,1);                    Bundle bundle = new Bundle();                    bundle.putString("test","111");                    replyMsg.setData(bundle);                    try {                        replyTo.send(replyMsg);                    } catch (remoteexception e) {                        e.printstacktrace();                    }                    break;            }            super.handleMessage(msg);        }    }}

4. AIDL

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

服务端首先建立一个Service监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中申明,最后在Service中实现这个AIDL接口。

客户端需要绑定这个服务端的Service,然后将服务端返回的Binder对象转换成AIDL接口的属性,然后就可以调用AIDL中的方法了。

5. ContentProvIDer

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

1、声明ProvIDer

<provIDer     androID:authoritIEs="com.test.lz"  //provIDer的唯一标识     androID:name=".BookProdiver"    androID:permission="com.test.permission"    androID:process=":provIDer"/>
androID:authoritIEs,唯一标识,一般用包名。外界在访问数据的时候都是通过uri来访问的,uri由四部分组成
content://com.test.lz/ table/ 100|            |          |       |固定字段   Authority   数据表名   数据ID
androID:permission,权限属性,还有readPermission,writePermission。如果provIDer声明了权限相关属性,那么其他应用也必须声明相应的权限才能进行读写 *** 作。比如:
//应用1<permission androID:name="me.test.permission" androID:protectionLevel="normal"/> <provIDer     androID:authoritIEs="com.test.lz"    androID:name=".BookProdiver"    androID:permission="com.test.permission"    androID:process=":provIDer"/>  //应用2<uses-permission androID:name="me.test.permission"/>

2、然后外界应用通过getContentResolver()的增删查改方法访问数据即可。

6. Socket

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

然后简单贴下关键代码:

//申请权限<uses-permission androID:name="androID.permission.INTERNET" /><uses-permission androID:name="androID.permission.ACCESS_NETWORK_STATE" />//服务端ServerSocket serverSocket = new ServerSocket(8688);while(isActive) {         try {         //不断获取客户端连接              final Socket clIEnt = serverSocket.accept();              new Thread(){                        @OverrIDe                        public voID run() {                            try {                               //处理消息                            } catch (IOException e) {                                e.printstacktrace();                            }                        }                    }.start();                } catch (IOException e) {                    e.printstacktrace();                }            }//客户端Socket socket = null;    while(socket==null){          //为空代表链接失败,重连        try {            socket = new Socket("localhost",8688);            out = new PrintWriter(socket.getoutputStream(),true);            handler.sendEmptyMessage(1);            final Socket finalSocket = socket;            new Thread(){                @OverrIDe                public voID run() {                    try {                        reader = new BufferedReader(new inputStreamReader(finalSocket.getinputStream()));                    } catch (IOException e) {                        e.printstacktrace();                    }                    while(!MainActivity.this.isFinishing()){                         //循环读取消息                        try {                            String msg = reader.readline();                        } catch (IOException e) {                            e.printstacktrace();                        }                    }                }            }.start();        } catch (IOException e) {            e.printstacktrace();        }    }

7. Binder连接池

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

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

具体怎么用呢?还是简单贴下关键源码

public class BinderPoolimpl extends IBinderPool.Stub {    private static final String TAG = "Service BinderPoolimpl";    public BinderPoolimpl() {        super();    }    @OverrIDe    public IBinder queryBinder(int binderCode) throws remoteexception {        Log.e(TAG, "binderCode = " + binderCode);        IBinder binder = null;        switch (binderCode) {            case 0:                binder = new securitycenterImpl();                break;            case 1:                binder = new ComputeImpl();                break;            default:                break;        }        return binder;    }}public class BinderPool {    //...    private IBinderPool mBinderPool;    private synchronized voID connectBinderPoolService() {        Intent service = new Intent();        service.setComponent(new Componentname("com.test.lz", "com.test.lz.BinderPoolService"));        mContext.bindService(service, mBinderPoolConnection, Context.BIND_auto_CREATE);    }        public IBinder queryBinder(int binderCode) {        IBinder binder = null;        try {            if (mBinderPool != null) {                binder = mBinderPool.queryBinder(binderCode);            }        } catch (remoteexception e) {            e.printstacktrace();        }        return binder;    }    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {        @OverrIDe        public voID onServiceConnected(Componentname name, IBinder service) {            mBinderPool = IBinderPool.Stub.asInterface(service);            try {                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipIEnt, 0);            } catch (remoteexception e) {                e.printstacktrace();            }        }        @OverrIDe        public voID onServicedisconnected(Componentname name) {        }    };    private IBinder.DeathRecipIEnt mBinderPoolDeathRecipIEnt = new IBinder.DeathRecipIEnt() {        @OverrIDe        public voID binderDIEd() {            Log.e(TAG, "binderDIEd");            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipIEnt, 0);            mBinderPool = null;            connectBinderPoolService();        }    };}

8. broadcastReceiver

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

总结

面试前做好准备战!

接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!

这里给大家提供一个方向,进行体系化的学习:

1、看视频进行系统学习

前几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频,有需要的我也可以分享给你。

2、进行系统梳理知识,提升储备

客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

系统学习方向:

架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO

AndroID高级UI与FrameWork源码:高级UI晋升+Framework内核解析+AndroID组件内核+数据持久化

360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化

解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+AndroID架构组件Jetpack

NDK模块开发:NDK基础知识体系+底层图片处理+音视频开发

微信小程序:小程序介绍+UI开发+API *** 作+微信对接

HybrID 开发与Flutter:HTML5项目实战+Flutter进阶

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

3、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

主要内含微信 MMKV 源码、AsyncTask 源码、Volley 源码、Retrofit源码、Okhttp 源码等等。

4、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

还有耗时一年多整理的一系列AndroID学习资源:AndroID源码解析、AndroID第三方库源码笔记、AndroID进阶架构师七大专题学习、历年BAT面试题解析包、AndroID大佬学习笔记等等。

以上这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。

总结

以上是内存溢出为你收集整理的去面试腾讯,万万没想到面试官拿Android多进程对着我刨根问底,救救孩纸...全部内容,希望文章能够帮你解决去面试腾讯,万万没想到面试官拿Android多进程对着我刨根问底,救救孩纸...所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1029571.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存