麓谷官网欢迎你访问长沙北大青鸟麓谷校区,支持你成为一个受人尊重的专业人才!
当前位置: 首页 > 青鸟知识 > android

提高Android应用进程存活率的小技巧

来源:长沙北大青鸟|发布时间:2017-03-27|浏览量:

学IT,好工作

就读长沙岳麓职业培训学校

求学热线: 400-160-2868

  Android 进程优先级

  1 进程优先级等级一般分法

  Activte process

  Visible Process

  Service process

  Background process

  Empty process

  2 Service技巧

  onStartCommand返回START_STICKY

  onDestroy中startself

  Service后台变前置,setForground(true)

  android:persistent = “true”

  3 进程优先级号

  ProcessList.java

// Adjustment used in certain places where we don't know it yet.
// (Generally this is something that is going to be cached, but we
// don't know the exact value in the cached range to assign yet.)
static final int UNKNOWN_ADJ = 16;

// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;

// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
static final int SERVICE_B_ADJ = 8;

// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app.  This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
static final int PREVIOUS_APP_ADJ = 7;

// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
static final int HOME_APP_ADJ = 6;

// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 5;

// This is a process with a heavy-weight application.  It is in the
// background, but we want to try to avoid killing it.  Value set in
// system/rootdir/init.rc on startup.
static final int HEAVY_WEIGHT_APP_ADJ = 4;

// This is a process currently hosting a backup operation.  Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 3;

// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 2;

// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 1;

// This is the process running the current foreground app.  We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;

// This is a process that the system or a persistent process has bound to,
// and indicated it is important.
static final int PERSISTENT_SERVICE_ADJ = -11;

// This is a system persistent process, such as telephony.  Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -12;

// The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -16;

// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
static final int NATIVE_ADJ = -17;

  Android Low Memory Killer

  Android系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。

  lowmemorykiller.c

static uint32_t lowmem_debug_level = 1;
static int lowmem_adj[6] = {
    0,
    1,
    6,
    12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
    3 * 512,    /* 6MB */
    2 * 1024,   /* 8MB */
    4 * 1024,   /* 16MB */
    16 * 1024,  /* 64MB */
};
static int lowmem_minfree_size = 4;

  ① 在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj值越小越不容易被杀死。其中,lowmem_minfree是杀进程的时机,谁被杀,则取决于lowmem_adj,具体值得含义参考上面 Android进程优先级 所述.

  ② 在init.rc中定义了init进程(系统进程)的oom_adj为-16,其不可能会被杀死(init的PID是1),而前台进程是0(这里的前台进程是指用户正在使用的Activity所在的进程),用户按Home键回到桌面时的优先级是6,普通的Service的进程是8.

  init.rc

# Set init and its forked children's oom_adj.
    write /proc/1/oom_adj -16

  关于Low Memory Killer的具体实现原理可参考Ref-2.

  查看某个App的进程

  步骤(手机与PC连接)

  adb shell

  ps | grep 进程名

  cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号

图1.png

  Linux AM命令

  am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令.其源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。

  拨打电话

  命令:am start -a android.intent.action.CALL -d tel:电话号码

  示例:am start -a android.intent.action.CALL -d tel:10086

  打开一个网页

  命令:am start -a android.intent.action.VIEW -d 网址

  示例:am start -a android.intent.action.VIEW -d //www.skyseraph.com

  启动一个服务

  命令:am startservice <服务名称>

  示例:am startservice -n com.android.music/ com.android.music.MediaPlaybackService

  NotificationListenerService

  “A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.” From Google

  用来监听到通知的发送以及移除和排名位置变化,如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉,我们都能通过这个service来监听到,甚至可以做一些管理工作。

  Android账号和同步机制

  属于Android中较偏冷的知识

  Android多进程

  实现:android:process

  好处:一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死

  大坑:每一个进程将有自己的Dalvik VM实例,意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。

  现有方法

  网络连接保活方法

  A. GCM

  B. 公共的第三方push通道(信鸽等)

  C. 自身跟服务器通过轮询,或者长连接

  具体实现请参考 微信架构师杨干荣的”微信Android客户端后台保活经验分享” (Ref-1).

  双service(通知栏) 提高进程优先级

  思路:(API level > 18 )

  应用启动时启动一个假的Service(FakeService), startForeground(),传一个空的Notification

  启动真正的Service(AlwaysLiveService),startForeground(),注意必须相同Notification ID

  FakeService stopForeground()

  效果:通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程)

  风险:Android系统前台service的一个漏洞,可能在6.0以上系统中修复

  实现:核心代码如下

  AlwaysLiveService 常驻内存服务

@Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       startForeground(R.id.notify, new Notification());
       startService(new Intent(this, FakeService.class));
       return super.onStartCommand(intent, flags, startId);
   }

  FakeService 临时服务

public class FakeService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        startForeground(R.id.notify, new Notification());
        stopSelf();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
        super.onDestroy();
    }
}

  Service及时拉起

  AlarmReceiver, ConnectReceiver,BootReceiver等

  Service设置(见上面基础部分)

  通过监听系统广播,如开机,锁屏,亮屏等重新启动服务

  通过alarm定时器,启动服务

  守护进程/进程互拉

  在分析360手机助手app时,发现其拥有N多个进程,一个进程kill后会被其它未kill的进程拉起,这也是一种思路吧,虽然有点流氓~

  守护进程一般有这样两种方式:

  多个java进程守护互拉

  底层C守护进程拉起App上层/java进程

  Linux Am命令开启后台进程

  一种底层实现让进程不被杀死的方法,在Android4.4以上可能有兼容性问题,具体参考Ref-7

  NotificationListenerService通知

  一种需要用户允许特定权限的系统拉起方式,4.3以上系统

  前台浮窗

  有朋友提出一种应用退出后启动一个不可交互的浮窗,个人觉得这种方法是无效的,读者有兴趣可以一试

  新方法(AccountSync)

  思路

  利用Android系统提供的账号和同步机制实现

  效果

  通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程),能提高进程优先级,对比如下图

图2.png

  正常情况

图3.png

  采用AccountSyncAdapter方法后

  进程被系统kill后,可以由syn拉起

  风险

  SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟

  用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启

  实现 (核心代码)

  1 建立数据同步系统(ContentProvider)

  通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可

public class XXAccountProvider extends ContentProvider {
    public static final String AUTHORITY = "包名.provider";
    public static final String CONTENT_URI_BASE = "content://" + AUTHORITY;
    public static final String TABLE_NAME = "data";
    public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);

    @Override
    public boolean onCreate() {
        return true;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        return new String();
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

  然后再Manifest中声明

<provider
android:name="**.XXAccountProvider"
android:authorities="@string/account_auth_provider"
android:exported="false"
android:syncable="true"/>

  2 建立Sync系统 (SyncAdapter)

  通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:

  创建Sync服务

public class XXSyncService extends Service {
    private static final Object sSyncAdapterLock = new Object();
    private static XXSyncAdapter sSyncAdapter = null;
    @Override
    public void onCreate() {
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return sSyncAdapter.getSyncAdapterBinder();
    }

    static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
        public XXSyncAdapter(Context context, boolean autoInitialize) {
            super(context, autoInitialize);
        }
        @Override
        public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
            getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
        }
    }
}

  声明Sync服务

<service
android:name="**.XXSyncService"
android:exported="true"
android:process=":core">
<intent-filter>
<action
android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter"/>
</service>

  其中sync_adapter为:

<sync-adapter xmlns:android="//schemas.android.com/apk/res/android"
              android:accountType="@string/account_auth_type"
              android:allowParallelSyncs="false"
              android:contentAuthority="@string/account_auth_provide"
              android:isAlwaysSyncable="true"
              android:supportsUploading="false"
              android:userVisible="true"/>

  参数说明:

  android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。

  android:accountType 表示进行同步的账号的类型。

  android:userVisible 设置是否在“设置”中显示

  android:supportsUploading 设置是否必须notifyChange通知才能同步

  android:allowParallelSyncs 是否支持多账号同时同步

  android:isAlwaysSyncable 设置所有账号的isSyncable为1

  android:syncAdapterSettingsAction 指定一个可以设置同步的activity的Action。

  账户调用Sync服务

  首先配置好Account(第三步),然后再通过ContentProvider实现

  手动更新

public void triggerRefresh() {
Bundle b = new Bundle();
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
account,
CONTENT_AUTHORITY,
b);
}

  添加账号

Account account = AccountService.GetAccount();
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(...)

  同步周期设置

Account account = AccountService.GetAccount();
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(...)

  3 建立账号系统 (Account Authenticator)

  通过建立Account账号,并关联SyncAdapter服务实现同步

  创建Account服务

public class XXAuthService extends Service {
    private XXAuthenticator mAuthenticator;

    @Override
    public void onCreate() {
        mAuthenticator = new XXAuthenticator(this);
    }

    private XXAuthenticator getAuthenticator() {
        if (mAuthenticator == null)
            mAuthenticator = new XXAuthenticator(this);
        return mAuthenticator;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return getAuthenticator().getIBinder();
    }

    class XXAuthenticator extends AbstractAccountAuthenticator {
        private final Context context;
        private AccountManager accountManager;
        public XXAuthenticator(Context context) {
            super(context);
            this.context = context;
            accountManager = AccountManager.get(context);
        }

        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
                throws NetworkErrorException {
// 添加账号 示例代码
            final Bundle bundle = new Bundle();
            final Intent intent = new Intent(context, AuthActivity.class);
            intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
            bundle.putParcelable(AccountManager.KEY_INTENT, intent);
            return bundle;
        }

        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
                throws NetworkErrorException {
// 认证 示例代码
            String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));
            //if not, might be expired, register again
            if (TextUtils.isEmpty(authToken)) {
                final String password = accountManager.getPassword(account);
                if (password != null) {
                    //get new token
authToken = account.name + password;
                }
            }
            //without password, need to sign again
            final Bundle bundle = new Bundle();
            if (!TextUtils.isEmpty(authToken)) {
                bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
                bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
                bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
                return bundle;
            }

            //no account data at all, need to do a sign
            final Intent intent = new Intent(context, AuthActivity.class);
            intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
            intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);
            bundle.putParcelable(AccountManager.KEY_INTENT, intent);
            return bundle;
        }

        @Override
        public String getAuthTokenLabel(String authTokenType) {
//            throw new UnsupportedOperationException();
            return null;
        }

        @Override
        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
            return null;
        }

        @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
                throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
                throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
                throws NetworkErrorException {
            return null;
        }
    }
}

  声明Account服务

<service
android:name="**.XXAuthService"
android:exported="true"
android:process=":core">
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>
</service>

  其中authenticator为:

<account-authenticator xmlns:android="//schemas.android.com/apk/res/android"
    android:accountType="@string/account_auth_type"
    android:icon="@drawable/icon"
    android:smallIcon="@drawable/icon"
    android:label="@string/app_name"
/>

  使用Account服务      同SyncAdapter,通过AccountManager使用

上一篇:安卓8.0新增文件管理器,媲美iOS系统

下一篇:用非对称加密加解密okhttp数据

扫码关注微信公众号了解更多详情

跟技术大咖,专业导师一起交流学习

姓名
电话
Q Q

在线留言

请您把问题留下,我们为您提供专业化的解答!

QQ咨询
  1. 招生问答
  2. 热门点击
  3. 最新更新
  4. 推荐文章

关于我们

学校成就

就业保障

联系方式

联系电话:400-160-2868

在线报名

预约报名

备案号:湘ICP备2020021619号-1
地址:湖南省长沙市高新区麓谷麓松路679号 版权所有:长沙市岳麓职业培训学校

在线咨询
课程咨询 学费咨询 学费分期 入学测试 免费预约 来校路线
初中生 高中生 待业者
400-160-2868

在线客服