• Android – 学习操作NFC – 2



    在<Android – 学习操作NFC – 1>说明了Android在处理NFC tag的机制、tag dispatch system的运作流程,以及三种
    ACTION_NDEF_DISCOVERED、ACTION_TECH_DISCOVERED与ACTION_TAG_DISCOVERED的处理方式与intent filter注册方法。
    该篇主要针对如何处理ACTION_NDEF_DISCOVERED的Reader、Writer进行说明。
    首先说明如何撰写常用的NDEF Records:
    〉Creating Common Types of NDEF Records:
    上一篇介绍简单Reader的方式,该篇先由Write tag开始。因此,如果今天应用程序使用Android 4.0(API Level 14)使用createUri()
    的方法帮助自动建立一个URI records;使用Android 4.1(API level 16)则可透过createExternal()与createMime()帮助建立MIME与
    external type的NDEF records。藉由使用这些方法以协助建立NDEF records。
    以下便介绍操作NDEF message的第一个Record,来写入资料至NFC tag或Beaming。
    A. TNF_ABSOLUTE_URI:
    建议使用RTD_URI类型取代TNF_ABSOLUTE_URI,因为RTD_URI是更有效的。
    Write:建立一个TNF_ABSOLUTE_URI的Ndef record:
    // 建立个NdefRecord,指定type与payload
    NdefRecord uriRecord = new NdefRecord(
     NdefRecord.TNF_ABSOLUTE_URI ,
     " http://developer.android.com/index.html ".getBytes(Charset.forName("US-ASCII")),
     new byte[0],
     new byte[0]);
    Read:定义intent filter取得Ndef record:
    <intent-filter>
     <action android:name="android.nfc.action.NDEF_DISCOVERED" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:scheme="http"
     android:host="developer.android.com"
     android:pathPrefix="/index.html" />
    </intent-filter>

    注册的Intent Filter只有在tag dispatch system侦测到对应的Ndef tag时才会触发。
    B. TNF_MIME_MEDIA:
    Write:
    (1) 建立一个TNF_MIME_MEDIA Ndef record:使用createMime()方法;(但仅在Android 4.1(API Level 16)以后才支援)
    // 指定MIME类型,再将内容转成byte[]
    NdefRecord mimeRecord = NdefRecord.createMime(
     "application/vnd.com.example.android.beam",
     "Beam me up, Android".getBytes(Charset.forName("US-ASCII")));


    (2) 建立一个TNF_MIME_MEDIA:使用NdefRecord对象:
    // 指定Type、MIME Type与payload

    NdefRecord mimeRecord = new NdefRecord(
     NdefRecord.TNF_MIME_MEDIA ,
     "application/vnd.com.example.android.beam".getBytes(Charset.forName("US-ASCII")),
     new byte[0],
     "Beam me up, Android!".getBytes(Charset.forName("US-ASCII")));


    Read:读取MIME:

    <intent-filter>
     <action android:name="android.nfc.action.NDEF_DISCOVERED" />
     <category android:name="android.intent.category.DEFAULT" />
     <!-- 定义要处理的MIME Type -->
     <data android:mimeType="application/vnd.com.example.android.beam" />
    </intent-filter>


    C. TNF_WELL_KNOWN with RTD_TEXT:
    Write:建立一个TNF_WELL_KNOWN的Ndef record:

    public NdefRecord createTextRecord(String payload, Locale locale, boolean encodeInUtf8) {
     // 取得默认的编码格式
     byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
     // 准备转换成UTF-8的编码
     Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
     // 将内容依默认编码转成byte[]
     byte[] textBytes = payload.getBytes(utfEncoding);
     // 往下做字符转换的位移
     int utfBit = encodeInUtf8 ? 0 : (1 << 7);
     char status = (char) (utfBit + langBytes.length);
     byte[] data = new byte[1 + langBytes.length + textBytes.length];
     data[0] = (byte) status;
     System.arraycopy(langBytes, 0, data, 1, langBytes.length);
     System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
     // 建立TNF_WELL_KNOWN的Ndef record
     NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
     NdefRecord.RTD_TEXT, new byte[0], data);
     return record;
    }


    Read:定义intent filter取得数据:

    <intent-filter>
     <action android:name="android.nfc.action.NDEF_DISCOVERED" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:mimeType="text/plain" />
    </intent-filter>


    D. TNF_WELL_KNOWN with RTD_URI:
    Write:建立TNF_WELL_KNOWN Ndef record,内容有RTD_URI;其方式与建立RTD_URI相似有分成二个:
    (1) 建立URI有二个方式,一个由String –> URI;另一个是直接用URI对象,如下:

    NdefRecord rtdUriRecord1 = NdefRecord.createUri(" http://example.com ");
    // 上下为相同效果
    Uri uri = new Uri(" http://example.com ");
    NdefRecord rtdUriRecord2 = NdefRecord.createUri(uri);
    byte[] uriField = "example.com".getBytes(Charset.forName("US-ASCII"));
    //add 1 for the URI Prefix
    byte[] payload = new byte[uriField.length + 1];
    //prefixes http://www. to the URI
    byte payload[0] = 0x01;
    //appends URI to payload
    System.arraycopy(uriField, 0, payload, 1, uriField.length);
    NdefRecord rtdUriRecord = new NdefRecord(
     NdefRecord.TNF_WELL_KNOWN,
     NdefRecord.RTD_URI,
     new byte[0],
     payload);


    Read:定义要处理的intent filter:

    <intent-filter>
     <action android:name="android.nfc.action.NDEF_DISCOVERED" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:scheme="http"
     android:host="example.com"
     android:pathPrefix="" />
    </intent-filter>


    E. TNF_EXTERNAL_TYPE:
    Write:使用createExternal()方法;或使用NdefRecord的方法手动建立;

    //assign to your data
    byte[] payload;
    //usually your app's package name
    String domain = "com.example";
    String type = "externalType";
    // 指定domain, type, payload
    NdefRecord extRecord = NdefRecord.createExternal(domain, type, payload);
    byte[] payload;
    ...
    // 注意写入的格式为: {domain}:{type}
    NdefRecord extRecord = new NdefRecord(
     NdefRecord.TNF_EXTERNAL_TYPE,
     "com.example:externalType",
     new byte[0],
     payload);


    Read:注册要处理的intent filter:

    <intent-filter>
     <action android:name="android.nfc.action.NDEF_DISCOVERED" />
     <category android:name="android.intent.category.DEFAULT" />
     <data android:scheme="vnd.android.nfc"
     android:host="ext"
    android:pathPrefix="/com.example:externalType"/>
    </intent-filter>


    使用TNF_EXTERNAL_TYPE是比较好用于一般的NFC tag,以支持Android或非Android系统可以读取到这些Tag。
    另外,要注意TNF_EXTERNAL_TYPE的URNs定义格式,如下:「urn:nfc:ext:example.com:externalType」;
    根据NFC Forum RTD规格宣告[urn:nfc:ext]在某些Ndef message会被省略掉,因此,需要额外定义 domain (例如:example.com)与
    type (例如:externalType)。当dispatching TNF_EXTERNAL_TYPE时,Android转换 run:nfc:ext:example.com:externalType URN为
    vnd.andorid.nfc://ext/example.com:externalType的URI,所以在定义intent filter时,在scheme为:vnd.android.nfc;host为ext;
    patchPrefix为/example.com:externalType。
    以上介绍了几个常用Ndef message与Ndef record type的撰写方式,需注意的是在Read的部分,在应用程序尚未被启动时,
    如果系统侦测到有Ndef Tag,它会发出对应的Intent,让有注册Filter intent接收到这个intent,进一步让用户选择要执行的
    应用程序。因此,如果应用程序本身支持多种不同的Filter intent均要记得加上去。
    往下针对程序面说明要将上述的内容怎么发布给device或NFC tag。需要有那些重要的类别来加以完成。
    参考< http://nfc.android.com/> 中的<StickyNotes sample code>范例来加以说明:
    步骤0-1:建立项目,指定使用<uses-sdk />要大于等于10,并且加入必要的<uses-permission />;

    <uses-sdk android:minSdkVersion="10" />
    <uses-permission android:name="android.permission.NFC"></uses-permission>


    步骤0-2:标记默认应用程序要处理的Intent Filter;

    <application android:icon="@drawable/icon" android:label="@string/app_name">
     <activity android:name=".MainActivity"
     android:label="@string/app_name">
     <intent-filter>
     <action android:name="android.intent.action.MAIN" />
     <category android:name="android.intent.category.LAUNCHER" />
     </intent-filter>


    <!-- 注册在应用程序外,系统所广播出来的intent filter -->
     

    <intent-filter>
     <!-- 注册仅处理NDEF Tag,并指定预设启动的Activity与处理的Type -->
     <action android:name="android.nfc.action.NDEF_DISCOVERED" />
     <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/plain" />
     </intent-filter>
     </activity>
    </application>


    注册应用程序要处理的Intent Filter,此部分注册的是应用程序非在前景模式时,如果系统有侦测到NDEF tag发出intent,
    应用程序注册了该intent filter则会被触发到,如果有多个注册相同的intent filter则会需要用户进行选择。
    步骤1:处理应用程序注册的Intent Filter,透过OnResume()事件进行处理将读取的内容放置画面的EditText中;
    1-1. 画面配置:

    <RelativeLayout xmlns:android=" http://schemas.android.com/apk/res/android "
     xmlns:tools=" http://schemas.android.com/tools "
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingBottom="@dimen/activity_vertical_margin"
     android:paddingLeft="@dimen/activity_horizontal_margin"
     android:paddingRight="@dimen/activity_horizontal_margin"
     android:paddingTop="@dimen/activity_vertical_margin"
     tools:context=".MainActivity" >
     <LinearLayout
     android:id="@+id/linearLayout1"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical" >
     <Button
     android:id="@+id/write_tag"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="Write to Tag" >
     </Button>
     <EditText
     android:id="@+idte"
     android:layout_width="fill_parent"
     android:layout_height="388dp"
     android:gravity="top"
     android:text="Edit me." >
     </EditText>
     </LinearLayout>
    </RelativeLayout>


    放置一个Button与EditText来显示读取到的内容,与负责写入资料至NDEF Tag。
    1-2. override处理OnResume()收到由系统送来的intent;

    @Override
    protected void onResume()
    {
     super.onResume();
     // 处理由Android系统送出应用程序处理的intent filter内容
     if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
     // 取得NdefMessage
     NdefMessage[] messages = getNdefMessages(getIntent());
     // 取得实际的内容
     byte[] payload = messages[0].getRecords()[0].getPayload();
     setNoteBody(new String(payload));
     // 往下送出该intent给其他的处理对象
     setIntent(new Intent());
     }
    }


    1-3. 拆解从Ndef Tag中取得的原始数据,并且转换成NdefMessage内容;

    NdefMessage[] getNdefMessages(Intent intent) {
     // Parse the intent
     NdefMessage[] msgs = null;
     String action = intent.getAction();
     // 识别目前的action为何
     if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)
     || NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
     // 取得parcelabelarrry的资料
     Parcelable[] rawMsgs =
     intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
     // 取出的内容如果不为null,将parcelable转成ndefmessage
     if (rawMsgs != null) {
     msgs = new NdefMessage[rawMsgs.length];
     for (int i = 0; i < rawMsgs.length; i++) {
     msgs[i] = (NdefMessage) rawMsgs[i];
     }
     } else {
     // Unknown tag type
     byte[] empty = new byte[] {};
     NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
     NdefMessage msg = new NdefMessage(new NdefRecord[] {
     record
     });
     msgs = new NdefMessage[] {
     msg
     };
     }
     } else {
     Log.d(TAG, "Unknown intent.");
     finish();
     }
     return msgs;
    }
    1-4. 设得资料内容并回写至画面中;
    private void setNoteBody(String body) {
     Editable text = gNote.getText();
     text.clear();
     text.append(body);
    }


    1-5. 让应用程序在前景模式下也能直接处理侦测到的Ndef Tdg,让系统侦测到Ndef Tag时无需再重新启动相同的应用程序;
    为了让应用程序在前景也可以处理intent filter,需要建立几个必要的项目:
    (1) 宣告PendingIntent:注册让应用程序的Activity负责处理所有接受到的NFC intents:
    // 注册让该Activity负责处理所有接收到的NFC Intents。
    gNfcPendingIntent = PendingIntent.getActivity(
    this, 0,
    // 指定该Activity为应用程序中的最上层Activity
    new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
    在onCreate()中建立该PendingIntent,并且将gNfcPendingIntent宣告成全局变量。指定负责的Activity为最上层的Activity。
    (2) 宣告IntentFilter[]:注册要在前景处理的Intent Filter类型;
    // 建立要处理的Intent Filter负责处理来自Tag或p2p交换的数据。

    IntentFilter ndefDetected = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
    try {
     ndefDetected.addDataType("text/plain");
    } catch (MalformedMimeTypeException e) { }
    gNdefExchangeFilters = new IntentFilter[] { ndefDetected };


    在onCreate()宣告要处理的IntentFilter,指定要处理的Data Type为MIME的文字,最后将IntentFilter加入全局变量的gNdefExchangeFilters。
    (3) 覆写onRsume()事件,让Activity启动时启动NfcAdapter支持前景模式下处理NFC Intent;

    @Override
    protected void onResume()
    {
     super.onResume();
     gResumed = true;
     // 处理由Android系统送出应用程序处理的intent filter内容
     if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
     // 取得NdefMessage
     NdefMessage[] messages = getNdefMessages(getIntent());
     // 取得实际的内容
     byte[] payload = messages[0].getRecords()[0].getPayload();
     setNoteBody(new String(payload));
     // 往下送出该intent给其他的处理对象
     setIntent(new Intent());
     }
     // 启动前景模式支持Nfc intent处理
     enableNdefExchangeMode();
    }
    /**
     * 启动Ndef交换数据模式。
     */
    private void enableNdefExchangeMode() {
     // 让NfcAdapter启动能够在前景模式下进行intent filter的dispatch。
     gNfcAdapter.enableForegroundDispatch(
     this, gNfcPendingIntent, gNdefExchangeFilters, null);
    }


    在onResume()中加入enableNdefExchangeMode()方法,里面使用了「gNfcAdapter.enableForegroundDispatch()」方法,
    启动NfcAdapter支持前景模式下处理NFC Intent。
    (4) 覆写onNewIntent()事件,补捉由其他应用程序或系统发出的Intent进行处理;

    @Override
    protected void onNewIntent(Intent intent) {
     // 覆写该Intent用于补捉如果有新的Intent进入时,可以触发的事件任务。
     // NDEF exchange mode
     if (!gWriteMode && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
     NdefMessage[] msgs = getNdefMessages(intent);
     promptForContent(msgs[0]);
     }
    }
    /**
     * 应用程序补捉到Ndef Message,询问用户是否要取代目前画面中的文件。
     * @param msg
     */
    private void promptForContent(final NdefMessage msg) {
     new AlertDialog.Builder(this).setTitle("Replace current content?")
     .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
     @Override
     public void onClick(DialogInterface arg0, int arg1) {
     String body = new String(msg.getRecords()[0].getPayload());
     setNoteBody(body);
     }
     })
     .setNegativeButton("No", new DialogInterface.OnClickListener() {
     @Override
     public void onClick(DialogInterface arg0, int arg1) {
     }
     }).show();
    }


    覆写onNewIntent()事件以补捉当Activity收到系统送来的Intent时可以直接进行处理,不需要重新建立一个新的Activity处理。
    直接使用已存在的instance负责,另外增加promptForContent()方法来询问用户如果画面中有数据是否要清除。
    完成步骤0至步骤1,即可以完成读取Ndef tag的功能。接下来步骤2要说明的是如何写入text/plain的内容至Ndef tag;
    步骤2:要让App可以操作NFC,需要先取得NdefAdapter对象,才能启动写入数据至NFC tag或是其他应用程序;
    2-1. 先针对画面中按钮、文字框二个控件,并且分加入对应的Listener;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
     // ...
     // 取得EditText与Button,并且注册对应的事件
     findViewById(R.id.write_tag).setOnClickListener(this.gTagWriter);
     gNote = (EditText)findViewById(R.id.note);
     gNote.addTextChangedListener(gTextWatcher);
     // ...
    }


    a. Button注册OnClickListener(),实作事件以启动写入数据至Tag或应用程序;

    private View.OnClickListener gTagWriter = new View.OnClickListener() {
     @Override
     public void onClick(View v)
     {
     // 先停止接收任何的Intent,准备写入资料至tag;
     disableNdefExchangeMode();
     // 启动写入Tag模式,监测是否有Tag进入
     enableTagWriteMode();
     // 显示对话框,告知将Tag或手机靠近本机的NFC感应区
     new AlertDialog.Builder(MainActivity.this)
     .setTitle("Touch tag to write")
     .setOnCancelListener(new DialogInterface.OnCancelListener() {
     @Override
     public void onCancel(DialogInterface dialog)
     {
     // 在取消模式下,先关闭监侦有Tag准备写入的模式,再启动等待数据交换的模式。
     // 停止写入Tag模式,代表已有Tag进入
     disableTagWriteMode();
     // 启动数据交换
     enableNdefExchangeMode();
     }
     }).create().show();
     }
    };
    /**
     * 启动Ndef交换数据模式。
     */
    private void enableNdefExchangeMode()
    {
     // 让NfcAdatper启动前景Push数据至Tag或应用程序。
     gNfcAdapter.enableForegroundNdefPush(MainActivity.this, getNoteAsNdef());
     // 让NfcAdapter启动能够在前景模式下进行intent filter的dispatch。
     gNfcAdapter.enableForegroundDispatch(this, gNfcPendingIntent, gNdefExchangeFilters, null);
    }
    private void disableNdefExchangeMode()
    {
     gNfcAdapter.disableForegroundNdefPush(this);
     gNfcAdapter.disableForegroundDispatch(this);
    }
    /**
     * 启动Tag写入模式,注册对应的Intent Filter来前景模式监听是否有Tag进入的讯息。
     */
    private void enableTagWriteMode()
    {
     gWriteMode = true;
     IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
     gWriteTagFilters = new IntentFilter [] {tagDetected};
     gNfcAdapter.enableForegroundDispatch(this, gNfcPendingIntent, gWriteTagFilters, null);
    }
    /**
     * 停止Tag写入模式,取消前景模式的监测。
     */
    private void disableTagWriteMode()
    {
     gWriteMode = false;
     gNfcAdapter.disableForegroundDispatch(this);
    }


    该按钮主要在点击后,注册对应的Intent Filter:ACTION_TAG_DISCOVERED,为了等待有Nfc Tag进入监侦范围所触发Intent事件,
    因此,搭配在onNewIntent()里增加了写识别intent.action进一步将EditText的内容写入Tag中;
    但在启动另一个监侦时,记得先将预先监侦的ACTION_NDEF_DISCOVERED先取消,以免冲突。等到onCancel()事件启动时,
    再取消ACTION_TAG_DISCOVERED的监侦,重新启动ACTION_NDEF_DISCOVERED的监听。
    =>另外需注意在enableNdefExchangedMode()里,也启动了前景模式发送信息至应用程序。
    b. EditText注册TextChangedListener(),宣告一个TextWatcher,处理在afterTextChanged()下启动写数据至应用程序;

    private TextWatcher gTextWatcher = new TextWatcher() {
     @Override
     public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
     }
     @Override
     public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
     }
     @Override
     public void afterTextChanged(Editable arg0) {
     // 如果是在Resume的状态下,当编辑完后,启动前景发布讯息的功能。
     if (gResumed) {
     gNfcAdapter.enableForegroundNdefPush(MainActivity.this, getNoteAsNdef());
     }
     }
    };


    在afterTextChanged()事件里,先识别目前是否从onResume()进入,如果是将启动NfcAdapter在前景模式推送讯息至其他App。
    2-2. 增加onNewIntent()事件,处理当监侦到Ndef Tag时,写入数据至Tag中;

    @Override
    protected void onNewIntent(Intent intent)
    {
     // 覆写该Intent用于补捉如果有新的Intent进入时,可以触发的事件任务。
     // NDEF exchange mode
     if (!gWriteMode && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
     NdefMessage [] msgs = getNdefMessages(intent);
     promptForContent(msgs[0]);
     }
     // 监测到有指定ACTION进入,代表要写入数据至Tag中。
     // Tag writing mode
     if (gWriteMode && NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
     Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
     writeTag(getNoteAsNdef(), detectedTag);
     }
    }


    2-3. 写入Ndef数据的方法;

    boolean writeTag(NdefMessage message, Tag tag) {
     int size = message.toByteArray().length;
     try {
     Ndef ndef = Ndef.get(tag);
     if (ndef != null) {
     ndef.connect();
     if (!ndef.isWritable()) {
     toast("Tag is read-only.");
     return false;
     }
     if (ndef.getMaxSize() < size) {
     toast("Tag capacity is " + ndef.getMaxSize() + " bytes, message is " + size
     + " bytes.");
     return false;
     }
     ndef.writeNdefMessage(message);
     toast("Wrote message to pre-formatted tag.");
     return true;
     } else {
    NdefFormatable format = NdefFormatable.get(tag);
     if (format != null) {
     try {
     format.connect();
     format.format(message);
     toast("Formatted tag and wrote message");
     return true;
     } catch (IOException e) {
     toast("Failed to format tag.");
     return false;
     }
     } else {
     toast("Tag doesn't support NDEF.");
     return false;
     }
     }
     } catch (Exception e) {
     toast("Failed to write tag");
     }
     return false;
    }
    private void toast(String text) {
     Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
    }


    writeTag()方法增加了一些判断的逻辑很值得参考。
    2-4. 覆写onPasue()事件,让Activity暂停时关闭NfcAdapter的前景模式;

    @Override
    protected void onPause()
    {
     super.onPause();
     gResumed = false;
     // 由于NfcAdapter启动前景模式将相对花费更多的电力,要记得关闭。
     gNfcAdapter.disableForegroundNdefPush(this);
    }


    [范例程序]
    ======
    按照上方的说明应能实作出与NFC tag、应用程序交换数据的范例程序。该文章主要撷录<StickyNotes sample code>的内容,
    有更多相关的程序细节还有待补充与了解。如果有撰写错误的地方,也请大家多多指教,谢谢。
    References:
    NFC Demo – Android sample code
    Near Field Communication (重要)
    NFC Basics & Advanced NFC
    Android NFC 开发教程(1):概述
    Android NFC 开发教程(2): ApiDemos->NFC->ForegoundDispatch
    Android NFC 开发教程(3): Mifare Tag 读写示例
    NFC Programming in Android & [Android] 简单范例: NFC Push
    Developer Document & NXP TagWriter & StickyNotes sample code
    onNewIntent调用时机 & onNewIntent的应用 & Activity – onNewIntent()
    Dotblogs Tags: Android

  • 相关阅读:
    闲着写了一个查看股票的程序
    Oracle10g正则表达式
    跨语言平台的RSA加密、解密、签名、验证算法的实现
    Base64转换:AQAB=65537,你知道为什么吗?
    无题
    07年了,新的一年又开始了
    简单生活
    近期关注
    闲话
    各大网站的WEB服务器分析
  • 原文地址:https://www.cnblogs.com/jyycnblogs/p/5069518.html
Copyright © 2020-2023  润新知