Android6.0源码分析之蓝牙显示接收到的文件

在蓝牙界面有个menu:显示接收到的文件。本文分析显示接收到的文件

chapter one---显示接收到的文件

/android/packages/apps/Settings/src/com/android/settings/bluetooth/文件夹下的BluetoothSettings.java开始分析

case MENU_ID_SHOW_RECEIVED:
                MetricsLogger.action(getActivity(), MetricsLogger.ACTION_BLUETOOTH_FILES);
                Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
                getActivity().sendBroadcast(intent);
                return true;

当点击显示接收到的文件menu时会发送广播,发送的广播为

private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES =
            "android.btopp.intent.action.OPEN_RECEIVED_FILES";

既然有发送广播,就要看哪个地方接收到广播并进行了处理

通过代码搜索定位到/android/packages/apps/Bluetooth/文件夹下的Androidmanifest.xml文件中进行了定义

可以看到实在opp文件夹下的BluetoothOppReceiver中进行处理的

在Constants中定义了全局变量

 /** the intent that gets sent from the Settings app to show the received files */
   public static final String ACTION_OPEN_RECEIVED_FILES = "android.btopp.intent.action.OPEN_RECEIVED_FILES";

在BluetoothOppReceiver中当检测到该action时会进行如下处理

 else if (action.equals(Constants.ACTION_OPEN_RECEIVED_FILES)) {
            if (V) Log.v(TAG, "Received ACTION_OPEN_RECEIVED_FILES.");

            Intent in = new Intent(context, BluetoothOppTransferHistory.class);
            in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            in.putExtra("direction", BluetoothShare.DIRECTION_INBOUND);
            in.putExtra(Constants.EXTRA_SHOW_ALL_FILES, true);
            context.startActivity(in);

跳转到BluetoothTransferHistory,在跳转时对intent做了一些处理,首先是设置了一些flags,说明一下

Intent.FLAG_ACTIVITY_CLEAR_TOP:如果在栈中有该实例,就会去重用该实例,并且会清除掉该实例上方的所有activity,简单举个例子,如果在栈1中存在有三个实例,Acivity1,Activity2,Activity3。可以看到处于栈顶的是Activity3,也就是目前显示的是窗口3,如果从窗口3跳转到窗口2,则会销毁Activity3,并且重用Activity2,也就是说目前栈中Activity存在情况如下Activity1,Activity2。

Intent.FLAG_ACTIVITY_NEW_TASK:如果在activity节点下存在taskAffinity属性,首先看该属性值有没有进行设置

            android:taskAffinity="">

如果设置了该属性值,就会去查找taskAffinity对应的栈,如果栈不存在,则会新建该栈 并将activity存入,如果栈存在,则直接入栈

如果没有设置该属性或者该属性值默认为空,则直接压入当前栈。在程序中未对BluetoothTransferHistory的该属性进行设置。

接下来对BluetoothTransferHistory.java分析,该类位于\android\packages\apps\Bluetooth\src\com\android\bluetooth\opp

设计思路:对于显示蓝牙接受到的文件是利用ContentProvider来访问uri获取到已接受到的文件并显示出来。

布局文件为bluetooth_transfers_page.xml

setContentView(R.layout.bluetooth_transfers_page);

该布局文件所用到的节点有些特别,分析一下

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>
    <ViewStub
        android:id="@+id/empty"
        android:layout="@layout/no_transfers"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"/>
</merge>

首先最外层布局为merge,接下来是两个子view:ListView和ViewStub。ListView很常见,但很少用到merge和ViewStub布局控件

关于这些的介绍想了解的可以看相关链接,在这里不再多说

Android中include和Merge节点分析

Android中ViewStub控件分析及使用

其中merge是默认的垂直的线性布局,也就是说该布局文件中显示一个listview列表,然后是一个动态布局的ViewStub,所引用的layout文件为no_transfers

no_transfers.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:text="@string/no_transfers"
    android:gravity="center"
    android:textStyle="bold"
    />

显示一个textview,

 <string name="no_transfers" msgid="3482965619151865672">"没有传输历史记录。"</string>

listview显示的传输文件列表,布局xml介绍完后进入对Java文件的分析。

@Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.bluetooth_transfers_page);
        mListView = (ListView)findViewById(R.id.list);
        mListView.setEmptyView(findViewById(R.id.empty));

        mShowAllIncoming = getIntent().getBooleanExtra(
                Constants.EXTRA_SHOW_ALL_FILES, false);

        String direction;
        int dir = getIntent().getIntExtra("direction", 0);
        if (dir == BluetoothShare.DIRECTION_OUTBOUND) {
            setTitle(getText(R.string.outbound_history_title));
            direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND
                    + ")";
        } else {
            if (mShowAllIncoming) {
                setTitle(getText(R.string.btopp_live_folder));
            } else {
                setTitle(getText(R.string.inbound_history_title));
            }
            direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND
                    + ")";
        }

        String selection = BluetoothShare.STATUS + " >= '200' AND " + direction;

        if (!mShowAllIncoming) {
            selection = selection + " AND ("
                    + BluetoothShare.VISIBILITY + " IS NULL OR "
                    + BluetoothShare.VISIBILITY + " == '"
                    + BluetoothShare.VISIBILITY_VISIBLE + "')";
        }

        final String sortOrder = BluetoothShare.TIMESTAMP + " DESC";
  mTransferCursor = managedQuery(BluetoothShare.CONTENT_URI, new String[] {
                "_id", BluetoothShare.FILENAME_HINT, BluetoothShare.STATUS,
                BluetoothShare.TOTAL_BYTES, BluetoothShare._DATA, BluetoothShare.TIMESTAMP,
                BluetoothShare.VISIBILITY, BluetoothShare.DESTINATION, BluetoothShare.DIRECTION
        }, selection, sortOrder);

        // only attach everything to the listbox if we can access
        // the transfer database. Otherwise, just show it empty
        if (mTransferCursor != null) {
            mIdColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare._ID);
            // Create a list "controller" for the data
            mTransferAdapter = new BluetoothOppTransferAdapter(this,
                    R.layout.bluetooth_transfer_item, mTransferCursor);
            mListView.setAdapter(mTransferAdapter);
            mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
            mListView.setOnCreateContextMenuListener(this);
            mListView.setOnItemClickListener(this);
        }

        mNotifier = new BluetoothOppNotification(this);
        mContextMenu = false;
    }

关键代码都在onCreate中,可以看出来通过调用managedQuery方法按指定的条件查询指定的uri,获取到cursor后传给adapter,并将adapter与listview绑定显示数据。

首先是对传输列表数据源的获取----managedQuery

public final Cursor managedQuery(Uri uri, String[] projection, String selection,
            String sortOrder) {
        Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder);
        if (c != null) {
            startManagingCursor(c);
        }
        return c;
    }

managedQuery方法定义在Activity中,可以看到首先获取到ContentResolver对象,然后调用query方法进行查询指定uri的数据。有几点需要注意,通过该方法获取到的cursor无需去调用close方法将其关闭,因为activity会在合适的时候将其关闭。但是有一点,如果你的cursor对象调用了stopManagingCursor方法时,必须手动去调用cursor.close方法将其关闭,因为此时,activity不会自动去关闭

需要传入四个参数

  • uri   : the uri of the content provider to query      所要查询的contentProvider的uri
  • projection  :  projection list of columns to return  查询到后所要返回的cursor的列名,如果想要全部返回可以将参数置为null
  • selection  :  selectio SQL WHERE clause            查询条件
  • sortOrder  : sortorder SQL ORDER BY clause    按某种条件排序

所查询的uri为

/**
 * The content:// URI for the data table in the provider
 */
    public static final Uri CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp");

想要读取该uri下的数据需要在Androidmanifest.xml配置文件中添加如下权限

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

查询到后传给adapter进行加载view

举一个例子,在item上显示远程蓝牙name的话可以使用如下代码

tv = (TextView)view.findViewById(R.id.targetdevice);
   //获取到本地蓝牙适配器 
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
       //获取到索引 
        int destinationColumnId = cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION);
       
       //获取到本条数据中所对应的远程设备
         BluetoothDevice remoteDevice = adapter.getRemoteDevice(cursor
                .getString(destinationColumnId));
        //获取到name
        String deviceName = BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
        tv.setText(deviceName);

显示接收到的文件源码其实实现起来不难,就是借助contentResolver来读取uri的数据并显示出来,那么数据必须要通过contentprovider的方式保存,但是接收到的文件时保存在哪个contentprovider??如何进行保存?会保存哪些信息?有哪些列?接下来就进行分析

首先根据uri---com.android.bluetooth.opp去查找清单配置文件

<provider android:name=".opp.BluetoothOppProvider"
            android:authorities="com.android.bluetooth.opp"
            android:exported="true"
            android:process="@string/process">
            <path-permission
                    android:pathPrefix="/btopp"
                    android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
        </provider>

可以发现存储所用的provider为BluetoothOppProvider.java,正好借此机会分析下provider的用法。

chapter two-----存储接收到的文件的ContentProvider

该类位于\android\packages\apps\Bluetooth\src\com\android\bluetooth\opp文件夹下,直接继承与ContentProvider。希望是在对contentProvider有一定的了解后阅读如下分析

provider属于组件,需要再清单配置文件中进行配置

<provider android:name=".opp.BluetoothOppProvider"
            android:authorities="com.android.bluetooth.opp"
            android:exported="true"
            android:process="@string/process">
            <path-permission
                    android:pathPrefix="/btopp"
                    android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
        </provider>

自定义一个provider需要添加如下属性authorities:域名,如果需要访问权限,就规定所需要的访问权限

java代码中的处理如下

首先对于BluetoothOppProvider的uri进行解析

定义一个urimatcher对象,以供应用对uri进行访问解析数据

/** URI matcher used to recognize URIs sent by applications */
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

接下来为urimatcher对象注册路径,在源码中只注册了两个

/** URI matcher constant for the URI of the entire share list 返回整个数据列表*/
    private static final int SHARES = 1;

    /** URI matcher constant for the URI of an individual share 返回一条记录*/
    private static final int SHARES_ID = 2;

    static {
        sURIMatcher.addURI("com.android.bluetooth.opp", "btopp", SHARES);
        sURIMatcher.addURI("com.android.bluetooth.opp", "btopp/#", SHARES_ID);
    }

紧接着看到源码中创建了数据库,也就是说contentprovider将数据存储到数据库

数据库的name为:btop.db

/** Database filename */
    private static final String DB_NAME = "btopp.db";

所创建的列包括14个,如下所示

 private void createTable(SQLiteDatabase db) {
        try {
            db.execSQL("CREATE TABLE " + DB_TABLE + "(" + BluetoothShare._ID
                    + " INTEGER PRIMARY KEY AUTOINCREMENT," + BluetoothShare.URI + " TEXT, "
                    + BluetoothShare.FILENAME_HINT + " TEXT, " + BluetoothShare._DATA + " TEXT, "
                    + BluetoothShare.MIMETYPE + " TEXT, " + BluetoothShare.DIRECTION + " INTEGER, "
                    + BluetoothShare.DESTINATION + " TEXT, " + BluetoothShare.VISIBILITY
                    + " INTEGER, " + BluetoothShare.USER_CONFIRMATION + " INTEGER, "
                    + BluetoothShare.STATUS + " INTEGER, " + BluetoothShare.TOTAL_BYTES
                    + " INTEGER, " + BluetoothShare.CURRENT_BYTES + " INTEGER, "
                    + BluetoothShare.TIMESTAMP + " INTEGER," + Constants.MEDIA_SCANNED
                    + " INTEGER); ");
        } catch (SQLException ex) {
            Log.e(TAG, "couldn't create table in downloads database");
            throw ex;
        }
    }

介绍一个小知识,在数据库类的oncreate方法中可以直接并且只调用createTable方法,但是在更新数据库时需要先将数据库删除然后再调用createTable创建,删除数据库方法如下

private void dropTable(SQLiteDatabase db) {
        try {
            db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
        } catch (SQLException ex) {
            Log.e(TAG, "couldn't drop table in downloads database");
            throw ex;
        }
    }

数据库实在provider的oncreate方法中进行创建的

然后就会provider对数据库进行增删改查操作--

--------------------------

未完待续

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android知识点总结

[番外]理一理Android多文件上传那点事

20610
来自专栏刘望舒

Android包管理机制之PackageInstaller安装APK

在本系列上一篇文章Android包管理机制(一)PackageInstaller的初始化中我们学习了PackageInstaller是如何初始化的,这一篇文章我...

19230
来自专栏mukekeheart的iOS之旅

Android基础总结(2)——活动Activity

1、什么是活动(Activity)   活动(Activity)是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或多个活动,但不...

33990
来自专栏developerHaoz 的安卓之旅

Android Volley 源码解析(三),图片加载的实现

在上一篇文章中,我们一起深入探究了 Volley 的缓存机制,通过源码分析对缓存的工作原理进行了了解,这篇文章将带大家一起探究「Volley 图片加载的实现」,...

10820
来自专栏分享达人秀

ViewPager快速实现引导页

在很多APP第一次启动时都会出现引导页,在一些APP里面还会包括一些左右滑动翻页和页面轮播切换的情况。在之前也已经学习了AdapterViewFlipp...

25470
来自专栏向治洪

android插件开发机制

插件机制实质上就是由主体程序定义接口,然后由插件去实现这些接口,以达到功能模块化。Android系统是基于Linux内核的,其安全机制也继承了Linux的特性...

23570
来自专栏沃趣科技

Oracle 12c系列(四)|资源隔离之IO、内存、CPU

作者 姚崇 出品 沃趣技术 服务器主机提供IO、内存、CPU、存储空间等资源为数据库使用,Oracle使用Flex Diskgroup为数据库提供存储空...

47650
来自专栏上善若水

037android初级篇之Activity的几个重要函数

手机屏幕事件的处理方法onTouchEvent。该方法在View类中的定义,并且所有的View子类全部重写了该方法,应用程序可以通过该方法处理手机屏幕的触摸事件...

11620
来自专栏wOw的Android小站

[Android][Framework] 从一个小问题了解STK加载内容的方式

这个界面从哪来的? 实际上,我们插入SIM卡,手机就会显示SimToolKit,打开就能看到一些和运营商相关的菜单。换了不同的卡菜单也会变。所以大概可以猜到,S...

27910
来自专栏李蔚蓬的专栏

Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)

本模块共有四篇文章,参考郭神的《第一行代码》,对Content Provider的学习做一个详细的笔记,大家可以一起交流一下:

13140

扫码关注云+社区

领取腾讯云代金券