Build Apps with Content Sharing

如何创建apps与设备之间共享数据的app.

Sharing Simple Data

使用IntentActionProvider在app之间收发简单数据。

Send Simple Data to Other Apps

当你构造一个intent时,你必须确认你想让intent来trigger什么行为.

Android 定义了几种行为,其中有一种ACTION_SEND,用来在activity之间传递数据,甚至在进程之间也可以。

Note 最佳实践是使用ShareActionProvider(Lesson -- Adding an Easy Share Action)添加action到ActionBar中(> API 14).

Send Text Content

Talk is cheap, I will show you the code.

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send");
sendIntent.setType("text/plain");
startActivity(sendIntent);

如果有多个App接收到该intent,系统会自动显示一个可选的dialog,当然,你可以调用Intent.createChooser(),在任何版本任何情况下都会显示选择框。优点:

  • 即使用户之前选择了默认的action,选择框还是会显示

  • 如果没有App match到,Android会提示系统消息

  • 你可以自定义选择框的title

    startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to));

作为可选项,你也可以为intent设置一些基本的extra信息:EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC, EXTRA_SUBJECT.

Note 一些e-mail应用(例如Gmail),会期望添加String[]作为extra.

Send Binary Content

使用ACTION_SENDEXTRA_STREAM(URI)结合来发送二进制内容。

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);
shareIntent.setType("image/jpeg");
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));

Note

  • 可以使用*/* MIME类型,但只能match到处理普通数据流的receiver.

  • 接收方需要权限来访问Uri指向的数据。这里有两种比较推荐的解决办法:

    • 创建app自身的ContentProvider,确保其他app可以访问你的provider:使用per-URI permissions -- 短期将权限授予给接收方.创建一个ContentProvider的简单方法是使用FileProvider的帮助类.
    • 使用系统MediaStore. 主要包含video, audio, image MIME 类型(在Android 3.0 以下还会包含一些非媒体类型). 文件可被插入到MediaStore中:使用scanFile(),然后将适合共享的Uri(content://)传递给onScanCompleted()回调.

Send Multiple Pieces of Content

使用ACTION_SEND_MULTIPLE action来发送多块数据(多条指向content的uri).
MIME 类型match你共享的所有文件.

ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris.add(imageUri1);
imageUris.add(imageUri2);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
shareIntent.setType("image/*");
startActivity(Intent.createChooser(shareIntent, "Share images to.."));

Receiving Simple Data from Other Apps

Update Your Manifest

Intent filters告诉系统App可以接受怎样的intent事件.

<activity android:name=".ui.MyActivity" >
    <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_MULTIPLE" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="image/*" />
    </intent-filter>
</activity>

Handle the Incoming Content

void onCreate(Bundle savedInstanceState) {
    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);    
        } else if (type.startsWith("image/")) {
            handleSendImage(intent);    
        }
    } else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
        if (type.startsWith("image/")) {
            handleSendMultipleImages(intent);    
        } else {
            //Handle other intents    
        }   
    }
}

void handleSendText(Intent intent) {
    String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    if (sharedText != null) {
        //Update UI    
    }
}

void handleSendImage(Intent intent) {
    Uri imageUri = (Uri) intent.getParcelbleExtra(Intent.EXTRA_STREAM);
    if (imageUri != null) {
        //Update    
    }
}

void handleSendMultipleImages(Intent intent) {
    ArrayList<Uri> imageUris = intent.getParcelbleArrayList(Intent.EXTRA_STREAM);
    if (imageUris != null) {
        //Update    
    }
}

Adding an Easy Share Action

使用 ShareActionProvider 在ActionBar上添加share action(> Android 4.0).

Update Menu Declarations

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

Set the Share Intent

使用ShareActionProvider,必须为其提供一个intent,需要先调用MenuItem.getActionProvider()来取回ShareActionProvider实例,再调用setShareIntent()为其指定intent.

private ShareActionProvider shareActionProvider;

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

    MenuItem item = menu.findItem(R.id.menu_item_share);

    shareActionProvider = (ShareActionProvider) item.getActionProvider();
    return true;
}

private void setShareIntent(Intent shareIntent) {
    if (shareActionProvider != null) {
        shareActionProvider.setShareIntent(shareIntent);    
    }    
}

Sharing Files

共享文件:提供App文件的URI给接收方,并给予短暂的可读/可写权限.
FileProvider => getUriForFile() => 生成文件的URI

共享较小的text/numeric数据:发送包含数据的Intent.

Setting Up File Sharing

FileProviderv4 Support Library 的一部分.

Specify the FileProvider

在配置文件中定义 FileProvider:

<application
    ...>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.example.myapp.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>
    ...
</application>
  • android:authorities: FileProvider 生成的content URIs

Specify Sharable Directories

一旦在配置文件中添加了FileProvider,就需要指定一个目录来放置你想共享的文件:

在res/xml中创建filepaths.xml.

<paths>
    <files-path path="images/" name="myimages" />
</paths>
  • files-path
  • external-path: share directories in external storage
  • cache-path: share directories in internal storage

所有准备工作做完以后,FileProvider会生成固定格式的访问URI:
content://com.example.myapp.fileprovider/myimages/<filename>

Sharing a File

从server app提供文件选择接口,以便让其他app可以唤起.

Receive File Requests

如果收到文件访问请求,你的app应该提供一个文件选择Activity. 请求方通过调用 startActivityForResult() (包含ACTION_PICK的Intent)启动该Activity.

Create a File Selection Activity

<intent-filter>

  • action: ACTION_PICK
  • category: CATEGORY_DEFAULT & CATEGORY_OPENABLE
  • data: mimeType

Define the file selection Activity in code

定义Activity来展示你的app中files/images的文件,并允许用户点击想要的文件.

public class MainActivity extends Activity {
    private File mPrivareRootDir;
    private File mImagesDir;
    File[] mImageFiles;
    String[] mImageFilenames;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mResultIntent = new Intent("com.example.myapp.ACTION_RETURN_FILE");
        mPrivateRootDir = getFilesDir();
        mImagesDir = new File(mPrivateRootDir, "images");
        mImageFiles = mImageDir.listFiles();
        setResult(Activity.RESULT_CANCELED, null);
    }
}

Respond to a File Selection

如果用户选择了文件,你的app必须考虑为选中的文件提供URI.

因为Android 6.0以上只支持运行时授予权限,所以需要避免使用Uri.fromFile():

  • 不支持文件共享
  • 你的app需要获得WRITE_EXTERNAL_STORAGE权限(ANDROID 4.4 及以下)
  • 接收文件的app需要有READ_EXTERNAL_STORAGE 权限

onItemClick() => File Object => call getUriFromFile().

    protected void onCreate(Bundle savedInstanceState) {
        mFileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                        View view,
                        int position,
                        long rowId) {
                File requestFile = new File(mImageFilename[position]);
                try {
                    fileUri = FileProvider.getUriForFile(
                            MainActivity.this,
                            "com.example.myapp.fileprovider",
                            requestFile);    
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector", 
                            "The selected file can't be shared: " + 
                            clickedFileName);    
                }
            }
        });    
    }

Grant Permissions for the File

通过给 Intent 设置permission flags赋予读取文件的权限.

protected void onCreate(Bundle savedInstanceState) {
    mFileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
                View view,
                int position,
                long rowId) {
            ...
            if (fileUri != null) {
                mResultIntent.addFlags(
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);    
            }    
        }
    });    
}

避免使用Context.grantUriPermission():只能使用Context.revokeUriPermission()收回权限

Share the File with the Requesting App

setResult

protected void onCreate(Bundle savedInstanceState) {
    ...
    mFileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
                View view,
                int position,
                long rowId) {
            ...
            if (fileUri != null) {
                ...
                mResultIntent.setDataAndType(
                    fileUri,
                    getContentResolver().getType(fileUri));
                MainActivity.this.setResult(Activity.RESULT_OK,
                    mResultIntent);
            } else {
                mResultIntent.setDataAndType(null, "");
                MainActivity.this.setResult(Activity.RESULT_CANCELED,
                    mResultIntent);
            }
        }
    });
}

Requesting a Shared File

Send a Request for the File

从server app请求文件数据的方式一般为:startActivityForResult() + Intent(包含actionMIME type).

public class MainActivity extends Activity {
    private Intent mRequestFileIntent;
    private ParcelFileDescriptor mInputPFD;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRequestFileIntent = new Intent(Intent.ACTION_PICK);
        mRequestFileIntent.setType("image/jpg");
        ...
    }
    ...
    protected void requestFile() {
        /**
         * When the user requests a file, send an Intent to the server app files.
        **/    
        startActivityForResult(mRequestFileIntent, 0);
        ...
    }
    ...
}

Access the Requested File

override onActivityResult() 来处理接收文件,一旦客户端app有了文件的content URI,可以通过获取其FileDescriptor来处理文件。

只有在server app赋予了访问权限,client app获取到文件访问入口,文件才可被处理。由于权限是临时的,所以一旦client app的任务栈结束,文件不再可被外部访问。

@Override
public void onActivityResult(int requestCode, int resultCode, Intent returnIntent) {
    if (resultCode != RESULT_OK) {
        return;    
    } else {
        Uri returnUri = returnIntent.getData();
        try {
            mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r");    
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Log.e("MainActivity", "File not found.");
            return;
        }

        FileDescriptor fd = mInputPFD.getFileDescriptor();
        ...
    }
}

Retrieving File Information

使用FileProvider来获取文件的类型和大小。

Retrieve a File's MIME Type

调用ContentResolver.getType()获取文件的数据类型(MIME)。一般地,FileProvider定义文件的MIME类型为其文件后缀。

Uri returnUri = returnIntent.getData();
String mimeType = getContentResolver().getType(returnUri);

Retrieve a File's Name and Size

FileProvider 有默认的query()实现:返回一个Cursor对象,用来查询含有文件名称和大小的content URI.

默认的实现中有两列:

  • DISPLAY_NAME: 文件名称,和File.getName()返回的数据一致

  • SIZE: 文件大小(long),和File.length()返回的数据一致

      Uri returnUri = returnIntent.getData();
      Cursor returnCursor = getContentResolver().query(returnUri, null, null, null, null);
      int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
      int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
      returnCursor.moveToFirst();
      TextView nameView = (TextView) findViewById(R.id.filename_Text);
      TextView sizeView = (TextView) findViewById(R.id.filesize_text);
      nameView.setText(returnCursor.getString(nameIndex));
      sizeView.setText(returnCursor.getString(sizeIndex));
    

Sharing Files with NFC

使用Android Beam(Android 自己的一个app,仅支持Android 4.0以上) 文件传输功能传输较大的文件.

虽然Android Beam 传输API处理大量的数据,但是Android 4.0中引入的Android Beam NDFF 传输API只能处理少量数据.

Sending Files to Another Device

使用Android Beam传输大型文件. 完成功能之前,需要申请使用NFC和外部存储的权限,测试你的设备是否支持NFC,提供URI给Android Beam文件传输.

Android Beam文件传输功能有以下需求:

  • Android 4.1 以上
  • 传输文件必须在外部存储中
  • 所传输的文件必须是全局可读的. => 调用File.setReadable(true, false)来设置
  • 必须提供文件的URI. Android Beam 文件传输不能处理通过FileProvider.getUriForFile生成的URI

Declare Features in the Manifest

Request Permissions

  • NFC: <uses-permission android:name="android.permission.NFC" />
  • READ_EXTERNAL_STORAGE: <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Specify the NFC feature

<manifest>下添加<uses-feature>标签,并设置android:required="true" => 声明如果没有NFC,app将无法工作.

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

Test for Android Beam File Transfer Support

如果没有NFC,你的app也可以工作,则设置required="false".

测试是否支持Android Beam文件传输:PackageManager.hasSystemFeature(FEATURE_NFC),然后检查Android版本是否在4.1以上.

如果支持:获取NFC控制器的实例(和NFC硬件进行交互).

public class MainActivity extends Activity {
    NfcAdapter mNfcAdapter;

    boolean mAndroidBeamAvailable = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
            //Disable NFC feature
        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            mAndroidBeamAvailable = false;    
        } else {
            mNfcAdapter = NfcAdapter.getDefaultAdapter(this);    
        }
    }
}

Create a Callback Method that Provides Files

Android Beam文件传输检测到用户想要给其他的NFC设备传输文件时,系统调用自定义的callback. 在这个callback方法中,返回一个Uri对象数组. Android Beam文件传输将这些文件传输给接收方.

实现NfcAdapter.CreateBeamUrisCallback 接口以及其方法createBeamUris().

public class MainActivity extends Activity {
    private Uri[] mFileUris = new Uri[10];

    private class FileUriCallback implements NfcAdapter.CreateBeamUrisCallback {
        public FileUriCallback() {} 

        @Override
        public Uri[] createBeamUri(NfcEvent event) {
            return mFileUris;    
        }
    }
}

实现以后,通过setBeamPushUrisCallback()激活该callback.

public class MainActivity extends Activity {
    private FileUriCallback mFileUriCallback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        mFileUriCallback = new FileUriCallback();
        mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback, this);
    }
}

你也可以直接提供一组uri给NfcAdapter:NfcAdapter.setBeamPushUris()

Specify the Files to Send

private Uri[] mFileUris = new Uri[10];
String transferFile = "transferimage.jpg";
File extDir = getExternalFileDir(null);
File requestFile = new File(extDir, transferFile);
requestFile.setReadable(true, false);
fileUri = Uri.fromFile(requestFile);
if (fileUri != null) {
    mFileUris[0] = fileUri;    
} else {
    Log.e("My Activity", "No File URI available for file.");    
}

Receiving Files from Another Device

使用Android Media Scanner查看文件,使用MediaStoreprovider为媒体文件添加内容.

Respond to a Request to Display Data

当Android Beam文件传输完毕,会发送一个包含ACTION_VIEW和MIME Type的Intent.
接收者需要定义<intent-filter>来接收对应的唤起事件:

  • <action android:name="android.intent.action.VIEW" />
  • <category android:name="android.intent.category.CATEGORY_DEFAULT" />
  • <data android:mimeType="mime-type" />

示例

<activity
    android:name="com.example.android.nfctransfer.ViewActivity"
    android:label="Android Beam Viewer" >
    ...
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        ...
    </intent-filter>
</activity>

Request File Permissions

权限申请:

  • 读:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • 写:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Get the Directory for Copied Files

Android Beam一次性传输多个文件,传输结束调用Intent的URI指向第一个文件. 然而,你的app可能也会接收到来自其他文件传输的intent(ACTION_VIEW). 为了处理接收时间,你需要检查其scheme和authority.

public class MainActivity extends Activity {
    private File mParentPath;
    private Intent mIntent;

    private void handleViewIntent() {
        mIntent = getIntent();
        String action = mIntent.getAction();

        if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
            Uri beamUri = mIntent.getData();
            if (TextUtils.equals(beamUri.getScheme(), "file") {
                mParentPath = handleFileUri(beamUri);    
            } else if (TextUtils.equals(beamUri.getScheme(), "content")) {
                mParentPath = handleContentUri(beamUri);
            }
        }
    }
}

Get the directory from a file URI

如果接收到的intent包含文件的URI,该URI包含文件的绝对路径和文件名称.

public String handleFileUri(Uri beamUri) {
    String fileName = beamUri.getPath();
    File copiedFile = new File(fileName);
    return copiedFile.getParent();
}

Get the directory from a content URI

如果接收到的intent包含内容的URI,该URI指向存储在MediaStore的内容提供者(指向文件夹和文件名称).你可以通过测试URI的认证值来检测MediaStore的content URI.

你也可以通过接收ACTION_VIEWintent,包含content URI(除MediaStore之外的content provider).这种情况下,content URI不包含MediaStore权限值,并且content URI通常不指向目录。

Determine the content provider

调用Uri.getAuthority()获取URI的认证级别:

  • MediaStore.AUTHORITY: 该URI用于由MediaStore追踪的一个或多个文件.从MediaStore检索完整的文件名,并从文件名获取目录.
  • Any other authority value: 来自其他content provider的content URI. 只可以显示该文件,不能获取文件目录.

为了获取MediaStore的content URI,通过过滤条件:收到的content URI(Uri)和MediaColumns.DATA(projection)查找对应的目标.返回的Cursor对象包含所有的传输文件的完整路径和文件名称.

public String handleContentUri(Uri beamUri) {
    int filenameIndex;
    File copiedFile;
    String fileName;

    if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY) {
        //other content provider    
    } else {
        String[] projection = { MediaStore.MediaColumns.DATA };
        Cursor pathCursor = getContentResolver().query(beamUri, projection, null, null, null);
        if (pathCursor != null && pathCursor.moveToFirst()) {
            filenameIndex = pathCursor.getColumnIndex(MediaStore.MediaColumns.DATA);
            fileName = pathCursor.getString(filenameIndex);
            copiedFile = new File(fileName);
            return new File(copiedFile.getParent());
        }
    } else {
        return null;    
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,537评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,187评论 0 17
  • Android7.0发布已经有一个多月了,Android7.0在给用户带来一些新的特性的同时,也给开发者带来了新的...
    东经315度阅读 1,307评论 0 14
  • NFC 基础 本文档介绍了在Android上的基本的NFC任务。它说明了如何发送和接收的NDEF消息(NDEF m...
    张云飞Vir阅读 3,184评论 0 52
  • 首先要清楚你到底想要过什么样的生活,你要为之付出什么?你想过的生活需要多少钱,也就是你的工资要达到什么程...
    释莲花阅读 365评论 0 1