内容简介:系统为每个应用分配一定大小的内存,从之前的 16M 到 32M、48M,甚至更高。但毕竟有限。进程是资源分配的基本单位。也就是说,一个应用有对个进程,那这个应用可以获得更多的内存。所以,开启多进程可以分担主进程的内存消耗,常见音乐类 APP 的后台播放,应用的推送服务等。
系统为每个应用分配一定大小的内存,从之前的 16M 到 32M、48M,甚至更高。但毕竟有限。
进程是资源分配的基本单位。也就是说,一个应用有对个进程,那这个应用可以获得更多的内存。
所以,开启多进程可以分担主进程的内存消耗,常见音乐类 APP 的后台播放,应用的推送服务等。
多进程的不足
1、数据共享问题
Android 系统为每个进程分配独立的虚拟机,不同的虚拟机之间数据不能共享,即使是静态成员还是单例模式。
2、线程同步机制失效
不同进程锁的不是同一个对象,无法保证线程同步了。
3、SharedPreferences 可靠性下降
SharedPreferences 还没有增加对多进程的支持。
4、Application 多次创建
当一个组件跑在新的进程中,系统要在创建进程的同时为其分配独立的虚拟机,自然就会创建新的 Application。这就导致了 application 的 onCreate方法重复执行全部的初始化代码。因此,可以根据进程需要进行最小的业务初始化。
多进程注意事项
- 静态成员和单例模式会失效
- 线程同步机制失效
- SharePreference 稳定性不能保证,使用 ContentProvider 封装,对外提供数据服务
- Application 会多次创建,需要注意多进程间使用的对象是否初始化 在 web 进程中调用主进程功能都需要注意 Context 和数据的读取,否则会出现空指针的问题.
Application 多次创建
不同进程共同的初始化业务逻辑 :
public class AppInitialization { /** * 不同进程共同的初始化代码 * @param application */ public void onAppCreate(Application application) { // init } 复制代码
简单工厂模式 :
根据进程名进行对应进程的初始化逻辑。
public class AppInitFactory { public static AppInitialization getAppInitialization(String processName) { AppInitialization appInitialization; if (processName.endsWith(":second")) { appInitialization = new SecondApplication(); } else if (processName.endsWith(":third")) { appInitialization = new ThirdApplication(); } else { appInitialization = new AppInitialization(); } return appInitialization; } static class SecondApplication extends AppInitialization { @Override public void onAppCreate(Application application) { super.onAppCreate(application); // init } } static class ThirdApplication extends AppInitialization { @Override public void onAppCreate(Application application) { super.onAppCreate(application); // init } } 复制代码
具体调用代码 :
public class MyApplication extends Application { private static final String TAG = "MyApplication"; @Override public void onCreate() { super.onCreate(); String currentProcessName = getCurrentProcessName(); Log.e(TAG, "currentProcessName : " + currentProcessName ); AppInitialization appInitialization = AppInitFactory.getAppInitialization(currentProcessName); if (appInitialization != null) { appInitialization.onAppCreate(this); } } /** * 获取当前进程名称 * @return */ private String getCurrentProcessName() { String currentProcessName = ""; int pid = android.os.Process.myPid(); ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { if (processInfo.pid == pid) { currentProcessName = processInfo.processName; break; } } return currentProcessName; } } 复制代码
Web 独立进程
WebView 拆分为独立进程运行,从而减轻主进程内存压力很有必要,当内存紧张时,系统则会自动杀死 web 进程.拆分为多进程后,主要问题在于进程间通讯与主进程保活.
拆分 Web 进程
独立进程分为两种模式,私有独立进程 和 全局独立进程 两种模式,开始方式也很简单.
<!-- 私有独立进程:与主进程同ShareUID,共享data目录、组件信息、共享内存数据 --> <activity android:name=".WebActivity" android:process=":web"/> <!-- 全局独立进程:与主进程不同ShareUID --> <activity android:name=".WebActivity" android:process=".web"/> 复制代码
如果只与本应用通信,不需要为全局独立进程.
进程间通信
进程间的通信方式:
- Bundle
- 文件共享
- AIDL
- Messager
- ContentProvider
- Socket
当打开网页时,发送请求应该带用户信息,原来的信息存储方式是SharePreference,但是这种方式对于多进程调用时,容易出现不稳定的情况,并且它的多进程调用方式已经被标记为废弃,所以为了保证稳定性,使用ContentProvider将其封装,供不同的进程调用.
/** * 在 web 进程里只是获取用户信息,当web里返回要登录信息时,跳转至主进程里的登录页面,所以只实现了 query 方法,更新 SharePreference 依旧使用了单进程读写模式. */ public class UserProvider extends ContentProvider { private static String sAuthoriry = BuildConfig.APPLICATION_ID + ".UserProvider"; @Override public boolean onCreate() { return true; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { //这个 name 获取的就是 xml 的文件名,默认取 uri 的 path 字段的第一个 if (!sAuthoriry.equals(uri.getAuthority())) { return null; } Bundle bundle = new Bundle(); if (getContext() != null) { bundle.putString("user", SharedPreferUtil.get(getContext(), Constants.EXTRA_USER_CACHE, "")); } return new BundleCursor(bundle); } private static final class BundleCursor extends MatrixCursor { private Bundle mBundle; public BundleCursor(Bundle extras) { super(new String[]{}, 0); mBundle = extras; } @Override public Bundle getExtras() { return mBundle; } @Override public Bundle respond(Bundle extras) { mBundle = extras; return mBundle; } } @Nullable @Override public String getType(@NonNull Uri uri) { throw new UnsupportedOperationException("No external call"); } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { throw new UnsupportedOperationException("No external call"); } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { throw new UnsupportedOperationException("No external call"); } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { throw new UnsupportedOperationException("No external call"); } } 复制代码
AndroidManifest.xml 配置
<!-- 由于只与本应用通信所以就没有配置权限 --> <provider android:name=".provider.UserProvider" android:authorities="${applicationId}.UserProvider" android:exported="true" /> 复制代码
这样就配置好了多进程读取 SharePreference 的 ContentProvider,凡是读取用户信息的地方都需替换为如下方式
@Nullable public static User getUser() { String authority = "content://" + BuildConfig.APPLICATION_ID + ".UserProvider"; Uri uri = Uri.parse(authority); //由于多进程模式,所以 Application 会多次初始化,MyApplication.getInstance() 的初始化不能写在某个进程里,如果这样则其他进程获取不到实例,导致这里会出现NPE Cursor cursor = MyApplication.getInstance().getContentResolver().query(uri, null, null, null, null); if (cursor != null) { Bundle args = cursor.getExtras(); cursor.close(); if (args != null) { return User.stringToUser(args.getString("user")); } } return null; } 复制代码
通过以上配置,就可以在不同进程里获取User对象.
我们业务中有个逻辑是分享网页,当用户点击网页分享按钮,调起分享页面,然后分享至微信,分享成功返回后调用 js,所以需要在微信回调中通知网页,使用BroadcastReceiver通知网页执行 js 脚本.
主进程保活
在完成进程拆分后测试中发现,当主进程占用一百多 MB 时红米 Note3 机器打开网页进程,再消耗一百多 MB时,系统会自动杀死主进程,导致返回到主进程会再次加载,为了避免这种问题发生,只能在网页进程启动后,将主进程置为前台进程. 进程保活话题如果要展开谈,可以写好多东西,这里只介绍我们应用的方法.逻辑很简单就是在主进程中启动一个前台 Service,然后再启动一个相同 ID 的 Service,最后停止一个 Service,这样通知栏里便不会出现通知,而应用在前台 oom_adj 值较高,进程不会被杀死.
public class KeepLiveService extends Service { public static final int NOTIFICATION_ID = 0x11; public KeepLiveService() { } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { //API 18 以下,直接发送 Notification 并将其置为前台 startForeground(NOTIFICATION_ID, new Notification()); } else { //API 18 以上,发送 Notification 并将其置为前台后,启动 InnerService Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.drawable.push); startForeground(NOTIFICATION_ID, builder.build()); ContextCompat.startForegroundService(getApplicationContext(), new Intent(this, InnerService.class)); } } public static class InnerService extends Service { public InnerService() { } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); //发送与 KeepLiveService 中I D 相同的 Notification,然后取消自己的前台显示 Notification.Builder builder = new Notification.Builder(this); builder.setSmallIcon(R.drawable.push); startForeground(NOTIFICATION_ID, builder.build()); stopSelf(); } } } 复制代码
当然 Service 也必须在 AndroidManifest 中注册.
- compleVerison 27 targetVersion 26 如果再为更高的版本则通知拦会显示出应用正在后台运行,给用户造成不好的体验
- 为了在用户体验和内存消耗间平衡,在 Application 的 onTrimMemory中,当 level 值大于等于 TRIM_MEMORY_MODERATE 且 Web 进程在后台后,主动杀死 web 进程.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 微信 Android 终端内存优化实践
- 前端内存优化的探索与实践
- Shooter 系统内存分析工具 OOM 实践
- 记一次网页内存溢出分析及解决实践
- Go 的内存对齐和指针运算详解和实践
- Android性能优化,Startalk会话页GIF内存优化实践
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Swift语言实战入门
伍星、罗飞、刘志华、王浩力、刘蕾 / 人民邮电出版社 / 2014-10-23 / 79
《Swift语言实战入门》以Swift语言的基础知识和实战技巧为主要内容,佐以大量的实例和图片进行讲解。全书内容分为三大部分,共11章节。第一大部分讲述Swift语言的基础知识和语法,第二大部分讲解开发框架和库的相关内容,第三大部分集中讲解以2048游戏为例的实战演练,从入门到实战层层递进。本书注重实战,秉承着学以致用的原则,让读者真正看后能够实际操作。120个代码清单全部共享,配套教学视频在线收......一起来看看 《Swift语言实战入门》 这本书的介绍吧!