Miui Note源码解析

我们先从AndroidManifest.xml中的代码看起。

<?xml version="1.0" encoding="utf-8"?>

<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.micode.notes" android:versionCode="1" android:versionName="0.1" >

    <uses-sdk android:minSdkVersion="14" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application  android:icon="@drawable/icon_app" android:label="@string/app_name" >
        <activity  android:name=".ui.NotesListActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/app_name" android:launchMode="singleTop" android:theme="@style/NoteTheme" android:uiOptions="splitActionBarWhenNarrow" android:windowSoftInputMode="adjustPan" >

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

        <activity  android:name=".ui.NoteEditActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:launchMode="singleTop" android:theme="@style/NoteTheme" >

            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/text_note" />
                <data android:mimeType="vnd.android.cursor.item/call_note" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.INSERT_OR_EDIT" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/text_note" />
                <data android:mimeType="vnd.android.cursor.item/call_note" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.intent.action.SEARCH" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>

            <meta-data  android:name="android.app.searchable" android:resource="@xml/searchable" />
        </activity>

        <provider  android:name="net.micode.notes.data.NotesProvider" android:authorities="micode_notes" android:multiprocess="true" />

        <receiver  android:name=".widget.NoteWidgetProvider_2x" android:label="@string/app_widget2x2" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
                <action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
            </intent-filter>

            <meta-data  android:name="android.appwidget.provider" android:resource="@xml/widget_2x_info" />
        </receiver>
        <receiver  android:name=".widget.NoteWidgetProvider_4x" android:label="@string/app_widget4x4" >

            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
                <action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
            </intent-filter>

            <meta-data  android:name="android.appwidget.provider" android:resource="@xml/widget_4x_info" />
        </receiver>

        <receiver android:name=".ui.AlarmInitReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <receiver  android:name="net.micode.notes.ui.AlarmReceiver" android:process=":remote" >
        </receiver>

        <activity  android:name=".ui.AlarmAlertActivity" android:label="@string/app_name" android:launchMode="singleInstance" android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
        </activity>

        <activity  android:name="net.micode.notes.ui.NotesPreferenceActivity" android:label="@string/preferences_title" android:launchMode="singleTop" android:theme="@android:style/Theme.Holo.Light" >
        </activity>

        <service  android:name="net.micode.notes.gtask.remote.GTaskSyncService" android:exported="false" >
        </service>

        <meta-data  android:name="android.app.default_searchable" android:value=".ui.NoteEditActivity" />
    </application>
</manifest>

.ui.NotesListActivity:我们需要知道几点:1.launchMode为singleTop(栈顶复用)。2.它为MAIN Activity。

栈顶复用
这种模式下,如果新的Activity已经位于任务栈的栈顶,此Activity不会被重新创建(onCreate,onStart不会被系统调用),同时它的onNewIntent方法会被回调。通过此方法的参数我们可以取出当前请求的信息。例如任务栈当前为ABCD,D在栈顶,当再次启动D时,若D的启动模式为singleTop,栈内情况依然为ABCD。如果D的启动模式为standard,那么栈内情况为ABCDD。

net.micode.notes.data.NotesProvider:需要知道:1.android:multiprocess=”true”。

关于android:multiprocess的官方解释
Whether or not an instance of the content provider can be created in every client process — “true” if instances can run in multiple processes, and “false” if not. The default value is “false”.
Normally, a content provider is instantiated in the process of the application that defined it. However, if this flag is set to “true”, the system can create an instance in every process where there’s a client that wants to interact with it, thus avoiding the overhead of interprocess communication.(避免了进程间通信)

.ui.AlarmInitReceiver: Android手机开机后,会发送android.intent.action.BOOT_COMPLETED广播,通过监听这个广播就能监听开机。

.ui.AlarmReceiver:通过设置android:process=”:remote”,使该Receiver运行在remote进程上。

任务栈分为前台任务栈后台任务栈,后台任务栈中的存放的是暂停状态下的activity,前台任务栈就是当下正在操作的任务栈啦!

.ui.AlarmAlertActivity:注意android:launchMode=”singleInstance”。(单实例模式)。

单实例模式:一种加强的singleTask模式,具有此种模式的Activity只能单独位于一个任务栈中。

net.micode.notes.gtask.remote.GTaskSyncService:其android:exported=”false”。

android:exported
官方文档说明:
Whether or not components of other applications can invoke the service or interact with it — “true” if they can, and “false” if not. When the value is “false”, only components of the same application or applications with the same user ID can start the service or bind to it.
The default value depends on whether the service contains intent filters. The absence of any filters means that it can be invoked only by specifying its exact class name. This implies that the service is intended only for application-internal use (since others would not know the class name). So in this case, the default value is “false”. On the other hand, the presence of at least one filter implies that the service is intended for external use, so the default value is “true”.

This attribute is not the only way to limit the exposure of a service to other applications. You can also use a permission to limit the external entities that can interact with the service (see the permission attribute).

在BackupUtils中,我们有下列代码:

//定义选择返回哪些列
private static final String[] NOTE_PROJECTION = {
                NoteColumns.ID,
                NoteColumns.MODIFIED_DATE,
                NoteColumns.SNIPPET,
                NoteColumns.TYPE
        };
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
                    NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
                        folderId
                    }, null);
String noteId = notesCursor.getString(notesCursor.getColumnIndex(NoteColumns.ID));
long time = notesCursor.getLong(notesCursor.getColumnIndex(NoteColumns.MODIFIED_DATE));

上面写法是一般的写法,且当projection为null时,要想获得对应的index,只有这种写法,不能用下列写法。
当projection不为null时,上面的cursor的get方法可以替换成下列:

private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
long time = notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE);

注意,此处的NOTE_COLUMN_ID ,NOTE_COLUMN_MODIFIED_DATE 的数值必须跟NOTE_PROJECTION中出现的顺序一一对应。
这说明,query调用后,返回projection对应的数据后index进行了重新排列,按照projection的顺序从0开始排序了,不再为原来的index。

AsyncQueryHandler:
A helper class to help make handling asynchronous ContentResolver queries easier.

关于他的示例使用:

// MainActivity.onCreate()

// AsyncQueryHandler object

AsyncQueryHandler queryHandler = new AsyncQueryHandler(getContentResolver()) {
    @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
        int idIndex = cursor.getColumnIndex(UserDictionary.Words._ID);
        int wordIndex = cursor.getColumnIndex(UserDictionary.Words.WORD);
        int localeIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);

        if (cursor == null) {
            // Some providers return null if an error occurs whereas others throw an exception
        }
        else if (cursor.getCount() < 1) {
            // No matches found
        }
        else {

            while (cursor.moveToNext()) {
                int id = cursor.getInt(idIndex);
                String word = cursor.getString(wordIndex);
                String locale = cursor.getString(localeIndex);

                // Dumps "ID: 1 Word: NewWord Locale: en_US"
                // I added this via Settings > Language & Input > Personal Dictionary
                Log.d(TAG, "ID: " + id + " Word: " + word + " Locale: " + locale);
            }

        }
    }
};


// Construct query and execute

// "projection" defines the columns that will be returned for each row
String[] projection = {
        UserDictionary.Words._ID,       // Contract class constant for the _ID column
        UserDictionary.Words.WORD,      // Contract class constant for the word column
        UserDictionary.Words.LOCALE     // Contract class constant for the locale column
};

// Defines WHERE clause columns and placeholders
String selectionClause = UserDictionary.Words._ID + " = ?";

// Define the WHERE clause's placeholder values
String[] selectionArgs = { "1" };

queryHandler.startQuery(
        1, null,
        UserDictionary.Words.CONTENT_URI,
        projection,
        selectionClause,
        selectionArgs,
        UserDictionary.Words.DEFAULT_SORT_ORDER // "frequency DESC"
);

关于ActionMode,用它替换掉Menu。它可以临时占据ActionBar,退出此状态后消失。
左右对比就能看出来了:
Miui Note源码解析_第1张图片
官方介绍
Represents a contextual mode of the user interface. Action modes can be used to provide alternative interaction modes and replace parts of the normal UI until finished. Examples of good action modes include text selection and contextual actions.

你可能感兴趣的:(源码,miui)