level Android5.0 toAndroid6.0

安卓6.0有什么特色?

  1. App Permissions(软件权限管理):在Android M里,应用许可提示可以自定义了。它允许对应用的权限进行高度管理,比如应用能否使用位置、相机、麦克风、通讯录等,这些都可以开放给开发者和用户。
    2.Chrome Custom Tabs(网页体验提升):新版的M对于Chrome的网页浏览体验进行了提升,它对登陆网站、存储密码、自动补全资料、多线程浏览网页的安全性进行了一些列的优化。
    3.App Links(APP关联):Android M加强了软件间的关联,谷歌在现场展示了 一个例子,比如你的手机邮箱里收到一封邮件,内文里有以个Twitter链接,用户点击该链接可以直接跳转到Twitter应用,而不再是网页。
    4.Android Pay(安卓支付):Android支付统一标准。新的M系统中集成了Android Pay。其特性在于简洁、安全、可选性。Android Pay是一个开放性平台,使用户就可以选择谷歌的服务或者使用银行的APP来使用它,Android Pay支持4.4以后系统设备。
    5.Fingerprint Support(指纹支持):Android M增加了对指纹的识别API,谷歌开始在M里自建官方的指纹识别支持,力求Android统一方案,目前所有的Android产品指纹识别都是使用非谷歌认证的技术和接口。
    6.Power & Change(电量管理 ):新的电源管理模块将更为智能,比如Android平板长时间不移动时,M系统将自动关闭一些App。同时Android M设备将支持USB Type-C接口,新的电源管理将更好的支持Type-C接口。

架构升级权限解析

Step1.PackageManagerService.main
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,factoryTest, onlyCore);
ServiceManager.addService(“package”, m);
return m;
}
这个函数创建了一个PMS的实例,然后把这个服务添加到ServicesManager中去。那么创建这个实例的时候就会去走
PMS的构造,如下。
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
mSettings = new Settings(mPackages); //实例化一个Settings类的对象,用于在解析这个apk的过程中存放各种变量
//加载一些全局的系统的配置,如system/etc/Permissions 文件下面的各个xml的解析(有权限,有特征等等)
//然后放到相应的成员变量如:
//1、final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();每个权限名字对应一个
//PermissionEntry,这个成员变量解析的是的标签。
//2、final SparseArray<ArraySet> mSystemPermissions = new SparseArray<>();每个uid,所对应的所有
//权限,这个成员变量是解析标签下的权限。
SystemConfig systemConfig = SystemConfig.getInstance();
ArrayMap<String, SystemConfig.PermissionEntry> permConfig = systemConfig.getPermissions();
for (int i=0; i<permConfig.size(); i++) {
SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
BasePermission bp = mSettings.mPermissions.get(perm.name);
if (bp == null) {
//一个权限对应一个BasePermission,和是那个应用的权限无关。最终都放到mSettings.mPermissions
bp = new BasePermission(perm.name, “android”, BasePermission.TYPE_BUILTIN);
mSettings.mPermissions.put(perm.name, bp);
}
if (perm.gids != null) {
bp.setGids(perm.gids, perm.perUser);
}
}
File frameworkDir = new File(Environment.getRootDirectory(), “framework”);
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
//扫描/vendor/overlay
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
//扫描 /system/framework
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);

        //扫描 /system/priv-app
        final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
        scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                                                                   | PackageParser.PARSE_IS_SYSTEM_DIR
                                                                          | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

        //扫描 /system/app
        final File systemAppDir = new File(Environment.getRootDirectory(), "app");
        scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                                             | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

        //扫描vendor/app
        File vendorAppDir = new File("/vendor/app");
        scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                                                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

        // 扫描oem/app
        final File oemAppDir = new File(Environment.getOemDirectory(), "app");

scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
//以上会调用scanDirLI函数来设备上的指定目录下的apk文件,注意其传入的参数有各种系统级别的解析才传入的以
//来证明是系统应用等等。

}

Step2.PackageManagerService.scanDirLI
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();

for (File file : files) {
//是否是个apk文件或者是个目录。
final boolean isPackage = (isApkFile(file) || file.isDirectory())&&
!PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}

try {
//调用scanPackageLI进一步解析
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {…}
}
}
此函数就是解析传过来的第一个参数,如果是apk文件或者是目录,就对其一一调用scanPackageLI进行解析。

Step3.PackageManagerService. scanPackageLI
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
PackageParser pp = new PackageParser();

final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {…}

| SCAN_UPDATE_SIGNATURE, currentTime, user);

return scannedPkg;
}
通过创建PackagePaser实例并调用它的parsePackage来解析pkg文件,注意这个函数的参数也是通过path路劲来解析的,最终还
得需要另外的一个重写的scanPackageLI方法来实现包解析玩的pkg保存在PMS中。

Step4.PackageParser.parsePackage
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
//通过判断是一个apk文件还是一个目录
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
此函数会进一步的判断传入的是一个apk文件还是一个目录,进而在调用不同的解析方法。最终都会调用到parseBaseApk里
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
throws PackageParserException {
if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Invalid package file: " + apkPath);
}

    // The AssetManager guarantees uniqueness for asset paths, so if this asset path
    // already exists in the AssetManager, addAssetPath will only return the cookie
    // assigned to it.
    int cookie = assets.addAssetPath(apkPath);
    if (cookie == 0) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                "Failed adding asset path: " + apkPath);
    }
    return cookie;
}

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
        throws PackageParserException {
    ......
    Resources res = null;
    XmlResourceParser parser = null;
    try {
        res = new Resources(assets, mMetrics, null);
        ......
        //zy this method is main to get ANDROID_MANIFEST_FILENAME out
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

        final String[] outError = new String[1];
        final Package pkg = parseBaseApk(res, parser, flags, outError);
       ......
        return pkg;

    } catch (PackageParserException e) {......  } catch (Exception e) {......
    } finally {
        IoUtils.closeQuietly(parser);
    }
}
这个函数就是先进行一些基本的判断那,如路径合不合法等,并且解析出apk文件中的AndroidManifest.xml文件,然后调用另一个 重载的parsePackage函数对这个文件进行进一步的解析。

private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final Package pkg = new Package(pkgName);//第一次出现这个类,用于封装包的各种信息
int outerDepth = parser.getDepth();
//没有到AndroidManifest.xml文件的结尾处,就一直循环。
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {

String tagName = parser.getName();
if (tagName.equals(“application”)) {

} else if (tagName.equals(“overlay”)) {

} else if (tagName.equals(“key-sets”)) {

} else if (tagName.equals(“permission-group”)) {
if (parsePermissionGroup(pkg, flags, res, parser, attrs, outError) == null) {
return null;
}
}else if(tagName.equals(“permission”)) {
if (parsePermission(pkg, res, parser, attrs, outError) == null) {
return null;
}
} else if (tagName.equals(“permission-tree”)) {
if (parsePermissionTree(pkg, res, parser, attrs, outError) == null) {
return null;
}
} else if(tagName.equals(“uses-permission”)) {
if (!parseUsesPermission(pkg, res, parser, attrs)) {
return null;
}
} else if (tagName.equals(“uses-permission-sdk-m”)
|| tagName.equals(“uses-permission-sdk-23”)) {
if (!parseUsesPermission(pkg, res, parser, attrs)) {
return null;
}
}
…//解析各种标签,然调用本类的相应的方法,完成解析并保存在pkg的变量当中,最后把pkg返回。
}
return pkg;
}
这个函数用来完成对AndroidManifest.xml文件的各个标签进行解析,此处我们只关心和权限相关的uses-permission标签。
各个标签的含义:http://developer.android.com/guide/topics/manifest/manifest-intro.html

Step5 PackageParser.parseUsesPermission
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser,
AttributeSet attrs) throws XmlPullParserException, IOException {
if ((maxSdkVersion == 0) || (maxSdkVersion >= Build.VERSION.RESOURCES_SDK_INT)) {
if (name != null) {
int index = pkg.requestedPermissions.indexOf(name);
if (index == -1) {
pkg.requestedPermissions.add(name.intern());
} else {… }
}
}
return true;
}
此函数就是把Uses-Permission标签下的每一个权限的名字添加到pkg.requestedPermissions的变量当中。
然后一层层返回到step3中
Step.6 PackageManagerService.scanPackageLI
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
boolean success = false;
try {
final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
currentTime, user);
success = true;
return res;
} finally {… }
}
继续调用scanPackageDirtyLI来完成解析。注意到现在为止pkg里面已经有了大量的信息。
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { …
//通过pkg创建一个PackageSetting对象,也是临时保存一个指定的package的数据和信息。
//如此出会找到这个package的uid、还会通过层层的父类的初始化new PermissionsState()类。
pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, user, false);
pkg.applicationInfo.uid = pkgSetting.appId; //赋值uid
pkg.mExtras = pkgSetting;
synchronized (mPackages) {
//把pkgSetting保存到Settings的变量mPackages中, String对应于包名。
//final ArrayMap<String, PackageSetting> mPackages = new ArrayMap();
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
//把pkg保存到PMS的成员变量mPackages 中,Stirng对应于包名。
//final ArrayMap<String, PackageParser.Package> mPackages = new ArrayMap<String, PackageParser.Package>();
mPackages.put(pkg.applicationInfo.packageName, pkg);
int N = pkg.providers.size();
…//解析providers 把相应的provider添加到mProviders当中。
N = pkg.services.size();
…//解析services 把相应的services添加到mServices当中。
N = pkg.receivers.size();
…//解析receivers 把相应的receivers 添加到mReceivers 当中。
N = pkg.activities.size();
…//解析activities 把相应的activities 添加到mActivities 当中。
N = pkg.permissionGroups.size();
…//解析permissionGroups把相应的permissionGroups添加到mPermissionGroups当中。
N = pkg.permissions.size();
…//解析permissions把相应的permissions添加到permissionMap当中。
N = pkg.instrumentation.size();
…//解析instrumentation把相应的instrumentation添加到mInstrumentation当中。 …
}
return pkg;
}
此函数现在看来除啦进一步解析pkg外,还把pkg的一些属性添加到PMS的成员变量中。
Step7.PackageManagerService
至此完成了对系统中的相应的目录下的apk的解析,那么它们的权限怎么设定的那?完全没有踪影啊,别急。接下来继续回到
PMS的构造当中。
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
//此处接着Step1的步骤分析。

int updateFlags = UPDATE_PERMISSIONS_ALL;
if (ver.sdkVersion != mSdkVersion) {
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);/
}
在构造中解析完系统的apk后调用updatePermissionsLPw来设定应用的权限。

              Step 8.PackageManagerService. updatePermissionsLPw
                    
                      private void updatePermissionsLPw(String changingPkg,
                                                                         PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
                                  ......
                                 //下面的这个flags从PMS构造中传入,所以符合条件可以进入,并且replace为false。
                                 if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
                                         //mPackages,在Step 6中已经完成保存
                                         for (PackageParser.Package pkg : mPackages.values()) {
                      if (pkg != pkgInfo) { 
                               final String volumeUuid = getVolumeUuidForPackage(pkg);
                               final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
                                                                                  && Objects.equals(replaceVolumeUuid, volumeUuid);
             grantPermissionsLPw(pkg, replace, changingPkg); 

}
}
//从PMS构造中传过来的是null,此处一般手动安装的应用会走,并且replace 为true。
if (pkgInfo != null) {
final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
&& Objects.equals(replaceVolumeUuid, volumeUuid);
grantPermissionsLPw(pkgInfo, replace, changingPkg);
}
}
此函数更具传入的参数的不同调用方法中不同地方的grantPermissionsLPw,并把相应的参数传入.

             Step 9.PackageManagerService. grantPermissionsLPw
                       private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
                                                                                                            String packageOfInterest) {
                                //在Step 6 已经赋值 pkg.mExtras。
                                 final PackageSetting ps = (PackageSetting) pkg.mExtras;
    if (ps == null) {
        return;
    }
    //此时的permissionsState不为null,因为在初始化PackageSetting 时,已经实例化它,但是它的成员变量很多
    //都是null因为实例化它的时候用的是无参数的构造,如:public ArrayMap<String, PermissionData> mPermissions;
    //key对应于权限名字,Values对应于一个用于封装的PermissionData类
    PermissionsState permissionsState = ps.getPermissionsState();
    PermissionsState origPermissions = permissionsState;
    ......
   //已经在Step 5中完成了解析。
   final int N = pkg.requestedPermissions.size();
   for (int i=0; i<N; i++) {
           final String name = pkg.requestedPermissions.get(i);
           //在Step1中(还有别的地方)已经初始化此项
           final BasePermission bp = mSettings.mPermissions.get(name);
           ......
 final String perm = bp.name;
 boolean allowedSig = false;
 int grant = GRANT_DENIED;
 ......

//从何处导致的protectionLevel的不同,暂时还没分析出来!!!!??????
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
switch (level) {
case PermissionInfo.PROTECTION_NORMAL: {
// 当不是PROTECTION_DANGEROUS类型的时候都安装为安装权限。
grant = GRANT_INSTALL;
} break;

          case PermissionInfo.PROTECTION_DANGEROUS: {
                 if (pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
                    // 当targtversion小于6.0是赋值这个。
                    grant = GRANT_INSTALL_LEGACY;
               } else if (origPermissions.hasInstallPermission(bp.name)) {
                    // For legacy apps that became modern, install becomes runtime.
                    grant = GRANT_UPGRADE;
                 } else if (mPromoteSystemApps&& isSystemApp(ps)
                               && mExistingSystemPackages.contains(ps.name)) {
                    grant = GRANT_UPGRADE;
                 } else {
                    // 想是正常的6.0的第一次安装的时候,就会赋值此处。
                    grant = GRANT_RUNTIME;
                 }
           } break;
        
          case PermissionInfo.PROTECTION_SIGNATURE: {
                // For all apps signature permissions are install time ones.
                allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
                if (allowedSig) {
                    grant = GRANT_INSTALL;
                }
            } break; 
   }
 ......
      }
      if(grant != GRANT_DENIED){
              ......
              switch (grant) {
                     case GRANT_INSTALL: {
                    for (int userId : UserManagerService.getInstance().getUserIds()) {
                          if (origPermissions.getRuntimePermissionState( bp.name, userId) != null) {
                              origPermissions.revokeRuntimePermission(bp, userId);
                              origPermissions.updatePermissionFlags(bp, userId, 
                                                              PackageManager.MASK_PERMISSION_FLAGS, 0);
                              changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                                           changedRuntimePermissionUserIds, userId);
                           }
                     }
                     // 允许所有的安装时的权限,通过grantInstallPermission。此处的逻辑在最后单独分析。
                     int flag = permissionsState.grantInstallPermission(bp);
                     android.util.Log.d("zy_test","GRANT_INSTALL flag = "+flag);
                     if (flag != PermissionsState.PERMISSION_OPERATION_FAILURE) {
                          changedInstallPermission = true;
                     }
                } break;

               case GRANT_INSTALL_LEGACY: {
                   //当应用的targetversion<23并且权限是运行时权限,才会走到这里。也是全部允许。
                    int flag = permissionsState.grantInstallPermission(bp);
                    if (flag !=PermissionsState.PERMISSION_OPERATION_FAILURE) {
                        changedInstallPermission = true;
                    }
                } break;
                 
               case GRANT_RUNTIME: {
                    for (int userId : UserManagerService.getInstance().getUserIds()) {
                           PermissionState permissionState = origPermissions .getRuntimePermissionState(bp.name, userId);
                           final int flags = permissionState != null    ? permissionState.getFlags() : 0;
                           //此时hasRuntimePermission会返回false,由于origPermissions.mPermissions==null
                           if (origPermissions.hasRuntimePermission(bp.name, userId)) {
                               //所以不会进入到这里进而允许所有的运行时权限。
                               if (permissionsState.grantRuntimePermission(bp, userId) ==
                                                             PermissionsState.PERMISSION_OPERATION_FAILURE) {
                                       changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                                                            changedRuntimePermissionUserIds, userId);
                                }
                            }
                            permissionsState.updatePermissionFlags(bp, userId, flags, flags);
                     }
                } break;

                case GRANT_UPGRADE: {......}
                ......
               }else{
                       ......
               }
      }
      ......
                       }

此函数真正的初始化了每个应用的framework层的权限设置,根据不同的权限类型和应用的targetversion。但是这样的话系统应用
很多必要的权限默认也是不允许的,这样体验很不好。
Step 10 PackageManagerService.systemReady()
systemServer调用完PMS的main方法后,会调用PMS的systemReady。
public void systemReady() {

for (int userId : grantPermissionsUserIds) {
//此处初始化所有用户的一些默认的权限。
mDefaultPermissionPolicy.grantDefaultPermissions(userId);
}

}
我们只分析和权限相关的,mDefaultPermissionPolicy 是DefaultPermissionGrantPolicy类的实例,在PMS创建的时候就完成了实
例化,通过调用grantDefaultPermissions来初始化一些应用的默认权限。

                 Step 11 DefaultPermissionGrantPolicy. grantDefaultPermissions
                           public void grantDefaultPermissions(int userId) {
                                  grantPermissionsToSysComponentsAndPrivApps(userId);//对PMS.mPackages里面的符合一定条件pkg的权限的初始化。
                                  grantDefaultSystemHandlerPermissions(userId);//对如:Mms、Dialer、Contact等应用的权限的初始化
                            }
                      此函数分别通过再次调用另外的方法,完成最终的某些应用的权限的初始化,此处就不再深入分析,有兴趣的可以自己在往下看,
                也 很简单。最终是通过PMS的grantRuntimePermission(,,)来完成对应用的权限的设置。至此我们完成了系统的apk的解析和默
                认权限的设置,当中忽略了很多细节有兴趣可以自己深入研究,此文只起到抛砖引玉作用。

3、解析下载好的apk(手动点击安装)
手动点击安装和系统的解析很多地方都会走相同的代码,只是一开始的入口方式不同,此处就不重复讨论相同的地方了。且下面的分析省略了具体的PackageInstaller对点击安装apk往外发送的广播的处理,直接分析最终的在PMS的实现。

从5.0到6.0组件分析

  1. 蓝牙新加载方法
    相关文件位于以下几个目录,
    1,\android\frameworks\base\core\java\android\bluetooth,该目录下存放有诸如BluetoothAdapter,BluetoothDevice,等一些底层文件,

2,\android\frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth,存放的是一些蓝牙协议,服务相关的文件

这些文件一般也不需要改动,除非需要新增一些蓝牙的通信协议,一般修改蓝牙的以下目录的文件
3,Z:\R3\android\packages\apps\Settings\src\com\android\settings\bluetooth

有关蓝牙的可检测性设置,可检测时间设置,界面UI布局,蓝牙的开关等等,均在该目录下设置

对所有蓝牙涉及到的文件目录有所了解后开始分析,不论是分析Android4.4.2.源码还是Android6.0源码逻辑方法是类似的,有什么疑问可参考我的有关Android4.4.2的源码的分析

Chapter One
蓝牙fragment为BluetoothSettings.java,先按覆写的方法进行分析,大体上过一遍
1,onActivityCreated中
[java] view plain copy

  1. mInitialScanStarted = (savedInstanceState != null);
    mInitialScanStarted为boolean型的值,是蓝牙扫描开始的开关,在扫描前会判断该Boolean的值,若为true,则表示不需要进行蓝牙扫描,若为false,则表示可以进行扫描
    如果蓝牙界面没有被销毁(比如蓝牙界面锁屏解锁后),也就是说有状态记录的话该值为true,则蓝牙没必要进行扫描
    [java] view plain copy
  2. mInitiateDiscoverable = true;

mInitiateDiscoverable顾名思义,蓝牙可检测性的开关,在对蓝牙的可检测性进行设置时首先判断该值,若为true,则设置为对附近所有设备可见
[java] view plain copy

  1. mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
  2.      getListView().setEmptyView(mEmptyView);  
    
  3.      mEmptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);  
    

当界面没有任何preference时(比如蓝牙未开启状态下不显示任何preference)初始化一个textview,在屏幕上垂直居中,水平居左,比如在蓝牙未开启时会显示“要搜索可用设备,请打开蓝牙功能”等等
[java] view plain copy

  1. final SettingsActivity activity = (SettingsActivity) getActivity();
  2.      mSwitchBar = activity.getSwitchBar();  
    
  3.      mBluetoothEnabler = new BluetoothEnabler(activity, mSwitchBar);  
    
  4.      mBluetoothEnabler.setupSwitchBar();  
    

这几句话值得重视一下,蓝牙界面有一个蓝牙开关,在Android4.4.2是无法进行滑动的,但是在Android6.0时开关和文字是分开呈现的,而且开关可滑动,类似ios的开关效果,多了一些美感。
在Android6.0中的开关是自定义的一个ToggleButton+TextView,具体自定义会在另一篇博客中交代,在获取到switchBar以后将其传给BluetoothEnabler,该类专门用于处理两件事,
一是根据蓝牙的当前状态对switch进行更新,
[java] view plain copy

  1. void handleStateChanged(int state) {
  2.      switch (state) {  
    
  3.          case BluetoothAdapter.STATE_TURNING_ON:  
    
  4.              mSwitch.setEnabled(false);  
    
  5.              break;  
    
  6.          case BluetoothAdapter.STATE_ON:  
    
  7.              setChecked(true);  
    
  8.              mSwitch.setEnabled(true);  
    
  9.              updateSearchIndex(true);  
    
  10.             mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);  
    
  11.             break;  
    
  12.         case BluetoothAdapter.STATE_TURNING_OFF:  
    
  13.             mSwitch.setEnabled(false);  
    
  14.             break;  
    
  15.         case BluetoothAdapter.STATE_OFF:  
    
  16.             setChecked(false);  
    
  17.             mSwitch.setEnabled(true);  
    
  18.             updateSearchIndex(false);  
    
  19.             break;  
    
  20.         default:  
    
  21.             setChecked(false);  
    
  22.             mSwitch.setEnabled(true);  
    
  23.             updateSearchIndex(false);  
    
  24.     }  
    
  25. }  
    

其实在这里可以看到在打开或者关闭蓝牙时,不仅是对switch进行设置操作,包括重新设置了蓝牙的可检测性,还有一个就是调用updateSearceIndex方法,用于更新数据的操作,在该方法中去更新跟蓝牙有关的一些数据,具体更新了什么数据,请稍待博客更新(不同于Android4.4.2)

二是,在switch开关滑动时对蓝牙的状态进行设
[java] view plain copy

  1. public void onSwitchChanged(Switch switchView, boolean isChecked) {
  2.      // Show toast message if Bluetooth is not allowed in airplane mode  
    
  3.      if (isChecked &&  
    
  4.              !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {  
    
  5.          Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();  
    
  6.          // Reset switch to off  
    
  7.          switchView.setChecked(false);  
    
  8.      }  
    
  9.     MetricsLogger.action(mContext, MetricsLogger.ACTION_BLUETOOTH_TOGGLE, isChecked);  
    
  10.     if (mLocalAdapter != null) {  
    
  11.      //在switch被check时去更新本地蓝牙状态(打开或者关闭)  
    
  12.        mLocalAdapter.setBluetoothEnabled(isChecked);  
    
  13.     }  
    
  14.    //设置switch不可点击  
    
  15.      mSwitch.setEnabled(false);  
    
  16. }  
    

在蓝牙状态发生改变时会发送广播BluetoothAdapter.ACTION_STATE_CHANGED,接受到广播后,程序会调用handleStateChanged方法对switch进行更新。
接下来回过头来接着分析BluetoothSetitngs.java,分析到这里onActivityCreated方法已经分析完毕,接下来继续
2,onConfigurationChanged方法
通过在Androidmanifest清单配置文件的activity节点下配置android:configChanges属性,则在activity形状(可以是size或者orientation )发生改变时会执行该方法。该方法可以避免activity的重新加载
[java] view plain copy

  1. if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
    判断屏幕切换为横屏时的处理。布局title,switchbar,actionbar(返回键)
    3,addPreferencesForActivity方法
    [java] view plain copy
  2. addPreferencesFromResource(R.xml.bluetooth_settings);

加载界面布局,可以看出蓝牙UI的xml布局文件为Bluetooth_settings.xml;
[java] view plain copy

  1. setHasOptionsMenu(true)
    允许创建菜单
    4,onResume方法
    [java] view plain copy
  2. if (isUiRestricted()) {
  3.          setDeviceListGroup(getPreferenceScreen());  
    
  4.          removeAllDevices();  
    
  5.          mEmptyView.setText(R.string.bluetooth_empty_list_user_restricted);  
    
  6.          return;  
    
  7.      }  
    

这句话是如果用户无权更改蓝牙设置时的处理,所有蓝牙相关的设置都无权更改
[java] view plain copy

  1. getActivity().registerReceiver(mReceiver, mIntentFilter);

注册广播,广播监听的action为BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED,当蓝牙名称发生改变时,会对显示本地蓝牙的preference信息进行更改,更改操作如下,信息显示在preference的summary
[java] view plain copy

  1. if (mLocalAdapter.isEnabled() && mMyDevicePreference != null) {
  2.              mMyDevicePreference.setSummary(context.getResources().getString(  
    
  3.                          R.string.bluetooth_is_visible_message, mLocalAdapter.getName()));  
    
  4.          }  
    

[java] view plain copy

  1. updateContent(mLocalAdapter.getBluetoothState());
    这句代码很关键,用来布局蓝牙界面,蓝牙布局的话可用设备和已配对设备基本都没什么改变,但是用来显示本机信息的preference显示在最后,而且只显示summary信息
    5,onCreateOptionsMenu方法
    添加菜单
    [java] view plain copy
  2. menu.add(Menu.NONE, MENU_ID_SCAN, 0, textId)//添加扫描菜单
  3.       .setEnabled(bluetoothIsEnabled && !isDiscovering)  
    
  4.       .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);  
    
  5. menu.add(Menu.NONE, MENU_ID_RENAME_DEVICE, 0, R.string.bluetooth_rename_device)
  6.       .setEnabled(bluetoothIsEnabled)//重命名设备  
    
  7.       .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);  
    
  8. menu.add(Menu.NONE, MENU_ID_SHOW_RECEIVED, 0, R.string.bluetooth_show_received_files)
  9.      .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);//显示接收到的文件  
    

6,onDevicePreferenceClick方法
为preference添加点击事件,当点击已配对设备或者可用设备时首先停止扫描,然后判断是已配对设备还是可用设备,进而进行连接或者配对操作
[java] view plain copy

  1. mLocalAdapter.stopScanning();
  2.   super.onDevicePreferenceClick(btPreference);  
    

7,onBluetoothStateChanged方法
当蓝牙状态发生改变时-----turn/off,会触发该方法,这是因为该方法继承与父类DeviceListPreferenceFragment,在BluetoothEventManager中对蓝牙状态改变进行了监听,当蓝牙状态改变时会调用该方法
[java] view plain copy

  1. if (BluetoothAdapter.STATE_ON == bluetoothState)
  2.          mInitiateDiscoverable = true;  
    
  3.      updateContent(bluetoothState);  
    

蓝牙状态改变时首先判断是否处于开启状态,如果处于开启状态,则将可检测性的开关打开
只要状态发生改变,都会对蓝牙界面的设备的preference进行更新
8,onScanningStateChanged方法
调用机制:在BluetoothEventManager方法中对蓝牙的扫描状态进行监听,当扫描状态发生改变时会调用该方法
[java] view plain copy

  1. if (getActivity() != null) {
  2.         getActivity().invalidateOptionsMenu();  
    
  3.     }  
    

用来重新加载menu,这是因为menu上有个扫描按钮,需要根据扫描状态来更新扫描按钮的可点击性
9,onDeviceBondStateChanged方法
当配对状态发生改变时会调用该方法,清除设备列表,根据蓝牙的状态重新加载

猜你喜欢

转载自blog.csdn.net/Duke_T/article/details/88398850