Tencentとのインタビューに行ったとき、インタビュアーがAndroidマルチプロセスを使用して私に質問し、私の子供を救うとは思っていませんでした...

序文

今日、Androidマルチプロセスに関するTencentのインタビューの質問を見たので、今日それについて話しましょう。

Androidで複数のプロセスを作成する方法

1)誰もが知っているように、最初のタイプは、4つの主要なコンポーネントAndroidManifestandroid: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、表現には2つの方法があります。

  • :test「:」は、現在のパッケージ名がcom.example.jimuの場合、現在のプロセス名の前に現在のパッケージ名を追加することを意味します。その場合、プロセス名はcom.example.jimu:testになります。コロンで始まるこの種のプロセスは、現在のアプリケーションのプライベートプロセスに属し、他のアプリケーションコンポーネントを彼と同じプロセスで実行することはできません。
  • com.example.test2番目の表現方法は、完全な命名方法です。これは、グローバルプロセスに属する新しいプロセスのプロセス名であり、他のアプリケーションは、shareUIDを介して同じプロセスで実行できます。

簡単に言うとshareUID、通常、Androidの各アプリは、一意のLinuxユーザーIDに対応する個別のプロセスであるため、アプリケーションのファイルまたはコンポーネントは、アプリケーションにのみ表示されたままにすることができます。ただし、shareUIDを使用して、異なるapkとファイルを共有する方法もあります。これにより、異なるapkが同じユーザー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)プロセスを作成する2番目の方法は、JNIを介してnativeレイヤー内のプロセスをフォークすることです。

これはもっと複雑です。インターネットでいくつかの情報を検索したところ、通常のプロセスのフォークが見つかりました。

//主要代码
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プロセスを通じて直接フォークアウトされることがわかっています(フォークは、ハッチングされた現在のプロセスのコピーとして理解できます)。ですから、接合子プロセスを直接操作することが成功するかどうかはわかりません。それを知っている人は誰でも、WeChatディスカッショングループでそれについて話すことができます。

ちなみに、なぜすべてのプロセスがzygoteプロセスフォークを使用しなければならないのか、という友人がいるかもしれません

  • これはfork、動作がユーザーの空間データ全体とすべてのシステムオブジェクトをコピーし、現在のスレッドのみを新しいプロセスにコピーするためです。つまり、親プロセス其他线程は子プロセスで消えます。さまざまな問題(デッドロックや一貫性のない状態など)を防ぐためにzygote、このシングルスレッドプロセスであるプロセスのみが新しいプロセスをフォークします。
  • またzygote、仮想マシンの起動やシステムリソースの読み込みなど一部の初期化作業がプロセスで実行さます。このように、フォーク時に子プロセスを直接共有することができ、効率が向上します。これもこのメカニズムの利点です。

アプリケーションに複数のプロセスを使用することに問題はありますか?

前述のように、プロセスの作成方法は非常に簡単です。android:process属性を記述するだけです。使い方はそれほど簡単ではありませんか?明らかにそうではありませんが、アプリケーション内の複数のプロセスは、主に次のようなさまざまな問題を引き起こします。

  • 静的メンバーとシングルトンパターンは完全に無効です。各プロセスは独立した仮想マシンに割り当てられ、異なる仮想マシンはメモリ割り当てで異なるアドレススペースを持っているため、異なるプロセス、つまり異なる仮想マシンで同じクラスのオブジェクトにアクセスすると、より多くのコピーが生成されます。
  • スレッド同期メカニズムは完全に無効です。上記のように、スレッドによってロックされるオブジェクトが異なるため、異なるメモリはスレッドの同期を保証できません。
  • SharedPreferencesは信頼できなくなりましたSharedPreferencesに関する以前の記事では、SharedPreferencesはマルチプロセスをサポートしていません。
  • アプリケーションは複数回作成されますマルチプロセスは実際には複数のアプリケーションに対応しているため、新しいプロセスを作成するプロセスは実際に新しいアプリケーションを開始し、当然、新しいアプリケーションが作成されます。アプリケーションと仮想マシンは、プロセス内のコンポーネントに1対1で対応します。

AndroidのIPCメソッド

複数のプロセスには多くの問題があるため、当然のことながら解決策があります。メモリを共有することはできませんが、データの相互作用を実行できます。つまり、IPCと呼ばれるマルチプロセス通信を実行できます。

以下では、Androidの8つのIPCメソッドについて具体的に説明します。

1. Bundle Androidの4つの主要コンポーネントはBundleデータを転送するためのインテントの使用をサポートしているため、4つの主要コンポーネントの直接プロセス間通信でBundleを使用できます。ただし、バンドルにはサイズ制限があります。bundleデータ転送制限は1Mであること注意してください。データがこのサイズを超える場合は、他の通信方法を使用する必要があります。

2.ファイル共有は、複数のプロセスが读写一个文件データを交換し、プロセス間通信を完了する方法です。ただし、この方法には、マルチスレッドの読み取りと書き込みに問題が発生しやすいという大きな欠点があります。つまり并发问题、同時読み取りまたは同時書き込みが発生すると問題が発生しやすいため、この方法は、プロセス間の直接通信に適しています。高データ同期。

ここで誰かが驚かれるかもしれSharedPreferenceませんが、それxmlファイルの読み取りと書き込みだけではありませんか?プロセス間通信をサポートしないのはなぜですか?

  • これは、システムにSharedPreference読み取り/書き込みキャッシュ戦略があるためです。つまり、メモリ内にSharedPreferenceファイルのキャッシュがあり、これにはメモリが含まれますが、マルチプロセスでは信頼性が低くなります。

3.メッセンジャーMessengerを使用してメッセージオブジェクトを転送し、転送するデータをメッセージに配置できますこれは軽量の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

メッセンジャーはメッセージを送受信できますが、同時に多数のメッセージを処理することはできず、メソッドをクロスプロセスすることもできません。しかし、AIDLはそれを行うことができます、ここAIDLに使用プロセスの簡単な説明があります:

サーバーは、最初にクライアントの接続要求をリッスンするサービスを確立し、次にAIDLファイルを作成し、このAIDLファイルでクライアントに公開されているインターフェイスを宣言し、最後にこのAIDLインターフェイスをサービスに実装します。

クライアントは、このサーバーのサービスをバインドしてから、サーバーBinderから返されたオブジェクトをAIDLインターフェイスの属性に変換する必要があります。そうすると、クライアントはAIDLのメソッドを呼び出すことができます。

5. ContentProvider

特に異なるアプリケーション間でのデータ共有のために、4つの主要なコンポーネントの1つであるこれに誰もが精通している必要があります。その基礎となる実装は、バインダーを介して実現されます。2つの主要なステップがあります:

1.プロバイダーを宣言する

<provider 
    android:authorities="com.test.lz"  //provider的唯一标识 
    android:name=".BookProdiver"
    android:permission="com.test.permission"
    android:process=":provider"/>
  • 一意の識別子であるandroid:authoritiesは、通常、パッケージ名を使用します。データにアクセスするとき、外の世界は4つの部分で構成されるURIを介してアクセスされます
content://com.test.lz/ table/ 100
|            |          |       |
固定字段   Authority   数据表名   数据ID
  • android:permission、permission属性などreadPermission,writePermissionプロバイダーがアクセス許可関連の属性を宣言する場合、他のアプリケーションも、読み取りおよび書き込み操作を実行するために対応するアクセス許可を宣言する必要があります。といった:
//应用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()のadd、delete、check、およびmodifyメソッドを介してデータにアクセスできます。

6.ソケット

ソケットは、TCPやUDPなどのネットワーク通信でよく使用されます。ソケット通信に関しては、ネットワーク上の写真を借りて次のことを説明してください。

次に、キーコードを貼り付けるだけです。

//申请权限
<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连接池すべてのAIDL要求は、作成されるサービスが多すぎるのを防ぐためにサービスを開く必要があるため、このBinder连接池テクノロジーが引用されています。バインダー接続プールの主な機能は、各ビジネスモジュールのBinder要求をリモートサービス均一に転送て実行することServiceです。これにより、繰り返し作成されるプロセスを回避できます。バインダー接続プールの動作原理を投稿します。

  • 各ビジネスモジュールは、独自のAIDLインターフェイスを作成し、このインターフェイスを実装してから、独自の一意の識別子とそれに対応するBinderオブジェクトをサーバーに提供します。
  • サーバー側の場合、必要なサービスは1つだけです。サーバー側は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.知識を体系的に整理し、埋蔵量を改善する

クライアント開発には非常に多くの知識ポイントがあり、インタビューにはまだほとんど何もありません。したがって、面接のための他のトリックはありません。これらの知識ポイントにどれだけ準備しているかを見てください。したがって、面接に出かけるときは、レビューでどの段階に到達したかを確認することをお勧めします。

システム学習の方向性:

  • アーキテクトが基盤を構築するために不可欠なスキル:詳細なJavaジェネリック+単純な言語でのアノテーション+並行プログラミング+データ送信とシリアル化+ Java仮想マシンの原則+リフレクションとクラスの読み込み+動的プロキシ+効率的なIO

  • Androidの高度なUIとFrameWorkのソースコード:高度なUIプロモーション+フレームワークカーネル分析+ Androidコンポーネントカーネル+データの永続性

  • 360°の全体的なパフォーマンスチューニング:設計アイデアとコード品質の最適化+プログラムパフォーマンスの最適化+開発効率の最適化

  • オープンソースフレームワーク設計アイデアの解釈:ホットリペア設計+プラグインフレームワーク解釈+コンポーネントフレームワーク設計+画像読み込みフレームワーク+ネットワークアクセスフレームワーク設計+ RXJavaレスポンシブプログラミングフレームワーク設計+ IOCアーキテクチャ設計+ AndroidアーキテクチャコンポーネントJetpack

  • NDKモジュール開発: NDK基本知識システム+基礎となる画像処理+オーディオおよびビデオ開発

  • WeChatミニプログラム:ミニプログラムの紹介+ UI開発+ API操作+ WeChatドッキング

  • ハイブリッド開発とFlutter: Html5プロジェクトの戦闘+ Flutter Advanced

知識が整理されたら、空席を確認して記入する必要があるので、これらの知識ポイントについては、手元にたくさんの電子書籍やメモを用意しました。これらのメモは、各知識ポイントの完全な要約を提供します。

3.ソースコードを読み、実際の戦闘ノートを見て、神の考えを学びます

「プログラミング言語はプログラマーが表現する方法であり、アーキテクチャーはプログラマーの世界の認識です」。したがって、プログラマーがアーキテクチャーをすばやく理解して学習したい場合は、ソースコードを読むことが不可欠です。ソースコードを読むことは、問題を解決し、物事を理解することであり、さらに重要なことは、ソースコードの背後にあるアイデアを確認することです。プログラマーは、1万行のソースコードを読み、1万種類の練習をします。

主にWeChatMMKVソースコード、AsyncTaskソースコード、Volleyソースコード、Retrofitソースコード、OkHttpソースコードなどが含まれています。

4.面接の前夜に、質問をスプリントします

面接前1週間以内に全力疾走を開始できます。質問をブラッシングするときは、テクノロジーが最優先であり、アルゴリズムは並べ替えなどの基本であり、知的質問は通常、学校の新入生でない限り質問されないことに注意してください。

面接の質問に関して、私はあなたがお互いから学ぶのを助けるために一連の体系的な面接の質問を個人的に準備しました:

整理に1年以上かかった一連のAndroid学習リソースもありますAndroid源码解析、Android第三方库源码笔记、Android进阶架构师七大专题学习、历年BAT面试题解析包、Android大佬学习笔记。待つ。

上記のコンテンツは、フルバージョンが必要なすべての人、友人と無料で共有できますすべてのコンテンツを表示するにはここをクリックしてください

おすすめ

転載: blog.csdn.net/weixin_44339238/article/details/114952251