Android入门基础:从这里开始


英文: http://developer.android.com/training/index.html

中文: http://hukai.me/android-training-course-in-chinese/basics/index.html

Intent的发送


验证是否有App去接收这个Intent

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

示例

    public void sendTel(View view) {
        Uri number = Uri.parse("tel:15888888888");
        Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
        startActivity(callIntent);
    }

    public void sendMap(View view) {
        // Build the intent
        Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
        Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);

        // Verify it resolves
        PackageManager packageManager = getPackageManager();
        List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
        boolean isIntentSafe = activities.size() > 0;

        // Start an activity if it's safe
        if (isIntentSafe) {
            startActivity(mapIntent);
        } else {
            Toast.makeText(MainActivity.this, "No Map Apps Found!", Toast.LENGTH_SHORT).show();
        }

    }

    public void sendWeb(View view) {
        Uri webpage = Uri.parse("http://iosdevlog.com");
        Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
        startActivity(webIntent);
    }

    public void sendEmail(View view) {
        Intent emailIntent = new Intent(Intent.ACTION_SEND);
        // The intent does not have a URI, so declare the "text/plain" MIME type
        emailIntent.setType("text/plain");
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"iosdevlog@iosdevlog.com"}); // recipients
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject");
        emailIntent.putExtra(Intent.EXTRA_TEXT, "Email message text");
        emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"));
        // You can also attach multiple items by passing an ArrayList of Uris
        startActivity(emailIntent);
    }

    public void sendCal(View view) {
        Intent calendarIntent = new Intent(Intent.ACTION_INSERT, CalendarContract.Events.CONTENT_URI);
        Calendar beginTime = Calendar.getInstance(TimeZone.getDefault());
        beginTime.set(2016, 9, 20, 10, 20, 30);
        Calendar endTime = Calendar.getInstance(TimeZone.getDefault());
        endTime.set(2016, 9, 20, 10, 30, 30);
        calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
        calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
        calendarIntent.putExtra(CalendarContract.Events.TITLE, "Android Dev Log");
        calendarIntent.putExtra(CalendarContract.Events.EVENT_LOCATION, "Home");
        startActivity(calendarIntent);
    }

Intent 返回结果

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView)findViewById(R.id.textView);
    }

    public void pickContact(View view) {
        Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
        pickContactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
        // 调用此方法可以用 onActivityResult 处理返回结果
        startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
    }

    // 重写些方法处理 Intent 返回结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // Check which request it is that we're responding to
        if (requestCode == PICK_CONTACT_REQUEST) {
            // Make sure the request was successful
            if (resultCode == RESULT_OK) {
                // Get the URI that points to the selected contact
                Uri contactUri = data.getData();
                // We only need the NUMBER column, because there will be only one row in the result
                String[] projection = {ContactsContract.CommonDataKinds.Phone.NUMBER};

                // Perform the query on the contact to get the NUMBER column
                // We don't need a selection or sort order (there's only one result for the given URI)
                // CAUTION: The query() method should be called from a separate thread to avoid blocking
                // your app's UI thread. (For simplicity of the sample, this code doesn't do that.)
                // Consider using CursorLoader to perform the query.
                Cursor cursor = getContentResolver()
                        .query(contactUri, projection, null, null, null);
                cursor.moveToFirst();

                // Retrieve the phone number from the NUMBER column
                int column = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
                String number = cursor.getString(column);

                // Do something with the phone number...
                textView.setText(number);
            }
        }
    }

Android分享操作(Building Apps with Content Sharing)


给ActionBar增加分享功能 - Adding an Easy Share Action

更新菜单声明(Update Menu Declarations)

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_item_share"
        android:showAsAction="ifRoom"
        android:title="Share"
        android:actionProviderClass="android.widget.ShareActionProvider" />
    ...
</menu>

Set the Share Intent(设置分享的intent)

private ShareActionProvider mShareActionProvider;
...

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate menu resource file.
    getMenuInflater().inflate(R.menu.share_menu, menu);

    // Locate MenuItem with ShareActionProvider
    MenuItem item = menu.findItem(R.id.menu_item_share);

    // Fetch and store ShareActionProvider
    mShareActionProvider = (ShareActionProvider) item.getActionProvider();

    // Return true to display menu
    return true;
}

// Call to update the share intent
private void setShareIntent(Intent shareIntent) {
    if (mShareActionProvider != null) {
        mShareActionProvider.setShareIntent(shareIntent);
    }
}

接收从其他App传送来的数据

可以与上个应用互测

过滤

Intent filters告诉Android系统一个程序愿意接受的数据类型。

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

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

        </activity>

处理接受到的数据(Handle the Incoming Content)

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

        textView = (TextView) findViewById(R.id.textView);
        imageView = (ImageView) findViewById(R.id.imageView);

        // Get intent, action and MIME type
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();

        if (Intent.ACTION_SEND.equals(action) && type != null) {
            if ("text/plain".equals(type)) {
                handleSendText(intent); // Handle text being sent
            } else if (type.startsWith("image/")) {
                handleSendImage(intent); // Handle single image being sent
            }
        } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
            if (type.startsWith("image/")) {
                handleSendMultipleImages(intent); // Handle multiple images being sent
            }
        } else {
            // Handle other intents, such as being started from the home screen
        }
    }

    // 处理文本
    void handleSendText(Intent intent) {
        String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
        if (sharedText != null) {
            // Update UI to reflect text being shared
            textView.setText(sharedText);
        }
    }

    // 处理单张图片
    void handleSendImage(Intent intent) {
        Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
        if (imageUri != null) {
            // Update UI to reflect image being shared
            imageView.setImageURI(imageUri);
        }
    }

    // 处理多张图片,暂时不处理
    void handleSendMultipleImages(Intent intent) {
        ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
        if (imageUris != null) {
            // Update UI to reflect multiple images being shared
        }
    }

由于无法知道其他程序发送过来的数据内容是文本还是其他类型的数据,若数据量巨大,则需要大量处理时间,因此我们应避免在UI线程里面去处理那些获取到的数据。

拍照


请求使用相机权限

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

使用相机应用程序进行拍照

static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

获取缩略图

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        mImageView.setImageBitmap(imageBitmap);
    }
}

将照片添加到相册中

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

解码一幅缩放图片

private void setPic() {
    // Get the dimensions of the View
    int targetW = mImageView.getWidth();
    int targetH = mImageView.getHeight();

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    mImageView.setImageBitmap(bitmap);
}

高效显示Bitmap


如何在加载并处理bitmaps的同时保持用户界面响应,防止超出内存限制。

高效的加载大图(Loading Large Bitmaps Efficiently)

如何解析很大的Bitmaps并且避免超出程序的内存限制。

非UI线程处理Bitmap(Processing Bitmaps Off the UI Thread)

处理Bitmap(裁剪,下载等操作)不能执行在主线程。这节课会带领你学习如何使用AsyncTask在后台线程对Bitmap进行处理,并解释如何处理并发带来的问题。

缓存Bitmaps(Caching Bitmaps)

如何使用内存与磁盘缓存来提升加载多张Bitmaps时的响应速度与流畅度。

管理Bitmap的内存使用(Managing Bitmap Memory)

如何管理Bitmap的内存占用,以此来提升程序的性能。

在UI上显示Bitmap(Displaying Bitmaps in Your UI)

这节课会综合之前章节的内容,演示如何在诸如ViewPager与GridView等控件中使用后台线程与缓存加载多张Bitmaps。

如何使用Android app framework绘制OpenGL图形并响应触摸。

如何给你的用户界面添加过渡动画。

View间渐变

学习在重叠View间的淡入淡出。作为一个例子,我们将会学习如何在一个进度条与一个包含了文本内容的View之间实现淡入淡出的效果。

用ViewPager实现屏幕滑动

学习怎样为水平相邻的界面提供滑动动画。

展示Card翻转动画

学习怎样实现两个View之间的翻转动画。

缩放View

学习怎样通过触控放大一个View。

布局变更动画

学习在增加、移除或更新子View时,怎样使用内置的动画效果。

执行网络操作


连接到网络

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

选择一个 HTTP Client, 推荐 HttpURLConnection

检查网络连接

在我们的 app 尝试连接网络之前,应通过函数 getActiveNetworkInfo() 和 isConnected() 检测当前网络是否可用。

public void myClickHandler(View view) {
    ...
    ConnectivityManager connMgr = (ConnectivityManager)
        getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        // fetch data
    } else {
        // display error
    }
    ...
}

在一个单独的线程中执行网络操作

网络操作会遇到不可预期的延迟。为了避免造成不好的用户体验,总是在 UI 线程之外单独的线程中执行网络操作。

AsyncTask 类提供了一种简单的方式来处理这个问题。

  • doInBackground() 执行 downloadUrl() 方法。它以网页的 URL 作为参数,方法 downloadUrl() 获取并处理网页返回的数据。执行完毕后,返回一个结果字符串。
  • onPostExecute() 接收结果字符串并把它显示到 UI 上。

主要处理流程

  1. 当用户点击按钮时调用 myClickHandler(),app 将指定的 URL 传给 AsyncTask 的子类 DownloadWebpageTask。
  2. AsyncTask 的 doInBackground() 方法调用 downloadUrl() 方法。
  3. downloadUrl() 方法以一个 URL 字符串作为参数,并用它创建一个 URL 对象。
  4. 这个 URL 对象被用来创建一个 HttpURLConnection。
  5. 一旦建立连接,HttpURLConnection 对象将获取网页的内容并得到一个 InputStream。
  6. InputStream 被传给 readIt() 方法,该方法将流转换成字符串。
  7. 最后,AsyncTask 的 onPostExecute() 方法将字符串展示在 main activity 的 UI 上。

连接并下载数据

// Given a URL, establishes an HttpUrlConnection and retrieves
// the web page content as a InputStream, which it returns as
// a string.
private String downloadUrl(String myurl) throws IOException {
    InputStream is = null;
    // Only display the first 500 characters of the retrieved
    // web page content.
    int len = 500;

    try {
        URL url = new URL(myurl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000 /* milliseconds */);
        conn.setConnectTimeout(15000 /* milliseconds */);
        conn.setRequestMethod("GET");
        conn.setDoInput(true);
        // Starts the query
        conn.connect();
        int response = conn.getResponseCode();
        Log.d(DEBUG_TAG, "The response is: " + response);
        is = conn.getInputStream();

        // Convert the InputStream into a string
        String contentAsString = readIt(is, len);
        return contentAsString;

    // Makes sure that the InputStream is closed after the app is
    // finished using it.
    } finally {
        if (is != null) {
            is.close();
        }
    }
}

将输入流(InputStream)转换为字符串

InputStream 是一种可读的 byte 数据源。如果我们获得了一个 InputStream,通常会进行解码(decode)或者转换为目标数据类型。

下面会演示如何把 InputStream 转换为字符串,以便显示在 UI 上。

// Reads an InputStream and converts it to a String.
public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
    Reader reader = null;
    reader = new InputStreamReader(stream, "UTF-8");
    char[] buffer = new char[len];
    reader.read(buffer);
    return new String(buffer);
}