Getting Started

Building Your First App

Supporting Different Devices

Building a Dynamic UI with Fragments

為了適配不同的屏幕尺寸(大屏幕可以比小屏幕多顯示幾個 Fragment),這一節(jié)主要說明如何通過 Fragments 創(chuàng)造動態(tài)化的用戶體驗,使你的 App 在不同的屏幕尺寸上都可獲得最優(yōu)的用戶體驗,設備最低支持到 Android 1.6.

Creating a Fragment

可以認為Fragment是Activity組合的一部分,有自己獨立的生命周期,自己的輸入事件,當其所依附的Activity還在運行時,可以自由添加或刪除Fragment。

在創(chuàng)建Fragment之前,需要讓App使用 Support Library

Create a Fragment Class

  1. extend Fragment
  2. override key lifecycle methods

必須使用 onCreateView() 的callback來定義layout組件

Add a Fragment to an Activity using XML

FragmentActivity 是用來支持 API level 11 以下的版本,如果版本在 11 及以上,則可以使用普通的Activity.

可以通過在 xml 文件中指定Fragmentname屬性,從而指定特定的Fragment class.

Building a Flexible UI

FragmentManager 類提供添加、移除、替換fragment的方法,給用戶動態(tài)的適配體驗。

Add a Fragment to an Activity at Runtime

使用FragmentManager 創(chuàng)建一個 FragmentTransaction。

在Activity運行時添加Fragment有一點需要注意:Activity必須包含一個你可以插入 fragment 的 View.

  • Get a FragmentManager: getSupportFragmentManager()
  • Create a FragmentTransaction: beginTransaction()
  • Add a Fragment: add()

Replace One Fragment with Another

使用 replace() 代替 add().

Best Practice:在進行Fragment替換時,最好允許用戶返回或者取消操作:addToBackStack()(在 FragmentTransaction.commit() 之前,F(xiàn)ragment 將不會被銷毀,只會被remove掉).

Communicating with Other Fragments

所有Fragment之間的信息交換都是通過與其相關的Activity來完成,任何Fragment不應該直接交流。

Define an Interface

在 Fragment 中定義一個 interface,在 Activity 中實現(xiàn)這個 interface。Fragment 將會在 onAttach() 中通過得到 Activity 對象捕獲這個實現(xiàn),從而通過 Activity 來進行信息交流。

Implement the Interface

Activity 需要實現(xiàn) Fragment 中聲明的 interface.

Deliver a Message to a Fragment

Activity 可以通過 findFragmentById() 獲取到 Fragment 實例,直接調用 Fragment 的方法。

場景:AFragment 有一堆文章列表,點擊某個文章,進入 BFragment,閱讀這篇文章。

  1. AFragment: callback.click(title)
  2. Activity: click(title) {title -> content -> replaceToBFragment(content) -> BFragment.updateArticleView(content)}
  3. BFragment: updateArticleView(content)

Saving Data

在 Android 中,有三種數(shù)據(jù)存儲方式:

  • shared preferences 文件:key-value
  • 文件系統(tǒng):任何文件
  • SQLite: 數(shù)據(jù)庫

Saving Key-Value Sets

一個 SharedPreferences 對象指向一個包含key-value的文件,提供簡單的方法進行讀寫。

Get a Handle to a SharedPreferences

創(chuàng)建或獲取一個 shared preference 文件:

  • getSharedPreferences():擁有多個shared preference文件,通過傳入文件名獲取對象,可以從任意的context中獲取。
  • getPreferences():如果一個activity只有一個shared preference文件,通過這個方法可以獲取activity對應的SP文件

Write to Shared Preferences

  1. 創(chuàng)建 SharedPreferences.Editor:調用 SharedPreferences 對象的 edit() 方法
  2. 寫入鍵值對:putInt(), putString, .etc
  3. 保存更改:commit()

Read from Shared Preferences

getInt(), getString, .etc.

Saving Files

使用 File API 來操作 Android 中的文件系統(tǒng)。

一個File對象適合讀寫大數(shù)據(jù)文件,從頭到尾沒有中斷的順序讀取。

Choose Internal or External Storage

所有的Android設備擁有兩個文件存儲域:“internal” 和 “external”:

  • Internal Storage
    • 永遠可用
    • 文件只能被 App 訪問
    • 當 App 被卸載時,所有存儲的 internal 的文件都會被刪除
  • External Storage
    • 不一定可用
    • 可被全局訪問
    • 當 App 被卸載時,系統(tǒng)只會刪除特定的文件夾(getExternalFilesDir()

App 會被默認載入到internal中,在代碼中如何設置下載位置?

在AndroidManifest中:更改android:installLocation

Obtain Permissions for External Storage

在external寫文件:需要權限 android.permission.WRITE_EXTERNAL_STORAGE

在external寫文件(in future):需要權限:android.permission.READ_EXTERNAL_STORAGE

Save a File on Internal Storage

  • getFilesDir():返回app在internal中的位置
  • getCacheDir(): 返回app在internal中保存cache的位置,一定要在不需要的時候及時刪掉
  • 寫文件:FileOutputStream fos = openFileOutput(filename, file_mode);
  • 創(chuàng)建cache文件:File file = File.createTempFile(filename, null, context.getCacheDir());

Save a File on External Storage

由于external文件有很多不在場的不確定因素,所以在訪問文件前最好驗證其可用性:
getExternalStorageState() 獲取external storage狀態(tài):如果返回MEDIA_MOUNTED,則可用。

  • Public Files: 需要留存 -> create from -> getExternalStoragePublicDirectory()
  • Private Files: 需要刪除 -> create from -> getExternalFilesDir()
  • 文件類型:DIRECTORY_PICTURES, DIRECTORY_MUSIC, DIRECTORY_RINGTONES, .etc

Query Free Space

  • getFreeSpace()
  • getTotalSpace()

Delete a File

  • file.delete()
  • context.deleteFile(filename)

Saving Data in SQL Databases

Define a Schema and Contract

在 Contract 類中通過實現(xiàn)BaseColumns內部類,可以獲得內部key_ID

Create a Database Using a SQL Helper

SQLiteOpenHelper 用來提供僅在的需要時候可長時間運行的操作(添加/更新數(shù)據(jù)庫),避免在項目運行時就實例化數(shù)據(jù)庫操作類。

  • getWritableDatabase():獲取可寫的database
  • getReadableDatabase():獲取可讀的database

只可在非UI線程調用以上兩種方法,例如AsyncTaskIntentService.

繼承SQLiteOpenHelper,需要重寫onCreate(), onUpgrade(), onOpen(), (可選)onDowngrade().

Put Information into a Database

Insert: Database -> ContentValues -> db.insert(table_name, action_if_content_values_empty, content_values)

Read Information from a Database

Read: Database -> db.query(table_name, columns_to_return, column_where, column_where_value, group, filter, sort_order)

Return: Cursor -> cursor starts at position -1.

  • moveToNext(): position+1
  • getXXX(): 獲取列值
  • getColumnIndex()/getColumnIndexOrThrow(): 獲取當前 position
  • close(): 關閉游標

Delete Information from Database

Delete: Database -> db.delete(table_name, selection, selection_args)

Update a Database

Update: combine insert() & delete() -> db.update(table_name, content_values, selection, selection_args)

Persisting Database Connection

一般在Activity被摧毀時關閉DBHelper -> dbHelper.close()

Interacting with Other Apps

Sending the User to Another App

在與其它的App進行交互時,只能使用implicit intent。

Build an Implicit Intent

定義Action去具體化啟動事件。

  • 使用Uri定義啟動事件:
    • 打開撥號頁面

        Uri number = Uri.parse("tel:5551234");
        Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
      
    • 打開地圖頁面

        Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
        Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
      
    • 打開網(wǎng)頁

        Uri webpage = Uri.parse("http://www.android.com");
        Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
      
    • 使用 extra data 具體化啟動事件:
      setType(): 指定MIME(Multipurpose Internet Mail Extensions) Type

      • 發(fā)送 email

          Intent emailIntent = new Intent(Intent.ACTION_SEND);
          emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
          emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"jon@example.com"});
          emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject");
          emailIntent.putExtra(Intent.EXTAR_TEXT, "Email message text");
          emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"));
        
      • 發(fā)送 calendar 事件

          Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
          Calendar beginTime = Calendar.getInstance().set(2012, 0, 19, 7, 30);
          Calendar endTime = Calendar.getInstance().set(2012, 0, 19, 10, 30);
          calendarIntent.putExrea(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
          calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
          calendarIntent.putExtra(Events.TITLE, "Ninja class");
          calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo);
        

Verify There is an App to Receive the Intent

如果intent聲明的喚起事件并不存在,app將會crash。

  • quertIntentActivities(): 查看可用事件

      PackageManager packageManager = getPackageManager();
      List activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
      boolean isIntentSafe = activities.size() > 0;
    

Start an Activity with the Intent

startActivity(intent)

Show an App Chooser

如果有多個喚起事件存在,需要用戶自行選擇具體的喚起事件,調用createChooser()來調起具體的被選事件。

Intent intent = new Intent(Intent.ACTION_SEND);
String title = getResources().getString(R.string.choose_title);
Intent chooser = Intent.createChooser(intent, title);
if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

Getting a Result from an Activity

使用startActivityForResult()來啟動一個activity并接收返回數(shù)據(jù)。
使用onActivityResult()來處理返回的數(shù)據(jù)

Start the Activity

static final int PICK_CONTACT_REQUEST = 1;
private void pickContact() {
    Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
    pickContactIntent.setType(Phone.CONTENT_TYPE);
    startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
}

Receive the Result

通過 resultCode 來判斷返回類型:

  • RESULT_OK: 操作成功

  • RESULT_CANCELED: 操作取消

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == PICK_CONTACT_REQUEST) {
    if (resultCode == RESULT_OK) {
    //do something...
    }
    }
    }

Allowing Other Apps to Start Your Activity

通過定義支持ACTION_SEND的intent:<intent-filter>.

Add an Intent Filter

intent-filter中定義以下幾種criteria:

  • Action : action 名稱,一般定義為ACTION_XXX格式

  • Data : 與 intent 相關的數(shù)據(jù)描述,可以多重定義:MIME Type / URI prefix / URI scheme / combination.

  • Category : 提供額外的方式描述處理intent的activity,通常與用戶行為或地址相關。一般定義為CATEGORY_DEFAULT.

    <activity android:name="ShareActivity"
    <intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
    <data android:mimeType="image/*" />
    </intent-filter>
    </activity>

必須定義 CATEGORY_DEFAULT 否則implicit intent 無法處理跳轉事件.

Handle the Intent in Your Activity

調用getIntent().
在activity的生命周期的任何時間段都可以調用,但是最好在onCreate() / onStart()中處理。

Return a Result

Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri"));
setResult(Activity.RESULT_OK, result);
finish();

Working with System Permissions

為了保證App的數(shù)據(jù)安全,Android 在每一個有權限控制的沙箱中運行App。

Declaring Permissions

Determine What Permissions Your App Needs

Android 5.1 以下,用戶會在安裝App的時候賦予權限,在Android 6.0 以上,用戶會在App運行時動態(tài)賦予權限。

Add Permissions to the Manifest

manifest屬性下,申請permission使用uses-permission標簽。

Requesting Permissions at Run Time

系統(tǒng)權限分為兩種:normal 和 dangerous:

  • 系統(tǒng)會自動賦予 normal 權限
  • dangerous 權限需要用戶手動授予

在Android 6.0以上,由于權限是動態(tài)授予的,所以需要保證在某些權限不可用時,App依然可以正常運行。

Check for Permissions

ContextCompat.checkSelfPermissions()

int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);
  • PackageManager.PERMISSION_GRANTED = permissionCheck: 權限被授予
  • PackageManager.PERMISSION_DENED = permissionCheck: 權限被拒絕

Request Permissions

最佳實踐:在用戶已經關閉權限時,App運行到需要使用權限才能正常運行的功能時,可以為用戶提供權限解釋。

Explain why the app needs permissions

shouldShowRequestPermissionRationale(): 如果App曾經請求過permission,用戶拒絕了請求,該方法會返回true;
如果App曾經請求過permission,用戶拒絕了請求,且選擇Don't ask again,該方法會返回false;
如果設備安全等級拒絕授予該permission請求,該方法會返回false.

Request the permissions you need

requestPermissions() 用來請求權限。

if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) {
        //Show an explanation    
    } else {
        ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS);
    }    
}

Handle the permissions request response

onRequestPermissionsResult() override 該方法用來查詢permission是否成功申請。

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTE) {
                //Do what you want    
            } else {
                //Do what when permission was denied    
            }
            return;
        }    
    }    
}

Permissions Usage Notes

權限控制準則

  • Consider Using an Intent
  • Only Ask for Permissions You Need
  • Don't Overwhelm the User
  • Explain Why You Need Permissions
  • Test for Both Permissions Models

使用 adb 工具管理權限:

  • 分組列出權限和狀態(tài)

    adb shell pm list permissions -d -g

  • 賦予/禁止一或多個權限

    adb shell pm [grant|revoke] <permission-name> ...

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容