Miui Note源码解析

本文深入解析MIUI笔记应用的源码,涉及AndroidManifest.xml中的配置,如NotesListActivity的singleTop启动模式,NotesProvider的multiprocess属性,AlarmInitReceiver对开机广播的监听,AlarmReceiver在remote进程的运行,以及AlarmAlertActivity的singleInstance模式。同时讨论了ContentProvider的android:exported属性和AsyncQueryHandler在异步查询中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们先从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,退出此状态后消失。
左右对比就能看出来了:
这里写图片描述
官方介绍
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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值