Getting Started

Building Your First App

Supporting Different Devices

Building a Dynamic UI with Fragments

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

Creating a Fragment

可以認(rèn)為Fragment是Activity組合的一部分客扎,有自己獨(dú)立的生命周期昼钻,自己的輸入事件燎潮,當(dāng)其所依附的Activity還在運(yùn)行時(shí),可以自由添加或刪除Fragment。

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

Create a Fragment Class

  1. extend Fragment
  2. override key lifecycle methods

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

Add a Fragment to an Activity using XML

FragmentActivity 是用來(lái)支持 API level 11 以下的版本豌汇,如果版本在 11 及以上,則可以使用普通的Activity.

可以通過(guò)在 xml 文件中指定Fragmentname屬性业簿,從而指定特定的Fragment class.

Building a Flexible UI

FragmentManager 類(lèi)提供添加瘤礁、移除、替換fragment的方法梅尤,給用戶(hù)動(dòng)態(tài)的適配體驗(yàn)柜思。

Add a Fragment to an Activity at Runtime

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

在A(yíng)ctivity運(yùn)行時(shí)添加Fragment有一點(diǎn)需要注意:Activity必須包含一個(gè)你可以插入 fragment 的 View.

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

Replace One Fragment with Another

使用 replace() 代替 add().

Best Practice:在進(jìn)行Fragment替換時(shí)巷燥,最好允許用戶(hù)返回或者取消操作:addToBackStack()(在 FragmentTransaction.commit() 之前赡盘,F(xiàn)ragment 將不會(huì)被銷(xiāo)毀,只會(huì)被remove掉).

Communicating with Other Fragments

所有Fragment之間的信息交換都是通過(guò)與其相關(guān)的Activity來(lái)完成缰揪,任何Fragment不應(yīng)該直接交流陨享。

Define an Interface

在 Fragment 中定義一個(gè) interface葱淳,在 Activity 中實(shí)現(xiàn)這個(gè) interface。Fragment 將會(huì)在 onAttach() 中通過(guò)得到 Activity 對(duì)象捕獲這個(gè)實(shí)現(xiàn)抛姑,從而通過(guò) Activity 來(lái)進(jìn)行信息交流赞厕。

Implement the Interface

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

Deliver a Message to a Fragment

Activity 可以通過(guò) findFragmentById() 獲取到 Fragment 實(shí)例,直接調(diào)用 Fragment 的方法定硝。

場(chǎng)景:AFragment 有一堆文章列表皿桑,點(diǎn)擊某個(gè)文章,進(jìn)入 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ù)存儲(chǔ)方式:

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

Saving Key-Value Sets

一個(gè) SharedPreferences 對(duì)象指向一個(gè)包含key-value的文件箱蟆,提供簡(jiǎn)單的方法進(jìn)行讀寫(xiě)沟绪。

Get a Handle to a SharedPreferences

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

  • getSharedPreferences():擁有多個(gè)shared preference文件,通過(guò)傳入文件名獲取對(duì)象空猜,可以從任意的context中獲取绽慈。
  • getPreferences():如果一個(gè)activity只有一個(gè)shared preference文件,通過(guò)這個(gè)方法可以獲取activity對(duì)應(yīng)的SP文件

Write to Shared Preferences

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

Read from Shared Preferences

getInt(), getString, .etc.

Saving Files

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

一個(gè)File對(duì)象適合讀寫(xiě)大數(shù)據(jù)文件久信,從頭到尾沒(méi)有中斷的順序讀取。

Choose Internal or External Storage

所有的Android設(shè)備擁有兩個(gè)文件存儲(chǔ)域:“internal” 和 “external”:

  • Internal Storage
    • 永遠(yuǎn)可用
    • 文件只能被 App 訪(fǎng)問(wèn)
    • 當(dāng) App 被卸載時(shí)漓摩,所有存儲(chǔ)的 internal 的文件都會(huì)被刪除
  • External Storage
    • 不一定可用
    • 可被全局訪(fǎng)問(wèn)
    • 當(dāng) App 被卸載時(shí)裙士,系統(tǒng)只會(huì)刪除特定的文件夾(getExternalFilesDir()

App 會(huì)被默認(rèn)載入到internal中,在代碼中如何設(shè)置下載位置管毙?

在A(yíng)ndroidManifest中:更改android:installLocation

Obtain Permissions for External Storage

在external寫(xiě)文件:需要權(quán)限 android.permission.WRITE_EXTERNAL_STORAGE

在external寫(xiě)文件(in future):需要權(quán)限:android.permission.READ_EXTERNAL_STORAGE

Save a File on Internal Storage

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

Save a File on External Storage

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

  • Public Files: 需要留存 -> create from -> getExternalStoragePublicDirectory()
  • Private Files: 需要?jiǎng)h除 -> create from -> getExternalFilesDir()
  • 文件類(lèi)型: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 類(lèi)中通過(guò)實(shí)現(xiàn)BaseColumns內(nèi)部類(lèi),可以獲得內(nèi)部key_ID卓舵。

Create a Database Using a SQL Helper

SQLiteOpenHelper 用來(lái)提供僅在的需要時(shí)候可長(zhǎng)時(shí)間運(yùn)行的操作(添加/更新數(shù)據(jù)庫(kù))南用,避免在項(xiàng)目運(yùn)行時(shí)就實(shí)例化數(shù)據(jù)庫(kù)操作類(lèi)。

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

只可在非UI線(xiàn)程調(diào)用以上兩種方法掏湾,例如AsyncTaskIntentService.

繼承SQLiteOpenHelper裹虫,需要重寫(xiě)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(): 獲取當(dāng)前 position
  • close(): 關(guān)閉游標(biāo)

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被摧毀時(shí)關(guān)閉DBHelper -> dbHelper.close()

Interacting with Other Apps

Sending the User to Another App

在與其它的App進(jìn)行交互時(shí),只能使用implicit intent融击。

Build an Implicit Intent

定義Action去具體化啟動(dòng)事件筑公。

  • 使用Uri定義啟動(dòng)事件:
    • 打開(kāi)撥號(hào)頁(yè)面

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

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

        Uri webpage = Uri.parse("http://www.android.com");
        Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
      
    • 使用 extra data 具體化啟動(dòng)事件:
      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將會(huì)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

如果有多個(gè)喚起事件存在匣屡,需要用戶(hù)自行選擇具體的喚起事件封救,調(diào)用createChooser()來(lái)調(diào)起具體的被選事件。

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()來(lái)啟動(dòng)一個(gè)activity并接收返回?cái)?shù)據(jù)捣作。
使用onActivityResult()來(lái)處理返回的數(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

通過(guò) resultCode 來(lái)判斷返回類(lèi)型:

  • 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

通過(guò)定義支持ACTION_SEND的intent:<intent-filter>.

Add an Intent Filter

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

  • Action : action 名稱(chēng)誉结,一般定義為ACTION_XXX格式

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

  • Category : 提供額外的方式描述處理intent的activity虾宇,通常與用戶(hù)行為或地址相關(guān)搓彻。一般定義為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 無(wú)法處理跳轉(zhuǎn)事件.

Handle the Intent in Your Activity

調(diào)用getIntent().
在activity的生命周期的任何時(shí)間段都可以調(diào)用如绸,但是最好在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 在每一個(gè)有權(quán)限控制的沙箱中運(yùn)行App怔接。

Declaring Permissions

Determine What Permissions Your App Needs

Android 5.1 以下搪泳,用戶(hù)會(huì)在安裝App的時(shí)候賦予權(quán)限,在A(yíng)ndroid 6.0 以上扼脐,用戶(hù)會(huì)在A(yíng)pp運(yùn)行時(shí)動(dòng)態(tài)賦予權(quán)限岸军。

Add Permissions to the Manifest

manifest屬性下,申請(qǐng)permission使用uses-permission標(biāo)簽瓦侮。

Requesting Permissions at Run Time

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

  • 系統(tǒng)會(huì)自動(dòng)賦予 normal 權(quán)限
  • dangerous 權(quán)限需要用戶(hù)手動(dòng)授予

在A(yíng)ndroid 6.0以上艰赞,由于權(quán)限是動(dòng)態(tài)授予的,所以需要保證在某些權(quán)限不可用時(shí)肚吏,App依然可以正常運(yùn)行方妖。

Check for Permissions

ContextCompat.checkSelfPermissions()

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

Request Permissions

最佳實(shí)踐:在用戶(hù)已經(jīng)關(guān)閉權(quán)限時(shí),App運(yùn)行到需要使用權(quán)限才能正常運(yùn)行的功能時(shí)罚攀,可以為用戶(hù)提供權(quán)限解釋党觅。

Explain why the app needs permissions

shouldShowRequestPermissionRationale(): 如果App曾經(jīng)請(qǐng)求過(guò)permission,用戶(hù)拒絕了請(qǐng)求斋泄,該方法會(huì)返回true;
如果App曾經(jīng)請(qǐng)求過(guò)permission杯瞻,用戶(hù)拒絕了請(qǐng)求,且選擇Don't ask again炫掐,該方法會(huì)返回false;
如果設(shè)備安全等級(jí)拒絕授予該permission請(qǐng)求魁莉,該方法會(huì)返回false.

Request the permissions you need

requestPermissions() 用來(lái)請(qǐng)求權(quán)限。

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 該方法用來(lái)查詢(xún)permission是否成功申請(qǐng)募胃。

@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

權(quán)限控制準(zhǔn)則

  • 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 工具管理權(quán)限:

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

    adb shell pm list permissions -d -g

  • 賦予/禁止一或多個(gè)權(quán)限

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旗唁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摔认,更是在濱河造成了極大的恐慌逆皮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件参袱,死亡現(xiàn)場(chǎng)離奇詭異电谣,居然都是意外死亡秽梅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)剿牺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)企垦,“玉大人,你說(shuō)我怎么就攤上這事晒来〕睿” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵湃崩,是天一觀(guān)的道長(zhǎng)荧降。 經(jīng)常有香客問(wèn)我,道長(zhǎng)攒读,這世上最難降的妖魔是什么朵诫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮薄扁,結(jié)果婚禮上剪返,老公的妹妹穿的比我還像新娘。我一直安慰自己邓梅,他們只是感情好脱盲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著日缨,像睡著了一般钱反。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殿遂,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天诈铛,我揣著相機(jī)與錄音,去河邊找鬼墨礁。 笑死幢竹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恩静。 我是一名探鬼主播焕毫,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼驶乾!你這毒婦竟也來(lái)了邑飒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤级乐,失蹤者是張志新(化名)和其女友劉穎疙咸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體风科,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡撒轮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年乞旦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片题山。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兰粉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顶瞳,到底是詐尸還是另有隱情玖姑,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布慨菱,位于F島的核電站焰络,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏抡柿。R本人自食惡果不足惜舔琅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望洲劣。 院中可真熱鬧,春花似錦课蔬、人聲如沸囱稽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)战惊。三九已至,卻和暖如春扎即,著一層夾襖步出監(jiān)牢的瞬間吞获,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工谚鄙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留各拷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓闷营,卻偏偏與公主長(zhǎng)得像烤黍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傻盟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容