我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android后台启动Activity的实现示例

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android后台启动Activity的实现示例

概述

前几天产品提了一个需求,想在后台的时候启动我们 APP 的一个 Activity,随着 Android 版本的更新,以及各家 ROM 厂商的无限改造,这种影响用户体验的功能许多都受到了限制,没办法,虽然是比较流氓的功能,但拿人钱财替人消灾,于是开启了哼哧哼哧的调研之路。

原生Android ROM

首先从 Android 的原生 ROM 开始,根据官方的介绍,后台启动 Activity 的限制是从 Android 10(API 29) 才开始的,在此之前原生 ROM 是没有这个限制的,于是我分别启动了一个 Android 9(API 28) 和 10(API 29) 版本的模拟器,发现在 API 28 上可以直接从后台启动 Activity,而在 API 29 上则受到了限制无法直接启动。参照官方 从后台启动 Activity 的限制 的说明,给出了一些不受限制的例外情况,此外官方的推荐是对于后台启动的需求,先向用户展示一个 Notification 而不是直接启动 Activity,然后在用户点击 Notification 后才处理对应的逻辑。还可以在设置 Notification 时通过 setFullScreenIntent 添加一个全屏 Intent 对象,该方法经过测试,可以在 Android 10 的模拟器上从后台启动一个 Activity 界面(需要 android.permission.USE_FULL_SCREEN_INTENT 权限)。代码如下:


object NotificationUtils {
    private const val ID = "channel_1"
    private const val NAME = "notification"

    private var manager: NotificationManager? = null

    private fun getNotificationManagerManager(context: Context): NotificationManager? {
        if (manager == null) {
            manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
        }
        return manager
    }

    fun sendNotificationFullScreen(context: Context, title: String?, content: String?) {
        if (Build.VERSION.SDK_INT >= 26) {
            clearAllNotification(context)
            val channel = NotificationChannel(ID, NAME, NotificationManager.IMPORTANCE_HIGH)
            channel.setSound(null, null)
            getNotificationManagerManager(context)?.createNotificationChannel(channel)
            val notification = getChannelNotificationQ(context, title, content)
            getNotificationManagerManager(context)?.notify(1, notification)
        }
    }

    private fun clearAllNotification(context: Context) {
        getNotificationManagerManager(context)?.cancelAll()
    }

    private fun getChannelNotificationQ(context: Context, title: String?, content: String?): Notification {
        val fullScreenPendingIntent = PendingIntent.getActivity(
            context,
            0,
            DemoActivity.genIntent(context),
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        val notificationBuilder = NotificationCompat.Builder(context, ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle(title)
            .setContentText(content)
            .setSound(null)
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .setCategory(Notification.CATEGORY_CALL)
            .setOngoing(true)
            .setFullScreenIntent(fullScreenPendingIntent, true)
        return notificationBuilder.build()
    }
}

到现在,整体上感觉还是不错的,现阶段的 Android 原生 ROM 都能正常地从后台启动 Activity 界面,无论是 Android 9 还是 10 版本,都美滋滋。

定制化ROM

问题开始浮出水面,由于各大厂商对 Android 的定制化各有不一,而 Android 并没有继承 GPL 协议,它使用的是 Apache 开源许可协议,即第三方厂商在修改代码后可以闭源,因此也无法得知厂商 ROM 的源码到底做了哪些修改。有的机型增加了一项权限——后台弹出界面,比如说在 MIUI 上便新增了这项权限且默认是关闭的,除非加入了它们的白名单,小米开放平台的文档 里有说明:该权限默认为拒绝的,既为应用默认不允许在后台弹出页面,针对特殊应用会提供白名单,例如音乐(歌词显示)、运动、VOIP(来电)等;白名单应用一旦出现推广等恶意行为,将永久取消白名单。

检测后台弹出界面权限

在小米机型上,新增的这个 后台弹出界面 的权限是在 AppOpsService 里扩展了新的权限,查看 AppOpsManager 源代码,可以在里面看到许多熟悉的常量:


@SystemService(Context.APP_OPS_SERVICE)
public class AppOpsManager {
    public static final int OP_GPS = 2;
    public static final int OP_READ_CONTACTS = 4;
    // ...
}

因此可以通过 AppOpsService 来检测是否具有 后台弹出界面 的权限,那么这个权限对应的 OpCode 是啥呢?网上有知情人士透露这个权限的 Code 是 10021,因此可以使用 AppOpsManager.checkOpNoThrow 或 AppOpsManager.noteOpNoThrow 等系列的方法检测该权限是否存在,不过这些方法都是 @hide 标识的,需要使用反射:


fun checkOpNoThrow(context: Context, op: Int): Boolean {
    val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    try {
        val method: Method = ops.javaClass.getMethod(
            "checkOpNoThrow", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java
        )
        val result = method.invoke(ops, op, myUid(), context.packageName) as Int
        return result == AppOpsManager.MODE_ALLOWED
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return false
}

fun noteOpNoThrow(context: Context, op: Int): Int {
    val ops = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    try {
        val method: Method = ops.javaClass.getMethod(
            "noteOpNoThrow", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java
        )
        return method.invoke(ops, op, myUid(), context.packageName) as Int
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return -100
}

另外如果想知道其它新增权限的 code, 可以通过上面的方法去遍历某个范围(如10000~10100)内的 code 的权限,然后手机操作去开关想要查询的权限,根据遍历的结果,就大致可以得到对应权限的 code 了。

Android P后台启动权限

在小米 Max3 上测试发现了两种方式可以实现从后台启动 Activity 界面,其系统是基于 Android 9 的 MIUI 系统。

方式一:moveTaskToFront

这种方式不算是直接从后台启动 Activity,而是换了一个思路,在后台启动目标 Activity 之前先将应用切换到前台,然后再启动目标 Activity,如果有必要的话,还可以通过 Activity.moveTaskToBack 方法将之前切换到前台的 Activity 重新移入后台,经过测试,在 Android 10 上这个方法已经失效了...但是 10 以下的版本还是可以抢救一下的(需要声明 android.permission.REORDER_TASKS 权限)。

启动目标 Activity 之前先判断一下应用是否在后台,判断方法可以借助 ActivityManager.getRunningAppProcesses 方法或者 Application.ActivityLifecycleCallbacks 来监听前后台,这两种方法网上都有文章讲解,就不赘述了。直接贴出后台切换到前台的代码:


fun moveToFront(context: Context) {
    val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
    activityManager?.getRunningTasks(100)?.forEach { taskInfo ->
        if (taskInfo.topActivity?.packageName == context.packageName) {
            Log.d("LLL", "Try to move to front")
            activityManager.moveTaskToFront(taskInfo.id, 0)
            return
        }
    }
}

fun startActivity(activity: Activity, intent: Intent) {
    if (!isRunningForeground(activity)) {
        Log.d("LLL", "Now is in background")
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            // TODO 防止 moveToFront 失败,可以多尝试调用几次
            moveToFront(activity)
            activity.startActivity(intent)
            activity.moveTaskToBack(true)
        } else {
            NotificationUtils.sendNotificationFullScreen(activity, "", "")
        }
    } else {
        Log.d("LLL", "Now is in foreground")
        activity.startActivity(intent)
    }
}

方式二:Hook

由于 MIUI 系统不开源,因此尝试再研究研究 AOSP 源码,死马当活马医看能不能找到什么蛛丝马迹。首先从 Activity.startActivity 方法开始追,如果阅读过 Activity 启动源码流程的话可以知道 Activity.startActivity 或调用到 Instrumentation.execStartActivity 中,然后通过 Binder 调用到 AMS 相关的方法,权限认证就在 AMS 中完成,如果权限不满足自然启动就失败了(Android 10)。


// APP 进程
public ActivityResult execStartActivity(Context who, IBinder contextThread, ...) {
    // ...
    // 这里会通过 Binder 调用到 AMS 相关的代码
    int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(),
            intent, intent.resolveTypeIfNeeded(who.getContentResolver()),
            token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
    // ...
}

// system_server进程
// AMS
public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
    String resolvedType, IBinder resultTo, String resultWho, int requestCode,
    int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
    // ...
}

看一下这几个参数:

  • caller: AMS 在完成相关任务后会通过它来 Binder 调用到客户端 APP 进程来实例化 Activity 对象并回调其生命周期方法,caller 的 Binder 服务端位于 APP 进程。
  • callingPackage: 这个参数标识调用者包名。
  • ...

这里可以尝试 Hook 一些系统的东西,具体怎么 Hook 的代码先不给出了,经过测试在 Android 9 的小米设备上可以成功,有兴趣可以自行研究谈论哈,暂时不公开了,有需要的同学可以留言告诉我。或者反编译小米 ROM 源码,可以从里面发现一些东西。

Android Q后台启动权限

在上面介绍过 Android Q 版本开始原生系统也加入了后台启动的限制,通过通知设置 fullScreenIntent 可以在原生 Android 10 系统上从后台启动 Activity。查看 AOSP 源码,可以在 AMS 找到这部分后台权限限制的代码,上面讲到 startActivity 的流程,在 APP 进程发起请求后,会通过 Binder 跨进程调用到 system_server 进程中的 AMS,然后调用到 ActivityStarter.startActivity 方法,关于后台启动的限制就这这里:


// 好家伙,整整二十多个参数,嘿嘿,嘿嘿
private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
        String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
        String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
        SafeActivityOptions options,
        boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
        TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
        PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
    // ...
    boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
        requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity,
        inTask != null, callerApp, resultRecord, resultStack);
    abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
            callingPid, resolvedType, aInfo.applicationInfo);
    abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
            callingPackage);

    boolean restrictedBgActivity = false;
    if (!abort) {
        restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                originatingPendingIntent, allowBackgroundActivityStart, intent);
    }
    // ...
}

这里的 shouldAbortBackgroundActivityStart 调用是在 Android Q 中新增的,看方法名就能菜刀这是针对后台启动的:


boolean shouldAbortBackgroundActivityStart(...) {
    final int callingAppId = UserHandle.getAppId(callingUid);
    if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
            || callingAppId == Process.NFC_UID) {
        return false;
    }
    if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
        return false;
    }
    // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
    if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
            == PERMISSION_GRANTED) {
        return false;
    }
    // don't abort if the caller has the same uid as the recents component
    if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
        return false;
    }
    // don't abort if the callingUid is the device owner
    if (mService.isDeviceOwner(callingUid)) {
        return false;
    }
    // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
    if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
        Slog.w(TAG, "Background activity start for " + callingPackage
                + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
        return false;
    }
    // ...
}

从这个方法可以看到后台启动的限制和官方文档 从后台启动 Activity 的限制 中的说明是可以对应上的,这里面都是针对 uid 去做权限判断的,且是在系统进程 system_server 中完成,单纯更改包名已经没用了。。。

在一些没有针对后台启动单独做限制的 ROM 上通过 全屏通知 可以成功弹出后台 Activity 页面,比如说小米 A3,另外还有一台 vivo 和一台三星手机,具体机型忘记了;在做了限制的设备上则弹不出来,比如说红米 Note 8 Pro。

对于红米 Note 8 Pro 这块硬骨头,不停尝试了好多方法,但其实都是碰运气的,因为拿不到 MIUI 的源码,后来想转变思路,是否可以尝试从这台手机上 pull 出相关的 framework.jar 包然后反编译呢?说不定就有收获!不过需要 Root 手机,这个好办,小米自己是有提供可以 Root 的开发版系统的,于是就去 MIUI 官网找了一下,发现这台红米 Note 8 Pro 机型没有提供开发版系统(笑哭),想起来好像之前是说过低端机小米不再提供开发版了。。。好吧,手里头没有其它可以尝试的手机了。

再转念一想,是否可以直接下载稳定版的 ROM 包,解压后有没有工具能够得到一些源码相关的痕迹呢?于是下载了一个 ROM.zip 后,解压看到里面只有一些系统映像 img 文件和 .dat.br 文件,这一块我还不太懂,猜想就算能得到我想要的东西,整套流程花费的时间成本估计也超出预期了,所以暂时只能先放下这个想法了。后续有足够的时间再深入研究研究吧。

总结

原生Android ROM

Android 原生 ROM 都能正常地从后台启动 Activity 界面,无论是 Android 9(直接启动) 还是 10 版本(借助全屏通知)。

定制化ROM

检测后台弹出界面权限:

  • 通过反射 AppOpsManager 相关方法检测对应 opCode 的权限;
  • opCode = 10021(小米机型);
  • 其它机型可以尝试遍历得到 opCode;

Android P版本的小米:

  • 通过Hook相关参数来后台启动Activity,代码由于某些原因不能给出了,有需要的同学可以留言告诉我哈;
  • 只测试过小米机型,其它机型不一定可用;
  • 理论上 P 版本以下的小米应该也支持;

Android P版本的机型:

  • 通过 moveTaskToFront 方法将应用切换到前台;
  • 这种方法毕竟是官方 API,因此兼容性可能更好一些;
  • 如果切换失败的话可以多尝试几次调用 moveTaskToFront 方法;
  • 理论上 P 版本以下的机型应该也支持;

Android Q版本的机型:

  • 通过系统全屏通知的方式调起后台 Activity;
  • 在一些另作了限制的 ROM 上可能调起失败;

至于反编译 MIUI 代码的方式只是一个猜想,时间原因未能付诸行动。看样子产品哥哥的需求暂时不能完全实现了,不知道有没有做过相关研究(或者知道内情)的小伙伴能不能提供一些参考思路,虽然是一个比较流氓的功能,但是代码是无罪的嘿嘿,朝着一个需求目标,为此思考解决方法,并从各个方向去调研,我觉得本身是一件有意思也有提升的事情!欢迎有过相关研究的同学在评论区提出建议,做好需求奥里给。

以上就是Android后台启动Activity的实现示例的详细内容,更多关于Android后台启动Activity的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android后台启动Activity的实现示例

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android Activity启动模式之singleTop实例详解

本文实例讲述了Android Activity启动模式之singleTop。分享给大家供大家参考,具体如下: 在前面文章《Android Activity启动模式之standard实例详解》中,我们介绍了活动的默认启动模式standard,
2022-06-06

Android Activity启动模式之standard实例详解

本文实例讲述了Android Activity启动模式之standard。分享给大家供大家参考,具体如下: Android的活动是通过任务Task来进行管理的,一个任务就是一组放在栈里的活动的集合,即所谓的返回栈(Back Stack)。栈
2022-06-06

Android Activity启动模式之singleTask实例详解

本文实例分析了Android Activity启动模式之singleTask。分享给大家供大家参考,具体如下: 前面的文章介绍了Android 活动Activity的启动模式:standard 和singleTop 。本文继续介绍Activ
2022-06-06

Android冷启动实现app秒开的示例分析

这篇文章将为大家详细讲解有关Android冷启动实现app秒开的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。具体如下:AndroidManifest里对应activity添加属性android:
2023-05-30

Android基于service实现音乐的后台播放功能示例

本文实例讲述了Android基于service实现音乐的后台播放功能。分享给大家供大家参考,具体如下: Service是一个生命周期长且没有用户界面的程序,当程序在各个activity中切换的时候,我们可以利用service来实现背景音乐的
2022-06-06

Android 后台启动startService()相关问题的解决

需求 有一个用户需要这样一个功能,要求是APP能在充电的时候自动进入APP的一个界面 我寻思着,这玩意用普通权限做不了呀,不过APP有root权限倒也无妨,于是便决定采用Service去做后台服务 问题 某天,在Bugly看到如下的错误报告
2023-08-17

Android补间动画的实现示例

本文主要介绍了Android补间动画的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-17

详解Android中Activity的四大启动模式实验简述

作为Android四大组件之一,Activity可以说是最基本也是最常见的组件,它提供了一个显示界面,从而实现与用户的交互,作为初学者,必须熟练掌握。今天我们就来通过实验演示,来帮助大家理解Activity的四大启动模式。 演示效果如下:第
2022-06-06

Android中实现开机自动启动服务(service)实例

最近在将 HevSocks5Client 移植到 Android 上了,在经过增加 signalfd 和 timerfd 相关的系统调用支持后,就可以直接使用 NDK 编译出 executable 了。直接的 native exectuab
2022-06-06

C#实现启动项管理的示例代码

这篇文章主要为大家详细介绍了如何利用C#实现启动项管理,文中的示例代码讲解详细,对我们学习C#有一定的帮助,感兴趣的小伙伴可以了解一下
2022-12-15

Android实现循环平移动画示例

实现用一张背景图做循环从左往右平移动画。 1、实现两个animation xml文件,一个起始位置在-100%p ,一个在0%p。设置repeat属性为循环,重复。代码如下: 2022-06-06

Linux中jar包启动和jar包后台运行的实现方式

linux 运行jar包命令如下: 方式一: java -jar shareniu.jar 特点:当前ssh窗口被锁定,可按CTRL + C打断程序运行,或直接关闭窗口,程序退出 那如何让窗口不锁定? 方式二 jawww.cppcns.co
2022-06-04

Android软件启动动画及动画结束后跳转的实现方法

本文实例讲述了Android软件启动动画及动画结束后跳转的实现方法。分享给大家供大家参考,具体如下: 自己写了个小程序,软件启动时,先显示几张图片,每3秒显示一张,图片显示完跳转到首页 1. 图片轮播使用Gallery,用法很简单Galle
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录