Android 必須要掌握的東西

1. 熟練掌握Java技術,熟悉面向對象思想,熟悉常用設計模式

  • 熟練掌握Java技術,熟悉面向對象思想,熟悉常用設計模式

  • 面向對象思想: 繼承, 封裝, 多態(tài)

  • 設計模式:

六大原則

  1. 單一職責(Single Responsibility Principle)

對于單一職責原則,我的建議是接口一定要做到單一職責,類的設計盡量做到只有一個原因引起變化。

  1. 里氏替換原則(Liskov Substitution Principle)
    所有引用基類的地方必須能透明地使用其子類的對象。
  2. 迪米特法則(Law of Demeter,LoD)
    一個對象應該對其他對象有最少的了解。通俗地講,一個類應該對自己需要耦合或調用的類知道得最少
  3. 接口隔離原則(Interface Segregation Principle)
    • 接口要盡量小
    • 接口要高內聚
    • 定制服務
    • 接口設計是有限度的
  4. 依賴倒置原則(Dependence Inversion Principle,DIP)
    • 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象;
    • 抽象不應該依賴細節(jié);
    • 細節(jié)應該依賴抽象
  5. 開閉原則(Open Closed Principle)
    開閉原則是一個終極目標,任何人包括大師級人物都無法百分之百做到,但朝這個方向努力,可以非常顯著地改善一個系統(tǒng)的架構,真正做到“擁抱變化”。

把這 6 個原則的首字母(里氏替換原則和迪米特法則的首字母重復,只取一個)聯(lián)合起來就是SOLID(穩(wěn)定的),其代表的含義也就是把這6個原則結合使用的好處:建立穩(wěn)定、靈活、健壯的設計,而開閉原則又是重中之重,是最基礎的原則,是其他 5 大原則的精神領袖。

常用設計模式

  • 單例模式
  • 迭代器模式
  • 適配器模式
  • 策略模式
  • 代理模式

2. Android 四大組件 和 Fragment的使用;

3. Android中的數(shù)據(jù)存儲(文件, 網(wǎng)絡, 數(shù)據(jù)庫存儲)

4. 熟悉掌握Android中常用的UI元素, 動畫, 樣式

動畫
android 3.0 新增屬性動畫(Object, Value)
android 5.0 新增矢量圖動畫

通常定義一個 AnimatedVectorDrawable 需要以下三個xml文件:

1.vector drawable 本身:res/drawable/中定義一個有<vector>元素的xml文件,參考上面對VectorDrawable的定義。
2.vector drawable 的動畫文件(Animated vector drawable):res/drawable/中定義一個有<animated-vector>元素的xml文件。
3.一個或者多個屬性動畫文件:res/drawable/中定義一個有<objectAnimator>元素的xml文件。

自定義 View

  1. 完全自定義 View
  2. 繼承已有 View, 重寫部分功能
  3. 繼承 ViewGroup

步驟

  1. 自定義屬性的聲明和獲取
  2. 測量 onMeasure
  3. 布局 onLayout(ViewGroup)
  4. 繪制 onDraw
  5. onTouchEvent
  6. onInterceptTouchEvent(ViewGroup) 是否攔截該手勢

5. 消息機制和多線程的使用

消息機制和多線程的使用

UI線程的消息循環(huán)是在 ActivityThread 方法中創(chuàng)建的, 該函數(shù)為 Android 應用程序的入口.

在 Android 應用啟動的時候, 會默認有一個主線程(UI線程), 這個線程會關聯(lián)一個消息隊列, 所有操作都會被封裝成消息交給主線程來處理.

Looper 總結:

  1. 通過 Looper.prepare 來創(chuàng)建 looper 對象(消息隊列封裝在 Looper 對象中), 并且保存在 sThreadLocal 中。
  2. 消息循環(huán)的建立通過 Looper.loop() 方法, loop 方法實際上是建立一個死循環(huán), 不斷從消息隊列中取出消息。

Handler 將消息發(fā)送給消息隊列, 消息隊列又將消息分發(fā)給 Handler 來處理。

Message 的兩個重要成員變量
Hander target;
Runnable callback;

不管是 post 一個Runnable(包裝成Message, callback設置為runnable成員變量)還是sendMessage, 都會調用sendMessageDelayed(msg, time)方法, Handler 最終將消息追加到 MessageQueue中, 而 Looper 不斷取出從MessageQueue 中讀取消息, 并調用 Handler的dispathMessage 來處理消息.
dispathMessage 方法是一個分發(fā)方法, 如果 Runnable 類型的 callback 變量不為空, 則 callback.run 中執(zhí)行更新UI的代碼, 否則會走到 handMessage 這個分支.

Handle r構造方法中通過 Looper.myLooper 來獲取 Looper 對象.

//1. 成員變量mLooper的獲得
mLooper = Looper.myLooper();

//1. Looper.myLooper的源碼
/**
  * Return the Looper object associated with the current thread.  Returns
  * null if the calling thread is not associated with a Looper.
  */
 public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}
安卓中多線程的實現(xiàn)

使用 JavaSE中線程 和 線程池,

安卓為開發(fā)者封裝了一些常用的類 AsyncTask, HandlerThread.

線程池的優(yōu)點

  1. 重用存在的線程, 減少對象創(chuàng)建,銷毀的開銷
  2. 有效控制最大的并發(fā)線程數(shù), 提高系統(tǒng)資源的使用率, 同時避免過多資源競爭, 避免堵塞
  3. 提供定時執(zhí)行, 單線程, 并發(fā)數(shù)控制等功能

在android平臺, 由于資源有限, 最常用的就是通過Executors.newFixedThreadPool(int size)來啟動固定數(shù)量的線程池.

AsyncTask 的原理

onPreExecute
doInBackground
onProgressUpdate
onPostExecute

概括來說, 調用 execute 方法后, execute方法先調用 onPreExecute 方法,
然后由 ThreadPoolExecutors 實例 sExecutor 執(zhí)行一個 FutureTask 任務, 這個過程中 doInBackground 將被調用, 如果在 doInBackground 中調用了publicProgress 方法, 則通過 sHandler 發(fā)送一條MESSAGE_POST_PROGRESS 消息, 更新進度;
如果遇到異常, 則發(fā)送一條 MESSAGE_POST_CANCEL消息, sHandler處理消息時, onCancelled 方法會被調用;
如果執(zhí)行成功, 則發(fā)送一條 MESSAGE_POST_RESULT 的消息, sHandler處理消息時會調用 onPostExectute(result)方法, 讓用戶得以在 UI 線程處理結果.
總的來說, AsyncTask 的本質是使用線程池技術 和 Handler 封裝, 減少了處理問題的復雜度, 提高了開發(fā)效率。

HandlerThread 的使用

6. 網(wǎng)絡通信機制及常用數(shù)據(jù)傳輸協(xié)議;

HTTP網(wǎng)絡請求原理
HTTP 是一種應用層協(xié)議, 通過 TCP 實現(xiàn)了可靠的數(shù)據(jù)傳輸, 能夠保證可靠的數(shù)據(jù)傳輸.

消息的交互流程有如下幾步:

  1. 客戶端執(zhí)行網(wǎng)絡請求, 從URL解析出服務器的主機名
  2. 將服務器的主機名轉換為服務器的IP地址;
  3. 將端口號從URL中解析出來
  4. 建立一條客戶端與Web服務器的TCP連接;
  5. 客戶端通過輸出流向服務器發(fā)送一條HTTP請求
  6. 服務器向客戶端回送一條HTTP響應報文
  7. 客戶端從輸入流獲取報文
  8. 客戶端解析報文, 關閉連接
  9. 客戶端將結果顯示在UI上
HTTP的請求方式(7種)

get
post
put
delete

trace
options
head

Android中執(zhí)行網(wǎng)絡請求
  1. 全面支持 HTTP 協(xié)議的HttpClient(在android2.3以前), 在android6.0 中該庫已被移除
  2. 最佳選擇 HttpURLConnection
網(wǎng)絡框架的簡單實現(xiàn)

7. Android中的屏幕適配

8. Android中的布局優(yōu)化, 內存優(yōu)化

布局優(yōu)化
  • 減少視圖層級
    • 通過工具分析視圖層級, 優(yōu)先相對布局, 約束布局
    • merge標簽, 去處理子布局的根視圖和父布局是同一類型的情況
  • 延遲加載的ViewStub
    通過這個不可見的和能在運行期間延遲加載目標視圖的, 寬高都為0的View.
內存優(yōu)化
  • 檢查自身可以內存
    每個app都有heap限制, 可以通過調用getMemory來獲取可用heap大小

  • 知曉內存的開支情況

    • 使用枚舉通常會比使用靜態(tài)常量要消耗兩倍以上的內存,在Android開發(fā)當中我們應當盡可能地不使用枚舉。
    • 任何一個Java類,包括內部類、匿名類,都要占用大概500字節(jié)的內存空間。
    • 任何一個類的實例要消耗12-16字節(jié)的內存開支,因此頻繁創(chuàng)建實例也是會一定程序上影響內存的。
    • 在使用 HashMap 時,即使你只設置了一個基本數(shù)據(jù)類型的鍵,比如說int,但是也會按照對象的大小來分配內存,大概是 32 字節(jié),而不是 4 字節(jié)。因此最好的辦法就是像上面所說的一樣,使用優(yōu)化過的數(shù)據(jù)集合。
  • 注意內存的開銷, 使用專門給為 android 優(yōu)化過的數(shù)據(jù)容器 SparseArray, SparseBoolArray, LongSparseArray, 比 HashMap 消耗更少的內存.通常的HashMap 的實現(xiàn)方式更加消耗內存,因為它需要一個額外的實例對象來記錄 Mapping 操作。另外,SparseArray 更加高效在于他們避免了對 key 與value 的 autobox 自動裝箱,并且避免了裝箱后的解箱。
    棄用枚舉類型而使用加上 IntDef, StringDef 注解修飾的全局常量

  • bitmap 的優(yōu)化

    • 千萬不要去加載不需要的分辨率, 會占用我們相當多寶貴的內存
    • 圖片的色彩格式, 來壓縮圖片質量
      ARGB_8888 代表32位ARGB位圖
      ARGB_4444 代表16位ARGB位圖
      RGB_565 代表8位RGB位圖
    • 使用成熟的圖片框架Picasso, ImageLoader
  • 當內存緊張時釋放內存
    onTrimMemory()方法還有很多種其它類型的回調,可以在手機內存降低的時候及時通知我們。我們應該根據(jù)回調中傳入的級別來去決定如何釋放應用程序的資源:

  • 善用 service 資源
    系統(tǒng)會傾向于將這個 Service 所依賴的進程進行保留. 因為service的運行代價很高. 例如使用 IntentService 處理一些單一短時間任務, 這種 Service 的最大特點就是當后臺任務執(zhí)行結束后會自動停止,從而極大程度上避免了Service 內存泄漏的可能性。

  • 為序列化的數(shù)據(jù)使用 nano protobufs

  • 盡量避免使用依賴注入框架

  • 謹慎使用 external libraries

  • 關注 lint 工具所提出的建議

  • 使用 ProGuard 來剔除不需要的代碼
    能夠通過移除不需要的代碼,重命名類,域與方法等方對代碼進行壓縮,優(yōu)化與混淆。使用 ProGuard可以是的你的代碼更加緊湊,這樣能夠使用更少mapped代碼所需要的RAM。

  • 對最終的 APK 使用 zipalign

  • 使用多進程
    一個典型的例子是創(chuàng)建一個可以長時間后臺播放的Music Player。如果整個app運行在一個進程中,當后臺播放的時候,前臺的那些UI資源也沒有辦法得到釋放。類似這樣的 app 可以切分成2個進程:一個用來操作UI,另外一個用來后臺的Service.
    你可以通過在manifest文件中聲明’android:process’屬性來實現(xiàn)某個組件運行在另外一個進程的操作。

  • 謹慎使用抽象編程
    許多程序員都喜歡各種使用抽象來編程,認為這是一種很好的編程習慣。當然,這一點不可否認,因為的抽象的編程方法更加面向對象,而且在代碼的維護和可擴展性方面都會有所提高。但是,在 Android 上使用抽象會帶來額外的內存開支,因為抽象的編程方法需要編寫額外的代碼,雖然這些代碼根本執(zhí)行不到,但是卻也要映射到內存當中,不僅占用了更多的內存,在執(zhí)行效率方面也會有所降低。當然這里我并不是提倡大家完全不使用抽象編程,而是謹慎使用抽象編程,不要認為這是一種很酷的編程方式而去肆意使用它,只在你認為有必要的情況下才去使用。

9. Android中的單元測試

優(yōu)點

  1. 為代碼提供保障
  2. 優(yōu)化設計, 編寫單元測試從調用者角度觀察, 迫使設計者吧程序設計成易于調試和可測試, 并且消除軟件中的耦合.
  3. 文檔記錄, 是一種展示函數(shù)或者類使用的最佳文檔
  4. 具有回歸性, 編寫完成后可以隨時快速測試.

JUnit簡介
基于Java語言的單元測試框架.

開發(fā)人員一般需要新建一個TestCase的類, 然后在該測試類中添加測試函數(shù).
需要注意的是, 每個測試方法, TestCase之間并沒有關聯(lián), 它們的執(zhí)行順序也不一定是代碼中的執(zhí)行順序, 因此, 測試方法不要存在依賴性.

測試哪些條件
  1. 邊界條件
    是單元測試需要重要測試的地方
  2. 覆蓋執(zhí)行路徑
模擬所需的功能模塊
  • 手動mock對象
  • 使用Mockito庫
Android中單元測試

Google在Junit的基礎上進行拓展, 使之能在Android上運行測試實例, Android平臺下所有的測試類都是InstrumentationTestCase的子類, 它的內部封裝了Instrumentation對四大組件進行操作, 而InstrumentationTestCase繼承在Junit的TestCase.

  • 需要Context的測試用例
    AndroidTestCase
  • AcitivityUnitTestCase<T>
    和 ActivityInstrumentationTestCase2<T>
  • 測試Service, 繼承自ServiceTestCase<T>
  • 測試ContentPrivider, 繼承自ContentPrividerTestCase2<T>

10. 網(wǎng)絡框架Volley, 圖片處理Picasso等;

第一部分Request
第二部分RequestQueue消息隊列, 維護了提交我給網(wǎng)絡框架的請求隊列, 并根據(jù)對應規(guī)則進行排序, 該隊列使用的線程安全的PriorityBlockingQueue, 所以支持并發(fā)訪問.
第三部分NetWorkExecutor, 也就是網(wǎng)絡的執(zhí)行者, 該Exectuor繼承自Thread, 在run方法中循環(huán)訪問請求隊列, 從請求隊列中獲取網(wǎng)絡請求, 請求完成后提交給UI線程
第四部分Response及其投遞類, 使用ResponseDelivery來封裝Response的投遞, 保證Response在UI線程中執(zhí)行, Response會根據(jù)用戶的不同需求返回特定的類型.

Picasso

Picasso不僅實現(xiàn)了圖片異步加載的功能,還解決了android中加載圖片時需要解決的一些常見問題:
1.在adapter中需要取消已經不在視野范圍的ImageView圖片資源的加載,否則會導致圖片錯位,Picasso已經解決了這個問題。
2.使用復雜的圖片壓縮轉換來盡可能的減少內存消耗
3.自帶內存和硬盤二級緩存功能

Cache,緩存類


Lrucache,主要是get和set方法,存儲的結構采用了LinkedHashMap,這種map內部實現(xiàn)了lru算法(Least Recently Used 近期最少使用算法)。

Request,操作封裝類

所有對圖形的操作都會記錄在這里,供之后圖形的創(chuàng)建使用

Action

Action 代表了一個具體的加載任務,主要用于圖片加載后的結果回調,有兩個抽象方法,complete 和error,也就是當圖片解析為bitmap后用戶希望做什么。最簡單的就是將 bitmap 設置給 imageview,失敗了就將錯誤通過回調通知到上層。


ImageViewAction 實現(xiàn)了Action,在complete中將 bitmap 和 imageview 組成了一個 PicassoDrawable,里面會實現(xiàn)淡出的動畫效果。

BitmapHunter


BitmapHunter 是一個 Runnable,其中有一個 decode 的抽象方法,用于子類實現(xiàn)不同類型資源的解析。

在 bitmaphunter 成功得到 bitmap 后,就是通過 dispatcher 將結果傳遞出去的,當然讓 bitmaphunter 執(zhí)行也要通過 Dispatcher。


Dispatcher 內有一個 HandlerThread,所有的請求都會通過這個 thread 轉換,也就是請求也是異步的,這樣應該是為了Ui線程更加流暢,同時保證請求的順序,因為 handler 的消息隊列。外部調用的是 dispatchXXX 方法,然后通過handler將請求轉換到對應的 performXXX 方法。例如生成 Action 以后就會調用 dispather 的 dispatchSubmit()來 請求執(zhí)行,

handler接到消息后轉換到 performSubmit 方法

這里將通過 action 得到具體的 BitmapHunder,然后交給 ExecutorService 執(zhí)行。

下面是 Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView)的過程

public static Picasso with(Context context) {
        if (singleton == null) {
            singleton = new Builder(context).build();
        }
        return singleton;
    }
                                                                                                                  
    public Picasso build() {
            Context context = this.context;
                                                                                                              
            if (downloader == null) {
                downloader = Utils.createDefaultDownloader(context);
            }
            if (cache == null) {
                cache = new LruCache(context);
            }
            if (service == null) {
                service = new PicassoExecutorService();
            }
            if (transformer == null) {
                transformer = RequestTransformer.IDENTITY;
            }
                                                                                                              
            Stats stats = new Stats(cache);
                                                                                                              
            Dispatcher dispatcher = new Dispatcher(context, service, HANDLER,
                    downloader, cache, stats);
                                                                                                              
            return new Picasso(context, dispatcher, cache, listener,
                    transformer, stats, debugging);
        }

在 Picasso.with() 的時候會將執(zhí)行所需的所有必備元素創(chuàng)建出來,如緩存cache、執(zhí)行 executorService、調度 dispatch 等,在load()時創(chuàng)建Request,在into()中創(chuàng)建action、bitmapHunter,并最終交給 dispatcher 執(zhí)行。

11. 目前流行的 MVP 模式構建應用

全稱 Model View Presenter
MVP 讓 UI 界面和數(shù)據(jù)分離, 解除 View 和 Model 直接的耦合。

MVP(Model-View-Presenter)是MVC的演化版本,MVP的角色定義如下。* Model:主要提供數(shù)據(jù)的存取功能。Presenter需要通過Model層來存儲、獲取數(shù)據(jù)。

  • View:負責處理用戶事件和視圖部分的展示。在Android中,它可能是Activity、Fragment類或者是某個View控件。
  • Presenter:作為View和Model之間溝通的橋梁,它從Model層檢索數(shù)據(jù)后返回給View層,使得View和Model之間沒有耦合。

MVP模式.png

在MVP里(見圖10-2),Presenter完全將Model和View進行了分離,主要的程序邏輯在Presenter里實現(xiàn)。而且,Presenter與具體的View是沒有直接關聯(lián)的,而是通過定義好的接口進行交互,從而使得在變更View時可以保持Presenter的不變,這點符合面向接口編程的特點。View只應該有簡單的Set/Get方法,以及用戶輸入和設置界面顯示的內容,除此之外就不應該有更多的內容。絕不允許View直接訪問Model,這就是其與MVC的很大不同之處。

public interface BasePresenter {
    void start();
}

public interface BaseView<T> {
    void setPresenter(T presenter);
}
/**
 * This specifies the contract between the view and the presenter.
 */
public interface StatisticsContract {

    interface View extends BaseView<Presenter> {

        void setProgressIndicator(boolean active);

        void showStatistics(int numberOfIncompleteTasks, int numberOfCompletedTasks);

        void showLoadingStatisticsError();

        boolean isActive();
    }

    interface Presenter extends BasePresenter {

    }
}


/**
 * Listens to user actions from the UI ({@link TaskDetailFragment}), retrieves the data and updates
 * the UI as required.
 */
public class TaskDetailPresenter implements TaskDetailContract.Presenter {

    private final TasksRepository mTasksRepository;

    private final TaskDetailContract.View mTaskDetailView;

    @Nullable
    private String mTaskId;

    public TaskDetailPresenter(@Nullable String taskId,
                               @NonNull TasksRepository tasksRepository,
                               @NonNull TaskDetailContract.View taskDetailView) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
        mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");

        mTaskDetailView.setPresenter(this);
    }

    @Override
    public void start() {
        openTask();
    }
....


/**
 * Main UI for the task detail screen.
 */
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {


```java
  @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }

一些View層的操作

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_EDIT_TASK) {
            // If the task was edited successfully, go back to the list.
            if (resultCode == Activity.RESULT_OK) {
                getActivity().finish();
            }
        }
    }

    @Override
    public void showEditTask(@NonNull String taskId) {
        Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
        intent.putExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
        startActivityForResult(intent, REQUEST_EDIT_TASK);
    }

這種情況下給 present

    // Fragment#onCreateView中

    fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.editTask();
            }
        });

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_delete:
                mPresenter.deleteTask();
                return true;
        }
        return false;
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容