图片 1image.png

众所周知,Android是免费开源的,所以我们每个人都可以获取到Android源码,最近手机耗电厉害,天天提醒,10分钟耗电20%。不是,我这干啥了,就耗电这么快。后来就网上搜搜看看,到底是怎么回事。顺便逆向了一个万年历。

前言

当我们用Intent传输大数据时,有可能会出现错误:

val intent = Intent(this@MainActivity, Main2Activity::class.java)val data = ByteArray(1024 * 1024)intent.putExtra("111", data)startActivity

如上我们传递了1M大小的数据时,结果程序就一直反复报如下TransactionTooLargeException错误:

图片 2image.png

但我们平时传递少量数据的时候是没问题的。由此得知,通过intent在页面间传递数据是有大小限制的。本文我们就来分析下为什么页面数据传输会有这个量的限制以及这个限制的大小具体是多少。

前段时间公司项目有个功能需要用到Android系统里面的悬浮窗功能,一般在实现这样的功能的步骤都是先判断悬浮窗权限是否对该应用打开,如果没有打开,则跳到相关的页权限页面,引导用户打开该开关。

每个产品都想让自己的程序在后台能够长期的运行,不管是监测用户的行为,还是能够让自己正常的push,所以这个问题就引申出来了。问:如何让自己的程序长期后台运行,杀不死。

startActivity流程探究

首先我们知道Context和Activity都含有startActivity,但两者最终都调用了Activity中的startActivity:

 @Override public void startActivity(Intent intent, @Nullable Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } }

而startActivity最终会调用自身的startActivityForResult,省略了嵌套activity的代码:

 public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { options = transferSpringboardActivityOptions; Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData; } if (requestCode >= 0) { // If this start is requesting a result, we can avoid making // the activity visible until the result is received. Setting // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the // activity hidden during this time, to avoid flickering. // This can only be done when a result is requested because // that guarantees we will get information back when the // activity is finished, no matter what happens to it. mStartedActivity = true; } cancelInputsAndStartExitTransition; }

然后系统会调用Instrumentation中的execStartActivity方法:

 public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; ... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess; int result = ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver, token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; }

接着调用了ActivityManger.getService().startActivity
,getService返回的是系统进程中的AMS在app进程中的binder代理:

 /** * @hide */ public static IActivityManager getService() { return IActivityManagerSingleton.get(); } private static final Singleton<IActivityManager> IActivityManagerSingleton = new Singleton<IActivityManager>() { @Override protected IActivityManager create() { final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE); final IActivityManager am = IActivityManager.Stub.asInterface; return am; } };

接下来就是App进程调用AMS进程中的方法了。简单来说,系统进程中的AMS集中负责管理所有进程中的Activity。app进程与系统进程需要进行双向通信。比如打开一个新的Activity,则需要调用系统进程AMS中的方法进行实现,AMS等实现完毕需要回调app进程中的相关方法进行具体activity生命周期的回调。

所以我们在intent中携带的数据也要从APP进程传输到AMS进程,再由AMS进程传输到目标Activity所在进程。有同学可能由疑问了,目标Acitivity所在进程不就是APP进程吗?其实不是的,我们可以在Manifest.xml中设置android:process属性来为Activity,
Service等指定单独的进程,所以Activity的startActivity方法是原生支持跨进程通信的。

接下来简单分析下binder机制。

本文不讨论如何跳到悬浮窗授权页面,还有各种方法可以绕过该权限实现悬浮窗,我们就单纯说一下如何在Vivo系统获取该状态。本人在这个问题上花了好几天进行研究,搜遍各种博客和Github都没找到答案,最后在反编译某个安全管家的代码获得的。希望能帮助有需要的同学,而不要跟我一样把时间花在这种技术含量较低的问题上。

前言 “前言”)前言

看完我自己都惊了,感觉手机每天运行的都是什么乱七八糟,因为自己手机上装了这款app,而且这款app要求的权限异常的多,所以就直接引起了我的注意,访问手机就账户列表,WIFI状态,照相机,读取联系人,锁屏,启动事件。一个万年历就要这么多权限,不禁引起了好奇,所以决定一探究竟

binder数据传输

图片 3image.png

普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是
110241024) –
:这个限制定义在frameworks/native/libs/binder/processState.cpp类中,如果传输说句超过这个大小,系统就会报错,因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的:

#define BINDER_VM_SIZE ((1*1024*1024) - 

并可以通过cat
proc/[pid]/maps命令查看到。而在内核中,其实也有个限制,是4M,不过由于APP中已经限制了不到1M,这里的限制似乎也没多大用途:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma){ int ret; struct vm_struct *area; struct binder_proc *proc = filp->private_data; const char *failure_string; struct binder_buffer *buffer; //限制不能超过4M if ((vma->vm_end - vma->vm_start) > SZ_4M) vma->vm_end = vma->vm_start + SZ_4M; 。。。 }

其实在TransactionTooLargeException中也提到了这个:

The Binder transaction buffer has a limited fixed size, currently 1Mb, whichis shared by all transactions in progress for the process. Consequently thisexception can be thrown when there are many transactions in progress even whenmost of the individual transactions are of moderate size.

只不过不是正好1MB,而是比1MB略小的值。

相信在做获取悬浮窗状态的同学都已经解决了在一般的Android系统上获取该状态的,网上一搜都是一大把的。可以参考一下相关文章。

一个万年历app是如何保活的 “一个万年历app是如何保活的”)一个万年历app是如何保活的

说起万年历如何保活的,用白话来说,就是这么几个方法来实现

  • 监听屏幕亮灭,如果屏幕灭,那么创建一个1像素的悬浮层。屏幕亮,把这个1像素的页面关闭,防止意外获取到焦点惹恼用户
  • 利用账户系统,系统定期唤醒账号更新服务,欺骗系统我们的app有账号服务,然后定期同步账户,再做一些其他的事情
  • android5.0以后可以利用JobSchedulerService,使我们的程序定时被运行,以及一些其他条件

小结

至此我们来解答开头提出的问题,startActivity携带的数据会经过BInder内核再传递到目标Activity中去,因为binder映射内存的限制,所以startActivity也就会这个限制了。

想必你已经发现在Vivo手机的FuntouchOS上获取悬浮窗状态时,不管打没打开,都是返回打开的状态给你。好了废话不多说,直接贴代码。

1像素悬浮层技术实现 “1像素悬浮层技术实现”)1像素悬浮层技术实现

package cn.etouch.ecalendar.keeplive;import android.app.Activity;import android.os.Bundle;import android.view.Window;import android.view.WindowManager.LayoutParams;import cn.etouch.ecalendar.manager.ad;public class KeepLiveActivity extends Activity { public static KeepLiveActivity a = null; private boolean b = false; protected void onCreate(Bundle bundle) { super.onCreate; Window window = getWindow(); window.setGravity; //创建一个1像素的悬浮层 LayoutParams attributes = window.getAttributes(); attributes.x = 0; attributes.y = 0; attributes.width = 1; attributes.height = 1; window.setAttributes(attributes); a = this; ad.b("ActivityManager--->KeepLiveActivity onCreate"); } protected void onResume() { super.onResume(); ad.b("ActivityManager--->KeepLiveActivity onResume"); if  { finish(); } else { this.b = true; } } protected void onDestroy() { super.onDestroy(); a = null; ad.b("ActivityManager--->KeepLiveActivity onDestroy"); }}

这个类的名称起的就很好,KeepLiveActivity,保活的页面,主要就是用于保活。创建一个1像素的悬浮层,对于手机来讲是可见的,对于人眼来讲,几乎是不可见的,所以我们也无法发现

先看看万年历是怎么做的

图片 4

其中EcalendarAccountProvider,SyncAccountService,SyncAccountUtils就是利用系统账户更新功能定期同步账户,我们可以看到万年历的代码

public class EcalendarAccountProvider extends ContentProvider { public static final Uri a = Uri.parse("content://cn.etouch.ecalendar.account.provider/data"); public boolean onCreate() { return true; } public Cursor query(Uri uri, String[] strArr, String str, String[] strArr2, String str2) { return null; } public String getType { return new String(); } public Uri insert(Uri uri, ContentValues contentValues) { return null; } public int delete(Uri uri, String str, String[] strArr) { return 0; } public int update(Uri uri, ContentValues contentValues, String str, String[] strArr) { return 0; }}

首先提供一个账户相关的ContentProvider,可是这个内部实现却什么也不干,欺骗系统app使用了系统账户功能,但是实际上其实只是利用账户同步功能长生不老

替代方案

一、写入临时文件或者数据库,通过FileProvider将该文件或者数据库通过Uri发送至目标。一般适用于不同进程,比如分离进程的UI和后台服务,或不同的App之间。之所以采用FileProvider是因为7.0以后,对分享本App文件存在着严格的权限检查。

二、通过设置静态类中的静态变量进行数据交换。一般适用于同一进程内,这样本质上数据在内存中只存在一份,通过静态类进行传递。需要注意的是进行数据校对,以防多线程操作导致的数据显示混乱。

相关架构视频资料

图片 5image.png图片 6image

发表评论

电子邮件地址不会被公开。 必填项已用*标注