1. 程式人生 > >完整學習筆記之Android基礎(詳版)

完整學習筆記之Android基礎(詳版)

Android專案的目錄結構(熟悉)

  • Activity:應用被開啟時顯示的介面
  • src:專案程式碼
  • R.java:專案中所有資原始檔的資源id
  • Android.jar:Android的jar包,匯入此包方可使用Android的api
  • libs:匯入第三方jar包
  • assets:存放資原始檔,比方說mp3、視訊檔案
  • bin:存放編譯打包後的檔案
  • res:存放資原始檔,存放在此資料夾下的所有資原始檔都會生成資源id
  • drawable:存放圖片資源
  • layout:存放佈局檔案,把佈局檔案通過資源id指定給activity,介面就會顯示出該佈局檔案定義的佈局
  • menu:定義選單的樣式
  • Strings.xml:存放字串資源,每個資源都會有一個資源id

Android的配置檔案(清單檔案)(熟悉)

  • 指定應用的包名

    package="com.itheima.helloworld"
    
    • data/data/com.itheima.helloworld(上面程式碼指定的包名)
    • 應用生成的檔案都會存放在此路徑下
  • Android的四大元件在使用前全部需要在清單檔案中配置

  • 的配置對整個應用生效
  • 的配置對該activity生效

DDMS(掌握)

  • Dalvik debug monitor service
  • Dalvik除錯監控服務

常用的adb指令(掌握)

Android debug bridge:安卓除錯橋

  • adb start-server:啟動adb程序
  • adb kill-server:殺死adb程序
  • adb devices:檢視當前與開發環境連線的裝置,此命令也可以啟動adb程序
  • adb install XXX.apk:往模擬器安裝apk
  • adb uninstall 包名:刪除模擬器中的應用
  • adb shell:進入linux命令列
    • ps:檢視執行程序
    • ls:檢視當前目錄下的檔案結構
  • netstat -ano:檢視佔用埠的程序

電話撥號器(掌握)

功能:使用者輸入一個號碼,點選撥打按鈕,啟動系統打電話的應用把號碼撥打出去

1. 定義佈局

  1. 元件必須設定寬高,否則不能通過編譯

    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    
  2. 如果要在java程式碼中操作某個元件,則元件需要設定id,這樣才能在程式碼中通過id拿到這個元件

    android:id="@+id/et_phone"
    

2. 給按鈕設定點選偵聽

  1. 給按鈕設定偵聽

     //通過id拿到按鈕物件
    Button bt_call = (Button) findViewById(R.id.bt_call);
    //給按鈕設定點選
    bt_call.setOnClickListener(new MyListener());
    

3. 得到使用者輸入的號碼

    //得到使用者輸入的號碼,先拿到輸入框元件
        EditText et_phone = (EditText) findViewById(R.id.et_phone);
        String phone = et_phone.getText().toString();

4. 把號碼打出去

  1. Android系統中基於動作機制,來呼叫系統的應用,你告訴系統你想做什麼動作,系統就會把能做這個動作的應用給你,如果沒有這個應用,會拋異常
  2. 設定動作,通過意圖告知系統

    //把號碼打出去
        //先建立一個意圖物件
        Intent intent = new Intent();
        //設定動作,打電話
        intent.setAction(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:" + phone));
        //把意圖告訴系統
        startActivity(intent);
    
  3. 新增許可權

    <uses-permission android:name="android.permission.CALL_PHONE"/>
    

點選事件的四種寫法(掌握)

第一種

  • 定義一個MyListener實現onClickListener介面

    Button bt1 = (Button) findViewById(R.id.bt1);
    bt1.setOnClickListener(new MyListener());
    

第二種

  • 定義一個匿名內部類實現onClickListener介面

    Button bt2 = (Button) findViewById(R.id.bt2);
    bt2.setOnClickListener(new OnClickListener() {
    
        @Override
        public void onClick(View v) {
            System.out.println("第二種");
    
        }
    });
    

第三種

  • 讓當前activity實現onClickListener介面

    Button bt3 = (Button) findViewById(R.id.bt3);
    bt3.setOnClickListener(this);
    

第四種

  • 給Button節點設定onClick屬性,

     android:onClick="click"
    
  • 然後在activity中定義跟該屬性值同名的方法

    public void click(View v){
        System.out.println("第四種");
    }
    

簡訊傳送器(掌握)

功能:使用者輸入號碼和簡訊內容,點擊發送按鈕,呼叫簡訊api把簡訊傳送給指定號碼

1. 定義佈局

  • 輸入框的提示

    android:hint="請輸入號碼"  
    

2. 完成點選事件

  • 先給Button元件設定onClick屬性

  • onClick=”send”
  • 在Activity中定義此方法

  • public void send(View v){}

3. 獲取到使用者輸入的號碼和內容

    EditText et_phone = (EditText) findViewById(R.id.et_phone);
    EditText et_content = (EditText) findViewById(R.id.et_content);
    String phone = et_phone.getText().toString();
    String content = et_content.getText().toString();

4. 呼叫傳送簡訊的api

    //呼叫傳送簡訊的api
    SmsManager sm = SmsManager.getDefault();

    //傳送簡訊
    sm.sendTextMessage(phone, null, content, null, null);

* 新增許可權

     <uses-permission android:name="android.permission.SEND_SMS"/>

* 如果簡訊過長,需要拆分

    List<String> smss = sm.divideMessage(content);

常用佈局

線性佈局

  • LinearLayout
  • 指定各個節點的排列方向

    android:orientation="horizontal"
    
  • 設定右對齊

    android:layout_gravity="right"
    
  • 當豎直佈局時,只能左右對齊和水平居中,頂部底部對齊豎直居中無效
  • 當水平佈局時,只能頂部底部對齊和豎直居中
  • 使用match_parent時注意不要把其他元件頂出去
  • 線性佈局非常重要的一個屬性:權重

    android:layout_weight="1"
    
  • 權重:按比例分配螢幕的剩餘寬度或者高度

常見佈局

相對佈局(掌握)

RelativeLayout
  • 元件預設左對齊、頂部對齊
  • 設定元件在指定元件的右邊

     android:layout_toRightOf="@id/tv1"
    
  • 設定在指定元件的下邊

    android:layout_below="@id/tv1"
    
  • 設定右對齊父元素

    android:layout_alignParentRight="true"
    
  • 設定與指定元件右對齊

     android:layout_alignRight="@id/tv1"
    

線性佈局(掌握)

LinearLayout
  • 指定各個節點的排列方向

    android:orientation="horizontal"
    
  • 設定右對齊

    android:layout_gravity="right"
    
  • 當豎直佈局時,只能左右對齊和水平居中,頂部底部對齊豎直居中無效
  • 當水平佈局時,只能頂部底部對齊和豎直居中
  • 使用match_parent時注意不要把其他元件頂出去
  • 線性佈局非常重要的一個屬性:權重

    android:layout_weight="1"
    
  • 權重:按比例分配螢幕的剩餘寬度或者高度

幀佈局(掌握)

FrameLayout
  • 預設元件都是左對齊和頂部對齊,每個元件相當於一個div
  • 可以設定上下左右對齊,水平豎直居中,設定方式與線性佈局一樣

    android:layout_gravity="bottom"
    
  • 不能相對於其他元件佈局

表格佈局(熟悉)

TableLayout
  • 每個節點是一行,它的每個子節點是一列
  • 表格佈局中的節點可以不設定寬高,因為設定了也無效

    • 根節點的子節點寬為匹配父元素,高為包裹內容
    • 節點的子節點寬為包裹內容,高為包裹內容
    • 以上預設屬性無法修改
  • 根節點中可以設定以下屬性,表示讓第1列拉伸填滿螢幕寬度的剩餘空間

    android:stretchColumns="1"
    

絕對佈局(熟悉)

AbsoluteLayout
  • 直接指定元件的x、y座標

    android:layout_x="144dp"
    android:layout_y="154dp"
    

logcat(掌握)

  • 日誌資訊總共分為5個等級
    • verbose
    • debug
    • info
    • warn
    • error
  • 定義過濾器方便檢視
  • System.out.print輸出的日誌級別是info,tag是System.out
  • Android提供的日誌輸出api

    Log.v(TAG, "加油吧,童鞋們");
    Log.d(TAG, "加油吧,童鞋們");
    Log.i(TAG, "加油吧,童鞋們");
    Log.w(TAG, "加油吧,童鞋們");
    Log.e(TAG, "加油吧,童鞋們");
    

檔案讀寫操作

  • Ram記憶體:執行記憶體,相當於電腦的記憶體
  • Rom記憶體:內部儲存空間,相當於電腦的硬碟
  • sd卡:外部儲存空間,相當於電腦的行動硬碟

在內部儲存空間中讀寫檔案(掌握)

小案例:使用者輸入賬號密碼,勾選“記住賬號密碼”,點選登入按鈕,登入的同時持久化儲存賬號和密碼

1. 定義佈局
2. 完成按鈕的點選事件
  • 彈土司提示使用者登入成功

    Toast.makeText(this, "登入成功", Toast.LENGTH_SHORT).show();
    
3. 拿到使用者輸入的資料
  • 判斷使用者是否勾選儲存賬號密碼

    CheckBox cb = (CheckBox) findViewById(R.id.cb);
    if(cb.isChecked()){
    
    }
    
4. 開啟io流把檔案寫入內部儲存
  • 直接開啟檔案輸出流寫資料

    //持久化儲存資料
        File file = new File("data/data/com.itheima.rwinrom/info.txt");
        FileOutputStream fos = new FileOutputStream(file);
        fos.write((name + "##" + pass).getBytes());
        fos.close();
    
  • 讀取資料前先檢測檔案是否存在

    if(file.exists())
    
  • 讀取儲存的資料,也是直接開檔案輸入流讀取

    File file = new File("data/data/com.itheima.rwinrom/info.txt");
    FileInputStream fis = new FileInputStream(file);
    //把位元組流轉換成字元流
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    String text = br.readLine();
    String[] s = text.split("##");
    
  • 讀取到資料之後,回顯至輸入框

    et_name.setText(s[0]);
    et_pass.setText(s[1]);
    
  • 應用只能在自己的包名目錄下建立檔案,不能到別人家去建立

直接複製專案

  • 需要改動的地方:
    • 專案名字
    • 應用包名
    • R檔案重新導包

使用路徑api讀寫檔案(掌握)

  • getFilesDir()得到的file物件的路徑是data/data/com.itheima.rwinrom2/files
    • 存放在這個路徑下的檔案,只要你不刪,它就一直在
  • getCacheDir()得到的file物件的路徑是data/data/com.itheima.rwinrom2/cache

    • 存放在這個路徑下的檔案,當記憶體不足時,有可能被刪除
  • 系統管理應用介面的清除快取,會清除cache資料夾下的東西,清除資料,會清除整個包名目錄下的東西

在外部儲存讀寫資料

sd卡的路徑(掌握)

  • 2.2之前,sd卡路徑:sdcard
  • 4.3之前,sd卡路徑:mnt/sdcard
  • 4.3開始,sd卡路徑:storage/sdcard

  • 最簡單的開啟sd卡的方式

    File file = new File("sdcard/info.txt");
    
  • 寫sd卡需要許可權

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
  • 讀sd卡,在4.0之前不需要許可權,4.0之後可以設定為需要

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    
  • 使用api獲得sd卡的真實路徑,部分手機品牌會更改sd卡的路徑

    Environment.getExternalStorageDirectory()
    
  • 判斷sd卡是否準備就緒

    if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
    

檢視原始碼查詢獲取sd卡剩餘容量的程式碼(掌握)

  • 匯入Settings專案
  • 查詢“可用空間”得到

     <string name="memory_available" msgid="418542433817289474">"可用空間"</string>
    
  • 查詢”memory_available”,得到

    <Preference android:key="memory_sd_avail" 
        style="?android:attr/preferenceInformationStyle" 
        android:title="@string/memory_available"
        android:summary="00"/>
    
  • 查詢”memory_sd_avail”,得到

    //這個字串就是sd卡剩餘容量
    formatSize(availableBlocks * blockSize) + readOnly
    //這兩個引數相乘,得到sd卡以位元組為單位的剩餘容量
    availableBlocks * blockSize
    
  • 儲存裝置會被分為若干個區塊,每個區塊有固定的大小

  • 區塊大小 * 區塊數量 等於 儲存裝置的總大小

Linux檔案的訪問許可權(掌握)

  • 在Android中,每一個應用是一個獨立的使用者
  • drwxrwxrwx
  • 第1位:d表示資料夾,-表示檔案
  • 第2-4位:rwx,表示這個檔案的擁有者使用者(owner)對該檔案的許可權
    • r:讀
    • w:寫
    • x:執行
  • 第5-7位:rwx,表示跟檔案擁有者使用者同組的使用者(grouper)對該檔案的許可權
  • 第8-10位:rwx,表示其他使用者組的使用者(other)對該檔案的許可權

openFileOutput的四種模式(熟悉)

  • MODE_PRIVATE:-rw-rw—-
  • MODE_APPEND:-rw-rw—-
  • MODE_WORLD_WRITEABLE:-rw-rw–w-
  • MODE_WORLD_READABLE:-rw-rw-r–

SharedPreference(掌握)

用SharedPreference儲存賬號密碼

  • 往SharedPreference裡寫資料

    //拿到一個SharedPreference物件
    SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
    //拿到編輯器
    Editor ed = sp.edit();
    //寫資料
    ed.putBoolean("name", name);
    ed.commit();
    
  • 從SharedPreference裡取資料

    SharedPreferences sp = getSharedPreferences("config", MODE_PRIVATE);
    //從SharedPreference裡取資料
    String name = sp.getBoolean("name", "");
    

生成XML檔案備份簡訊

  • 建立幾個虛擬的簡訊物件,存在list中
  • 備份資料通常都是備份至sd卡

使用StringBuffer拼接字串(瞭解)

  • 把整個xml檔案所有節點append到sb物件裡

    sb.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>");
    //新增smss的開始節點
    sb.append("<smss>");
    .......
    
  • 把sb寫到輸出流中

    fos.write(sb.toString().getBytes());
    

使用XMl序列化器生成xml檔案(掌握)

  • 得到xml序列化器物件

    XmlSerializer xs = Xml.newSerializer();
    
  • 給序列化器設定輸出流

    File file = new File(Environment.getExternalStorageDirectory(), "backupsms.xml");
    FileOutputStream fos = new FileOutputStream(file);
    //給序列化器指定好輸出流
    xs.setOutput(fos, "utf-8");
    
  • 開始生成xml檔案

    xs.startDocument("utf-8", true);
    xs.startTag(null, "smss");
    ......
    

Pull解析xml檔案(掌握)

  • 先自己寫一個xml檔案,存一些天氣資訊

拿到xml檔案

    InputStream is = getClassLoader().getResourceAsStream("weather.xml");

拿到pull解析器

    XmlPullParser xp = Xml.newPullParser();

開始解析

  • 拿到指標所在當前節點的事件型別

    int type = xp.getEventType();
    
  • 事件型別主要有五種

    • START_DOCUMENT:xml頭的事件型別
    • END_DOCUMENT:xml尾的事件型別
    • START_TAG:開始節點的事件型別
    • END_TAG:結束節點的事件型別
    • TEXT:文字節點的事件型別
  • 如果獲取到的事件型別不是END_DOCUMENT,就說明解析還沒有完成,如果是,解析完成,while迴圈結束

    while(type != XmlPullParser.END_DOCUMENT)
    
  • 當我們解析到不同節點時,需要進行不同的操作,所以判斷一下當前節點的name

    • 當解析到weather的開始節點時,new出list
    • 當解析到city的開始節點時,建立city物件,建立物件是為了更方便的儲存即將解析到的文字
    • 當解析到name開始節點時,獲取下一個節點的文字內容,temp、pm也是一樣

      case XmlPullParser.START_TAG:
      //獲取當前節點的名字
          if("weather".equals(xp.getName())){
              citys = new ArrayList<City>();
          }
          else if("city".equals(xp.getName())){
              city = new City();
          }
          else if("name".equals(xp.getName())){
              //獲取當前節點的下一個節點的文字
              String name = xp.nextText();
              city.setName(name);
          }
          else if("temp".equals(xp.getName())){
              String temp = xp.nextText();
              city.setTemp(temp);
          }
          else if("pm".equals(xp.getName())){
              String pm = xp.nextText();
              city.setPm(pm);
          }
          break;
      
  • 當解析到city的結束節點時,說明city的三個子節點已經全部解析完了,把city物件新增至list

    case XmlPullParser.END_TAG:
        if("city".equals(xp.getName())){
                citys.add(city);
        }
    

測試(瞭解)

  • 黑盒測試
    • 測試邏輯業務
  • 白盒測試

    • 測試邏輯方法
  • 根據測試粒度

    • 方法測試:function test
    • 單元測試:unit test
    • 整合測試:integration test
    • 系統測試:system test
  • 根據測試暴力程度

    • 冒煙測試:smoke test
    • 壓力測試:pressure test

單元測試junit(熟悉)

  • 定義一個類繼承AndroidTestCase,在類中定義方法,即可測試該方法

  • 在指定指令集時,targetPackage指定你要測試的應用的包名

    <instrumentation 
    android:name="android.test.InstrumentationTestRunner"
    android:targetPackage="com.itheima.junit"
    ></instrumentation>
    
  • 定義使用的類庫

    <uses-library android:name="android.test.runner"></uses-library>
    
  • 斷言的作用,檢測執行結果和預期是否一致

  • 如果應用出現異常,會拋給測試框架

SQLite資料庫(掌握)

  • 輕量級關係型資料庫
  • 建立資料庫需要使用的api:SQLiteOpenHelper

    • 必須定義一個構造方法:

      //arg1:資料庫檔案的名字
      //arg2:遊標工廠
      //arg3:資料庫版本
      public MyOpenHelper(Context context, String name, CursorFactory factory, int version){
      
      }
      
    • 資料庫被建立時會呼叫:onCreate方法
    • 資料庫升級時會呼叫:onUpgrade方法

建立資料庫

//建立OpenHelper物件
MyOpenHelper oh = new MyOpenHelper(getContext(), "person.db", null, 1);
//獲得資料庫物件,如果資料庫不存在,先建立資料庫,後獲得,如果存在,則直接獲得
SQLiteDatabase db = oh.getWritableDatabase();
  • getWritableDatabase():開啟可讀寫的資料庫
  • getReadableDatabase():在磁碟空間不足時開啟只讀資料庫,否則開啟可讀寫資料庫
  • 在建立資料庫時建立表

    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        db.execSQL("create table person (_id integer primary key autoincrement, name char(10), phone char(20), money integer(20))");
    }
    

資料庫的增刪改查(掌握)

SQL語句

  • insert into person (name, phone, money) values (‘張三’, ‘159874611’, 2000);
  • delete from person where name = ‘李四’ and _id = 4;
  • update person set money = 6000 where name = ‘李四’;
  • select name, phone from person where name = ‘張三’;

執行SQL語句實現增刪改查

    //插入
    db.execSQL("insert into person (name, phone, money) values (?, ?, ?);", new Object[]{"張三", 15987461, 75000});
    //查詢
    Cursor cs = db.rawQuery("select _id, name, money from person where name = ?;", new String[]{"張三"});

* 測試方法執行前會呼叫此方法

    protected void setUp() throws Exception {
        super.setUp();
        //                  獲取虛擬上下文物件
        oh = new MyOpenHelper(getContext(), "people.db", null, 1);
    }

* 測試方法執行後會呼叫此方法:tearDown()

使用api實現增刪改查

  • 插入

    //以鍵值對的形式儲存要存入資料庫的資料
    ContentValues cv = new ContentValues();
    cv.put("name", "劉能");
    cv.put("phone", 1651646);
    cv.put("money", 3500);
    //返回值是改行的主鍵,如果出錯返回-1
    long i = db.insert("person", null, cv);
    
  • 刪除

    //返回值是刪除的行數
    int i = db.delete("person", "_id = ? and name = ?", new String[]{"1", "張三"});
    
  • 修改

    ContentValues cv = new ContentValues();
    cv.put("money", 25000);
    int i = db.update("person", cv, "name = ?", new String[]{"趙四"});
    
  • 查詢

    //arg1:要查詢的欄位
    //arg2:查詢條件
    //arg3:填充查詢條件的佔位符
    Cursor cs = db.query("person", new String[]{"name", "money"}, "name = ?", new String[]{"張三"}, null, null, null);
    while(cs.moveToNext()){
        //                          獲取指定列的索引值
        String name = cs.getString(cs.getColumnIndex("name"));
        String money = cs.getString(cs.getColumnIndex("money"));
        System.out.println(name + ";" + money);
    }
    

事務

  • 保證多條SQL語句要麼同時成功,要麼同時失敗
  • 最常見案例:銀行轉賬
  • 事務api

    try {
        //開啟事務
        db.beginTransaction();
        ...........
        //設定事務執行成功
        db.setTransactionSuccessful();
    } finally{
        //關閉事務
        //如果此時已經設定事務執行成功,則sql語句生效,否則不生效
        db.endTransaction();
    }
    

把資料庫的資料顯示至螢幕(瞭解)

  1. 任意插入一些資料

    • 定義業務bean:Person.java
    • 讀取資料庫的所有資料

      Cursor cs = db.query(“person”, null, null, null, null, null, null);
      while(cs.moveToNext()){
      String name = cs.getString(cs.getColumnIndex(“name”));
      String phone = cs.getString(cs.getColumnIndex(“phone”));
      String money = cs.getString(cs.getColumnIndex(“money”));
      //把讀到的資料封裝至Person物件
      Person p = new Person(name, phone, money);
      //把person物件儲存至集合中
      people.add(p);
      }

    • 把集合中的資料顯示至螢幕

      LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
      for(Person p : people){
      //建立TextView,每條資料用一個文字框顯示
      TextView tv = new TextView(this);
      tv.setText(p.toString());
      //把文字框設定為ll的子節點
      ll.addView(tv);
      }

    • 分頁查詢

      Cursor cs = db.query(“person”, null, null, null, null, null, null, “0, 10”);

ListView(掌握)

  • 用於顯示列表
  • MVC結構
    • M:model模型層,要顯示的資料 ————people集合
    • V:view檢視層,使用者看到的介面 ————ListView
    • c:control控制層,操作資料如何顯示 ————adapter物件
  • 每一個條目都是一個View物件

BaseAdapter

  • 必須實現的兩個方法

    • 第一個

      //系統呼叫此方法,用來獲知模型層有多少條資料
      @Override
      public int getCount() {
          return people.size();
      }
      
    • 第二個

      //系統呼叫此方法,獲取要顯示至ListView的View物件
      //position:是return的View物件所對應的資料在集合中的位置
      @Override
      public View getView(int position, View convertView, ViewGroup parent) {
          System.out.println("getView方法呼叫" + position);
          TextView tv = new TextView(MainActivity.this);
          //拿到集合中的元素
          Person p = people.get(position);
          tv.setText(p.toString());
      
          //把TextView的物件返回出去,它會變成ListView的條目
          return tv;
      }
      
  • 螢幕上能顯示多少個條目,getView方法就會被呼叫多少次,螢幕向下滑動時,getView會繼續被呼叫,建立更多的View物件顯示至螢幕

條目的快取

  • 當條目劃出螢幕時,系統會把該條目快取至記憶體,當該條目再次進入螢幕,系統在重新呼叫getView時會把快取的條目作為convertView引數傳入,但是傳入的條目不一定是之前被快取的該條目,即系統有可能在呼叫getView方法獲取第一個條目時,傳入任意一個條目的快取

ArrayAdapter(熟悉)

  • 在條目中顯示一個字串

    String[] objects = new String[]{
            "張三",
            "李四",
            "王五"
    };
    
    ListView lv = (ListView) findViewById(R.id.lv);
    //arg1:指定要填充的佈局檔案
    //arg2:指定文字顯示至哪一個文字框內
    lv.setAdapter(new ArrayAdapter<String>(this, R.layout.item_array, R.id.tv_name, objects));
    

SimpleAdapter(熟悉)

  • 可在條目中顯示多種資料
  • 要顯示的資料封裝在List中,集合的每一個元素存放的是一個條目會顯示的資料,因為可能會有多種資料,而集合的泛型只能指定一種資料,所以把資料先存放在Map中,在把Map放入List中

     List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
    
     //張三的頭像和名字是兩種型別的資料,先封裝至Map
     Map<String, Object> map1 = new HashMap<String, Object>();
     map1.put("name", "張三");
     map1.put("image", R.drawable.photo1);
     //把Map封裝至List
     data.add(map1);
     ...
    
  • 通過兩個陣列的下標對應指定資料存放入對應的控制元件中
    lv.setAdapter(new SimpleAdapter(this, data, R.layout.item_array,
    new String[]{“name”, “image”}, new int[]{R.id.tv_name, R.id.iv_photo}));

網路圖片檢視器(掌握)

  • 確定圖片的網址
  • 傳送http請求
    //1.使用網址構造一個URL物件
    URL url = new URL(address);
    //2.獲取連線物件,並沒有建立連線
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    //3.設定一些屬性
    //設定連線和讀取超時
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    //設定請求方法,注意必須大寫
    conn.setRequestMethod(“GET”);
    //建立連線,傳送get請求
    //conn.connect();
    //建立連線,然後獲取響應嗎,200說明請求成功
    conn.getResponseCode();

  • 伺服器的圖片是以流的形式返回給瀏覽器的
    //拿到伺服器返回的輸入流
    InputStream is = conn.getInputStream();
    //把流裡的資料讀取出來,並構造成圖片
    Bitmap bm = BitmapFactory.decodeStream(is);

  • 把圖片設定為ImageView的顯示內容

    ImageView iv = (ImageView) findViewById(R.id.iv);
    iv.setImageBitmap(bm);
    
  • 新增許可權

主執行緒不能被阻塞

  • 在Android中,主執行緒被阻塞會導致應用不能重新整理ui介面,不能響應使用者操作,使用者體驗將非常差
  • 主執行緒阻塞時間過長,系統會丟擲ANR異常
  • ANR:Application Not Response;應用無響應
  • 任何耗時操作都不可以寫在主執行緒
  • 因為網路互動屬於耗時操作,如果網速很慢,程式碼會阻塞,所以網路互動的程式碼不能執行在主執行緒

只有主執行緒能重新整理ui

  • 重新整理ui的程式碼只能執行在主執行緒,執行在子執行緒是沒有任何效果的
  • 如果需要在子執行緒中重新整理ui,使用訊息佇列機制
  • 主執行緒也叫ui執行緒

訊息佇列(重點掌握)

  • 主執行緒建立時,系統會同時建立訊息佇列物件(MessageQueue)和訊息輪詢器物件(Looper)
  • 輪詢器的作用,就是不停的檢測訊息佇列中是否有訊息(Message)
  • Looper一旦發現Message Queue中有訊息,就會把訊息取出,然後把訊息扔給Handler物件,Handler會呼叫自己的handleMessage方法來處理這條訊息
  • handleMessage方法執行在主執行緒
  • 主執行緒建立時,訊息佇列和輪詢器物件就會被建立,但是訊息處理器物件,需要使用時,自行建立

    //訊息佇列
    Handler handler = new Handler(){
        //主執行緒中有一個訊息輪詢器looper,不斷檢測訊息佇列中是否有新訊息,如果發現有新訊息,自動呼叫此方法,注意此方法是在主執行緒中執行的
        public void handleMessage(android.os.Message msg) {
    
        }
    };
    
  • 在子執行緒中使用Handler物件往訊息佇列裡發訊息

    //建立訊息物件
    Message msg = new Message();
    //訊息的obj屬性可以賦值任何物件,通過這個屬性可以攜帶資料
    msg.obj = bm;
    //what屬性相當於一個標籤,用於區分出不同的訊息,從而執行不能的程式碼
    msg.what = 1;
    //傳送訊息
    handler.sendMessage(msg);
    
  • 通過switch語句區分不同的訊息

    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
        //如果是1,說明屬於請求成功的訊息
        case 1:
            ImageView iv = (ImageView) findViewById(R.id.iv);
            Bitmap bm = (Bitmap) msg.obj;
            iv.setImageBitmap(bm);
            break;
        case 2:
            Toast.makeText(MainActivity.this, "請求失敗", 0).show();
            break;
        }       
    }
    

加入快取圖片的功能(熟悉)

  • 把伺服器返回的流裡的資料讀取出來,然後通過檔案輸入流寫至本地檔案

    //1.拿到伺服器返回的輸入流
    InputStream is = conn.getInputStream();
    //2.把流裡的資料讀取出來,並構造成圖片
    
    FileOutputStream fos = new FileOutputStream(file);
    byte[] b = new byte[1024];
    int len = 0;
    while((len = is.read(b)) != -1){
        fos.write(b, 0, len);
    }
    
  • 建立bitmap物件的程式碼改成

    Bitmap bm = BitmapFactory.decodeFile(file.getAbsolutePath());
    
  • 每次傳送請求前檢測一下在快取中是否存在同名圖片,如果存在,則讀取快取

獲取開原始碼的網站(熟悉)

  • code.google.com
  • github.com
  • 在github搜尋smart-image-view
  • 下載開源專案smart-image-view
  • 使用自定義元件時,標籤名字要寫包名

    <com.loopj.android.image.SmartImageView/>
    
  • SmartImageView的使用

    SmartImageView siv = (SmartImageView) findViewById(R.id.siv);
    siv.setImageUrl("http://192.168.1.102:8080/dd.jpg");
    

Html原始檔檢視器(掌握)

  • 傳送GET請求

    URL url = new URL(path);
    //獲取連線物件
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    //設定連線屬性
    conn.setRequestMethod("GET");
    conn.setConnectTimeout(5000);
    conn.setReadTimeout(5000);
    //建立連線,獲取響應嗎
    if(conn.getResponseCode() == 200){
    
    }
    
  • 獲取伺服器返回的流,從流中把html原始碼讀取出來

    byte[] b = new byte[1024];
    int len = 0;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    while((len = is.read(b)) != -1){
        //把讀到的位元組先寫入位元組陣列輸出流中存起來
        bos.write(b, 0, len);
    }
    //把位元組陣列輸出流中的內容轉換成字串
    //預設使用utf-8
    text = new String(bos.toByteArray());
    

亂碼的處理

  • 亂碼的出現是因為伺服器和客戶端碼錶不一致導致

    //手動指定碼錶
    text = new String(bos.toByteArray(), "gb2312");
    

提交資料(掌握)

GET方式提交資料

  • get方式提交的資料是直接拼接在url的末尾

    final String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + name + "&pass=" + pass;
    
  • 傳送get請求,程式碼和之前一樣

    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    if(conn.getResponseCode() == 200){
    
    }
    
  • 瀏覽器在傳送請求攜帶資料時會對資料進行URL編碼,我們寫程式碼時也需要為中文進行URL編碼

    String path = "http://192.168.1.104/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;
    

POST方式提交資料

  • post提交資料是用輸出流寫給伺服器的
  • 協議頭中多了兩個屬性

    • Content-Type: application/x-www-form-urlencoded,描述提交的資料的mimetype
    • Content-Length: 32,描述提交的資料的長度

      //給請求頭新增post多出來的兩個屬性
      String data = "name=" + URLEncoder.encode(name) + "&pass=" + pass;
      conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
      conn.setRequestProperty("Content-Length", data.length() + "");
      
  • 設定允許開啟post請求的流

    conn.setDoOutput(true);
    
  • 獲取連線物件的輸出流,往流裡寫要提交給伺服器的資料

    OutputStream os = conn.getOutputStream();
    os.write(data.getBytes());
    

HttpClient(掌握)

傳送get請求

  • 建立一個客戶端物件

    HttpClient client = new DefaultHttpClient();

  • 建立一個get請求物件

    HttpGet hg = new HttpGet(path);

  • 傳送get請求,建立連線,返回響應頭物件

    HttpResponse hr = client.execute(hg);

  • 獲取狀態行物件,獲取狀態碼,如果為200則說明請求成功

    if(hr.getStatusLine().getStatusCode() == 200){
        //拿到伺服器返回的輸入流
        InputStream is = hr.getEntity().getContent();
        String text = Utils.getTextFromStream(is);
    }
    

傳送post請求

    //建立一個客戶端物件
    HttpClient client = new DefaultHttpClient();
    //建立一個post請求物件
    HttpPost hp = new HttpPost(path);
  • 往post物件裡放入要提交給伺服器的資料

    //要提交的資料以鍵值對的形式存在BasicNameValuePair物件中
    List<NameValuePair> parameters = new ArrayList<NameValuePair>();
    BasicNameValuePair bnvp = new BasicNameValuePair("name", name);
    BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass);
    parameters.add(bnvp);
    parameters.add(bnvp2);
    //建立實體物件,指定進行URL編碼的碼錶
    UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");
    //為post請求設定實體
    hp.setEntity(entity);
    

非同步HttpClient框架(熟悉)

傳送get請求

    //建立非同步的httpclient物件
    AsyncHttpClient ahc = new AsyncHttpClient();
    //傳送get請求
    ahc.get(path, new MyHandler());

* 注意AsyncHttpResponseHandler兩個方法的呼叫時機

    class MyHandler extends AsyncHttpResponseHandler{

        //http請求成功,返回碼為200,系統回撥此方法
        @Override
        public void onSuccess(int statusCode, Header[] headers,
                //responseBody的內容就是伺服器返回的資料
                byte[] responseBody) {
            Toast.makeText(MainActivity.this, new String(responseBody), 0).show();

        }

        //http請求失敗,返回碼不為200,系統回撥此方法
        @Override
        public void onFailure(int statusCode, Header[] headers,
                byte[] responseBody, Throwable error) {
            Toast.makeText(MainActivity.this, "返回碼不為200", 0).show();

        }

    }

傳送post請求

  • 使用RequestParams物件封裝要攜帶的資料

    //建立非同步httpclient物件
    AsyncHttpClient ahc = new AsyncHttpClient();
    //建立RequestParams封裝要攜帶的資料
    RequestParams rp = new RequestParams();
    rp.add("name", name);
    rp.add("pass", pass);
    //傳送post請求
    ahc.post(path, rp, new MyHandler());
    

多執行緒下載(掌握)

原理:伺服器CPU分配給每條執行緒的時間片相同,伺服器頻寬平均分配給每條執行緒,所以客戶端開啟的執行緒越多,就能搶佔到更多的伺服器資源

確定每條執行緒下載多少資料(掌握)

  • 傳送http請求至下載地址

    String path = "http://192.168.1.102:8080/editplus.exe";     
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");                   
    
  • 獲取檔案總長度,然後建立長度一致的臨時檔案

    if(conn.getResponseCode() == 200){
        //獲得伺服器流中資料的長度
        int length = conn.getContentLength();
        //建立一個臨時檔案儲存下載的資料
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        //設定臨時檔案的大小
        raf.setLength(length);
        raf.close();
    
  • 確定執行緒下載多少資料

        //計算每個執行緒下載多少資料
        int blockSize = length / THREAD_COUNT;
    

計算每條執行緒下載資料的開始位置和結束位置(掌握)

    for(int id = 1; id <= 3; id++){
        //計算每個執行緒下載資料的開始位置和結束位置
        int startIndex = (id - 1) * blockSize;
        int endIndex = id * blockSize - 1;
        if(id == THREAD_COUNT){
            endIndex = length;
        }

        //開啟執行緒,按照計算出來的開始結束位置開始下載資料
        new DownLoadThread(startIndex, endIndex, id).start();
    }

再次傳送請求至下載地址,請求開始位置至結束位置的資料(掌握)

    String path = "http://192.168.1.102:8080/editplus.exe";

    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(5000);
    conn.setConnectTimeout(5000);
    conn.setRequestMethod("GET");

    //向伺服器請求部分資料
    conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    conn.connect();

* 下載請求到的資料,存放至臨時檔案中

    if(conn.getResponseCode() == 206){
        InputStream is = conn.getInputStream();
        RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");
        //指定從哪個位置開始存放資料
        raf.seek(startIndex);
        byte[] b = new byte[1024];
        int len;
        while((len = is.read(b)) != -1){
            raf.write(b, 0, len);
        }
        raf.close();
    }

帶斷點續傳的多執行緒下載(掌握)

  • 定義一個int變數記錄每條執行緒下載的資料總長度,然後加上該執行緒的下載開始位置,得到的結果就是下次下載時,該執行緒的開始位置,把得到的結果存入快取檔案

    //用來記錄當前執行緒總的下載長度
    int total = 0;
    while((len = is.read(b)) != -1){
        raf.write(b, 0, len);
        total += len;
        //每次下載都把新的下載位置寫入快取文字檔案
        RandomAccessFile raf2 = new RandomAccessFile(threadId + ".txt", "rwd");
        raf2.write((startIndex + total + "").getBytes());
        raf2.close();
    }
    
  • 下次下載開始時,先讀取快取檔案中的值,得到的值就是該執行緒新的開始位置

    FileInputStream fis = new FileInputStream(file);
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    String text = br.readLine();
    int newStartIndex = Integer.parseInt(text);
    //把讀到的值作為新的開始位置
    startIndex = newStartIndex;
    fis.close();
    
  • 三條執行緒都下載完畢之後,刪除快取檔案

    RUNNING_THREAD--;
    if(RUNNING_THREAD == 0){
        for(int i = 0; i <= 3; i++){
            File f = new File(i + ".txt");
            f.delete();
        }
    }
    

手機版的斷點續傳多執行緒下載器

  • 把剛才的程式碼直接貼上過來就能用,記得在訪問檔案時的路徑要改成Android的目錄,新增訪問網路和外部儲存的路徑

用進度條顯示下載進度(掌握)

  • 拿到下載檔案總長度時,設定進度條的最大值

    //設定進度條的最大值
    pb.setMax(length);
    
  • 進度條需要顯示三條執行緒的整體下載進度,所以三條執行緒每下載一次,就要把新下載的長度加入進度條

    • 定義一個int全域性變數,記錄三條執行緒的總下載長度

      int progress;
      
    • 重新整理進度條

      while((len = is.read(b)) != -1){
          raf.write(b, 0, len);
      
      
          //把當前執行緒本次下載的長度加到進度條裡
          progress += len;
          pb.setProgress(progress);
      
  • 每次斷點下載時,從新的開始位置開始下載,進度條也要從新的位置開始顯示,在讀取快取檔案獲取新的下載開始位置時,也要處理進度條進度

    FileInputStream fis = new FileInputStream(file);
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    String text = br.readLine();
    int newStartIndex = Integer.parseInt(text);
    
    //新開始位置減去原本的開始位置,得到已經下載的資料長度
    int alreadyDownload = newStartIndex - startIndex;
    //把已經下載的長度設定入進度條
    progress += alreadyDownload;
    

新增文字框顯示百分比進度(熟悉)

    tv.setText(progress * 100 / pb.getMax() + "%");

HttpUtils框架(github上面的開原始碼)的使用(熟悉)

HttpUtils本身就支援多執行緒斷點續傳,使用起來非常的方便
* 建立HttpUtils物件

    HttpUtils http = new HttpUtils();

* 下載檔案

    http.download(url, //下載請求的網址
            target, //下載的資料儲存路徑和檔名
            true, //是否開啟斷點續傳
            true, //如果伺服器響應頭中包含了檔名,那麼下載完畢後自動重新命名
            new RequestCallBack<File>() {//偵聽下載狀態

        //下載成功此方法呼叫
        @Override
        public void onSuccess(ResponseInfo<File> arg0) {
            tv.setText("下載成功" + arg0.result.getPath());
        }

        //下載失敗此方法呼叫,比如檔案已經下載、沒有網路許可權、檔案訪問不到,方法傳入一個字串引數告知失敗原因
        @Override
        public void onFailure(HttpException arg0, String arg1) {
            tv.setText("下載失敗" + arg1);
        }

        //在下載過程中不斷的呼叫,用於重新整理進度條
        @Override
        public void onLoading(long total, long current, boolean isUploading) {
            super.onLoading(total, current, isUploading);
            //設定進度條總長度
            pb.setMax((int) total);
            //設定進度條當前進度
            pb.setProgress((int) current);
            tv_progress.setText(current * 100 / total + "%");
        }
    });

建立第二個Activity(掌握)

  • 需要在清單檔案中為其配置一個activity標籤
  • 標籤中如果帶有這個子節點,則會在系統中多建立一個快捷圖示

     <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>
    
  • 一個應用程式可以在桌面建立多個快捷圖示。
  • activity的名稱、圖示可以和應用程式的名稱、圖示不相同

    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    

Activity的跳轉(掌握)

Activity的跳轉需要建立Intent物件,通過設定intent物件的引數指定要跳轉Activity

通過設定Activity的包名和類名實現跳轉,稱為顯式意圖

通過指定動作實現跳轉,稱為隱式意圖

顯式意圖(掌握)

  • 跳轉至同一專案下的另一個Activity,直接指定該Activity的位元組碼即可

    Intent intent = new Intent();
    intent.setClass(this, SecondActivity.class);
    startActivity(intent);
    
  • 跳轉至其他應用中的Activity,需要指定該應用的包名和該Activity的類名

    Intent intent = new Intent();
    //啟動系統自帶的撥號器應用
    intent.setClassName("com.android.dialer", "com.android.dialer.DialtactsActivity");
    startActivity(intent);
    

隱式意圖(掌握)

  • 隱式意圖跳轉至指定Activity

    Intent intent = new Intent();
    //啟動系統自帶的撥號器應用
    intent.setAction(Intent.ACTION_DIAL);
    startActivity(intent);
    
  • 要讓一個Activity可以被隱式啟動,需要在清單檔案的activity節點中設定intent-filter子節點

    <intent-filter >
        <action android:name="com.itheima.second"/>
        <data android:scheme="asd" android:mimeType="aa/bb"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
    
    • action 指定動作(可以自定義,可以使用系統自帶的)
    • data 指定資料(操作什麼內容)
    • category 類別 (預設類別,機頂盒,車載電腦)
  • 隱式意圖啟動Activity,需要為intent設定以上三個屬性,且值必須與該Activity在清單檔案中對三個屬性的定義匹配
  • intent-filter節點及其子節點都可以同時定義多個,隱式啟動時只需與任意一個匹配即可
獲取通過setData傳遞的資料(掌握)
    //獲取啟動此Activity的intent物件
    Intent intent = getIntent();
    Uri uri = intent.getData();

顯式意圖和隱式意圖的應用場景(掌握)

  • 顯式意圖用於啟動同一應用中的Activity
  • 隱式意圖用於啟動不同應用中的Activity
    • 如果系統中存在多個Activity的intent-filter同時與你的intent匹配,那麼系統會顯示一個對話方塊,列出所有匹配的Activity,由使用者選擇啟動哪一個

Activity跳轉時的資料傳遞(掌握)

  • Activity通過Intent啟動時,可以通過Intent物件攜帶資料到目標Activity

    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtra("maleName", maleName);
    intent.putExtra("femaleName", femaleName);
    startActivity(intent);
    
  • 在目標Activity中取出資料

    Intent intent = getIntent();
    String maleName = intent.getStringExtra("maleName");
    String femaleName = intent.getStringExtra("femaleName");
    

Activity生命週期(掌握)

void onCreate()

  • Activity已經被建立完畢

void onStart()

  • Activity已經顯示在螢幕,但沒有得到焦點

void onResume()

  • Activity得到焦點,可以與使用者互動

void onPause()

  • Activity失去焦點,無法再與使用者互動,但依然可見

void onStop()

  • Activity不可見,進入後臺

void onDestroy()

  • Activity被銷燬

void onRestart()

  • Activity從不可見變成可見時會執行此方法

使用場景

  • Activity建立時需要初始化資源,銷燬時需要釋放資源;或者播放器應用,在介面進入後臺時需要自動暫停

完整生命週期(entire lifetime)

onCreate–>onStart–>onResume–>onPause–>onStop–>onDestory

可視生命週期(visible lifetime)

onStart–>onResume–>onPause–>onStop

前臺生命週期(foreground lifetime)

onResume–>onPause

Activity的四種啟動模式(掌握)

每個應用會有一個Activity任務棧,存放已啟動的Activity

Activity的啟動模式,修改任務棧的排列情況

  • standard 標準啟動模式
  • singleTop 單一頂部模式
    • 如果任務棧的棧頂存在這個要開啟的activity,不會重新的建立activity,而是複用已經存在的activity。保證棧頂如果存在,不會重複建立。
    • 應用場景:瀏覽器的書籤
  • singeTask 單一任務棧,在當前任務棧裡面只能有一個例項存在

    • 當開啟activity的時候,就去檢查在任務棧裡面是否有例項已經存在,如果有例項存在就複用這個已經存在的activity,並且把這個activity上面的所有的別的activity都清空,複用這個已經存在的activity。保證整個任務棧裡面只有一個例項存在
    • 應用場景:瀏覽器的activity
    • 如果一個activity的建立需要佔用大量的系統資源(cpu,記憶體)一般配置這個activity為singletask的啟動模式。webkit核心 c程式碼
  • singleInstance啟動模式非常特殊, activity會執行在自己的任務棧裡面,並且這個任務棧裡面只有一個例項存在

    • 如果你要保證一個activity在整個手機作業系統裡面只有一個例項存在,使用singleInstance
    • 應用場景: 電話撥打介面

橫豎屏切換的生命週期(熟悉)

預設情況下 ,橫豎屏切換, 銷燬當前的activity,重新建立一個新的activity

快捷鍵ctrl+F11

在一些特殊的應用程式常見下,比如遊戲,不希望橫豎屏切換activity被銷燬重新建立
需求:禁用掉橫豎屏切換的生命週期

  1. 橫豎屏寫死
    android:screenOrientation=”landscape”
    android:screenOrientation=”portrait”

  2. 讓系統的環境 不再去敏感橫豎屏的切換。

     android:configChanges="orientation|screenSize|keyboardHidden"
    

掌握開啟activity獲取返回值(掌握)

從A介面開啟B介面, B介面關閉的時候,返回一個數據給A介面

步驟:
  1. 開啟activity並且獲取返回值

    startActivityForResult(intent, 0);
    
  2. 在新開啟的介面裡面實現設定資料的邏輯

    Intent data = new Intent();
    data.putExtra("phone", phone);
    //設定一個結果資料,資料會返回給呼叫者
    setResult(0, data);
    finish();//關閉掉當前的activity,才會返回資料
    
  3. 在開啟者activity裡面實現方法

    //通過data獲取返回的資料
    onActivityResult(int requestCode, int resultCode, Intent data) {
    
    }
    
  4. 通過判斷請求碼和結果碼確定返回值的作用

廣播(掌握)

  • 廣播的概念
    • 現實:電臺通過傳送廣播發布訊息,買個收音機,就能收聽
    • Android:系統在產生某個事件時傳送廣播,應用程式使用廣播接收者接收這個廣播,就知道系統產生了什麼事件。
      Android系統在執行的過程中,會產生很多事件,比如開機、電量改變、收發簡訊、撥打電話、螢幕解鎖

廣播接收者(掌握)

  • 當一條廣播被髮送出來時,系統是在所有清單檔案中遍歷,通過匹配意圖過濾器找到能接收這條廣播的廣播接收者

IP撥號器(掌握)

原理:接收撥打電話的廣播,修改廣播內攜帶的電話號碼
* 定義廣播接收者接收打電話廣播

public class CallReceiver extends BroadcastReceiver {

    //當廣播接收者接收到廣播時,此方法會呼叫
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到使用者撥打的號碼
        String number = getResultData();
        //修改廣播內的號碼
        setResultData("17951" + number);
    }
}

* 在清單檔案中定義該廣播接收者接收的廣播型別

    <receiver android:name="com.itheima.ipdialer.CallReceiver">
        <intent-filter >
            <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
        </intent-filter>
    </receiver>

* 接收打電話廣播需要許可權

    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>

* 即使廣播接收者的程序沒有啟動,當系統傳送的廣播可以被該接收者接收時,系統會自動啟動該接收者所在的程序

簡訊攔截器(熟悉)

系統收到簡訊時會產生一條廣播,廣播中包含了簡訊的號碼和內容

  • 定義廣播接收者接收簡訊廣播

    public void onReceive(Context context, Intent intent) {
    //拿到廣播裡攜帶的簡訊內容
    Bundle bundle = intent.getExtras();
    Object[] objects = (Object[]) bundle.get("pdus");
    for(Object ob : objects ){
        //通過object物件建立一個簡訊物件
        SmsMessage sms = SmsMessage.createFromPdu((byte[])ob);
        System.out.println(sms.getMessageBody());
        System.out.println(sms.getOriginatingAddress());
    }
    

    }

  • 系統建立廣播時,把簡訊存放到一個數組,然後把資料以pdus為key存入bundle,再把bundle存入intent
  • 清單檔案中配置廣播接收者接收的廣播型別,注意要設定優先順序屬性,要保證優先順序高於簡訊應用,才可以實現攔截

    <receiver android:name="com.itheima.smslistener.SmsReceiver">
        <intent-filter android:priority="1000">
            <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
        </intent-filter>
    </receiver>
    
  • 新增許可權

    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    
  • 4.0之後,廣播接收者所在的應用必須啟動過一次,才能生效

  • 4.0之後,如果廣播接收者所在應用被使用者手動關閉了,那麼再也不會啟動了,直到使用者再次手動啟動該應用

監聽SD卡狀態(掌握)

  • 清單檔案中定義廣播接收者接收的型別,監聽SD卡常見的三種狀態,所以廣播接收者需要接收三種廣播

     <receiver android:name="com.itheima.sdcradlistener.SDCardReceiver">
        <intent-filter >
            <action android:name="android.intent.action.MEDIA_MOUNTED"/>
            <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
            <action android:name="android.intent.action.MEDIA_REMOVED"/>
            <data android:scheme="file"/>
        </intent-filter>
    </receiver>
    
  • 廣播接收者的定義

    public class SDCardReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 區分接收到的是哪個廣播
            String action = intent.getAction();
    
            if(action.equals("android.intent.action.MEDIA_MOUNTED")){
                System.out.println("sd卡就緒");
            }
            else if(action.equals("android.intent.action.MEDIA_UNMOUNTED")){
                System.out.println("sd卡被移除");
            }
            else if(action.equals("android.intent.action.MEDIA_REMOVED")){
                System.out.println("sd卡被拔出");
            }
        }
    }
    

勒索軟體(掌握)

  • 接收開機廣播,在廣播接收者中啟動勒索的Activity
  • 清單檔案中配置接收開機廣播

    <receiver android:name="com.itheima.lesuo.BootReceiver">
        <intent-filter >
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
        </intent-filter>
    </receiver>
    
  • 許可權

    <uses-permissi