【Mark 】AndroidStudio_移動應用開發

  • 什麼是 Android?

安卓是一種基於Linux內核(不包含GNU組件)的自由及開放源代碼的操作系統
主要使用於移動設備,如智能手機和平板電腦,由美國Google公司和開放手機聯盟領導及開發
----百度

Android開發環境的配置與準備


採用 Java 語言開發 Android 應用

SDK

  • 更新 sdk 通過 SDK Manager
    NDK 是底層開發用的,暫時不管

Genymotion

Virtural Box

  • VirtualBox 是一款開源虛擬機軟件,號稱最強免費虛擬機軟件,
    可以在當前運行的系統上構建出一臺虛擬電腦,
    在這臺虛擬電腦上可以安裝系統和軟件,就像真實的電腦一樣操作
    20.2.14

鏈接 聯想thinkpad筆記本電腦怎麼開vt虛擬化.
(我並沒有開BIOS的VT的虛化,竟也能用虛擬機…可能是以前開過不知道?)

  • 查看是否已經開虛擬化,打開任務管理器 > 性能 > CPU > 虛擬化 : 已啓用

build.gradle 文件

  • 完成上述的許多步驟以後,如果 AndroidStudio 中仍會有許多 Error 的出現,
    根本原因可能是頂級 build.gradle 文件中的問題
    可更改爲如下:
buildscript {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter'}
        mavenLocal()
        mavenCentral()
        //google()
        //jcenter()	//我的並沒有將這兩條語句註釋,不然會出問題
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
    }
}

allprojects {
    repositories {
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter'}
        mavenLocal()
        mavenCentral()
        //google()
        //jcenter()	//還有這條
        
    }
}

遇到的問題1:

Gradle sync failed: Cause: invalid type code: 85 
Consult IDE log for more details (Help | Show Log) (22 s 73 ms)

解決1 :
鏈接 下載新的Gradle.zip文件替換.

問題2 插件無法更新
AndroidStudio Firebase services 3.6更新報錯.
用瀏覽器打開報錯給的網址,直接下載插件壓縮包
然後在 Installed 右邊的設置 Install Plugin from Disk,導入壓縮包即可
網址 鏈接.

http://plugins.jetbrains.com/pluginManager/?action=download&id=com.google.services.firebase&build=AI-192.7142.36&uuid=325b0776-a0e1-4765-8883-2a3dfe7cb602

問題3 刪除後的應用無法安裝
原因: 已經安裝過了
解決: cmd 中

adb uninstall 應用程序包名

鏈接 Android開發環境的設置與準備_問答. 20.2.17

Android 概述


  • 什麼是 API?
    Application Programing Interface (應用程序編程接口)
    簡單來說就是封裝了複雜操作的函數
    鏈接 什麼是API?.

  • 什麼是MSDN?
    操作系統的API有很多,需要一個說明文檔
    MSDN就是Windows API 的說明文檔

Android 版本

  • Android 版本,目前
    平臺版本10,API 級別 29,版本號 (VERSION_CODE) Q
    Android 主要用於移動設備

Android平臺結構
Android 平臺結構,基於 Linux 內核 (Kernel) 的分層結構
五層
① System Apps--------------------------------------------(系統應用)
② Java API FrameWork ---------------------------------(Java API 框架)
③Native C/C++ Libraries、Android Runtime ------(C/C++原生庫、ART)
④Hardware Abstraction Layer -------------------------(HAL 硬件抽象層)
⑤Linux Kernel----------------------------------------------(Linux 內核)
在這裏插入圖片描述

上層功能可以依靠 Linux 內核來執行底層功能
硬件抽象層 (HAL),提供標準界面,顯示硬件設備功能

Android 5.0 以上,
每個應用都在其自己的進程中運行,都有其自己的 Android Runtime (ART) 實例
DEX (Dalvik Executable) 文件是一種專爲 Android 設計的字節碼格式
編譯工具鏈 (如 Jack) ,
將 Java 源代碼 編譯爲 DEX 字節碼;
刪1

  • 原生 C/C++ 庫
    許多核心 Android 系統組件和服務(例如 ART 和 HAL)構建自 原生代碼
    代碼在 以 C 和 C++ 編寫的原生庫中

  • Java API 框架
    以Java語言編寫的API
    以此使用 Android OS 的整個功能集

  • 系統應用
    Android 系統隨附的應用,
    和第三方應用一樣,沒有特殊的狀態

APK

  • Android SDK 工具將你的 代碼 + 所有數據資源 等 編譯到一個APK中
    Android 軟件包 (Android application package),.apk
    一個 APK 文件包含一個 Android 應用的所有內容,
    它是 基於 Android 系統的設備 用來安裝應用的文件

Android 應用的運行方式和運行權限

  • Android 操作系統是一種多用戶 Linux 系統,其中 每個應用都是一個不同的用戶
    系統 分配 Linux 用戶 ID(該 ID 僅由系統使用,應用並不知曉)
    系統爲應用中的 所有文件 設置權限,只有 用戶 ID 匹配才能訪問
  • 每個進程 都具有自己的虛擬機 (VM)
    因此應用代碼是在 與其他應用隔離 的環境中運行
  • 默認情況,每個應用都 在其自己的 Linux 進程 內運行
  • Android 會在需要執行 任何應用組件 時啓動相應進程,
    然後在不需要時關閉該線程,實現 最小權限原則
  • 可以安排兩個應用共享同一 Linux 用戶 ID,這樣,
    它們能夠相互訪問彼此的文件,可在同一 Linux 進程中運行;
    應用可以請求訪問設備數據的權限

組件

組件 (Component)
組成 Android 應用
可以單獨調用,每個組件都是一個不同的入口點
共有四種不同的應用組件類型:
Activity、服務 Service、廣播接收器 BroadcastReceiver、內容提供程序 ContentProvider

  • Activity 表示具有用戶界面的 單一屏幕
    一個應用可有多個 Activity,它們之間互相獨立

  • 服務是一種在 後臺運行 的組件,用於執行長時間運行的操作或爲遠程進程執行作業
    其他組件可以啓動服務,讓其運行或與其綁定以便與其進行交互

  • 廣播接收器,用於響應系統範圍廣播通知
    不顯示用戶界面,會創建狀態欄通知;作爲通向其他組件的“通道”;
    廣播接收器作爲 BroadcastReceiver 的子類實現,並且每條廣播都作爲 Intent 對象進行傳遞

  • 內容提供程序管理一組 共享 的應用數據
    其他應用可以通過內容提供程序查詢、修改數據 (需要權限)
    內容提供程序作爲 ContentProvider 的子類實現,並且必須實現讓其他應用能夠執行事務的一組標準 API

啓動組件
Android 系統設計的獨特之處,任何應用都可通過 Android 系統間接啓動其他應用的組件
向系統傳遞一則消息,說明想啓動特定組件的 Intent,系統隨後便會啓動該組件
當系統啓動某個組件時,會啓動該應用的進程 (如果尚未運行),並實例化該組件所需的類

  • Activity、服務、廣播接收器,這三種組件類型通過名爲 Intent 的異步消息進行啓動
    Intent,異步消息機制,一個載體
    Intent 使用 Intent 對象創建,它定義的消息用於啓動特定組件或特定類型的組件
    可以指定要執行操作的數據的 URI(對於廣播組件,只能定義要廣播的通知)
  • 內容提供程序 組件,只會在成爲 ContentResolver 的請求目標時啓動

清單

應用清單文件
用來確認組件存在,聲明組件
AndroidManifast.xml,是核心配置文件
包含一個應用的所有組件的聲明

  • 聲明組件
    <activity> ___Activity
    <service> ____服務
    <receiver> ___廣播接收器
    <provider> ___內容提供程序
    源代碼中有,但是未在清單中聲明的組件,系統看不見
    不過,廣播接收器可以在清單文件中聲明或在代碼中動態創建
    (如 BroadcastReceiver 對象) 並通過調用 registerReceiver() 在系統中註冊

  • 聲明組件功能
    Intent 的真正強大之處 在於隱式 Intent 概念
    作用: 描述要執行的 操作 類型 (可選擇描述要執行的操作所針對的數據類型),
    讓系統 能夠 在設備上找到可執行該操作的組件,並啓動該組件
    若有多個組件可以執行 Intent 所描述的操作,則由用戶選擇使用哪一個組件
    系統如何找?
    系統通過將接收到的 Intent 與設備上的 其他應用的清單文件中 提供的 Intent 過濾器 進行比較來確定可以響應 Intent 的組件
    爲組件聲明 Intent 過濾器
    <intent-filter> 元素作爲組件聲明元素的子項進行添加 (意圖接收器)

  • 聲明應用要求
    在清單中聲明,爲應用支持的設備類型明確定義一個配置文件
    android:required = "true" ,聲明應用要求,例如:沒有相機不能安裝
    若不要求,則改爲 false
    建議API級別 19,對應Android版本 4.4

<manifest ... >
    <uses-feature android:name="android.hardware.camera.any"
                  android:required="true" />
    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
    ...
</manifest>

R類

  • Android 項目中包括的每一項資源,SDK 構建工具都會定義一個唯一的整型 ID
    R.java 生成一個系統常量,R.目錄.文件名,即資源 ID
    這裏的 R類 是系統自動生成的,程序員 不能修改 此類中的內容

  • 限定符
    是一種加入到資源目錄名稱中,用來定義這些 資源適用的設備配置 的簡短字符串

開發者模式

在搭載 Android 4.2 及更高版本的設備上,“開發者選項”默認情況下處於隱藏狀態
開發者模式 > 打開USB調試 > USB連接方式選擇 傳輸文件 >
打開Android Studio > 選擇Troubleshoot Devices Connections

可以通過 WLAN 連接到設備,不過用USB比用WLAN方便一些

  • Android 調試橋 (Android Debug Bridge) 是一種功能多樣的命令行工具
    客戶端,發送adb命令給服務器
    守護進程 (adbd),接收abd命令的轉接,在設備 (手機) 上運行命令
    服務器,接收abd命令,確定手機上有守護進程,發給手機

日誌

  • Logcat,日誌,View > Tool Windows > Logcat
    Log level:Verbose (所有日誌),Debug,Info,Warn,Error,Assert (級別遞增)
    寫日誌Log.v(String, String),兩個參數分別爲 項名(鍵),值 20.3.2
    可通過 過濾器 限制日誌的輸出

鏈接 Android Basic_Activity Empty_Activity Bottom_Navigation_Activity Fragment + ViewModel etc.
鏈接 Android 概述_問答.
鏈接 Android 概述_習題.

Android UI 開發


  • UI 就是用戶界面

ViewGroup 和 View

  • ViewGroup 繼承自 View
    View 對象是一塊矩形區域,用於在屏幕上繪製可供用戶交互的內容
    ViewGroup 對象是一種不可見的容器

佈局聲明

  • 在 XML 中聲明 UI 的優點
    將應用的 外觀 與控制應用行爲的 代碼 分離
    UI 描述位於應用代碼外部,在修改或調整外觀的描述時,無需修改源代碼

加載 XML 資源
啓動 Activity 時,Android 框架調用其 onCreate 方法加載佈局資源
on開頭,通常都是回調方法 (當系統中發生某種事件時,由系統調用的方法)

  • 每個佈局文件都必有且只能有一個根元素 (可爲 View 或 ViewGroup對象)
    小部件 (微件 widget) 中不能再嵌入其他元素

  • ID 用於在結構樹中對 View 對象進行唯一標識
    XML 標記內部的 ID 語法

android:id="@+id/my_button"		// + 號表示創建該名稱並將其添加到資源內 (R.java中)

android:id="@android:id/empty"	//引用 Android 資源 ID
  • 創建視圖並從應用中引用
//在佈局文件中定義一個視圖/小部件,併爲其分配一個唯一的 ID:
<Button android:id="@+id/my_button"/>
        
//然後創建一個 view 對象實例,並從佈局中捕獲它(通常在 onCreate() 方法中):
Button myButton = (Button) findViewById(R.id.my_button);

在 Java 代碼中通過 findViewById()方法,獲取該 View 的引用,並強制轉型爲 Button 類型,此後,代碼中就可以使用 myButton 來引用該按鈕了

佈局參數

  • 所有視圖組都包括寬度和高度 (layout_width 和 layout_height),並且每個視圖都必須定義它們
    wrap_content 同內容一樣的尺寸
    match_parent 同父視圖幾近一樣的尺寸

佈局位置

  • 每個 view 的形狀爲一個矩形,left 和 top 表示它的位置,width 和 height 表示它的大小
    下圖 利用各種方法獲取 View 座標
    在這裏插入圖片描述

度量單位

顯示度量單位

  • 長度單位
    in,英寸 (inch),每英寸 2.54 釐米,屏幕物理尺寸
    pt,表示一個點 (Points),1 / 72 英寸,在 72dpi 屏幕上 1pt = 1px,屏幕物理尺寸
  • 像素單位
    px,對應屏幕上實際像素點
    dip = dp,密度無關的像素,一種基於屏幕密度的抽象單位
    sp,和dp的概念相似,用於字體大小的單位
  • 下面六種密度集
    ldpi (low) ~120dpi
    mdpi (medium) ~160dpi
    hdpi (high) ~240dpi
    xhdpi (extra-high) ~320dpi
    xxhdpi (extra-extra-high) ~480dpi
    xxxhdpi (extra-extra-extra-high) ~640dpi
    若把 mdpi 作爲基準密度,其他密度的實際像素px = dp * (dpi / 160)

視圖 View 的尺寸通過寬度和高度表示
測量寬度和測量高度,視圖想要在其父項內具有的大小
繪製寬度和繪製高度,簡稱寬度高度,視圖在繪製時和佈局後在屏幕上的實際尺寸
因爲程序代碼中可能在真正顯示佈局的時候,
修改了它的大小,最終使得其顯示大小和定義大小不一致

常見佈局

常見佈局
佈局的嵌套佈局越少,繪製速度越快(扁平的視圖層次結構優於深層的視圖層次結構)
佈局用 LinearLayout (線性佈局)足夠,約束佈局 (ConstraintLayout) 比較高級比較難

  • 相對佈局,RelativeLayout
    每個 view 的位置通過相對它的兄弟 view 或父 view 來進行定位
  • 線性佈局,LinearLayout
    使用單個水平行或垂直行來組織子項的佈局
  • 表格佈局,TableLayout 是 LinearLayout 的子類
    行由 TableRow 表示,表列的個數由包含最多子元素的 TableRow 所決定
  • 網格佈局,GridLayout 在 Android4.0之後
    用一組沒有寬度的線將屏幕區域劃分爲縱橫交錯的格子,將子控件依次放在格子 (cell) 裏
    網格線用下標表示
  • 幀佈局,FrameLayout
    爲每個加入其中的控件創建一個空白區域,只在左上角顯示
  • 絕對佈局,AbsoluteLayout (不推薦使用)
    需要通過指定 layout_x、layout_y 座標來控制每一個控件的位置

視圖可以定義內邊距 (Padding),但不支持外邊距 (Margin)
在表示視圖組中的視圖之間時,可用 margin,別的不可
width 設爲 0dp 後,再改 weight 權重,比例關係 (因爲是 剩餘空間 按權重比例分配)
gravity (重力) 指內容
layout_gravity 指相對父控件的位置
下去自己學 20.3.9

鏈接 Android UI 開發_問答_1.
鏈接 Android UI 開發_問答_2.
鏈接 Android UI 開發_問答_3.
鏈接 【Android】Android UI 開發_習題.

Activity


鏈接 【Android】Activity.(佔篇幅較大)
鏈接 【Android】Activity_問答.
鏈接 【Android】Activity_習題.

數據存儲


Internal 內部存儲區 只能app訪問
External 外部存儲區 世界可讀,不具有保密性
卸載app時,Internal 中的都刪,External public 的文件保留 private 的文件刪除
app默認安裝到 Internal 存儲區

  • 內部存儲

獲取合適的目錄作爲File對象
getFilesDir() 爲你的app返回一個代表internal目錄的File對象
getDir(name,mode) 你的app的內部存儲目錄中創建或者打開一個目錄
getCacheDir() 返回一個用於存放你的app 臨時 緩存文件的internal目錄

  • 寫入文件 (注 openFileOutput 方法默認打開內部存儲區目錄)
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;

try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(string.getBytes());	//轉換爲字節流
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}
  • 寫一個緩存文件
public File getTempFile(Context context, String url) {
    File file;
    try {
        String fileName = Uri.parse(url).getLastPathSegment();
        //createTempFile(String prefix, String suffix, File directory)
        //在指定目錄中創建空文件, 用給定的前綴名prefix和後綴名suffix
        file = File.createTempFile(fileName, null, context.getCacheDir());
    } catch (IOException e) {
        // Error while creating file
    }
    return file;
}

fileList()方法獲取你的app的所有文件名的字符數組

保存靜態文件
在項目的 res/raw/ 目錄中保存該文件
可以使用 openRawResource() 打開該資源並傳遞 R.raw.<filename> 資源 ID
創建 raw :res右擊 > New > Android Resource Directory

  • 外部存儲

  • 申請權限 獲取外部存儲的訪問權限
    寫權限本身是包含讀權限的

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>
  • 測試狀態 校驗外部存儲是否可用
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}	//是否可寫

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}	//是否可讀

getExternalStoragePublicDirectory() 獲取外部存儲的公共區域的目錄
getExternalFilesDir() 獲取外部存儲的私有區域的目錄

  • XML序列化 模板
1.設置文件編碼方式:serializer.setOutput(fos,"utf-8");//fos爲文件輸出流對象

2.寫入xml文件的開始標籤:serializer.startDocument("utf-8",true); 第一個參數設置文檔的編碼格式,第二個參數設置是否是一個獨立的文檔,一般設置爲true

3.依次寫入各元素(如果有多個元素則可以使用迭代的方式寫入,如果標籤是嵌套的,則在寫入順序上也是嵌套的):
a) 寫入開始標籤:serializer.startTag(null,"Persons"); 這裏第一個參數爲xml的命名空間,沒有可以用null,第二個參數爲標籤名
b) 如果該標籤有屬性:serializer.attribute(null,"id",1);其中第一個參數爲命名空間,第二個參數是屬性名,第三個參數爲屬性值
c) 寫入元素內容:serializer.text(person.getName());該參數爲實例對象中的某個屬性值
d) 寫入結束標籤:serializer.endTag(null,"Persons");第一個參數爲命名空間,一般爲null即可,第二個參數爲結束標籤的標籤名

4.以這個語句表示文檔的寫入結束:serializer.endDocument()

5.通過serializer.flush()將流寫入文件中
最後,關閉輸出流:fos.close()

XML解析

  • DOM解析 只能解析較小的文件
  • SAX解析 逐行掃描 可以解析超大的
  • PULL解析 和SAX差不多

SQLite,是一款輕型的數據庫
設計目標 嵌入式設備 用的資源比較少
點命令中的命令名同點之間沒有空白符
點命令必須在同一行
點命令不能在普通SQL語句中
點命令不接受註釋

.open 數據庫名稱,如果該數據庫在磁盤中不存在,則創建並打開,如果存在,則直接打開
.save 數據庫名稱,如果sqlite3的啓動是通過雙擊windows中的sqlite3.exe的圖標打開的,系統會在內存中創建一個數據庫,這個數據庫如果需要存放到磁盤,則需要使用本命令進行存放。注意如果磁盤上如有同名的數據庫,會覆蓋。
.databases,用於列出數據庫
.tables,用於列出數據庫中的數據表

控制檯中輸入:chcp 65001,把控制檯的字符編碼由GBK切換爲utf-8
contract類 協約類 夥伴類
定義表名和單個表的列名

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    public FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static abstract class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        ...
    }
}

創建和刪除表的語句

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" +
    FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
    ... // Any other options for the CREATE command
    " )";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedReaderContract.FeedEntry.TABLE_NAME;

少用 execSQL,因SQL注入

鏈接 【Android】數據存儲.
鏈接 數據存儲_問答.
鏈接 數據存儲_習題.

UI進階


使用 AdapterView 的三個步驟

  • 1.設置數據源
  • 2.創建 Adapter,並將數據源和 Adapter 綁定在一起
  • 3.將裝載好的 Adapter 綁定到我們想要展示的控件上面

AdapterView 的兩個子類,ListView、GridView

ListView 是一個可滾動的條目列表,是一個 ViewGroup
通過 Adapter 將數據從數據源自動加載到表中
常用方法 setAdapter 將控件和適配器聯繫起來

public void setAdapter(ListAdapter adapter)

ListAdapter 是 Adapter 的一個子接口

主要方法
abstract View getView(int position, View convertView, ViewGroup parent)
參數
position 顯示數據項的位置,若數據集是數組,該參數即爲下標
convertView 被複用的老的 view,若爲 null,則回收器中沒有,需新建
這是防止數據項(表項)過多 設計者對控件進行復用,調用 setTag getTag 指定 ViewHolder 對應的控件
//滾出頁面的數據條目,放在回收器裏
parent 綁定的父 view

Adapter 的實現子類 主要:BaseAdapter ArrayAdapter SimpleAdapter

  • BaseAdapter 抽象類
    繼承時需要覆寫 getView 例如
public View getView(int position, View convertView, ViewGroup parent) {
    //ViewHolder用於保存對子view的引用
    ViewHolder holder;
    if (convertView == null) {
        convertView = View.inflate(MainActivity.this,R.layout.list_item, null);		//新建
        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.tv_list);
        holder.icon = (ImageView) convertView.findViewById(R.id.image);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();		//若非空 取出來 設置相應的值
    }
//通過holder來 綁定數據
    holder.text.setText(names[position]);
    holder.icon.setImageResource(icons[position]);
    return convertView;
}
static class ViewHolder {	//內嵌亦可
    TextView text;
    ImageView icon;		//此處一個表項裏包含一個文本和一個圖片
}
  • ArrayAdapter
    繼承自 BaseAdapter,用於顯示數據源爲任意對象的數組

構造方法

  • 1.public ArrayAdapter (Context context, int resource)
    resource:佈局文件的 ID,有且只能有一個 TextView (R.layout.)
    也可用系統給的android.R.layout.simple_expandable_list_item_1
  • 2.public ArrayAdapter (Context context, int resource, int textViewResourceId)
    resource 限制解除
    textViewResourceId:把佈局文件中的 TextView id 寫上即可 (R.id.)
  • 3.public ArrayAdapter (Context context, int resource, T[] objects)
    比之1,不用 add,直接在第三個參數放入數組
  • 4.public ArrayAdapter (Context context, int resource, int textViewResourceId, T[] objects)
    2 + 3,結合一下

後面就是用表,不說了

  • SimpleAdapter
    用來將靜態數據顯示在XML文件所定義的視圖中

構造方法

public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
  • from 對應 to,to 數組包含的是佈局的所有控件 id,from 數組包含的是 data 取值對應的字符串
public class MainActivity extends ListActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.activity_main,
                new String[]{"img","title"},
                new int[]{R.id.iv_img,R.id.tv_title});

        setListAdapter(adapter);
    }

    private List<Map<String,Object>> getData(){

        List<Map<String,Object>> list = new ArrayList<>();

        Map<String,Object> map = new HashMap<>();
        map.put("img",R.drawable.jd);
        map.put("title","京東商城");
        map.put("info", "no.1");	//因 from 數組中不包含 info,故 no.1 不會被寫入,也無處可寫
        list.add(map);

        return list;
    }
}

Fragment 分段

Fragment 類和 Activity 類很像,它包含的回調方法同 activity 相似
其中 Stoppd 狀態:移除並且在 back 棧中時

f 與 a 生命週期顯著不同在於:

  • activity 停止時,被系統自動放入 系統管理的 back 棧
  • Fragment 停止時,放入宿主 activity 所管理的 back 棧中,
    而且是在事務中移除 Fragment 時,調用 addToBackStack 顯式放入的

別的都類似,而且 Activity 的生命週期直接影響 Fragment 的,
只有 Activity 在 Resumed 狀態時,Fragment 的生命週期能獨立變化
多的回調方法

  • onAttach,Fragment 和 Activity 關聯時調用
  • onCreateView,創建同 Fragment 關聯的視圖層次時調用
  • onDestoryView,視圖層次被移除的時候
  • onDetach,解除關聯的時候

引入佈局,需要 LayoutInflater

public class ArticleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 把佈局應用到fragment
        return inflater.inflate(R.layout.article_view, container, false);	//false 未綁定 ViewGroup
    }
}

可以把一個 Fragment 當作一個 view 一樣
1.靜態,佈局文件
Fragment 三種給 id 的方法

  • 1.id 屬性
  • 2.tag 屬性
  • 3.用其容器 view 的 id

2.編程加入 viewGroup

FragmentManager fragmentManager = getFragmentManager()		//管理 Fragment
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();	//開始一個事務 

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);		//對 Fragment 進行 添加 移除 替換等都可
fragmentTransaction.commit();		//記得提交

添加沒有 UI 的 Fragment,用來爲 activity 提供後臺行爲
不用實現 onCreateView,沒有 id 屬性,只能用 tag

放入 back 棧是人工調用 addToBackStack 方法,不會自動放

Fragment 通過 getActivity() 獲取 activity 的實例

View listView = getActivity().findViewById(R.id.list);	//在 Fragment 調用 getActivity 時,必須已綁定一起,否則返回 null

activity 通過 FragmentManager 中的findFragmentById/Tag 方法來獲取 Fragment 的引用

ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

Fragment 之間傳遞信息需要藉助 其宿主 Activity

public static class FragmentA extends ListFragment {
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    
    //判斷 宿主 activity 是否實現接口
    public void onAttach(Activity activity) {	// Fragment 和 Activity 關聯時調用
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    
    public void onListItemClick(ListView l, View v, int position, long id) {	//id 點擊項
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);	//調用該方法
    }
}

內容提供者


ContentProvider 四大組件之一,管理對結構化數據集的訪問

  • 是一個進程同另一個進程連接數據的標準接口
    應用程序之間共享數據的手段之一
  • 以 URI 的形式對外提供數據存取途徑
  • 增刪改查

ContentResolver,作爲客戶端,自動與 Provider 暴露的數據進行進程間通信 (可以有多個)

  • ContentProvider 封裝數據,並通過 ContentResolver 向其他應用提供數據
  • 通過 Uri 的 authority 找到對應的 ContentProvider

URI 統一資源標識符(Uniform Resource Identifier),標識某一資源名稱的字符串

  • URI = scheme + authority + path
    authority = host + port
    path 分層,稱片段
  • 給定字符串轉換爲 URI 用 parse 方法

UriMatcher 用於匹配content provider中的Uri,建立 URI 樹

public static final int NO_MATCH	//值 -1
public UriMatcher (int code)	//參數值爲 NO_MATCH  創建 URI 樹根節點
public void addURI (String authority, String path, int code)	//添加 uri 到樹中,同一個樹中 code 不重複

int match = sURIMatcher.match(uri);		//	取其 code 值

ContentUris 關於Content uri的工具類
語法 content://authority/path/id

  • 可用 parseId 方法取其 id 值
  • 用 withAppendedId (Uri contentUri, long id) 方法 在uir 後追加 id

MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型
ContentProvider 有兩個方法返回 MIME 類型

  • getType() 返回一個MIME格式的字符串
    你必須實現的一個方法
    若某個 provider 的 authority 是 com.example.app.provider,
    且有一個表的名字爲 table1,table1 的多行MIME類型是
vnd.android.cursor.dir/vnd.com.example.provider.table1
單行就是將 dir 改爲 item
  • getStreamTypes() 返回一個MIME類型的字符串數組
    如果你的 provider 提供文件的話,你需要實現的方法

關於訪問權限
默認情況下,無權限,都可訪問

  • 設置權限:<provider> 元素的 android:permission 屬性 假設A
    爲了讓權限只針對你的provider,使用android:name屬性 假設B
  • 訪問者 需 <permission> 元素的 name 屬性與 A 匹配
    <uses-permission> 元素的 name 屬性與 B 匹配

Message 類
常用域

  • public int arg1,arg2 傳兩個 int,多了只能用 setData
    替代 setData 方法的低成本方式
  • public Object obj 傳一個 obj,多了同上
  • public int what 消息代碼,類似於 requestCode (隱式意圖裏的)
    每個 Handler 都有自己的消息代碼的命名空間,無需擔心衝突

常用方法

  • setData
  • getData
  • obtain
    public static Message obtain (Handler h, int what, int arg1, int arg2, Object obj)
    無參數是傳給默認的
    參數爲 Message orig 複製參數消息,消息複製
  • setTarget
    然後 sendToTarget

Looper
常用方法

  • loop 啓動線程中的消息隊列
  • prepare 將當前線程轉換爲 looper 線程
class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
      Looper.prepare();

      mHandler = new Handler() {	//新建的 Handler 和所在的線程的 looper 及消息隊列相關聯 此過程在構造方法中完成
          public void handleMessage(Message msg) {
              // process incoming messages here
          }
      };

      Looper.loop();
    }
}

Handler
Handler 使得你可以發送和處理線程的消息隊列中的消息
每個handler對象同某個線程及它的消息隊列相關

  • 一個線程可有多個,若和消息對象則只能有一個
  • handler 同創建其的線程關聯,在哪個線程創建,就和哪個線程的 looper 相關聯
  • 可向消息對象發送 消息 和 Runable 對象 (也是個線程對象)

主要用途
1.安排消息 並且在未來的某個時候執行某個 Runable 對象
post 發送的是 Runable 對象,send 發送的纔是 Message
2.排隊 在其他線程要執行的動作

構造方法

  • Handler(),無參,關聯當前
  • Handler(Looper looper),指定關聯的 looper

常用方法

  • void handleMessage(Message msg),必須覆寫

廣播接收者


BroadcastReceiver 組件,用於監聽並處理系統中的廣播
需要創建並進行註冊 (組件都需要註冊,在清單文件中註冊)
註冊的兩種方式

  • 靜態註冊 <receiver>,常駐型廣播接收者
<receiver android:name = ".MyBroadcastReceiver">	//java 文件
   <intent-filter android:priority = "1000">		//優先級 -1000 ~ 1000 默認爲0
       <action android:name = "android.provider.Telephony.SMS_RECEIVED" />	//接收廣播的類型
   </intent-filter >
</receiver >
  • 動態註冊 Context.registerReceiver(),非常駐廣播接收者,依賴其所在的組件
    若組件被銷燬,該廣播也不再啓動
1.定義廣播接收者
private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
       // 相關處理,如收短信,監聽電量變化信息
    }
};

2.創建IntentFilter對象
IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED " );	//指定接收類型
優先級通過 setPriority 方法設置

3.註冊
registerReceiver( mBatteryInfoReceiver , intentFilter);

註冊建議放在 onResume 回調方法中
註銷建議放在 onPause 中

生命週期
廣播接收者對象只在 onReceiver 方法被調用期間有效,方法結束就銷燬,所以不能執行異步
所以勿考慮比較耗時的操作,耗時的可用 service

廣播的類型

  • 普通,通過 Context.sendBroadcast (intent, receiverPermission) 進行廣播,異步,
    接收者以無序方式同時接收,處理結果不能傳遞,廣播不能被截斷
    有時系統會讓接收者一個一個來,但還是普通廣播
    參數
    intent,廣播通過意圖發送,載體是意圖
    receiverPermission,指定權限,可省
  • 有序,通過 Context.sendOrderBroadcast (intent, string) 廣播,接收者輪流接收,處理結果可傳給下一個
    優先級高的先接收
    public abstract void sendOrderedBroadcast (Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras)
    BroadcastReceiver ,不管是否中斷,指定最終接收者
  • 注,廣播用的 intent 與 activity 用的 intent 機制不同

判斷廣播是否有序 isOrderedBroadcast

有序廣播常用方法

  • 中斷 abortBroadcast
  • 取消中斷 clearAbortBroadcast
  • 獲取結果狀態和數據
    getResultCode getResultData getResultExtras 結果碼,結果數據,附加數據
  • 設置結果狀態和數據
    總 public final void setResult (int code, String data, Bundle extras)
    setResultCode setResultData setResultExtras 與上對應

服務


服務的兩種形式

  • 1.啓動 其它組件調用 startService,啓動後無限期運行 需實現 onStartCommand onBind
  • 2.綁定 其它組件調用 bindService,所有綁定取消,服務銷燬 需實現 onBind

密集型建議另開單獨線程
可以降低發生“應用無響應”(ANR) 錯誤的風險,而應用的主線程仍可繼續專注於運行用戶與 Activity 之間的交互

onBind 必須實現,不允許綁定返回 null

service 必須在 清單 中聲明
只能通過顯示 intent 來啓動,所以發佈後最好不要改 name

<service android:name=".ExampleService" />

android:exported //false 則服務只能用於你的 app

啓動服務
調用 startService() 方法並傳遞 Intent 對象
服務通過 onStartCommand() 方法接收此 Intent

Intent intent = new Intent(this, HelloService.class);
startService(intent);		//後面系統調用各種方法

intent 是組件和服務之間的唯一通信模式

停止服務:服務調用 stopSelf,組件調用 stopService 方法
只要調用 onStartCommand,就是啓動服務,不會自動銷燬

繼承類

  • Service
    默認使用 主線程,儘量創建一個用於執行所有服務工作的新線程
  • IntentService
    逐一處理所有啓動請求,需實現 onHandleIntent

IntentService

創建一個 worker 線程來執行提交到 onStartCommand() 方法的意圖,該線程區別於你的 app 的主線程
創建一個工作隊列,每次向 onHandleIntent() 傳遞一個 intent,所以你從來不必擔心多線程
當所有的請求處理完畢以後,你從來不必調用 stopSelf()
提供一個返回 null 的 onBind() 的實現
提供一個 onStartCommand() 的默認實現,該實現把intent提交到工作隊列及你的 onHandleIntent() 實現

通過廣播的形式顯示結果

Service
實現代碼示例

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);		//發送給線程

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

START_NOT_STICKY
不會重建,除非有掛起的 intent 需處理

START_STICK
服務重新啓動的時候無需提交 intent,除非有掛起的 intent 需處理

START_REDELIVER_INTENT
再次提交 intent

綁定服務
生成 IBinder 接口
解綁 unbindService

實現 onBind
多個client連接到service,系統只執行第一個 client 的 onBind() 方法,返回 IBinder 對象
綁定調用

public abstract boolean bindService (Intent service, ServiceConnection conn, int flags)
兩個方法
public abstract void onServiceConnected (ComponentName name, IBinder service)
public abstract void onServiceDisconnected (ComponentName name)

定義 IBinder 接口的方法:

  • 繼承Binder類,同一個進程
  • Messenger,不同進程

cast 類型轉換

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();
    // Random number generator
    private final Random mGenerator = new Random();		//nothing

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** method for clients */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);	//只是一個例子
    }
}

下面的 Activity 綁定上面的 LocalService 並調用其中方法

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Called when a button is clicked (the button in the layout file attaches to
      * this method with the android:onClick attribute) */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章