一篇文章看明白 Android 从点击应用图标到界面显示的过程

Android - Activity 启动过程

概述

从点击桌面应用图标到应用显示的过程我们再熟悉不过了,下面我们来分析下这个过程都做了什么。

本文主要对以下问题分析:

  • ActivityThread 是什么,它是一个线程吗,如何被启动的?
  • ActivityClientRecord 与 ActivityRecord 是什么?
  • Context 是什么,ContextImpl,ContextWapper 是什么?
  • Instrumentation 是什么?
  • Application 是什么,什么时候创建的,每个应用程序有几个 Application?
  • 点击 Launcher 启动 Activity 和应用内部启动 Activity 的区别?
  • Activity 启动过程,onCreate(),onResume() 回调时机及具体作用?

Launcher

如不了解 Android 是如何从开机到 Launcher 启动的过程,请先阅读Android - 系统启动过程

这里写图片描述

我们知道 Android 系统启动后已经启动了 Zygote,ServiceManager,SystemServer 等系统进程;ServiceManager 进程中完成了 Binder 初始化;SystemServer 进程中 ActivityManagerService,WindowManagerService,PackageManagerService 等系统服务在 ServiceManager 中已经注册;最后启动了 Launcher 桌面应用。

其实 Launcher 本身就是一个应用程序,运行在自己的进程中,我们看到的桌面就是 Launcher 中的一个 Activity。

应用安装的时候,通过 PackageManagerService 解析 apk 的 AndroidManifest.xml 文件,提取出这个 apk 的信息写入到 packages.xml 文件中,这些信息包括:权限、应用包名、icon、apk 的安装位置、版本、userID 等等。packages.xml 文件位于系统目录下/data/system/packages.xml。

同时桌面 Launcher 会为安装过的应用生成不同的应用入口,对应桌面上的应用图标,下面分析点击应用图标的到应用启动的过程。

点击 Launcher 中应用图标

这里写图片描述

点击 Launcher 中应用图标将会执行以下方法

Launcher.startActivitySafely()
Launcher.startActivity()
//以上两个方法主要是检查将要打开的 Activity 是否存在

Activity.startActivity()
//这段代码大家已经很熟悉,经常打开 Activity 用的就是这个方法

Activity.startActivityForResult()
//默认 requestCode = -1,也可通过调用 startActivityForResult() 传入 requestCode。 
//然后通过 MainThread 获取到 ApplicationThread 传入下面方法。

Instrumentation.execStartActivity()
//通过 ActivityManagerNative.getDefault() 获取到 ActivityManagerService 的代理为进程通讯作准备。

ActivityManagerNative.getDefault().startActivity()
ActivityManagerProxy.startActivity()
//调用代理对象的 startActivity() 方法,发送 START_ACTIVITY_TRANSACTION 命令。

在 system_server 进程中的服务端 ActivityManagerService 收到 START_ACTIVITY_TRANSACTION 命令后进行处理,调用 startActivity() 方法。

ActivityManagerService.startActivity() -> startActivityAsUser(intent, requestCode, userId)
//通过 UserHandle.getCallingUserId() 获取到 userId 并调用 startActivityAsUser() 方法。

ActivityStackSupervisor.startActivityMayWait() -> resolveActivity()
//通过 intent 创建新的 intent 对象,即使之前 intent 被修改也不受影响。 然后调用 resolveActivity()。
//然后通过层层调用获取到 ApplicationPackageManager 对象。

PackageManagerService.resolveIntent() -> queryIntentActivities()
//获取 intent 所指向的 Activity 信息,并保存到 Intent 对象。

PackageManagerService.chooseBestActivity()
//当存在多个满足条件的 Activity 则会弹框让用户来选择。

ActivityStackSupervisor.startActivityLocked()
//获取到调用者的进程信息。 通过 Intent.FLAG_ACTIVITY_FORWARD_RESULT 判断是否需要进行 startActivityForResult 处理。 
//检查调用者是否有权限来调用指定的 Activity。 
//创建 ActivityRecord 对象,并检查是否运行 App 切换。

ActivityStackSupervisor.startActivityUncheckedLocked() -> startActivityLocked()
//进行对 launchMode 的处理[可参考 Activity 启动模式],创建 Task 等操作。
//启动 Activity 所在进程,已存在则直接 onResume(),不存在则创建 Activity 并处理是否触发 onNewIntent()。

ActivityStack.resumeTopActivityInnerLocked()
//找到 resume 状态的 Activity,执行 startPausingLocked() 暂停该 Activity,同时暂停所有处于后台栈的 Activity,找不到 resume 状态的 Activity 则回桌面。
//如果需要启动的 Activity 进程已存在,直接设置 Activity 状态为 resumed。 调用下面方法。

ActivityStackSupervisor.startSpecificActivityLocked()
//进程存在调用 realStartActivityLocked() 启动 Activity,进程不存在则调用下面方法。

fork 新进程

从 Launcher 点击图标,如果应用没有启动过,则会 fork 一个新进程。创建新进程的时候,ActivityManagerService 会保存一个 ProcessRecord 信息,Activity 应用程序中的AndroidManifest.xml 配置文件中,我们没有指定 Application 标签的 process 属性,系统就会默认使用 package 的名称。每一个应用程序都有自己的 uid,因此,这里 uid + process 的组合就可以为每一个应用程序创建一个 ProcessRecord。每次在新建新进程前的时候会先判断这个 ProcessRecord 是否已存在,如果已经存在就不会新建进程了,这就属于应用内打开 Activity 的过程了。

ActivityManagerService.startProcessLocked()
//进程不存在请求 Zygote 创建新进程。 创建成功后切换到新进程。

进程创建成功切换至 App 进程,进入 app 进程后将 ActivityThread 类加载到新进程,并调用 ActivityThread.main() 方法

ActivityThread.main()
//创建主线程的 Looper 对象,创建 ActivityThread 对象,ActivityThread.attach() 建立 Binder 通道,开启 Looper.loop() 消息循环。

ActivityThread.attach()
//开启虚拟机各项功能,创建 ActivityManagerProxy 对象,调用基于 IActivityManager 接口的 Binder 通道 ActivityManagerProxy.attachApplication()。

ActivityManagerProxy.attachApplication()
//发送 ATTACH_APPLICATION_TRANSACTION 命令

此时只创建了应用程序的 ActivityThread 和 ApplicationThread,和开启了 Handler 消息循环机制,其他的都还未创建, ActivityThread.attach(false) 又会最终到 ActivityMangerService 的 attachApplication,这个工程其实是将本地的 ApplicationThread 传递到 ActivityMangerService。然后 ActivityMangerService 就可以通过 ApplicationThread 的代理 ApplicationThreadProxy 来调用应用程序 ApplicationThread.bindApplication,通知应用程序的 ApplicationThread 已和 ActivityMangerService 绑定,可以不借助其他进程帮助直接通信了。此时 Launcher 的任务也算是完成了。

在 system_server 进程中的服务端 ActivityManagerService 收到 ATTACH_APPLICATION_TRANSACTION 命令后进行处理,调用 attachApplication()。

ActivityMangerService.attachApplication() -> attachApplicationLocked()
//首先会获取到进程信息 ProcessRecord。 绑定死亡通知,移除进程启动超时消息。 获取到应用 ApplicationInfo 并绑定应用 IApplicationThread.bindApplication(appInfo)。
//然后检查 App 所需组件。
  • Activity: 检查最顶层可见的 Activity 是否等待在该进程中运行,调用 ActivityStackSupervisor.attachApplicationLocked()。
  • Service:寻找所有需要在该进程中运行的服务,调用 ActiveServices.attachApplicationLocked()。
  • Broadcast:检查是否在这个进程中有下一个广播接收者,调用 sendPendingBroadcastsLocked()。

此处讨论 Activity 的启动过程,只讨论 ActivityStackSupervisor.attachApplicationLocked() 方法。

ActivityStackSupervisor.attachApplicationLocked() -> realStartActivityLocked()
//将该进程设置为前台进程 PROCESS_STATE_TOP,调用 ApplicationThreadProxy.scheduleLaunchActivity()。

ApplicationThreadProxy.scheduleLaunchActivity()
//发送 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令

发送送完 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令,还会发送 BIND_APPLICATION_TRANSACTION 命令来创建 Application。

ApplicationThreadProxy.bindApplication()
//发送 BIND_APPLICATION_TRANSACTION 命令

App 进程初始化

在 app 进程中,收到 BIND_APPLICATION_TRANSACTION 命令后调用 ActivityThread.bindApplication()。

ActivityThread.bindApplication()
//缓存 Service,初始化 AppBindData,发送消息 H.BIND_APPLICATION。

ApplicationThreadProxy.bindApplication(…) 会传来这个应用的一些信息,如ApplicationInfo,Configuration 等,在 ApplicationThread.bindApplication 里会待信息封装成A ppBindData,通过

sendMessage(H.BIND_APPLICATION, data)

将信息放到应用里的消息队列里,通过 Handler 消息机制,在 ActivityThread.handleMeaasge 里处理 H.BIND_APPLICATION 的信息,调用 AplicationThread.handleBindApplication。

handleBindApplication(AppBindData data) {
    Process.setArgV0(data.processName);//设置进程名
    ...
    //初始化 mInstrumentation
    if(data.mInstrumentation!=null) {
        mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    } else {
        mInstrumentation = new Instrumentation();
    }
    //创建Application,data.info 是个 LoadedApk 对象。
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    mInitialApplication = app;
    //调用 Application 的 onCreate()方法。
    mInstrumentation.callApplicationOnCreate(app);
}

public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {

    if (mApplication != null) {   
       return mApplication;
    }

    String appClass = mApplicationInfo.className;
    java.lang.ClassLoader cl = getClassLoader();

    //此时新建一个 Application 的 ContextImpl 对象,
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

    //通过在 handleBindApplication 创建的 mInstrumentation 对象新建一个 Application 对象,同时进行 attach。
    app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
    appContext.setOuterContext(app);
}

//设置进程名,获取 LoadedApk 对象,创建 ContextImpl 上下文
//LoadedApk.makeApplication() 创建 Application 对象,调用 Application.onCreate() 方法。

Instrumentation:

public Application newApplication(ClassLoader cl, String className, Context context) {    
    return newApplication(cl.loadClass(className), context);
}
Instrumentation类:
static public Application newApplication(Class<?> clazz, Context context)  {
    //实例化 Application
    Application app = (Application)clazz.newInstance();     

    // Application 和 context绑定
    app.attach(context);    
    return app;
}
//attach 就是将新建的 ContextImpl 赋值到 mBase,这个 ContextImpl 对象就是所有Application 内 Context 的具体实现,同时赋值一些其他的信息如 mLoadedApk。
final void attach(Context context) {    
    mBase = base;  
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

这时 Application 就创建好了,这点很重要,很多资料里说 Application 是在performLaunchActivity() 里创建的,因为 performLaunchActivity() 也有mInstrumentation.newApplication 这个调用,newApplication() 函数中可看出会先判断是否以及创建了 Application,如果之前已经创建,就返回已创建的 Application 对象。

Activity 启动

上面 fork 进程时会发送 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令,在 app 进程中,收到 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令后调用 ApplicationThread.scheduleLaunchActivity()。

ApplicationThread.scheduleLaunchActivity()
//发送消息 H.LAUNCH_ACTIVITY。

sendMessage(H.LAUNCH_ACTIVITY, r);

ActivityThread.handleLaunchActivity()
//最终回调目标 Activity 的 onConfigurationChanged(),初始化 WindowManagerService。
//调用 ActivityThread.performLaunchActivity()

ActivityThread.performLaunchActivity() {
    //类似 Application 的创建过程,通过 classLoader 加载到 activity.
    activity = mInstrumentation.newActivity(classLoader, 
               component.getClassName(), r.intent);
    //因为 Activity 有界面,所以其 Context 是 ContextThemeWrapper 类型,但实现类仍是ContextImpl.
    Context appContext = createBaseContextForActivity(r, activity);
    activity.attach(context,mInstrumentation,application,...);
    //与 Window 进行关联

    //attach 后调用 activity 的 onCreate()方法。
    mInstrumentation.callActivityOnCreate(activity,...)

}
//在ActivityThread.handleLaunchActivity里,接着调用

Activity.performCreate() -> onCreate()
//最终回调目标 Activity 的 onCreate()。

Activity.setContentView()
//设置 layout 布局

ActivityThread.performResumeActivity()
//最终回调目标 Activity 的 onResume()。

与 Window 进行关联,具体过程详见:Activity,Window,View 之间的关系

总结

Activity 的整体启动流程如图所示:

这里写图片描述

  • ActivityThread 是什么,它是一个线程吗,如何被启动的?

它不是一个线程,它是运行在 App 进程中的主线程中的一个方法中。当 App 进程创建时会执行 ActivityThread.main(),ActivityThread.main() 首先会创建 Looper 执行 Looper.prepareMainLooper();然后创建 ActivityThread 并调用 ActivityThread.attach() 方法告诉 ActivityManagerService 我们创建了一个应用 并将 ApplicationThread 传给 ActivityManagerService;最后调用 Looper.loop()。

  • ActivityClientRecord 与 ActivityRecord 是什么?

记录 Activity 相关信息,比如:Window,configuration,ActivityInfo 等。
ActivityClientRecord 是客户端的,ActivityRecord 是 ActivityManagerService 服务端的。

  • Context 是什么,ContextImpl,ContextWapper 是什么?

Context 定义了 App 进程的相关环境,Context 是一个接口,ContextImpl 是子类,ContextWapper 是具体实现。

应用资源是在 Application 初始化的时候,也就是创建 Application,ContextImpl 的时候,ContextImpl 就包含这个路径,主要就是对就是 ResourcesManager 这个单例的引用。

可以看出每次创建 Application 和 Acitvity 以及 Service 时就会有一个 ContextImpl 实例,ContentProvider 和B roadcastReceiver 的 Context 是其他地方传入的。

所以 Context 数量 = Application 数量 + Activity 数量 + Service 数量,单进程情况下 Application 数量就是 1。

  • Instrumentation 是什么?

管理着组件Application,Activity,Service等的创建,生命周期调用。

  • Application 是什么,什么时候创建的,每个应用程序有几个 Application?

Application 是在 ActivityThread.handleBindApplication() 中创建的,一个进程只会创建一个 Application,但是一个应用如果有多个进程就会创建多个 Application 对象。

  • 点击 Launcher 启动 Activity 和应用内部启动 Activity 的区别?

点击 Launcher 时会创建一个新进程来开启 Activity,而应用内打开 Activity,如果 Activity 不指定新进程,将在原来进程打开,是否开启新进程实在 ActivityManagerService 进行控制的,上面分析得到,每次开启新进程时会保存进程信息,默认为 应用包名 + 应用UID,打开 Activity 时会检查请求方的信息来判断是否需要新开进程。Launcher 打开 Activity 默认 ACTIVITY_NEW_TASK,新开一个 Activity 栈来保存 Activity 的信息。

  • Activity 启动过程,onCreate(),onResume() 回调时机及具体作用?

Activity.onCreate() 完成了 App 进程,Application,Activity 的创建,调用 setContentView() 给 Activity 设置了 layout 布局。

Activity.onResume() 完成了 Activity 中 Window 与 WindowManager 的关联,并对所有子 View 进行渲染并显示。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前言 第一部分 准备篇 第1章 Android开发简介 1.1 Android基本概念 1.1.1 Android简介 1.1.2 Android的系统构架 1.1.3 Android应用程序框架 1.2 OMS介绍 1.2.1 OPhone介绍 1.2.2 Widget介绍 1.3 小结 第2章 Android开发环境搭建 2.1 Android开发准备工作 2.2 开发包及其工具的安装和 配置 2.2.1 安装JDK和配置Java开发 环境 2.2.2 Eclipse的安装与汉化 2.2.3 SDK和ADT的安装和 配置 2.3 创建第一个Android项目——HeUoAndroid 2.3.1 创建HelloAndroid项目 2.3.2 运行HelloAndroid及模拟器的使用 2.3.3 调试HelloAndroid 2.4 小结 第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命周期 3.3 Android程序U设计 3.4 小结 第4章 用户界面开发 4.1 用户界面开发详解 4.1.1 用户界面简介 4.1.2 事件处理 4.2 常用控件应用 4.2.1 文本框(Textiew) 4.2.2 列表(ListView) 4.2.3 提示(T0ast) 4.2.4 编辑框(EditText) 4.2.5 单项选择(RadioGroup、RadioButton 4.2.6 多项选择(CheckBox) 4.2.7 下拉列表(Spinner) 4.2.8 自动提示(AutoComplete.TextⅥew) 4.2.9 日期和时间(DatePicker、TimePicker) 4.2.10 按钮(Button) 4.2.1l 菜单(Menu) 4.2.12 对话框(Dialog) 4.2.13 图片视图(ImageView) 4.2.14 带图标的按钮(ImageButton) 4.2.15 拖动效果(Gallery) 4.2.16 切换图片(hmgeSwilcher) 4.2.17 网格视图(GridView) 4.2.18 卷轴视图(ScrollView) 4.2.19 进度条(ProgressBar) 4.2.20 拖动条(SeekBar) 4.2.21 状态栏提示(Notification、NotificationManager) 4.2.22 对话框中的进度条(ProgressDialog) 4.3 界面布局 4.3.1 垂直线性布局 4.3.2 水平线性布局 4.3.3.相对布局(RelativeLayout) 4.3.4 表单布局(TableLayout) 4.3.5 切换卡(TabWidget) 4,4 小结 第5章 Android游戏开发 5.1 Android游戏开发框架 5.1.1 View类开发框架 5.1.2 SurfaceView类开发框架 5.2 Graphics类开发 5.5.1 Paint和Color类介绍 5.2.2 Canvas类介绍 5.2.3 几何图形绘制 5.2.4 字符串绘制 5.2.5 图像绘制 5.2.6 图像旋转 5.2.7 图像缩放 5.2.8 图像像素操作 5.2.9 Shader类介绍 5.2.10 双缓冲技术 5.2.11 全屏显示 5.2.12 获得屏幕属性 5.3 动画实现 5.3.1 Tween动画 5.3.2 Frame动画 5.3.3 GIF动画播放 5.4 小结 第6章 Android数据存储 6.1 Android数据存储初探 6.2 数据存储之Shared Preferences 6.3 数据存储之Files 6.4 数据存储之Network 6.5 Android数据库编程 6.5.1 SQLite简介 6.5.2 SQLite编程详解 6.5.3 SQLiteOpenHelper应用 6.6 数据共享(ContentProviders) 6.7 小结 第7 章多媒体开发 7.1 多媒体开发详解 7.1.1 Open Core 7.1.2 MediaPlayer 7.1.3 MediaRecorder 7.2 播放音乐 7.3 播放视频 7.4 录制歌曲 7.5 相机设置 7.6 闹钟设置 7.7 铃声设置 7.8 小结 第8章 网络与通信 8.1 网络通信基础 8.1.1 无线网络技术 8.1.2 Android网络基础 8.2 HTTP通信 8.2.1 HttpURLConnection接口 8.2.2 HttpClient接口 8.2.3 实时更新 8.3 Socket通信 8.3.
APK软件汉化教程 (转载请注明来自藕粉网www.Allphone.com.cn的Smwenzi,谢谢) 随着Android手机种类的不断丰富,各种应用软件也开始遍地开花,Android用起来也越来越爽了……不过有很多软件都是老外编的,虽然英文界面也能用,但是毕竟不如咱们中文看着舒服,特别是一些功能相对比较复杂的软件,中文界面还是很有必要的。因此,本人综合网上多位前辈大侠的教程,加上自己的一点心得,对如何汉化APK软件,进行简要说明。由于我也是个新手,很多地方也许会不完善甚至存在错误,请各位兄弟不吝赐教。 一、概述 目前,网上常用的APK汉化方法有三种: 一是使用Hex Worksho,这种方法操作繁琐、费时费力,而且经常出现搜不到想要修改的字符的情况; 二是使用汉化浪子大侠的Android ResEdit(目前最新版本AndroidResEdit v1.5),这个软件使用简单直观,但是对不在资源文件中的词条无能为力,只能用于简单的汉化; 第三种就是我在这里要向大家推荐的APKTOOL,他的原理是把APK进行反编译,生成程序的源代码和图片、XML配置、语言资源等文件。我们秩序对有关图片和语言资源等文件进行修改,再编译打包成APK并签名,就可以得到汉化版的APK软件了。 二、搭建汉化环境 由于APKTOOL工具是用Java编制的,因此需要电脑中安装了JRE或者JDK(JAVA工具,用JRE即可)。 下载地址:………… 接下来就是我们要用到的关键工具——APKTOOL。这个是我在其他论坛找到的leasea大侠集成的一个批处理,可以省去自己输入指令的麻烦,实现解包、打包、签名自动处理,我自己也作了一点小小的修改,在此谢谢leasea大侠。 下载地址:………… 还有一个辅助工具Notepad++,这是一个功能强大的文本编辑软件,可以在指定文件夹中搜索字符串,对于在不在资源文件中的词条汉化,用这个可以极大减少工作量。 下载地址:………… 安装的步骤很简单了,下载上面三个文件,安装JRE和Notepad++,将APKTool压缩包解压到任意位置,就OK了。 另外,为了调试方便,推荐安装Android相应版本的SDK(或OMS),并建立虚拟机运行环境。当然用手机调试也可以,但是没那么方便而且容易发生不可预料的问题。关于虚拟机环境搭建请参考其他资料。 三、解包软件 将你需要汉化的APK文件,放到APKTOOL文件夹下面,双击运行“解包软件.BAT”,批处理会自动识别APK文件并开始解包,在这里我用FlipSilent(一个翻转静音工具)作为示例。 解包出来的资源存储在“汉化中的资源”文件夹中,原始的APK文件则被备份到“原始软件备份”文件夹中。 打开解包的资源文件,里面有两个文件夹:Res存储的各种资源文件;Smail:存储的是反编译的程序代码。至此解包操作完成。 四、初步汉化 汉化的第一步,是汉化res文件夹里面的程序界面资源。一般来说,这个文件夹里面会包含以下文件夹: Drawable:程序调用的图标以及各种图片; layout-land:竖版界面xml描述文件; layout-port:横版界面xml描述文件; menu:菜单布局xml描述文件; values:字符串资源定义,我们要汉化的绝大部分工作就在这里面; xml:其他不再详细分类的布局xml描述文件。 根据软件的不同,文件夹可能会多些也可能会少些,具体内容大家打开一看就大概能知道是什么了。这里FlipSilent软件相对比较简单,所以只有三个文件夹,如下图: 在values文件夹下的文件: 一般来说,汉化需要处理是arrays.xml和strings.xml这两个文件。如何判断哪些是需要我们翻译的词条呢?有三种方法,综合起来一般就差不离了: 第一个当然是在使用软件中界面出现的词条,这个可以用手机仔细查看,当然有一部分可能很少出现的提示会有遗漏; 第二个凡是一对tag里面的,一般情况下就是需要翻译的英文字符串。格式一般为:<item>*******</item> ,或<string name="****">****</string> 第三个是以大写字母开头的词条,一般都是需要汉化的。 注意:这三条要综合起来运用,特别是在比较大型的软件中,情况会比较复杂,需要自己不断摸索。 在解包FlipSilent软件中,没有arrays.xml文件,因此我们的对象就是strings.xml了。用记事本打开strings.xml文件,内容如下: 对比一下FlipSilent软件运行界面,一切都就都明白了…… 接下来的工作,就是考验各位的英语水准了…… 上面就是我翻译的结果了,水平不行,大家见笑…… 修改完毕了,保存退出。 五、打包签名 改完了strings.xml,下面就是打包看看效果了……有了leasea大侠的批处理,就很简单了,什么都不用管,双击运行“打包签名.BAT”就可以了。 如果出现的是上面的信息,那么恭喜你,过关了…… 如果有多余的信息,并提示你找不到“已汉化签名的软件”文件夹,那么说明你修改不该修改的词条,好好找找吧,这个就没办法一概而论了。 在弹出的资源管理器窗口里面,你就可以看到汉化后的软件了。现在试验一下吧。 注意:试验之前,要删除安装的原版软件,否则会安装失败的。 基本就搞定了……等等,怎么那个About按钮还是英文啊,对于追求完美的人,这是绝对不用需的。 可是在语言资源文件里面,就是找不到这个词啊。接着就是下一节了…… 六、深度汉化 先解释一下为什么还有一些词条没有汉化的原因。 一般而言,一个编写规范的Android程序,会把所有字符串资源都分离出来,放在values的strings.xml文件中,values目录中存放的是默认语言字符串资源(一般为英文)。APK程序在处理字符串资源时会先判断语言环境,然后自己调用对应语言的。可是,有的程序员会自觉不自觉地,将部分文字内容写到程序代码里面,因此就出现了在资源文件中找不到的词条了。 对这部分词条的修改,相对就比较复杂一些了,幸好我们有神兵利器:Notepad++。 打开Notepad++软件,选择菜单中的“搜索”—“在文件中查找”,查找目标中输入要查找的字符串,这里为“About”,在目录中,选择解包后的“汉化中的资源”文件夹中的“smali”文件夹,一定要勾选“包含子目录”选项。 选定后,点击“全部查找”按钮,NotePad++会将这个文件夹中所有包含“About”字符的地方给你列出来。 这里一共找到了6个,很明显前面的几个开头字符都是小写,不符合我们的要求,那么必然就是最后一个了,双击最后一行直接定位,修改为“关于软件”,然后保存…… 注意: 1.进行这部分修改的时候,不要在修改内容里面包含空格,否则极有可能在重新编译打包的时候出错; 2.在示例中需要修改的词条很简单,也很好找,但是实际操作中,可能会比这个复杂很多,尤其是可能搜索出很多一样的结果,究竟哪些能改,在这里的只有一个大致的判断方法,那就是只能修改const-string v* “***”  引号中的字符。 3.在同时出现多个重复的关键字时,请谨慎修改,有些可能是定义函数的, 具体参考http://code.google.com/p/smali/source/browse/#svn/dalvik-docs/opcodes 4.保存时的文本编码要设置为ANSI格式,具体操作在菜单栏的“格式”中。打包前确定classes 文件夹下没有其他无用文件,如notepad++的备份文件(为避免这种情况,请确认在菜单栏“设置”—“首选项”—“备份与自动完成”中,禁用自动备份功能)。 现在再打包签名,安装试验看看效果。 已经改过了吧,现在就可以收工了…… 七、修改图标 软件的内容汉化完成了,应该说就搞定了,不过,软件的图标往往会与手机的主题风格不一致,不够美观,甚至有的软件作者主要精力都在编程序上面,用的就是标准的小绿人,看不出来软件的功能。因此,对图标的美化也是有必要的。接下来我们对图标开刀。 还是在解包后的汉化资源中,这次我们的目标是res\Drawable下面的图片。原始图片是这样的: 我们要修改的是icon.png,注意左边“详细信息”里面的内容,显示该图像尺寸是48 x 48像素。剩下的就是用自己喜欢的图像替换它就可以了。我这里用的图像是藕粉网风格的,顺手把那个通知图标也换了,这个是24 x 24像素的,呵呵: 换完的效果就不单独上图了。 注意:编辑png图像,推荐使用Photoshop软件,兼容性好。这里我给大家附上一个藕粉风格图标的模板,希望大家能帮忙推广藕粉网。 图标模板下载地址:………… 八、其他 (一)一些软件中界面没有使用文字,而是调用的图片(这在游戏软件中更多见),对于此类界面的汉化,请参考替换图标,直接用同尺寸的图片替换就可以了。 (二)有的软件本身支持多语种,这样可能会在res文件夹下会出现多个以values开头的文件夹,对于这类软件,汉化也有两种做法: 一种是按照上面的方法,直接把默认语言文件夹values里面的资源都汉化掉,干脆利落,不过这样到了英文系统里面也显示汉字。 第二种方法,就在res目录里面建立对应的语言资源文件夹(简体中文资源的目录名是values-zh-rCN,繁体中文是values-zh-rTW),将英文资源values里面的arrays.xml和strings.xml复制到新目录里面进行汉化,让Android系统自己识别语种调用,从而达到在对应的语言环境中显示对应的界面。不过,如果软件的作者将一些词条放到了代码里面的话,这样汉化也就没什么必要了,因为代码中的词条是不会自己更换的,呵呵。 能把加密的XML文档转化为可见可编辑的形式。 打开google code发现Apktool更新了,居然支持win了。。。。 那本文就是一篇介绍在windows环境下使用Apktool的笔记。 安装 1.先装JAVA环境,JDK/JRE都行,官网下载 装过的就跳过吧 2.下载apktool.jar及相关文件,这里下apktool-1.0.0.tar.bz2 和apktool-install-windows-2.1_r01-1.zip 3.解压apktool.jar到 C:\Windows 解压apktool-install-windows.zip到任意文件夹(例如E盘根目录) 4.Win+R 运行CMD,用cd命令转到apktool-install-windows所在文件夹,输入apktool看看。会列出一些帮助的话就成功了。Apktool 命令 apktool d XXX.apk ABC 反编译XXX.apk到文件夹ABC apktool b ABC 从文件夹ABC重建APK,输出到ABC\dist\out.apk 然后我们反编译一枚软件玩玩… AutoMemoryManager的免费版底部有一条广告,去掉它吧。 把com.lim.android.automemman.apk放到同文件夹(我的就是E盘根目录) Win+R 运行CMD E:<回车> E:\>apktool d com.lim.android.automemman.apk AMM <回车> I: Baksmaling… I: Decoding resource table… I: Decoding resources… I: Copying assets and libs… 现在文件被decode到E:\AMM了,打开E:\AMM\res\layout\main.xml看,所有都可见了吧~ 编辑第59行 <com.admob.android.ads.AdView android:id=”@id/ad” android:layout_width=”fill_parent” android:layout_height=”wrap_content” admobsdk:backgroundColor=”#ff000000″ admobsdk:textColor=”#ffffffff” admobsdk:keywords=”Android application” /> 改为 <com.admob.android.ads.AdView android:id=”@id/ad” android:layout_width=”0.0dip” android:layout_height=”0.0dip” admobsdk:backgroundColor=”#ff000000″ admobsdk:textColor=”#ffffffff” admobsdk:keywords=”Android application” /> 然后CMD输入 E:\>apktool b AMM I: Checking whether sources has changed… I: Smaling… I: Checking whether resources has changed… I: Building resources… I: Building apk file… 用Auto-sign签名E:\AMM\dist\out.apk 安装 这样广告就不见了 再看,嘿嘿~很帅吧… 这其实就是改了AndroidManifest.xml里的ADmob广告ID罢了 Settings.Secure.setLocationProviderEnabled(getContentResolver(),LocationManager.NETWORK_PROVIDER, mNetwork.isChecked()); 根据mNetwork这个控件是否选中决定是否打开基站定位,mNetwork就是界面那个选择框
杨丰盛,Android应用开发先驱,对Android有深入研究,实战经验极其丰富。精通Java、C、C++等语言,专注于移动通信软件开发,在机顶盒软件开发和MTK平台软件开发方面有非常深厚的积累。2007年获得中国软件行业协会游戏软件分会(CGIA)认证及国际游戏开发教育联合会国际认证。曾经领导和参与《三国群英传说》、《大航海传奇》、《美少女养成计划》等经典游戏的开发 前言 第一部分 准备篇 第1章 Android开发简介 1.1 Android基本概念 1.1.1 Android简介 1.1.2 Android的系统构架 1.1.3 Android应用程序框架 1.2 OMS介绍 1.2.1 OPhone介绍 1.2.2 Widget介绍 1.3 小结 第2章 Android开发环境搭建 2.1 Android开发准备工作 2.2 开发包及其工具的安装和配置 2.2.1 安装JDK和配置Java开发环境 2.2.2 Eclipse的安装与汉化 2.2.3 SDK和ADT的安装和配置 2.3 创建第一个Android项目——HeUoAndroid 2.3.1 创建HelloAndroid项目 2.3.2 运行HelloAndroid及模拟器的使用 2.3.3 调试HelloAndroid 2.4 小结 第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命周期 3.3 Android程序U设计 3.4 小结 第4章 用户界面开发 4.1 用户界面开发详解 4.1.1 用户界面简介 4.1.2 事件处理 4.2 常用控件应用 4.2.1 文本框(Textiew) 4.2.2 列表(ListView) 4.2.3 提示(T0ast) 4.2.4 编辑框(EditText) 4.2.5 单项选择(RadioGroup、RadioButton 4.2.6 多项选择(CheckBox) 4.2.7 下拉列表(Spinner) 4.2.8 自动提示(AutoComplete.TextⅥew) 4.2.9 日期和时间(DatePicker、TimePicker) 4.2.10 按钮(Button) 4.2.1l 菜单(Menu) 4.2.12 对话框(Dialog) 4.2.13 图片视图(ImageView) 4.2.14 带图标的按钮(ImageButton) 4.2.15 拖动效果(Gallery) 4.2.16 切换图片(hmgeSwilcher) 4.2.17 网格视图(GridView) 4.2.18 卷轴视图(ScrollView) 4.2.19 进度条(ProgressBar) 4.2.20 拖动条(SeekBar) 4.2.21 状态栏提示(Notification、NotificationManager) 4.2.22 对话框中的进度条(ProgressDialog) 4.3 界面布局 4.3.1 垂直线性布局 4.3.2 水平线性布局 4.3.3.相对布局(RelativeLayout) 4.3.4 表单布局(TableLayout) 4.3.5 切换卡(TabWidget) 4,4 小结 第5章 Android游戏开发 5.1 Android游戏开发框架 5.1.1 View类开发框架 5.1.2 SurfaceView类开发框架 5.2 Graphics类开发 5.5.1 Paint和Color类介绍 5.2.2 Canvas类介绍 5.2.3 几何图形绘制 5.2.4 字符串绘制 5.2.5 图像绘制 5.2.6 图像旋转 5.2.7 图像缩放 5.2.8 图像像素操作 5.2.9 Shader类介绍 5.2.10 双缓冲技术 5.2.11 全屏显示 5.2.12 获得屏幕属性 5.3 动画实现 5.3.1 Tween动画 5.3.2 Frame动画 5.3.3 GIF动画播放 5.4 小结 第6章 Android数据存储 6.1 Android数据存储初探 6.2 数据存储之Shared Preferences 6.3 数据存储之Files 6.4 数据存储之Network 6.5 Android数据库编程 6.5.1 SQLite简介 6.5.2 SQLite编程详解 6.5.3 SQLiteOpenHelper应用 6.6 数据共享(ContentProviders) 6.7 小结 第7 章多媒体开发 7.1 多媒体开发详解 7.1.1 Open Core 7.1.2 MediaPlayer 7.1.3 MediaRecorder 7.2 播放音乐 7.3 播放视频 7.4 录制歌曲 7.5 相机设置 7.6 闹钟设置 7.7 铃声设置 7.8 小结 第8章 网络与通信 8.1 网络通信基础 8.1.1 无线网络技术 8.1.2 Android网络基础 8.2 HTTP通信 8.2.1 HttpURLConnection接口 8.2.2 HttpClient接口 8.2.3 实时更新 8.3 Socket通信 8.3.1 Socket基础 8.3.2 Socket应用(简易聊天室) 8.4 网络通信的中文乱码问题 8.5 WebKit应用 8.5.1 WebKjt概述 8.5.2 WebView浏览网页 8.5.3 WebView与Javascript 8.6 WtFi介绍 8.7 蓝牙 8.8 小结 第9章 Android特色开发 9.1 传感器 9.2 语音识别 9.3 GoogleMap 9.3.1 GoogleMap概述 9.3.2 准备工作 9.3.3 GoogleMapAPI的使用 9.3.4 定位系统 9.4 桌面组件 9.4.1 快捷方式 9.4.2 实时文件夹 9.4.3 Widget开发 9.5 账户管理 9.6 小结 第三部分 实例篇 第10章 Android应用开发实例 10.1 情境模式 10.2 文件管理器 10.3 通讯录 10.4 音乐播放器 10.5 天气预报 10.6 个人地图 10.7 Widget日历 10.8 小结 第11 章Android游戏开发实例 11.1 手机游戏开发简介 11.2 游戏框架设计 11.3 地图设计 11.4 主角设计 11.5 图层管理器 11.6 游戏音效 11.7 游戏存档 11.8 小结 第四部分 高级篇 第12章 AndroidOpenGL开发基础 12.1 OpenGL简介 12.2 多边形 12.3 颜色 12.4 旋转 12.5 3D 空间 12.6 纹理映射 12.7 光照和事件 12.8 混合 12.9 小结 第13章 AndroidOpenGL综合应用 13.1 移动图像 13.2.3D 世界 13.3 飘动的旗帜 13.4 显示列表 13.5 雾 13.6 粒子系统 13.7 蒙版 13.8 变形 13.9 小结 第14章 游戏引擎实现 14.1 游戏引擎介绍 14.1.1 什么是引擎 14.1.2 引擎的进化 14.1.3 常见的游戏引擎 14.1.4 Android游戏引擎 14.2 游戏引擎结构 14.2.1 游戏引擎原理 14.2.2 游戏引擎定位 14.2.3 游戏引擎框架 14.3 游戏引擎设计 14.3.1 游戏引擎结构和功能设计 14.3.2 游戏引擎设计注意事项 14.4 游戏引擎实现 14.4.1 Activity类实现 14.4.2 流程控制和线程 14.4.3 游戏对象与对象管理 14.4.4 图形引擎 14.4.5 物理引擎 14.4.6 事件模块 14.4.7 工具模块 14.4.8 脚本引擎、音效模块、网络模块 14.5 小结 第15章 优化技术 15.1 优化的基本知识 15.1.1 如何书写出优秀代码 15.1.2 编程规范 15.2 程序性能测试 15.2.1 计算性能测试 15.2.2 内存消耗测试 15.3 初级优化 15.4 高级优化 15.5 Android高效开发 15.6 AndroidUI优化 15.7 其他优化 15.7.1 zipalign 15.7.2 图片优化 15.8 小结 第五部分 扩展篇 第16章 Android NDK开发 16.1 AndroidNDK简介 16.2 安装和配置NDK开发环境 16.2.1 系统和软件需求 16.2.2 NDK开发环境搭建 16.2.3 编译第一个NDK程序 16.3 AndroidNDK开发 16.3.1 JNI接口设计 16.3.2 使用C\C++实现本地方法 16.3 编译C\C++代码 16.4 AndroidNDK中使用0penGL 16.5小结 第17章 Android脚本环境 17.1 Android脚本环境简介 17.2 Android脚本环境安装 17.3如何编写Android脚本程序 17.4小结
内容提要 --------------------------------------------------------------------------------   国内第一本基于Android 2.0的经典著作,5大专业社区联袂推荐,权威性毋庸置疑!《Android应用开发揭秘》内容全面,不仅详细讲解了Android框架、Android组件、用户界面开发、游戏开发、数据存储、多媒体开发和网络开发等基础知识,而且还深入阐述了传感器、语音识别、桌面组件开发、Android游戏引擎设计、Android应用优化、OpcnGL等高级知识,最重要的是还全面介绍了如何利用原生的C,C++(NDK)和Python、Lua等脚本语言(AndroidScriptingEnvironment)来开发Android应用,《Android应用开发揭秘》实战性强,书中的每个知识点都有配精心设计的示例,尤为值得一提的是,它还以迭代的方式重现了各种常用的Android应用和经典Android游戏的开发全过程,既可以以它们为范例进行实战演练,又可以将它们直接应用到实际开发中去。 目录 -------------------------------------------------------------------------------- 前言 第一部分 准备篇 第1章 Android开发简介 1.1 Android基本概念 1.1.1 Android简介 1.1.2 Android的系统构架 1.1.3 Android应用程序框架 1.2 OMS介绍 1.2.1 OPhone介绍 1.2.2 Widget介绍 1.3 小结 第2章 Android开发环境搭建 2.1 Android开发准备工作 2.2 开发包及其工具的安装和 配置 2.2.1 安装JDK和配置Java开发 环境 2.2.2 Eclipse的安装与汉化 2.2.3 SDK和ADT的安装和 配置 2.3 创建第一个Android项目——HeUoAndroid 2.3.1 创建HelloAndroid项目 2.3.2 运行HelloAndroid及模拟器的使用 2.3.3 调试HelloAndroid 2.4 小结 第二部分 基础篇 第3章 Android程序设计基础 3.1 Android程序框架 3.1.1 Android项目目录结构 3.1.2 Android应用解析 3.2 Android的生命周期 3.3 Android程序U设计 3.4 小结 第4章 用户界面开发 4.1 用户界面开发详解 4.1.1 用户界面简介 4.1.2 事件处理 4.2 常用控件应用 4.2.1 文本框(Textiew) 4.2.2 列表(ListView) 4.2.3 提示(T0ast) 4.2.4 编辑框(EditText) 4.2.5 单项选择(RadioGroup、RadioButton 4.2.6 多项选择(CheckBox) 4.2.7 下拉列表(Spinner) 4.2.8 自动提示(AutoComplete.TextⅥew) 4.2.9 日期和时间(DatePicker、TimePicker) 4.2.10 按钮(Button) 4.2.1l 菜单(Menu) 4.2.12 对话框(Dialog) 4.2.13 图片视图(ImageView) 4.2.14 带图标的按钮(ImageButton) 4.2.15 拖动效果(Gallery) 4.2.16 切换图片(hmgeSwilcher) 4.2.17 网格视图(GridView) 4.2.18 卷轴视图(ScrollView) 4.2.19 进度条(ProgressBar) 4.2.20 拖动条(SeekBar) 4.2.21 状态栏提示(Notification、NotificationManager) 4.2.22 对话框中的进度条(ProgressDialog) 4.3 界面布局 4.3.1 垂直线性布局 4.3.2 水平线性布局 4.3.3.相对布局(RelativeLayout) 4.3.4 表单布局(TableLayout) 4.3.5 切换卡(TabWidget) 4,4 小结 第5章 Android游戏开发 5.1 Android游戏开发框架 5.1.1 View类开发框架 5.1.2 SurfaceView类开发框架 5.2 Graphics类开发 5.5.1 Paint和Color类介绍 5.2.2 Canvas类介绍 5.2.3 几何图形绘制 5.2.4 字符串绘制 5.2.5 图像绘制 5.2.6 图像旋转 5.2.7 图像缩放 5.2.8 图像像素操作 5.2.9 Shader类介绍 5.2.10 双缓冲技术 5.2.11 全屏显示 5.2.12 获得屏幕属性 5.3 动画实现 5.3.1 Tween动画 5.3.2 Frame动画 5.3.3 GIF动画播放 5.4 小结 第6章 Android数据存储 6.1 Android数据存储初探 6.2 数据存储之Shared Preferences 6.3 数据存储之Files 6.4 数据存储之Network 6.5 Android数据库编程 6.5.1 SQLite简介 6.5.2 SQLite编程详解 6.5.3 SQLiteOpenHelper应用 6.6 数据共享(ContentProviders) 6.7 小结 第7 章多媒体开发 7.1 多媒体开发详解 7.1.1 Open Core 7.1.2 MediaPlayer 7.1.3 MediaRecorder 7.2 播放音乐 7.3 播放视频 7.4 录制歌曲 7.5 相机设置 7.6 闹钟设置 7.7 铃声设置 7.8 小结 第8章 网络与通信 8.1 网络通信基础 8.1.1 无线网络技术 8.1.2 Android网络基础 8.2 HTTP通信 8.2.1 HttpURLConnection接口 8.2.2 HttpClient接口 8.2.3 实时更新 8.3 Socket通信 8.3.1 Socket基础 8.3.2 Socket应用(简易聊天室) 8.4 网络通信的中文乱码问题 8.5 WebKit应用 8.5.1 WebKjt概述 8.5.2 WebView浏览网页 8.5.3 WebView与Java 8.6 WtFi介绍 8.7 蓝牙 8.8 小结 第9章 Android特色开发 9.1 传感器 9.2 语音识别 9.3 GoogleMap 9.3.1 GoogleMap概述 9.3.2 准备工作 9.3.3 GoogleMapAPI的使用 9.3.4 定位系统 9.4 桌面组件 9.4.1 快捷方式 9.4.2 实时文件夹 9.4.3 Widget开发 9.5 账户管理 9.6 小结 第三部分 实例篇 第10章 Android应用开发 实例 10.1 情境模式 10.2 文件管理器 10.3 通讯录 10.4 音乐播放器 10.5 天气预报 10.6 个人地图 10.7 Widget日历 10.8 小结 第11 章Android游戏开发实例 11.1 手机游戏开发简介 11.2 游戏框架设计 11.3 地图设计 11.4 主角设计 11.5 图层管理器 11.6 游戏音效 11.7 游戏存档 11.8 小结 第四部分 高级篇 第12章 AndroidOpenGL开发 基础 12.1 OpenGL简介 12.2 多边形 12.3 颜色 12.4 旋转 12.5 3D 空间 12.6 纹理映射 12.7 光照和事件 12.8 混合 12.9 小结 第13章 AndroidOpenGL综合 应用 13.1 移动图像 13.2.3D 世界 13.3 飘动的旗帜 13.4 显示列表 13.5 雾 13.6 粒子系统 13.7 蒙版 13.8 变形 13.9 小结 第14章 游戏引擎实现 14.1 游戏引擎介绍 14.1.1 什么是引擎 14.1.2 引擎的进化 14.1.3 常见的游戏引擎 14.1.4 Android游戏引擎 14.2 游戏引擎结构 14.2.1 游戏引擎原理 14.2.2 游戏引擎定位 14.2.3 游戏引擎框架 14.3 游戏引擎设计 14.3.1 游戏引擎结构和功能 设计 14.3.2 游戏引擎设计注意事项 14.4 游戏引擎实现 14.4.1 Activity类实现 14.4.2 流程控制和线程 14.4.3 游戏对象与对象管理 14.4.4 图形引擎 14.4.5 物理引擎 14.4.6 事件模块 14.4.7 工具模块 14.4.8 脚本引擎、音效模块、网络 模块 14.5 小结 第15章 优化技术 15.1 优化的基本知识 15.1.1 如何书写出优秀代码 15.1.2 编程规范 15.2 程序性能测试 15.2.1 计算性能测试 15.2.2 内存消耗测试 15.3 初级优化 15.4 高级优化 15.5 Android高效开发 15.6 AndroidUI优化 15.7 其他优化 15.7.1 zipalign 15.7.2 图片优化 15.8 小结 第五部分 扩展篇 第16章 Android NDK开发 16.1 AndroidNDK简介 16.2 安装和配置NDK开发环境 16.2.1 系统和软件需求 16.2.2 NDK开发环境搭建 16.2.3 编译第一个NDK程序 16.3 AndroidNDK开发 16.3.1 JNI接口设计 16.3.2 使用C\C++实现本地 方法 16.3.3 Android.mk实现 16.3.4 Application.mk实现 16.3.5 编译C\C++代码 16.4 AndroidNDK中使用0penGL 16.5小结 第17章 Android脚本环境 17.1 Android脚本环境简介 17.2 Android脚本环境安装 17.3如何编写Android脚本程序 17.4小结
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值