ホームページ >ウェブフロントエンド >フロントエンドQ&A >Android の面接の質問を最も詳細に共有

Android の面接の質問を最も詳細に共有

藏色散人
藏色散人転載
2020-07-31 13:52:226238ブラウズ

おすすめ: 「2020 Android 面接質問まとめ [コレクション]

Android の基礎知識

#1. 一般知識

#1. Android クラスローダー

Android 開発において、プラグインかどうかコンポーネント化でもコンポーネント化でも、それらはすべて Android システムの ClassLoader に基づいて設計されています。 Android プラットフォーム上の仮想マシンは、クラス ファイル最適化の産物である Dex バイトコードを実行しているだけです。従来の Class ファイルは .class ファイルを生成する Java ソース コード ファイルですが、Android はすべての Class ファイルをマージして最適化します。最終的な class.dex を生成します。その目的は、異なるクラス ファイルに重複するもののコピーを 1 つだけ保持することです。初期の Android アプリケーション開発では、Android アプリケーションが dex に分割されていなかった場合、最後のアプリケーションの apk はdexファイルがあります。

Android には、DexClassLoader と PathClassLoader という 2 つの一般的に使用されるクラス ローダーがあり、どちらも BaseDexClassLoader を継承しています。違いは、親クラスのコンストラクターを呼び出すときに、DexClassLoader が追加の最適化されたディレクトリ パラメーターを渡すことです。このディレクトリは内部ストレージ パスである必要があり、システムによって作成された Dex ファイルをキャッシュするために使用されます。 PathClassLoader のパラメータは null であり、内部ストレージ ディレクトリ内の Dex ファイルのみをロードできます。したがって、DexClassLoader を使用して外部 APK ファイルをロードできます。これは、多くのプラグイン テクノロジの基礎でもあります。
Android の面接の質問を最も詳細に共有

2. サービス

Android サービスを理解するには、次の側面から理解できます:

  • サービスはメイン スレッドの実行、時間- 消費操作 (ネットワーク リクエスト、データベースのコピー、大きなファイル) はサービスでは実行できません。
  • Service が配置されるプロセスを XML で設定し、別のプロセスで Service を実行できるようにすることができます。
  • Service で実行される操作は最大 20 秒、BroadcastReceiver は 10 秒、Activity は 5 秒です。
  • Activity は、bindService (Intent、ServiceConnection、flag) を通じて Service にバインドされます。
  • Activity は、startService と bindingService を通じてサービスを開始できます。

IntentService

IntentService は Service から継承された抽象クラスで、内部に ServiceHandler (ハンドラー) と HandlerThread (スレッド) があります。 IntentService は非同期リクエストを処理するクラスです。IntentService には時間のかかる操作を処理するワーカー スレッド (HandlerThread) が存在します。IntentService の起動方法は通常と同じですが、タスクが完了すると自動的に停止します。また、IntentService は複数回起動でき、時間のかかる各操作は、IntentService の onHandleIntent コールバック内でワーク キューの形式で実行され、毎回 1 つのワーカー スレッドが実行されます。 IntentService の本質は、HandlerThread と Handler をカプセル化する非同期フレームワークです。

2.1. ライフサイクル図

サービスは Android の 4 つの主要コンポーネントの 1 つであり、広く使用されています。アクティビティと同様に、サービスにも、次の図に示すように、一連のライフサイクル コールバック関数があります。
Android の面接の質問を最も詳細に共有

通常、Service を開始するには、startService と bindingService の 2 つの方法があります。

2.2. startService ライフ サイクル

Context の startService メソッドを呼び出すと、Service が開始されます。startService メソッドによって開始された Service は無期限に実行され続けます。Service は実行を停止し、 Context の stopService が外部から呼び出された場合、または Service の stopSelf メソッドが内部的に呼び出された場合にのみ破棄されます。

onCreate

onCreate: startService メソッドを実行するとき、サービスが実行されていない場合は、サービスが作成され、サービスの onCreate コールバック メソッドが実行されます。サービスがすでに実行されている場合は、サービスが実行されます。実行中の場合、startService メソッドは実行されず、Service の onCreate メソッドが実行されます。つまり、Context の startService メソッドを複数回実行して Service を開始した場合、Service メソッドの onCreate メソッドは、Service を初めて作成するときに 1 回だけ呼び出され、次回以降は呼び出されません。未来。サービスの初期化関連の操作の一部を onCreate メソッドで完了できます。

onStartCommand

onStartCommand: startService メソッドの実行後、Service の onCreate メソッドが呼び出される可能性があり、その後、Service の onStartCommand コールバック メソッドが確実に実行されます。つまり、Context の startService メソッドが複数回実行されると、それに応じて Service の onStartCommand メソッドも複数回呼び出されます。 onStartCommand メソッドは非常に重要で、このメソッドでは受信した Intent パラメータに基づいて実際の操作を実行します (たとえば、データをダウンロードしたり、音楽を再生したりするためのスレッドがここに作成されます)。

public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
}

Android がメモリ不足に直面すると、現在実行中のサービスを破棄し、メモリが十分になったときにサービスを再作成することがあります。強制的に破棄され、再構築されるサービスの動作は Android システムによって異なります。 Service の onStartCommand メソッドの戻り値。私たちが使用する一般的に使用される戻り値は、START_NOT_STICKYSTART_STICKYSTART_REDELIVER_INTENT の 3 つです。これら 3 つの値は、サービス。

START_NOT_STICKY

START_NOT_STICKY が返された場合、サービスを実行しているプロセスが Android システムによって強制終了された場合、サービスは再作成されないことを意味します。もちろん、強制終了された場合は、一定期間後に startService が再度呼び出されると、Service は再度インスタンス化されます。では、どのような状況でこの値を返すのが適切なのでしょうか?サービスの 1 つによって実行される作業が何回中断されても、または Android のメモリが不足しているときにサービスを強制終了してすぐに再作成する必要がない場合は、この動作が許容されるため、戻り値を次のように設定できます。 onStartCommand を START_NOT_STICKY に設定します。たとえば、サービスはサーバーから最新のデータを定期的に取得する必要があります。タイマーを使用して、指定された N 分ごとにサービスを開始し、サーバーから最新のデータを取得します。サービスの onStartCommand が実行されると、このメソッドでは、サービスを再度開始し、新しいスレッドを開いてネットワーク操作を実行するために N 分間のタイマーが計画されます。サーバーから最新のデータを取得するプロセス中に Android システムによってサービスが強制終了されたと仮定すると、サービスは再作成されませんが、タイマーによってサービスが再度開始され、データが再取得されるため、これは問題ではありません。 N分後のデータ。

START_STICKY

START_STICKY が返された場合、サービスを実行しているプロセスが Android システムによって強制的に強制終了された後も、Android システムは依然としてサービスを次のように設定することを意味します。開始された状態 (つまり、実行中のステータス) ですが、onStartCommand メソッドで渡されたインテント オブジェクトは保存されなくなり、Android システムはサービスを再度再作成して onStartCommand コールバック メソッドを実行しようとしますが、その Intent パラメータはonStartCommand コールバック メソッドが null です。つまり、onStartCommand メソッドは実行されますが、インテント情報を取得できません。サービスがいつでも問題なく実行または終了でき、インテント情報が必要ない場合は、onStartCommand メソッドで START_STICKY を返すことができます。たとえば、バックグラウンド ミュージックの再生に使用されるサービスは、この値を返すのに適しています。

START_REDELIVER_INTENT

START_REDELIVER_INTENT が返された場合、START_STICKY を返した場合と同様に、サービスを実行しているプロセスが Android システムによって強制終了されたことを意味します。 Android システムはサービスを再度再作成し、onStartCommand コールバック メソッドを実行しますが、違いは、Android システムは、サービスが強制終了される前に最後に onStartCommand メソッドに渡されたインテントを保持し、それを再びサービスの onStartCommand メソッドに渡すことです。 Service. を再作成して、インテントパラメータを読み取ることができるようにします。 START_REDELIVER_INTENT が返される限り、onStartCommand のインテントは null であってはなりません。サービスを実行するために特定のインテントに依存する必要があり (インテントから関連データ情報を読み取る必要があるなど)、強制的に破棄した後にサービスを再作成する必要がある場合、そのようなサービスは START_REDELIVER_INTENT を返すのに適しています。

onBind

Service の onBind メソッドは抽象メソッドであるため、Service クラス自体も抽象クラスです。つまり、使用しない場合でも、onBind メソッドを書き直す必要があります。それ。 startService を通じて Service を使用する場合、onBind メソッドをオーバーライドするときに null を返すだけで済みます。 onBind メソッドは主に、bindService メソッドの Service を呼び出すときに使用されます。

onDestroy

onDestroy: startService メソッドで開始された Service は無期限に実行されます。Context の StopService が呼び出されるか、Service 内で stopSelf メソッドが呼び出された場合にのみ、Service の実行が停止します。破棄されると、Service コールバック関数が実行されます。

2.3、bindService ライフ サイクル

Android の面接の質問を最も詳細に共有
bindService メソッドは、主に次のライフ サイクル関数を使用してサービスを開始します:

onCreate():

システムは、サービスが最初に作成されるときにこのメソッドを呼び出します。サービスがすでに実行されている場合、このメソッドは呼び出されず、1 回だけ呼び出されます。

onStartCommand():

別のコンポーネントが startService() を呼び出してサービスの開始を要求すると、システムはこのメソッドを呼び出します。

onDestroy():

サービスが使用されなくなって破棄されると、システムはこのメソッドを呼び出します。

onBind():

別のコンポーネントがbindService()を呼び出してサービスにバインドすると、システムはこのメソッドを呼び出します。

onUnbind():

別のコンポーネントが unbindService() を呼び出してサービスからバインド解除されると、システムはこのメソッドを呼び出します。

onRebind():

古いコンポーネントがサービスからバインド解除され、別の新しいコンポーネントがサービスにバインドされ、onUnbind() が true を返すと、システムはこのメソッドを呼び出します。

3、fragemnt

3.1、创建方式

(1)静态创建

首先我们需要创建一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。

 <fragment
        android:name="xxx.BlankFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </fragment>
(2)动态创建

动态创建Fragment主要有以下几个步骤:

  1. 创建待添加的fragment实例。
  2. 获取FragmentManager,在Activity中可以直接通过调用 getSupportFragmentManager()方法得到。
  3. 开启一个事务,通过调用beginTransaction()方法开启。
  4. 向容器内添加或替换fragment,一般使用repalce()方法实现,需要传入容器的id和待添加的fragment实例。
  5. 提交事务,调用commit()方法来完成。

3.2、Adapter对比

FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响。

FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

3.3、Activity生命周期

Activity的生命周期如下图:
Android の面接の質問を最も詳細に共有

(1)动态加载:

动态加载时,Activity的onCreate()调用完,才开始加载fragment并调用其生命周期方法,所以在第一个生命周期方法onAttach()中便能获取Activity以及Activity的布局的组件;

(2)静态加载:

1.静态加载时,Activity的onCreate()调用过程中,fragment也在加载,所以fragment无法获取到Activity的布局中的组件,但为什么能获取到Activity呢?

2.原来在fragment调用onAttach()之前其实还调用了一个方法onInflate(),该方法被调用时fragment已经是和Activity相互结合了,所以可以获取到对方,但是Activity的onCreate()调用还未完成,故无法获取Activity的组件;

3.Activity的onCreate()调用完成是,fragment会调用onActivityCreated()生命周期方法,因此在这儿开始便能获取到Activity的布局的组件;

3.4、与Activity通信

fragment不通过构造函数进行传值的原因是因为横屏切换的时候获取不到值。

Activity向Fragment传值:

Activity向Fragment传值,要传的值放到bundle对象里;
在Activity中创建该Fragment的对象fragment,通过调用setArguments()传递到fragment中;
在该Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。

Fragment向Activity传值:
第一种:

在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id),例如:

FragmentManager fragmentManager = getFragmentManager();

Fragment fragment = fragmentManager.findFragmentByTag(tag);
第二种:

通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中

Fragment与Fragment之间是如何传值的:
第一种:

通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。

第二种:

通过接口回调的方式。

第三种:

通过setArguments,getArguments的方式。

3.5、api区别

add

一种是add方式来进行show和add,这种方式你切换fragment不会让fragment重新刷新,只会调用onHiddenChanged(boolean isHidden)。

replace

而用replace方式会使fragment重新刷新,因为add方式是将fragment隐藏了而不是销毁再创建,replace方式每次都是重新创建。

commit/commitAllowingStateLoss

两者都可以提交fragment的操作,唯一的不同是第二种方法,允许丢失一些界面的状态和信息,几乎所有的开发者都遇到过这样的错误:无法在activity调用了onSaveInstanceState之后再执行commit(),这种异常时可以理解的,界面被系统回收(界面已经不存在),为了在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候我们再去修改界面理论上肯定是不允许的,所以为了避免这种异常,要使用第二种方法。

3. 遅延読み込み

フラグメントをよく使用する場合、ビューページャーと組み合わせて使用​​することが多いのですが、その場合、フラグメントを初期化するときにフラグメントがネットワークに組み込まれてしまうという問題が発生します。リクエストを作成しました。実行には非常にパフォーマンスがかかります。理想的な方法は、ユーザーが現在のフラグメントをクリックまたはスライドしたときにのみネットワークをリクエストすることです。そこで、遅延読み込みという用語を思いつきました。

Viewpager はフラグメントとともに使用され、最初の 2 つのフラグメントがデフォルトでロードされます。ネットワークのパケットロスや輻輳などの問題が発生しやすくなります。

Fragment には setUserVisibleHint メソッドがあり、このメソッドは onCreate() メソッドよりも優れています。現在の Fragment が isVisibleToUser を通じて表示されているかどうかがわかり、表示されたときにネットワークを再度ロードできます。 。

ログから見ると、setUserVisibleHint() は onCreateView よりも前に呼び出されているため、setUserVisibleHint() で遅延読み込みを実装する場合は、null ポインターを避けるために View およびその他の変数が初期化されていることを確認する必要があります。

使用手順:

現在のページが作成されたかどうかを示す変数 isPrepare=false、isVisible=false を宣言します。
onViewCreated サイクル中に isPrepare=true を設定します。
setUserVisibleHint で( boolean isVisible) を使用して表示するかどうかを決定し、 isVisible=true
を設定して isPrepare と isVisible を判断し、両方とも true にしてデータのロードを開始します。その後、 isPrepare と isVisible を false に戻してロードの繰り返しを防ぎます。

Android Fragment の遅延読み込みについては、次のリンクを参照してください: Fragment の遅延読み込み

4、Activity

4.1、Activity 起動プロセス

User ランチャー プログラムからアプリケーション アイコンをクリックして、アプリケーションのエントリ アクティビティを開始します。アクティビティが開始されると、複数のプロセス間の対話が必要になります。Android システムには、Android フレームワーク層のプロセスをインキュベートする専用の zygote プロセスがあり、アプリケーション層プログラム。多くのバインダー サービスを実行する system_server プロセスもあります。たとえば、ActivityManagerService、PackageManagerService、WindowManagerService、これらのバインダー サービスはそれぞれ異なるスレッドで実行され、そのうちの ActivityManagerService はアクティビティ スタック、アプリケーション プロセス、およびタスクの管理を担当します。

Launcher アイコンをクリックしてアクティビティを開始します

ユーザーが Launcher プログラムのアプリケーション アイコンをクリックすると、ActivityManagerService はアプリケーションのエントリ Activity を開始するように通知されます。アプリケーションが開始されていない場合は、Zygote プロセスに通知されます。アプリケーション プロセスをハッチし、この dalvik アプリケーション プロセス内の ActivityThread の main メソッドを実行します。次に、アプリケーション プロセスは、アプリケーション プロセスが開始されたことを ActivityManagerService に通知します。ActivityManagerService は、アプリケーション プロセスのプロキシ オブジェクトを保存し、ActivityManagerService がこのプロキシ オブジェクトを通じてアプリケーション プロセスを制御できるようにします。その後、ActivityManagerService は、エントリ Activity のインスタンスを作成するようにアプリケーション プロセスに通知します。そしてそのライフサイクルメソッドを実行します。

Android描画プロセスウィンドウ起動プロセス分析

4.2、アクティビティライフサイクル
Android の面接の質問を最も詳細に共有

(1)アクティビティフォーム

アクティブ/実行中:

アクティビティはアクティブ状態にあります。この時点で、アクティビティはスタックの最上位にあり、表示され、ユーザーと対話できます。

一時停止:

アクティビティがフォーカスを失った場合、または新しい非全画面アクティビティまたは透明なアクティビティによってスタックの一番上に配置された場合、アクティビティは次のように変換されます。一時停止状態。ただし、現時点では、アクティビティはユーザーと対話する機能を失っただけであり、そのすべてのステータス情報とメンバー変数はまだ存在しており、システム メモリが不足している場合にのみ、システムによってリサイクルできることを理解する必要があります。

停止:

アクティビティが別のアクティビティによって完全にカバーされると、カバーされたアクティビティは停止状態になります。この時点では表示されなくなりますが、ステータスは一時停止として維持されます。 state. すべてのステータス情報とそのメンバー変数。

Killed:

アクティビティがシステムによってリサイクルされると、アクティビティは Killed 状態になります。

アクティビティは上記の4つの形式に切り替わりますが、どのように切り替えるかはユーザーの操作によって異なります。 Activity の 4 つの形式を理解した後、Activity のライフ サイクルについて説明します。

Activity のライフ サイクル

いわゆる典型的なライフ サイクルとは、ユーザーの参加により、Activity が作成、実行、停止、破棄に至る通常のライフ サイクル プロセスを経るというものです。

onCreate

このメソッドは、アクティビティの作成時に呼び戻されます。これは、ライフサイクルで呼び出される最初のメソッドです。通常、アクティビティの作成時にこのメソッドをオーバーライドする必要があります。 setContentView によるインターフェイス レイアウト リソースの設定、必要なコンポーネント情報の初期化など、メソッド内でいくつかの初期化操作を実行します。

onStart

このメソッドがコールバックされると、Activity が開始されていることを意味します。この時点では、Activity はすでに表示されていますが、まだ最前面に表示されていないため、ユーザーと対話することはできません。単純に、Activity が表示されていて、それを見ることができないと理解できます。

onResume

當此方法回呼時,則說明Activity已在前台可見,可與使用者互動了(處於前面所說的Active/Running形態),onResume方法與onStart的相同點是兩者都表示Activity可見,只不過onStart回呼時Activity還是後台無法與使用者交互,而onResume則已顯示在前台,可與使用者互動。當然從流程圖,我們也可以看出當Activity停止後(onPause方法和onStop方法被呼叫),重新回到前台時也會呼叫onResume方法,因此我們也可以在onResume方法中初始化一些資源,例如重新初始化在onPause或onStop方法中釋放的資源。

onPause

此方法回呼時則表示Activity正在停止(Paused形態),一般情況下onStop方法會緊接著被回呼。但透過流程圖我們還可以看到一種情況是onPause方法執行後直接執行了onResume方法,這屬於比較極端的現象了,這可能是用戶操作使當前Activity退居後台後又迅速地再回到到目前的Activity,此時onResume方法就會被回調。當然,在onPause方法中我們可以做一些資料儲存或動畫停止或資源回收的操作,但不能太耗時,因為這可能會影響到新的Activity的顯示-onPause方法執行完成後,新Activity的onResume方法才會被執行。

onStop

一般在onPause方法執行完成直接執行,表示Activity即將停止或完全被覆蓋(Stopped形態),此時Activity不可見,僅在背景運行。同樣地,在onStop方法可以做一些資源釋放的操作(不能太耗時)。

onRestart

表示Activity正在重新啟動,當Activity由不可見變成可見狀態時,該方法被回呼。這種情況一般是用戶打開了一個新的Activity時,當前的Activity就會被暫停(onPause和onStop被執行了),接著又回到當前Activity頁面時,onRestart方法就會被回調。

onDestroy

此時Activity正在被銷毀,也是生命週期最後一個執行的方法,一般我們可以在此方法中做一些回收工作和最終的資源釋放。

小結

到這裡我們來個小結,當Activity啟動時,依序會呼叫onCreate(),onStart(),onResume(),而當Activity退居後台時(不可見,點擊Home或被新的Activity完全覆蓋),onPause()和onStop()會依序被呼叫。當Activity重新回到前台(從桌面回到原Activity或被覆蓋後又回到原Activity)時,onRestart(),onStart(),onResume()會依序被呼叫。當Activity退出銷毀時(點擊back鍵),onPause(),onStop(),onDestroy()會依序被調用,到此Activity的整個生命週期方法回調完成。現在我們再回頭看看之前的流程圖,應該是相當清楚了吧。嗯,這就是Activity整個典型的生命週期過程。

2、View部分知識點

Android的Activity、PhoneWindow和DecorView的關係可以用下面的圖表示:
Android の面接の質問を最も詳細に共有

2.1、DecorView淺析

例如,有下面一個視圖,DecorView為整個Window介面的最頂層View,它只有一個子元素LinearLayout。代表整個Window介面,包含通知列、標題列、內容顯示列三塊區域。其中LinearLayout中有兩個FrameLayout子元素。
Android の面接の質問を最も詳細に共有

DecorView的作用

DecorView是頂級View,本質是一個FrameLayout它包含兩個部分,標題列和內容欄,都是FrameLayout。內容欄id是content,也就是activity中設定setContentView的部分,最終將佈局加入到id為content的FrameLayout。
取得content:ViewGroup content=findViewById(android.id.content)
取得設定的View:getChildAt(0).

使用總結

每個Activity都包含一個Window對象,Window物件通常是由PhoneWindow實現的。
PhoneWindow:將DecorView設定為整個應用程式視窗的根View,是Window的實作類別。它是Android中的最基本的視窗系統,每個Activity都會創建一個PhoneWindow對象,是Activity和整個View系統互動的介面。
DecorView:是頂層視圖,將要顯示的具體內容呈現在PhoneWindow上,DecorView是目前Activity所有View的祖先,它並不會向使用者呈現任何東西。

2.2、View的事件分發

View的事件分發機制可以使用下圖表示:
Android の面接の質問を最も詳細に共有
如上圖,圖分為3層,從上往下依序是Activity、ViewGroup、View。

  1. 事件從左上角那個白色箭頭開始,由Activity的dispatchTouchEvent做分發
  2. #箭頭的上面字代表方法傳回值,(return true、return false、return super.xxxxx(), super
    的意思是呼叫父類別實作。
  3. dispatchTouchEvent和onTouchEvent的框裡有個【true---->消費】的字,表示的意思是如果方法回傳true,那麼代表事件就此消費,不會繼續往別的地方傳了,事件終止。
  4. 目前所有的圖的事件是針對ACTION_DOWN的,對於ACTION_MOVE和ACTION_UP我們最後做分析。
  5. 之前圖中的Activity 的dispatchTouchEvent 有誤(圖已修復),只有return
    super.dispatchTouchEvent(ev) 才是往下走,回傳true 或false 事件就被消費了(終止傳遞)。

#ViewGroup事件分發

當一個點擊事件產生後,它的傳遞過程將遵循以下順序:

Activity -> Window -> View




Android の面接の質問を最も詳細に共有

Android の面接の質問を最も詳細に共有

##事件總是會傳給Activity,之後Activity再傳給Window,最後Window再傳給頂級的View,頂級的View就會在接收到事件後依照事件分發機制去分發事件。如果一個View的onTouchEvent回傳了FALSE ,那麼它的父容器的onTouchEvent將會被調用,依次類推,如果所有都不處理這個事件的話,那麼Activity將會處理這個事件。

對於ViewGroup的事件分發過程,大概是這樣的:如果頂級的ViewGroup攔截事件即onInterceptTouchEvent返回true的話,則事件會被調用,否則的話onTouchEvent將會被調用,也就是說:兩者都設定的話,onTouch將會屏蔽掉onTouchEvent,在onTouchEvent中,如果設定了onClickerListener的話,那麼onClick將會被呼叫。如果頂級ViewGroup不攔截的話,那麼事件將會被傳遞給它所在的點擊事件的子view ,這時候子view的dispatchTouchEvent將會被呼叫

View的事件分發Android の面接の質問を最も詳細に共有
dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClickAndroid の面接の質問を最も詳細に共有

#onTouchouch) -> onTouchEvent -> onClick

#onTouch和onTouchEvent的差別

兩者都是在dispatchTouchEvent中呼叫的,onTouch優先於onTouchEvent,如果onTouch回傳true,那麼onTouchEvent則不執行,及onClick也不執行。

    2.3、View的繪製
  1. 在xml佈局檔中,我們的layout_width和layout_height參數可以不用寫特定的尺寸,而是wrap_content或是match_parent。這兩個設定並沒有指定真正的大小,可是我們繪製到螢幕上的View必須是要有具體的寬高的,正是因為這個原因,我們必須自己去處理和設定尺寸。當然了,View類別給了預設的處理,但如果View類別的預設處理不符合我們的要求,我們就得重寫onMeasure函數啦~。

  2. onMeasure函數是一個int整數,裡面放了測量模式和尺寸大小。 int型資料佔用32個bit,而google實現的是,將int資料的前面2個bit用來區分不同的佈局模式,後面30個bit存放的是尺寸的資料。
  3. onMeasure函數的使用如下圖:

    ###MeasureSpec有三種測量模式:############match_parent—>EXACTLY。怎麼理解呢? match_parent就是要利用父View提供給我們的所有剩餘空間,而父View剩餘空間是確定的,也就是這個測量模式的整數裡面存放的尺寸。 ######wrap_content—>AT_MOST。怎麼理解:就是我們想要將大小設定為包裹我們的view內容,那麼尺寸大小就是父View給我們作為參考的尺寸,只要不超過這個尺寸就可以啦,具體尺寸就根據我們的需求去設定。 ######固定尺寸(如100dp)—>EXACTLY。使用者自己指定了尺寸大小,我們就不用再去干涉了,當然是以指定的大小為主啦。 #####################2.4、ViewGroup的繪製######自訂ViewGroup可就沒那麼簡單~,因為它不只管好自己的,還要兼顧它的子View。我們都知道ViewGroup是個View容器,它裝納child View並且負責把child View放入指定的位置。 ############首先,我們得知道各個子View的大小吧,只有先知道子View的大小,我們才知道目前的ViewGroup該設定為多大去容納它們。 ############根據子View的大小,以及我們的ViewGroup要實現的功能,決定出ViewGroup的大小###
  4. ViewGroup和子View的大小算出来了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得根据你定制的需求去摆放了,比如,你想让子View按照垂直顺序一个挨着一个放,或者是按照先后顺序一个叠一个去放,这是你自己决定的。

  5. 已经知道怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间”分割”成大大小小的空间,每个空间对应一个子View,我们接下来就是把子View对号入座了,把它们放进它们该放的地方去。
    Android の面接の質問を最も詳細に共有
    Android の面接の質問を最も詳細に共有

自定义ViewGroup可以参考:Android自定义ViewGroup

3、系统原理

3.1、打包原理

Android的包文件APK分为两个部分:代码和资源,所以打包方面也分为资源打包和代码打包两个方面,这篇文章就来分析资源和代码的编译打包原理。

具体说来:

  1. 通过AAPT工具进行资源文件(包括AndroidManifest.xml、布局文件、各种xml资源等)的打包,生成R.java文件。
  2. 通过AIDL工具处理AIDL文件,生成相应的Java文件。
  3. 通过Javac工具编译项目源码,生成Class文件。
  4. 通过DX工具将所有的Class文件转换成DEX文件,该过程主要完成Java字节码转换成Dalvik字节码,压缩常量池以及清除冗余信息等工作。
  5. 通过ApkBuilder工具将资源文件、DEX文件打包生成APK文件。
  6. 利用KeyStore对生成的APK文件进行签名。
  7. 如果是正式版的APK,还会利用ZipAlign工具进行对齐处理,对齐的过程就是将APK文件中所有的资源文件举例文件的起始距离都偏移4字节的整数倍,这样通过内存映射访问APK文件的速度会更快。

Android の面接の質問を最も詳細に共有

3.2、安装流程

Android apk的安装过程主要氛围以下几步:

  1. 复制APK到/data/app目录下,解压并扫描安装包。
  2. 资源管理器解析APK里的资源文件。
  3. 解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
  4. 然后对dex文件进行优化,并保存在dalvik-cache目录下。
  5. 将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
  6. 安装完成后,发送广播。

可以使用下面的图表示:
Android の面接の質問を最も詳細に共有

4、 第三方库解析

4.1、Retrofit网络请求框架

概念:Retrofit是一个基于RESTful的HTTP网络请求框架的封装,其中网络请求的本质是由OKHttp完成的,而Retrofit仅仅负责网络请求接口的封装。

原理:App应用程序通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数,Header、URL等信息,之后由OKHttp完成后续的请求,在服务器返回数据之后,OKHttp将原始的结果交给Retrofit,最后根据用户的需求对结果进行解析。

retrofit使用

1.在retrofit中通过一个接口作为http请求的api接口

public interface NetApi {
    @GET("repos/{owner}/{repo}/contributors")
    Call<responsebody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}</responsebody>

2.创建一个Retrofit实例

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();

3.调用api接口

NetApi repo = retrofit.create(NetApi.class);

//第三步:调用网络请求的接口获取网络请求
retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("username", "path");
call.enqueue(new Callback<ResponseBody>() { //进行异步请求
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        //进行异步操作
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        //执行错误回调方法
    }
});

retrofit动态代理

retrofit执行的原理如下:
1.首先,通过method把它转换成ServiceMethod。
2.然后,通过serviceMethod,args获取到okHttpCall对象。
3.最后,再把okHttpCall进一步封装并返回Call对象。
首先,创建retrofit对象的方法如下:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();

在创建retrofit对象的时候用到了build()方法,该方法的实现如下:

public Retrofit build() {
  if (baseUrl == null) {
    throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {
    callFactory = new OkHttpClient(); //设置kHttpClient
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {
    callbackExecutor = platform.defaultCallbackExecutor(); //设置默认回调执行器
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

  // Make a defensive copy of the converters.
  List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

  return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
      callbackExecutor, validateEagerly); //返回新建的Retrofit对象
}

该方法返回了一个Retrofit对象,通过retrofit对象创建网络请求的接口的方式如下:

NetApi repo = retrofit.create(NetApi.class);

retrofit对象的create()方法的实现如下:‘

public <T> T create(final Class<T> service) {
  Utils.validateServiceInterface(service);
  if (validateEagerly) {
    eagerlyValidateMethods(service);
  }
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
      new InvocationHandler() {
        private final Platform platform = Platform.get();

        @Override public Object invoke(Object proxy, Method method, Object... args)
            throws Throwable {
          // If the method is a method from Object then defer to normal invocation.
          if (method.getDeclaringClass() == Object.class) {
            return method.invoke(this, args); //直接调用该方法
          }
          if (platform.isDefaultMethod(method)) {
            return platform.invokeDefaultMethod(method, service, proxy, args); //通过平台对象调用该方法
          }
          ServiceMethod serviceMethod = loadServiceMethod(method); //获取ServiceMethod对象
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); //传入参数生成okHttpCall对象
          return serviceMethod.callAdapter.adapt(okHttpCall); //执行okHttpCall
        }
      });
}

4.2、图片加载库对比

Picasso:120K

Glide:475K

Fresco:3.4M

Android-Universal-Image-Loader:162K

图片函数库的选择需要根据APP的具体情况而定,对于严重依赖图片缓存的APP,例如壁纸类,图片社交类APP来说,可以选择最专业的Fresco。对于一般的APP,选择Fresco会显得比较重,毕竟Fresco3.4M的体量摆在这。根据APP对图片的显示和缓存的需求从低到高,我们可以对以上函数库做一个排序。

Picasso

2.介紹:

Picasso :和Square的網路庫一起能發揮最大作用,因為Picasso可以選擇將網路請求的快取部分交給了okhttp實作。

Glide:模仿了Picasso的API,而且在他的基礎上加了很多的擴充(例如gif等支援),Glide預設的Bitmap格式是RGB_565,比Picasso預設的ARGB_8888格式的記憶體開銷要小一半;Picasso快取的是全尺寸的(只緩存一種),而Glide快取的是跟ImageView尺寸相同的(即5656和128128是兩個快取) 。

FB的圖片載入框架Fresco:最大的優勢在於5.0以下(最低2.3)的bitmap載入。在5.0以下系統,Fresco將圖片放到一個特別的記憶體區域(Ashmem區)。當然,在圖片不顯示的時候,佔用的記憶體會自動被釋放。這會使得APP更加流暢,減少因圖片記憶體佔用而引發的OOM。為什麼說是5.0以下,因為在5.0以後系統預設就是儲存在Ashmem區了。

3.總​​結:

Picasso所能實現的功能,Glide都能做,無非是所需的設定不同。但是Picasso體積比起Glide小太多如果專案中網路請求本身用的就是okhttp或retrofit(本質還是okhttp),那麼建議用Picasso,體積會小很多(Square全家桶的干活)。 Glide的好處是大型的圖片串流,例如gif、Video,如果你們是做美拍、愛拍這種影片應用,建議使用。

Fresco在5.0以下的記憶體最佳化非常好,代價就是體積也非常的大,以體積算Fresco>Glide>Picasso

不過在使用起來也有些不便(小建議:他只能用內建的一個ImageView來實現這些功能,用起來比較麻煩,我們通常是根據Fresco自己改改,直接使用他的Bitmap層)

4.3、各種json解析庫使用

參考連結:https://www.cnblogs.com/kunpengit/p/4001680.html

(1)Google的Gson

Gson是目前功能最完整的Json解析神器,Gson當初是為因應Google公司內部需求而由Google自行研發而來,但自從在2008年五月公開發布第一版後已被許多公司或用戶應用。 Gson的應用主要為toJson與fromJson兩個轉換函數,無依賴,不需要例外額外的jar,能夠直接跑在JDK上。而在使用這種物件轉換之前需先建立好物件的類型以及其成員才能成功的將JSON字串成功轉換成相對應的物件。類別裡面只要有get和set方法,Gson完全可以將複雜型別的json到bean或bean到json的轉換,是JSON解析的神器。 Gson在功能上面無可挑剔,但是性能上面比FastJson有所差距。

(2)阿里巴巴的FastJson

Fastjson是一個Java語言編寫的高效能的JSON處理器,由阿里巴巴公司開發。

無依賴,不需要例外額外的jar,能夠直接跑在JDK上。 FastJson在複雜類型的Bean轉換Json上會出現一些問題,可能會出現引用的類型,導致Json轉換出錯,需要製定引用。 FastJson採用獨創的演算法,將parse的速度提升到極致,超過所有json庫。

綜上Json技術的比較,在專案選型的時候可以使用Google的Gson和阿里巴巴的FastJson兩種並行使用,如果只是功能要求,沒有效能要求,可以使用google的Gson,如果有效能上面的要求可以使用Gson將bean轉換json確保資料的正確,使用FastJson將Json轉換Bean

5、熱點技術

參考連結- Android元件化方案

5.1、元件化

(1)概念:

元件化:是將一個APP分成多個module,每個module都是一個元件,也可以是基礎函式庫供元件依賴,開發中可以單獨調試部分元件,元件中不需要相互依賴但是可以互相調用,最終發布的時候所有元件以lib的形式被主APP工程依賴打包成一個apk。

(2)由來:

  1. APP版本迭代,新功能不斷增加,業務變得複雜,維護成本高
  2. 業務耦合度高,程式碼臃腫,團隊內部多人協作開發困難
  3. Android編譯程式碼卡頓,單一工程下程式碼耦合嚴重,修改一處需要重新編譯打包,耗時耗力。
  4. 方便單元測試,單獨改一個業務模組,不需要專注於其他模組。

(3)優勢:

  1. 元件化將通用模組獨立出來,統一管理,以提高複用,將頁面拆分為粒度更小的元件,元件內部出了包含UI實現,還可以包含資料層和邏輯層
  2. 每個元件度可以獨立編譯、加快編譯速度、獨立打包。
  3. 每個工程內部的修改,不會影響其他工程。
  4. 業務庫工程可以快速分割出來,整合到其他App。
  5. 迭代頻繁的業務模組採用元件方式,業務線研發可以互不干擾、提升協作效率,並控制產品質量,加強穩定性。
  6. 並行開發,團隊成員只專注於自己的開發的小模組,降低耦合性,後期維護方便等。

(4)考慮問題:

模式切換:如何讓APP在單獨調試跟整體調試自由切換

組件化後的每一個業務的module都可以是單獨的APP(isModuleRun=false), release 包的時候各個業務module作為lib依賴,這裡完全由一個變數控制,在根項目gradle.properties裡面isModuleRun=true。 isModuleRun狀態不同,載入application和AndroidManifest都不一樣,以區分是獨立的APK還是lib。

在build.grade裡面配置:

Android の面接の質問を最も詳細に共有

資源衝突

當我們創建了多個Module的時候,如何解決相同資源檔案名稱合併的衝突,業務Module和BaseModule資源檔案名稱重複會產生衝突,解決方案在於:

每個module 都有app_name,為了不讓資源名稱重名,在每個元件的build. gradle 中增加resourcePrefix 「xxx_強制檢查資源名稱前綴。固定每個元件的資源前綴。但是resourcePrefix 這個值只能限定xml 裡面的資源,並不能限定圖片資源。

#依賴關係

多個Module之間如何引用一些共同的library以及工具類別

元件通訊

元件化之後,Module之間是相互隔離的,如何進行UI跳躍以及方法調用,具體可以使用阿里巴巴ARouter或美團的WMRouter等路由框架。

各業務Module之前不需要任何依賴可以透過路由跳轉,完美解決業務之間耦合。

入口參數

我們知道元件之間是有連結的,所以在單獨偵錯的時候如何拿到它的Module傳遞過來的參數

Application

當元件單獨運行的時候,每個Module自成一個APK,那麼就意味著會有多個Application,很顯然我們不願意重複寫這麼多程式碼,所以我們只需要定義一個BaseApplication即可,其它的Application直接繼承此BaseApplication就OK了,BaseApplication裡面還可定義公用的參數。

關於如何進行元件化,可以參考:安居客Android專案架構演進

5.2、外掛程式

#參考連結- 插件化入門

(1)概述

提到插件化,就不得不提起方法數超過65535的問題,我們可以透過Dex分包來解決,同時也可以透過使用外掛化開發來解決。外掛化的概念就是由宿主APP去載入以及運行外掛APP。

(2優點)

在一個大的專案裡面,為了明確的分工,往往不同的團隊負責不同的插件APP,這樣分工更加明確。各個模組封裝成不同的插件APK,不同模組可以單獨編譯,提高了開發效率。
解決了上述的方法數超過限制的問題。可以透過上線新的插件來解決線上的BUG,達到「熱修復」的效果。
減少了宿主APK的體積。

(3缺點)

外掛開發的APP不能在Google Play上線,也就是沒有海外市場。

6、螢幕適合

6.1、基本概念

螢幕尺寸

意思:手機對角線的物理尺寸單位:英吋(inch) ,1吋=2.54cm

Android手機常見的尺寸有5吋、5.5吋、6吋,6.5吋等等

螢幕解析度

意思:手機在橫向、縱向上的像素點數總和

一般描述成螢幕的」寬x高”=AxB 意義:螢幕在橫向方向(寬度)上有A個像素點,在縱向方向

#(高)有B個像素點範例:1080x1920,即寬度方向上有1080個像素點,在高度方向上有1920個像素點

單位:px(pixel),1px=1像素點

UI設計師的設計圖會以px作為統一的計量單位

Android手機常見的解析度:320x480、480x800、720x1280、1080x1920

螢幕像素密度

意義:每英吋的像素點數單位:dpi(dots per ich)

假設裝置內每英吋有160個像素,那麼該裝置的螢幕像素密度=160dpi

6.2、適配方法

1.支援各種螢幕尺寸: 使用wrap_content, match_parent, weight.要確保佈局的彈性並適應各種尺寸的螢幕,應使用“wrap_content”、“match_parent” 控制某些視圖組件的寬度和高度。

2.使用相對佈局,停用絕對佈局。

3.使用LinearLayout的weight屬性

假如我們的寬度不是0dp(wrap_content和0dp的效果相同),則是match_parent呢?

android:layout_weight的真實意義是:如果View設定了該屬性並且有效,那麼該 View的寬度等於原有寬度(android:layout_width)加上剩餘空間的佔比。

從這個角度我們來解釋上面的現象。在上面的程式碼中,我們設定每個Button的寬度都是match_parent,假設螢幕寬度為L,那麼每個Button的寬度也應該都為L,剩餘寬度就等於L-(L L)= -L。

Button1的weight=1,剩餘寬度佔比為1/(1 2)= 1/3,所以最終寬度為L 1/3*(-L)=2/3L,Button2的計算類似,最終寬度為L 2/3(-L)=1/3L。

4.使用.9圖片

6.3、「今日頭條螢幕」適合

參考連結:「今日頭條螢幕適應方案終極版

7、效能最佳化

參考連結:Android 效能監測工具,優化記憶體、卡頓、耗電、APK大小的方法
Android的效能最佳化,主要是從以下幾個方面進行最佳化的:
穩定(記憶體溢出、崩潰)
流暢(卡頓)
耗損(耗電、流量)
安裝包(APK瘦身)
影響穩定性的原因很多,例如記憶體使用不合理、程式碼異常場景考慮不周全、程式碼邏輯不合理等,都會對應用程式的穩定性造成影響。其中最常見的兩個場景是:Crash 和 ANR,這兩個錯誤將使得程式無法使用。所以做好Crash全域監控,處理閃退同時把崩潰訊息、異常訊息收集記錄起來,以便後續分析;合理使用主執行緒處理業務,不要在主執行緒做耗時操作,防止ANR程式無回應發生。

(一)穩定-記憶體最佳化

(1)Memory Monitor 工具:

它是Android Studio自帶的記憶體監視工具,它可以很好地幫助我們進行記憶體即時分析。透過點擊Android Studio右下角的Memory Monitor標籤,開啟工具可以看見較淺藍色代表free的內存,而深色的部分代表使用的內存從內存變換的走勢圖變換,可以判斷關於內存的使用狀態,例如當記憶體持續增加時,可能發生記憶體洩漏;當記憶體突然減少時,可能發生GC等,如下圖所示。

LeakCanary工具:
LeakCanary是Square公司基於MAT開發的一款監控Android記憶體洩漏的開源框架。其工作的原理是:
監測機制利用了Java的WeakReference和ReferenceQueue,透過將Activity包裝到WeakReference中,被WeakReference包裝過的Activity物件如果被回收,該WeakReference引用會被放到ReferenceQueue中,透過監測ReferenceQueue裡面的內容就能檢查到Activity是否能夠被回收(在ReferenceQueue中說明可以被回收,不存在洩漏;否則,可能存在洩漏,LeakCanary是執行一遍GC,如果還未在ReferenceQueue中,就會認定為洩漏)。

如果Activity被認定為洩漏了,就抓取記憶體dump檔案(Debug.dumpHprofData);之後透過HeapAnalyzerService.runAnalysis進行分析記憶體檔案分析;接著透過HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace)來進行記憶體記憶體洩漏分析。最後透過DisplayLeakService進行記憶體洩漏的展示。

(3)Android Lint 工具:

Android Lint Tool 是Android Sutido種整合的一個Android程式碼提示工具,它可以給你佈局、程式碼提供非常強大的幫助。硬編碼會提示以層級警告,例如:在佈局檔案中寫了三層冗餘的LinearLayout佈局、直接在TextView中寫要顯示的文字、字體大小使用dp而不是sp為單位,就會在編輯器右邊看到提示。

(二)流暢-卡頓優化

卡頓的場景通常是發生在使用者互動體驗最直接的面向。影響卡頓的兩大因素,分別是介面繪製和資料處理。

介面繪製:主要原因是繪製的層級深、頁面複雜、刷新不合理,由於這些原因導致卡頓的場景更多出現在UI 和啟動後的初始介面以及跳到頁面的繪製上。

資料處理:導致這種卡頓場景的原因是資料處理量太大,一般分為三種情況,一是資料在處理UI 線程,二是資料處理佔用CPU 高,導致主線程拿不到時間片,三是記憶體增加導致GC 頻繁,進而引起卡頓。

(1)佈局優化

在Android種系統對View進行測量、佈局和繪製時,都是透過對View數的遍歷來進行操作的。如果一個View數的高度太高就會嚴重影響測量、佈局和繪製的速度。 Google也在其API文件中建議View高度不宜哦過10層。現在版本種Google使用RelativeLayout取代LineraLayout作為預設根佈局,目的就是降低LineraLayout嵌套產生佈局樹的高度,進而提高UI渲染的效率。

佈局重複使用,使用標籤重複使用layout;
提高顯示速度,使用延遲V​​iew載入;
減少層級,使用標籤取代父級佈局;
注意使用wrap_content,會增加measure計算成本;
刪除控制項中無用屬性;

(2)繪製最佳化

過度繪製是指在螢幕上的某個像素在同一幀的時間內被繪製了多次。在多層次重疊的 UI 結構中,如果不可見的 UI 也在做繪製的操作,就會導致某些像素區域被繪製了多次,浪費了多餘的 CPU 以及 GPU 資源。如何避免過度繪製?

佈局上的最佳化。移除 XML 中非必須的背景,移除 Window 預設的背景、按需顯示佔位背景圖片

自訂View優化。使用 canvas.clipRect() 幫助系統辨識那些可見的區域,只有在這個區域內才會被繪製出來。

(3)啟動最佳化

應用程式一般都有快閃螢幕頁SplashActivity,優化閃屏頁的 UI 佈局,可以透過 Profile GPU Rendering 偵測丟幀情況。

(三)節省-耗電優化

在Android5.0 以前,關於應用電量消耗的測試即麻煩又不準確,而5.0 之後Google專門引入了一個獲取設備上電量消耗資訊的API—— Battery Historian。 Battery Historian 是 Google 提供的 Android 系統電量分析工具,直觀地展示出手機的電量消耗過程,透過輸入電量分析文件,顯示消耗情況。

最後提供一些可供參考耗電優化的方法:

(1)計算優化。演算法、for迴圈最佳化、Switch…case取代if…else、避開浮點運算。

浮點運算:計算機裡整數和小數形式就是按普通格式進行存儲,例如1024、3.1415926等等,這個沒什麼特點,但是這樣的數精度不高,表達也不夠全面,為了能夠有一種數的通用表示法,就發明了浮點數。浮點數的表示方式有點像科學計數法(.×10***),它的表示形式是0.*****×10 #,在計算機中的形式為.*** e ±**),其中前面的星號代表定點小數,也就是整數部分為0的純小數,後面的指數部分是定點整數。利用這樣的形式就能表示出任一個整數和小數,例如1024就能表示成0.1024×10^4,也就是.1024e 004,3.1415926就能表示成0.31415926×10^1,也就是.31415926e 001,這就是浮點數。浮點數進行的運算就是浮點運算。浮點運算比常規運算更複雜,因此電腦進行浮點運算速度比進行常規運算慢得多。

(2)避免 Wake Lock 使用不當。

Wake Lock是一種鎖的機制,主要是相對系統的休眠而言的,,只要有人拿著這個鎖,系統就無法進入休眠意思就是我的程式給CPU加了這個鎖那系統就不會休眠了,這樣做的目的是為了全力配合我們程式的運作。有的情況如果不這麼做就會出現一些問題,例如微信等及時通訊的心跳包會在熄滅螢幕不久後停止網路存取等問題。所以微信裡面是有大量使用了Wake_Lock鎖。系統為了節省電量,CPU在沒有任務忙碌的時候就會自動進入休眠。有任務需要喚醒CPU高效率執行的時候,就會為CPU加上Wake_Lock鎖定。大家常犯的錯誤,我們很容易去喚醒CPU來工作,但很容易忘記釋放Wake_Lock。

(3)使用 Job Scheduler 管理背景任務。

在Android 5.0 API 21 中,google提供了一個稱為JobScheduler API的元件,來處理當某個時間點或是滿足某個特定的條件時執行一個任務的場景,例如當使用者在夜間休息時或裝置接通電源轉接器連接WiFi啟動下載更新的任務。這樣可以在減少資源消耗的同時提升應用的效率。

(四)安裝套件-APK瘦身

(1)安裝套件的組成結構

assets資料夾。存放一些設定檔、資源文件,assets不會自動產生對應的 ID,而是透過 AssetManager 類別的介面取得。

res。 res 是 resource 的縮寫,這個目錄存放資源文件,會自動產生對應的 ID 並映射到 .R 文件中,存取直接使用資源 ID。

META-INF。保存應用的簽名訊息,簽名資訊可以驗證 APK 檔案的完整性。

AndroidManifest.xml。這個檔案用來描述 Android 應用的設定訊息,一些元件的註冊資訊、可使用權限等。

classes.dex。 Dalvik 字節碼程序,讓 Dalvik 虛擬機可執行,一般情況下,Android 應用程式在打包時透過 Android SDK 中的 dx 工具將 Java 字節碼轉換為 Dalvik 字節碼。

resources.arsc。記錄資源檔案和資源 ID 之間的映射關係,用來根據資源 ID 尋找資源。

(2)減少安裝套件大小

程式碼混淆。使用IDE 自帶的 proGuard 程式碼混淆器工具 ,它包括壓縮、最佳化、混淆等功能。
資源優化。例如使用 Android Lint 刪除冗餘資源,資源檔案最少化等。
圖片優化。例如利用 PNG優化工具 對圖片做壓縮處理。推薦目前最先進的壓縮工具Googlek開源函式庫zopfli。如果應用程式在0版本以上,建議使用 WebP圖片格式。
避免重複或無用功能的第三方函式庫。例如,百度地圖接入基礎地圖即可、訊飛語音無需接入離線、圖片庫Glide\Picasso等。
外掛化開發。例如功能模組放在伺服器上,按需下載,可以減少安裝包大小。
可以使用微信開源資源檔案混淆工具-AndResGuard。一般可以壓縮apk的1M左右大。

7.1、冷啟動與熱啟動

參考連結:https://www.jianshu.com/p/03c0fd3fc245

冷啟動
在啟動應用程式時,系統中沒有該應用程式的進程,這時系統會建立一個新的進程分配給該應用程式;

熱啟動
在啟動應用程式時,系統中已有該應用的進程(例:按back鍵、home鍵,應用程式雖然會退出,但是該應用程式的進程還是保留在後台);

區別
冷啟動:系統沒有該應用的進程,需要創建一個新的進程分配給應用,所以會先創建和初始化Application類,再創建和初始化MainActivity類(包括一系列的測量、佈局、繪製),最後顯示在介面上。熱啟動: 從現有的進程中來啟動,不會建立和初始化Application類,直接建立和初始化MainActivity類別(包括一系列的測量、佈局、繪製),最後顯示在介面上。

冷啟動流程
Zygote進程中fork建立出一個新的進程;建立和初始化Application類別、建立MainActivity; inflate佈局、當onCreate/onStart/onResume方法都走完畢;contentView的measure/layout/draw顯示在介面上。

冷啟動優化
減少在Application和第一個Activity的onCreate()方法的工作量; 不要讓Application參與業務的操作; 不要在Application進行耗時操作; 不要以靜態變數的方式在Application中保存資料;減少佈局的複雜性和深度;

8、MVP模式架構

8.1、MVP模式

##MVP架構由MVC發展而來。在MVP中,M代表Model,V代表View,P代表Presenter。

模型層(Model):主要是取得資料功能,業務邏輯和實體模型。

視圖層(View):對應於Activity或Fragment,負責視圖的部分展示和業務邏輯用戶交互

控制層(Presenter):負責完成View層與Model層間的交互,透過P層來取得M層中資料後回傳給V層,使得V層與M層間沒有耦合。

在MVP中,Presenter層完全將View層和Model層進行了分離,把主要程式邏輯放在Presenter層實現,Presenter與具體的View層(Activity)是沒有直接的關聯,是透過定義介面來進行互動的,使得當View層(Activity)發生改變時,Persenter依然可以保持不變。 View層介面類別只應該只有set/get方法,以及一些介面顯示內容和使用者輸入,除此之外不應該有多餘的內容。絕不允許View層直接存取Model層,這是與MVC最大區別之處,也是MVP核心優點。

9、虛擬機

9.1、Android Dalvik虛擬機與ART虛擬機比較

Dalvik

Android4.4及以前使用的都是Dalvik虛擬機,我們知道Apk在打包的過程中會先將java等源碼通過javac編譯成.class文件,但是我們的Dalvik虛擬機只會執行.dex文件,這個時候dx會將.class文件轉換成Dalvik虛擬機執行的.dex檔。 Dalvik虛擬機在啟動的時候會先將.dex檔案轉換成快速運行的機器碼,又因為65535這個問題,導致我們在應用冷啟動的時候有一個合包的過程,最後導致的一個結果就是我們的app啟動慢,這就是Dalvik虛擬機的JIT特性(Just In Time)。

ART

ART虛擬機是在Android5.0才開始使用的Android虛擬機,ART虛擬機必須相容於Dalvik虛擬機的特性,但是ART有一個很好的特性AOT (ahead of time),這個特性就是我們在安裝APK的時候就將dex直接處理成可直接供ART虛擬機使用的機器碼,ART虛擬機將.dex文件轉換成可直接運行的.oat文件,ART虛擬機天生支援多dex,所以也不會有一個合包的過程,所以ART虛擬機會很大的提升APP冷啟動速度。

ART優點:

加快APP冷啟動速度

提升GC速度

提供功能全面的Debug特性

ART缺点:

APP安装速度慢,因为在APK安装的时候要生成可运行.oat文件

APK占用空间大,因为在APK安装的时候要生成可运行.oat文件

arm处理器

关于ART更详细的介绍,可以参考Android ART详解

总结

熟悉Android性能分析工具、UI卡顿、APP启动、包瘦身和内存性能优化

熟悉Android APP架构设计,模块化、组件化、插件化开发

熟练掌握Java、设计模式、网络、多线程技术

Java基本知识点

1、Java的类加载过程

jvm将.class类文件信息加载到内存并解析成对应的class对象的过程,注意:jvm并不是一开始就把所有的类加载进内存中,只是在第一次遇到某个需要运行的类才会加载,并且只加载一次

主要分为三部分:1、加载,2、链接(1.验证,2.准备,3.解析),3、初始化

1:加载

类加载器包括 BootClassLoader、ExtClassLoader、APPClassLoader

2:链接

验证:(验证class文件的字节流是否符合jvm规范)

准备:为类变量分配内存,并且进行赋初值

解析:将常量池里面的符号引用(变量名)替换成直接引用(内存地址)过程,在解析阶段,jvm会把所有的类名、方法名、字段名、这些符号引用替换成具体的内存地址或者偏移量。

3:初始化

主要对类变量进行初始化,执行类构造器的过程,换句话说,只对static修试的变量或者语句进行初始化。

范例:Person person = new Person();为例进行说明。

Java编程思想中的类的初始化过程主要有以下几点:

  1. 找到class文件,将它加载到内存
  2. 在堆内存中分配内存地址
  3. 初始化
  4. 将堆内存地址指给栈内存中的p变量

2、String、StringBuilder、StringBuffer

StringBuffer里面的很多方法添加了synchronized关键字,是可以表征线程安全的,所以多线程情况下使用它。

执行速度:

StringBuilder > StringBuffer > String

StringBuilder牺牲了性能来换取速度的,这两个是可以直接在原对象上面进行修改,省去了创建新对象和回收老对象的过程,而String是字符串常量(final)修试,另外两个是字符串变量,常量对象一旦创建就不可以修改,变量是可以进行修改的,所以对于String字符串的操作包含下面三个步骤:

  1. 创建一个新对象,名字和原来的一样
  2. 在新对象上面进行修改
  3. 原对象被垃圾回收掉

3、JVM内存结构

Java对象实例化过程中,主要使用到虚拟机栈、Java堆和方法区。Java文件经过编译之后首先会被加载到jvm方法区中,jvm方法区中很重的一个部分是运行时常量池,用以存储class文件类的版本、字段、方法、接口等描述信息和编译期间的常量和静态常量。

3.1、JVM基本结构

类加载器classLoader,在JVM启动时或者类运行时将需要的.class文件加载到内存中。
执行引擎,负责执行class文件中包含的字节码指令。
本地方法接口,主要是调用C/C++实现的本地方法及返回结果。
内存区域(运行时数据区),是在JVM运行的时候操作所分配的内存区,
主要分为以下五个部分,如下图:
Android の面接の質問を最も詳細に共有

  • 方法区:用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。
  • Java堆(heap):存储Java实例或者对象的地方。这块是gc的主要区域。
  • Java栈(stack):Java栈总是和线程关联的,每当创建一个线程时,JVM就会为这个线程创建一个对应的Java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是线程私有的。
  • 程序计数器:用于保存当前线程执行的内存地址,由于JVM是多线程执行的,所以为了保证线程切换回来后还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
  • 本地方法栈:和Java栈的作用差不多,只不过是为JVM使用到的native方法服务的。

3.2、JVM源码分析

https://www.jianshu.com/nb/12554212

4、GC机制

垃圾收集器一般完成两件事

  1. 检测出垃圾;
  2. 回收垃圾;

4.1 Java对象引用

通常,Java对象的引用可以分为4类:强引用、软引用、弱引用和虚引用。
强引用:通常可以认为是通过new出来的对象,即使内存不足,GC进行垃圾收集的时候也不会主动回收。

Object obj = new Object();

软引用:在内存不足的时候,GC进行垃圾收集的时候会被GC回收。

Object obj = new Object();
SoftReference<object> softReference = new SoftReference(obj);</object>

弱引用:无论内存是否充足,GC进行垃圾收集的时候都会回收。

Object obj = new Object();
WeakReference<object> weakReference = new WeakReference(obj);</object>

虚引用:和弱引用类似,主要区别在于虚引用必须和引用队列一起使用。

Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);

引用队列:如果软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,如果是虚引用,在回收前就会被加到引用队列里。

垃圾检测方法:

引用计数法:给每个对象添加引用计数器,每个地方引用它,计数器就+1,失效时-1。如果两个对象互相引用时,就导致无法回收。
可达性分析算法:以根集对象为起始点进行搜索,如果对象不可达的话就是垃圾对象。根集(Java栈中引用的对象、方法区中常量池中引用的对象、本地方法中引用的对象等。JVM在垃圾回收的时候,会检查堆中所有对象是否被这些根集对象引用,不能够被引用的对象就会被垃圾回收器回收。)

垃圾回收算法:

常见的垃圾回收算法有:

标记-清除

标记:首先标记所有需要回收的对象,在标记完成之后统计回收所有被标记的对象,它的标记过程即为上面的可达性分析算法。
清除:清除所有被标记的对象
缺点:
效率不足,标记和清除效率都不高
空间问题,标记清除之后会产生大量不连续的内存碎片,导致大对象分配无法找到足够的空间,提前进行垃圾回收。

复制回收算法
将可用的内存按容量划分为大小相等的2块,每次只用一块,当这一块的内存用完了,就将存活的对象复制到另外一块上面,然后把已使用过的内存空间一次清理掉。

缺点:

将内存缩小了原本的一般,代价比较高
大部分对象是“朝生夕灭”的,所以不必按照1:1的比例划分。
现在商业虚拟机采用这种算法回收新生代,但不是按1:1的比例,而是将内存区域划分为eden 空间、from 空间、to 空间 3 个部分。
其中 from 空间和 to 空间可以视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于存放未被回收的对象。

在垃圾回收时,eden 空间中的存活对象会被复制到未使用的 survivor 空间中 (假设是 to),正在使用的 survivor 空间 (假设是 from) 中的年轻对象也会被复制到 to 空间中 (大对象,或者老年对象会直接进入老年带,如果 to 空间已满,则对象也会直接进入老年代)。此时,eden 空间和 from 空间中的剩余对象就是垃圾对象,可以直接清空,to 空间则存放此次回收后的存活对象。这种改进的复制算法既保证了空间的连续性,又避免了大量的内存空间浪费。

标记-整理

在老年代的对象大都是存活对象,复制算法在对象存活率教高的时候,效率就会变得比较低。根据老年代的特点,有人提出了“标记-压缩算法(Mark-Compact)”

标记过程与标记-清除的标记一样,但后续不是对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。

这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。

分带收集算法

根据对象存活的周期不同将内存划分为几块,一般是把Java堆分为老年代和新生代,这样根据各个年代的特点采用适当的收集算法。

新生代每次收集都有大量对象死去,只有少量存活,那就选用复制算法,复制的对象数较少就可完成收集。
老年代对象存活率高,使用标记-压缩算法,以提高垃圾回收效率。

5、类加载器

程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

5.1、双亲委派原理

每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但是可以用做其他ClassLoader实例的父类加载器。

当一个ClassLoader 实例需要加载某个类时,它会试图在亲自搜索这个类之前先把这个任务委托给它的父类加载器,这个过程是由上而下依次检查的,首先由顶层的类加载器Bootstrap CLassLoader进行加载,如果没有加载到,则把任务转交给Extension CLassLoader视图加载,如果也没有找到,则转交给AppCLassLoader进行加载,还是没有的话,则交给委托的发起者,由它到指定的文件系统或者网络等URL中进行加载类。还没有找到的话,则会抛出CLassNotFoundException异常。否则将这个类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的Class实例对象。

5.2、 为什么使用双亲委托模型

JVM在判断两个class是否相同时,不仅要判断两个类名是否相同,还要判断是否是同一个类加载器加载的。

避免重复加载,父类已经加载了,则子CLassLoader没有必要再次加载。
考虑安全因素,假设自定义一个String类,除非改变JDK中CLassLoader的搜索类的默认算法,否则用户自定义的CLassLoader如法加载一个自己写的String类,因为String类在启动时就被引导类加载器Bootstrap CLassLoader加载了。

关于Android的双亲委托机制,可以参考android classloader双亲委托模式

6、集合

Java集合类主要由两个接口派生出:Collection和Map,这两个接口是Java集合的根接口。

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是 Set和List。Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

6.1、区别

List,Set都是继承自Collection接口,Map则不是;
List特点:元素有放入顺序,元素可重复; Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法;
LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
HashMap是非线程安全的,HashTable是线程安全的;

6.2、List和Vector比较

Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;
两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。
Vector可以设置增长因子,而ArrayList不可以。
Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

6.3、HashSet如何保证不重复

HashSet底层通过HashMap来实现的,在往HashSet中添加元素是

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}


// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

在HashMap中进行查找是否存在这个key,value始终是一样的,主要有以下几种情况:

  • 如果hash码值不相同,说明是一个新元素,存;
  • 如果hash码值相同,且equles判断相等,说明元素已经存在,不存;
  • 如果hash码值相同,且equles判断不相等,说明元素不存在,存;
  • 如果有元素和传入对象的hash值相等,那么,继续进行equles()判断,如果仍然相等,那么就认为传入元素已经存在,不再添加,结束,否则仍然添加;

6.4、HashSet与Treeset的适用场景

  • HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。
  • TreeSet 是二叉树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值
  • HashSet是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。
  • HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。

6.5、HashMap与TreeMap、HashTable的区别及适用场景

HashMap 非线程安全,基于哈希表(散列表)实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。其中散列表的冲突处理主要分两种,一种是开放定址法,另一种是链表法。HashMap的实现中采用的是链表法。
TreeMap:非线程安全基于红黑树实现,TreeMap没有调优选项,因为该树总处于平衡状态

7、 常量池

7.1、Interger中的128(-128~127)

当数值范围为-128~127时:如果两个new出来Integer对象,即使值相同,通过“”比较结果为false,但两个对象直接赋值,则通过“”比较结果为“true,这一点与String非常相似。
当数值不在-128~127时,无论通过哪种方式,即使两个对象的值相等,通过“”比较,其结果为false;
当一个Integer对象直接与一个int基本数据类型通过“
”比较,其结果与第一点相同;
Integer对象的hash值为数值本身;

@Override
public int hashCode() {
return Integer.hashCode(value);
}

7.2、为什么是-128-127?

在Integer类中有一个静态内部类IntegerCache,在IntegerCache类中有一个Integer数组,用以缓存当数值范围为-128~127时的Integer对象。

8、泛型

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

它提供了编译期的类型安全,确保你只能把正确类型的对象放入 集合中,避免了在运行时出现ClassCastException。

使用Java的泛型时应注意以下几点:

  • 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
  • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
  • 泛型的类型参数可以有多个。
  • 泛型的参数类型可以使用extends语句,例如。习惯上称为“有界类型”。
  • 泛型的参数类型还可以是通配符类型。例如Class> classType =
    Class.forName(“java.lang.String”);

8.1 T泛型和通配符泛型

  • ? 表示不确定的java类型。
  • T  表示java类型。
  • K V 分别代表java键值中的Key Value。
  • E 代表Element。

8.2 泛型擦除

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

8.3 限定通配符

限定通配符对类型进行了限制。

一種是 extends T>它透過確保類型必須是T的子類別來設定類型的上界,
另一種是 super T>它透過確保型別必須是T的父類別來設定類型的下界。
另一方面>表 示了非限定通配符,因為>可以用任意型別來取代。
例如List extends Number>可以接受List或List。

8.4 泛型面試題

你可以把List傳給一個接受List參數的方法嗎?

對任何一個不太熟悉泛型的人來說,這個Java泛型題目看起來令人疑惑,因為乍看起來String是一種Object,所以List應該可以用在需要List的地方,但是事實並非如此。真這樣做的話會導致編譯錯誤。如 果你再深一步考慮,你會發現Java這樣做是有意義的,因為List可以儲存任何類型的物件包含String, Integer等等,而List卻只能用來儲存Strings。

Array中可以用泛型嗎?

Array事實上並不支援泛型,這也是為什麼Joshua Bloch在Effective Java一書中建議使用List來取代Array,因為List可以提供編譯期的型別安全保證,而Array卻不能。

Java中List和原始類型List之間的區別?

原始類型和帶參數類型之間的主要區別是,在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查,透過使用Object作為類型,可以告知編譯器該方法可以接受任何類型的對象,例如String或Integer。這題的考察點在於對泛型中原始類型的正確理解。它們之間的第二點差異是,你可以把任何帶有參數的型別傳遞給原始型別List,但不能把List傳遞給接受 List的方法,因為會產生編譯錯誤。

List> 是未知類型的List,而List 其實是任意類型的List。你可以把List, List賦值給List>,卻不能把List賦值給 List。

9、反射

9.1、概念

JAVA反射機制是在運作狀態中,對於任何一個類,都能夠知道這個類別的所有屬性和方法;對於任意一個對象,都能夠呼叫它的任意一個方法;這種動態所獲取的資訊以及動態呼叫對象的方法的功能稱為java語言的反射機制。

9.2、作用

Java反射機制主要提供了以下功能: 在執行時判斷任一物件所屬的類別;執行時建構任意一個類別的物件;執行時判斷任意一個類別所具有的成員變數和方法;在運行時呼叫任意一個物件的方法;產生動態代理。

10、代理

代理這個詞大家肯定已經非常熟悉,因為現實中接觸的很多,其實現實中的東西恰恰可以非常形象和直觀地反映出模式的抽象過程以及本質。現在房子不是吵得熱火嗎?我們就以房子為例,來撥開代理的面紗。

假設你有一套房子要賣,一種方法是你直接去網上發佈出售信息,然後直接帶要買房子的人來看房子、過戶等一直到房子賣出去,但是可能你很忙,你沒有時間去處理這些事情,所以你可以去找中介,讓中介幫你處理這些瑣碎事情,中介其實就是你的代理。本來是你要做的事情,現在中介幫助你一一處理,對於買方來說跟你直接交易跟同中介直接交易沒有任何差異,買方甚至可能覺察不到你的存在,這實際上就是代理的一個最大好處。

接下來我們再深入思考為什麼你不直接買房子而需要仲介?其實一個問題恰恰解答了什麼時候該用代理模式的問題。

原因一:你可能在外地上班,買房子的人無法找到你直接交易。

對應到我們程式設計的時候就是:客戶端無法直接操作實際物件。那為什麼無法直接操作?一種情況是你需要調用的對像在另外一台機器上,你需要跨越網絡才能訪問,如果讓你直接coding去調用,你需要處理網絡連接、處理打包、解包等等非常複雜的步驟,所以為了簡化客戶端的處理,我們使用代理模式,在客戶端建立一個遠端物件的代理,客戶端就像調用本地物件一樣調用該代理,再由代理去跟實際物件聯繫,對於客戶端來說可能根本沒有感覺到所呼叫的東西在網路另外一端,這其實就是Web Service的工作原理。另一種情況雖然你所要呼叫的物件就在本地,但是由於呼叫非常耗時,你怕影響你正常的操作,所以特意找個代理來處理這種耗時情況,一個最容易理解的就是Word裡面裝了很大一張圖片,在word被打開的時候我們肯定要加載裡面的內容一起打開,但是如果等加載完這個大圖片再打開Word用戶等得可能早已經跳腳了,所以我們可以為這個圖片設定一個代理,讓代理慢慢開啟這個圖片而不影響Word本來的開啟的功能。申明一下我只是猜可能Word是這麼做的,具體到底怎麼做的,俺也不知道。

原因二:你不知道怎麼辦過戶手續,或者說除了你現在會幹的事情外,還需要做其他的事情才能達成目的。

對應到我們程式設計的時候就是:除了目前類別能夠提供的功能外,我們還需要補充一些其他功能。最容易想到的情況就是權限過濾,我有一個類做某項業務,但是由於安全原因只有某些用戶才可以調用這個類,此時我們就可以做一個該類的代理類,要求所有請求必須通過該代理類,由該代理類做權限判斷,如果安全性則呼叫實際類的業務開始處理。可能有人說為什麼我要多加個代理類別?我只需要在原來類別的方法裡面加上權限過濾不就完了嗎?在程式設計中有一個類別的單一性原則問題,這個原則很簡單,就是每個類別的功能盡可能單一。為什麼要單一,因為只有功能單一這個類別被改動的可能性才會最小,就拿剛才的例子來說,如果你將權限判斷放在當前類裡面,當前這個類就既要負責自己本身業務邏輯、又要負責權限判斷,那麼就有兩個導致該類別變化的原因,現在如果權限規則一旦變化,這個類別就必需得改,顯然這不是一個好的設計。

好了,原理的東西已經講得差不多了,要是再講個沒完可能大家要丟磚頭了。呵呵,接下來就看看怎麼來實現代理。

資料結構與演算法

https://zhuanlan.zhihu.com/p/27005757?utm_source=weibo&utm_medium=social

http://crazyandcoder.tech/2016 /09/14/android 演算法與資料結構-排序/

1、排序

#排序有內部排序和外部排序,內部排序是資料記錄在記憶體中進行排序,而外部排序是因排序的資料很大,一次不能容納全部的排序記錄,在排序過程中需要存取外存。

1.1、 直接插入排序

想法:

將第一個數字和第二個數排序,然後構成一個有序序列
將第三個數插入進去,構成一個新的有序序列。
對第四數、第五個數…直到最後一個數,重複第二步。
程式碼:

先設定插入次數,即循環次數,for(int i=1;i設定插入數和得到已經排好序列的最後一個數的位數。 insertNum和j=i-1。

2、設計模式

參考:Android開發中的一些設計模式

2.1、單例設計模式

單例主要分為:懶漢式單例、餓漢式單例、登記式單例。

特點:

  1. 單例類別只有一個實例
  2. 單例類別必須自己建立自己的唯一實例
  3. 單例類別必須給所有其他物件提供此實例。

在電腦系統中,像執行緒池,快取、日誌物件、對話方塊、印表機等常被設計成單例。

懶漢式單例:

Singleton透過將建構方法限定為private避免了類別在外部被實例化,在同一個虛擬機器範圍內,Singleton的唯一實例只能透過getInstance()方法訪問。 (事實上,透過Java反射機制是能夠實例化建構方法為private的類別的,那基本上會使所有的Java單例實作失效。
Android の面接の質問を最も詳細に共有
它是執行緒不安全的,並發情況下很有可能出現多個Singleton實例,要實現線程安全,有以下三種方式:
1.在getInstance方法上加上同步
Android の面接の質問を最も詳細に共有
2.雙重檢查鎖定
Android の面接の質問を最も詳細に共有
3.靜態內部類別
Android の面接の質問を最も詳細に共有
這種方式對比前兩種,既實現了執行緒安全,又避免了同步帶來的效能影響。

餓漢式單例:

餓漢式在創建類別的同時就已經創建好了一個靜態的物件供系統使用,以後不再改變,所以天生是系統安全。
Android の面接の質問を最も詳細に共有


##

以上がAndroid の面接の質問を最も詳細に共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。