精华内容
下载资源
问答
  • 一般内存被占用都是一些数据与文稿,应用软件的都可以自己清理清理,可是人不能理解的是,还有一个“系统”选项占据非常高的储存空间。如下图所示:系统占据41.37GB系统占据29.16GB 这些系统占据较的储存空间,...

    系统内存超级大,鹿尚科技来帮你

    在使用iPhone手机的过程中,想必大家会出现内存不够用的情况,那种购买高容量的土豪就不必说,想用都用不完;一般内存被占用都是一些数据与文稿,应用软件的都可以自己清理清理,可是让人不能理解的是,还有一个“系统”选项占据非常高的储存空间。如下图所示:

    26b7f52a799f6183c96bdb11269d1534.png

    系统占据41.37GB

    2e1900193394469ab2dc530cb21c6c15.png

    系统占据29.16GB

    这些系统占据较大的储存空间,一般除了本身iPhone系统占用一定存储空间之外,很多情况是由于系统不断使用和在线升级下载固件导致的,外加其他软件的数据缓存。今天小编就告诉大家怎么清理系统内存。

    清理之前先备份

    利用PC端第三方软件进行iPhone备份,这里备份主要是将一些重要文件、照片、软件进行备份,能不备份的软件尽量不备份,重新安装就行了。

    1、找到助手工具中的“备份/恢复数据”

    5ee3f2a83775912e8223771d5de1be61.png

    2、根据个人需要进行分类备份

    a64c2f2ffce79171fade64bf48911ea6.png

    3、选择备份的数据进行备份

    acaaa5ae23a472055dbe2a888e07b1d4.png

    清理iPhone系统内存

    这里清理用到的方法是抹除iPhone数据,就相当于重置iPhone,其详细步骤如下:

    1、打开【设置】,找到【通用】选项

    8c036ac3551171b46b20baad43cd382c.png

    2、在【通用】中下滑找到【还原】

    f5269239a82da991bd5fd712794ff501.png

    3、在【还原】选项中找到【抹掉所以内容和设置】

    669beefa5b264157ed72785867d04d81.png

    4、在弹框中选择【立即抹掉】

    e45959f34f6daa426e7c237fcb4bf9c0.png

    完成以上步骤就可以等着手机被重置,最后重置成功可以选择设置为新iPhone使用。

    重要文件恢复备份

    完成iPhone手机的重置就可以将之前备份的重要文件进行恢复,恢复依然用到PC端的第三方软件,在恢复中选择“分类恢复”就可以了。完成以上操作,基本结束本次系统内存的清理工作。

    清理前后对比如下图:

    49d181a8bc38fd81fd160c240ac9a558.png

    清理前

    d533dfe76a7d91011d82fa8e5040c680.png

    清理后

    大家的iPhone内存使用情况是怎样的,欢迎大家截图留言告诉小编,看看谁的系统内存用量最多,谁的系统内存用量最少,小编来给大家点赞。

    想要了解更多资讯,记得关注鹿尚科技的头条号,感谢你们一路的支持与陪伴。

    展开全文
  • 这是因为我们在长期使用手机时,手机内部会产生一些缓存垃圾,如应用缓存、安装包等,而这些垃圾就会让手机变得越来越卡,运行越来越慢。   那我们该怎么彻底清理手机内存,释放手机空间呢? 1、删除应用缓存垃圾 ...
    随着手机的频繁更新换代,现在市面上的手机也是越来越多了,但无论是什么手机,都会面临清理内存的问题。这是为什么呢?
    这是因为我们在长期使用手机时,手机内部会产生一些缓存垃圾,如应用缓存、安装包等,而这些垃圾就会让手机变得越来越卡,运行越来越慢。
     
    那我们该怎么彻底清理手机内存,释放手机空间呢?
    1、删除应用缓存垃圾

    我们经常刷微博,聊微信,看手机视频,这会使手机应用产生大量的缓存,所以我们在清理手机内存之前,要先删除手机应用缓存。
    可以先在手机储存空间中查看占手机内存的大户,然后打开该应用设置,点击删除手机缓存。
     
    2、清理手机垃圾文件
    除了应用缓存外,别忘了使用手机自带清理功能将手机打扫一番哟。
     
    3、删除不常用程序
    手机上总会有一些不常用或根本不会去用的应用程序,如预装软件等。这些应用留下来又没啥卵用,还占内存,删了它们,还能释放出多余的手机空间呢?
    不过卸载预装软件必须ROOT手机后才可以操作,所以我们需要借助第三方软件“强力一键ROOT”。
    PS:使用强力一键ROOT软件后,还可以移除ROOT哟~
     
    4、关闭自启动应用程序
    现在的APP都很鸡贼,在你不知道的时候,都是默认开机自启动的。长期如此,也会产生很多缓存文件的哟。
     
    5、关闭后台应用程序
    安卓手机一个不注意,就有后台程序在偷偷运行。这些程序不仅会偷走流量,也会产生缓存文件呢。
     
    6、恢复出厂设置
    当手机卡到无药可救,你又无法解决时,那就恢复出厂设置吧。
    让手机回到原点~
     
    恢复出厂设置前,别忘了手机备份哟。
    展开全文
  • 手机卡顿是个一直伴随安卓机用户的问题,很多人都知道手机卡顿是无法避免的,那么身为一名OPPO...并且这些锁屏壁纸还是自动更新出来的,会占用到手机内存空间。一旦所占比例过,就会影响手机运行,出现卡顿。所以...

    手机卡顿是个一直伴随安卓机用户的问题,很多人都知道手机卡顿是无法避免的,那么身为一名OPPO用户,手机用久了变卡顿怎么办?

    那就花一分钟关闭这4个设置,让OPPO手机瞬间流畅起来!

    791a3f66efa824e808770d45291114f4.png

    1、 关闭锁屏壁纸

    当手机锁屏时,OPPO 手机默认会出现一些锁屏壁纸,这些锁屏壁纸虽然很好看,但是会消耗手机电量。并且这些锁屏壁纸还是自动更新出来的,会占用到手机内存空间。

    一旦所占比例过大,就会影响手机运行,出现卡顿。所以多数OPPO手机用户会选择关闭这个设置。

    关闭方法:设置—显示与亮度——锁屏杂志与壁纸—关闭锁屏杂志;

    9576a037a467c15f9dde7ed3d7774dbd.png

    2、

    关闭用户体验计划

    OPPO手机里面带有用户体验计划功能,OPPO手机厂家设置这个功能是为了手机用户的使用情况,并进行改进。但是开启这个功能后,就会在后台一直运转,也会造成手机卡顿,所以不需要的用户可以直接关闭。

    关闭步骤:设置—其它设置—设置与隐私—用户体验计;

    8d734a80e87707b7f21ef5a8c572a62b.png

    3、

    关闭应用自启动

    不知道大家有没有留意这个设置,该功能在

    【手机管家】的【权限隐私】中。开启这功能,可以让应用在后台自行运行,接收消息或者推送通知。

    8015d28016c478767d9f48c1f6a4118f.png

    但是部分工具类应用就没有启用自动后台运行的必要,所以可以针对一些应用关闭后台自启动的权限。

    如:能够

    实时转写语音的录音转文字助手,就是一款专业的语音功能,

    不仅能够转写文字,还可以语音翻译等,但是大家也可以关闭该工具的自启动,在需要的时候使用就可以了。

    27b55c3928516e7c6cf882c2bf76ea19.png

    4、

    系统更新

    有些人会设置成手机自动系统更新,但是有的时候一些

    新版本的系统,手机硬件完全跟不上,配置不行的原因导致手机卡顿。所以部分OPPO用户都会选择关闭手机自动更新设置。

    关闭方法:设置—软件更新—右上角图标按钮—夜间自动更新;

    710a5bf4f2c407a8e15ebf3755e473b8.png

    如果你也是一名OPPO手机用户,那么你也可以花1分钟的时间关闭这些设置,让自己的OPPO手机瞬间流畅,

    不过由于OPPO手机的Color OS版本的不同,设置路径也有可能不同,只能让大家参考,找不到的可以私信我,我会为大家进行解答的!

    感谢大家的阅读,喜欢记得转发,让更多的OPPO手机用户知道如何减缓手机卡顿,谢谢!

    展开全文
  • 近期被一些朋友问到关于进程保活这块的知识点,想必是很多开发者研究的重点,虽然我不支持做成这类【流氓软件】,因为大家都这么干的话,Android系统的内存永远不够用,电量消耗的贼快,流畅度肯定就大大的降低了;...

    前言

    近期被一些朋友问到关于进程保活这块的知识点,想必是很多开发者研究的重点,虽然我不支持做成这类【流氓软件】,因为大家都这么干的话,Android系统的内存永远不够用,电量消耗的贼快,流畅度肯定就大大的降低了;但是程序猿可能也架不住产品的需求,哪一个产品经理不希望自己的APP在用户的手机上随叫随到

    其实说实话一个APP很难做到真正的不死,特別是Android 5.0以后Google对应用进程管理的更加严格,杀的也很彻底,除非你的应用被手机厂商拉到了白名单中;那么作为一个不在白名单中的应用怎么才能做到尽量不死呢?就算死了也能立马活过来呢?这篇文章就总结下几种实现方法

    APP进程被系统杀死

    在实操前先阐述一些基本知识

    第三方应用退出后台后,并不会一直在后台运行,在某些情况下其应用所在进程会被干掉以释放出系统资源,一般被干掉的原因有:

    • 手机厂商:比如华为、小米、OV等厂商出于对性能的考虑,会对第三方应用进行检测,看是否有开启后台服务且是长时间运行的,那么在某些情况下就会将其杀死释放资源;当然了,如果你的应用做的够大够强,比如微信支付宝,那设备厂商直接就将你的应用放到白名单了,也就不存在进程保活了
    • 原生系统回收机制:当手机系统可用内存过低时,系统会根据进程优先级,杀死优先级最低的进程以释放资源

    Low Memory Killer

    Android对于内存的回收主要依靠Low Memory Killer完成:系统出于用户体验和性能的考虑,app在退到后台时,系统并不会立即将其kill掉,而是将其缓存起来;但是当打开的应用越多,后台缓存的进程也就越多,也就意味着系统可用内存越来越少;那么当内存不足时,系统就根据oom_adj值触发相应力度的进程回收机制来判断要杀死哪些进程,以释放出内存来供当前的应用使用,这套杀死进程回收内存的机制就叫Low Memory Killer

    进程优先级

    其中一个重要的判断依据就是进程优先级,要知道Android系统会尽量长时间保持应用进程,但是为了新建进程或运行更重要的进程,需要清除一些旧进程来回收内存;为了确保保留或终止哪些进程,系统会对进程进行分类,确定进程优先级;需要时系统首先清除优先级最低的进程,再清除稍低的进程,以此类推来回收资源;其中进程优先级划分如下:

    • 前台进程 Active Process:一般是前台正在交互的activity、与前台activity绑定的service、调用startForeground()方法使之位于前台运行的Service、执行它的某个生命周期回调方法,比如onCreate()、 onStart()或onDestroy()的Service、正在执行onReceive事件处理的函数的BroadCast Receiver等所在的进程,这几种情况的进程就是可见进程,这些是android通过回收资源尽力保护的进程

    • 可见进程 Visible Process:比如一个activity处于可见但并不是处于前台或者不响应用户事件,处于暂停(OnPause)状态;还有一种就是被这种Activity绑定的Service,这种就是可见进程;这些情况一般发生在当一个activity被部分遮盖的时候(被一个非全屏或者透明的Activity)。可见进程只在极端的情况下,才会被杀死来保护前台进程的运行。

    • 服务进程 Service Process:包含已经启动的service,service以动态的方式持续运行但没有可见的界面。因为Service不直接和用户交互,它们拥有比Visible Process较低的优先级

    • 后台进程 Background Process:进程中的Activity不可见或进程中没有任何启动的service,这些进程都可以是后台进程;比如按home键,activity的前台进程就变成了后台进程;在系统中,拥有大量的后台进程,并且Android会按照后看见先杀掉的原则来杀掉后台进程以获取系统资源给前台进程

    • 空进程 Empty Process:为了改善整个系统的性能,android经常在内存中保留那些已经走完生命周期的应用程序。android维护这些缓存来改善应用程序重新启动的时间,为使用总体系统资源在进程缓存和底层内核缓存之间保存平衡,系统会随时终止这些进程

    用一幅图展示下

    在这里插入图片描述
    那系统是怎么知道这些进程的优先级的呢?

    这就要说到oom_adj这个东西了,进程的优先级通过进程的adj值来反映,它是Linux内核分配给每个系统进程的一个值,adj值越小,进程优先级越高,进程回收机制根据这个值来决定是否进行回收;adj越大,占用内存越多,越会被系统最先干掉,所以进程保活的实现就成了如何降低oom_adj的值,以及如何使应用所占内存降低

    有一张adj表可以看到其与进程优先级的更详细的关系

    在这里插入图片描述
    红色部分代表比较容易被杀死的进程(oom_adj>4),绿色部分代表不容易被杀死的进程,白色部分表示非Android进程

    可以通过 cat /proc/进程id/oom_adj命令查看进程的adj值,不过需要root权限,不同厂商的手机获取到的值会有所不同

    比如:当activity处于前台的时候,adj值是0
    在这里插入图片描述
    当按下home键回到桌面,adj值会变大
    在这里插入图片描述
    当按下返回键,adj值会变得更大,也就意味着进程优先级变得越来越低


    进程保活

    从上面那张图也可以看出,我们首先要做的就是将我们的进程的adj值尽量往绿色部分移,也就是降低adj值,白色部分的不用管;其实现方式有如下几种:

    1px Activity

    在一些设备上,有些第三方应用或者系统管理工具在检测到锁屏事件后一段时间内还存在的一些后台进程,并将其回收已达到省电和释放内存的目的;那么我们就在锁屏后将应用置位前台进程,因为系统基本不会杀死前台进程,做法就是当手机屏幕关闭时打开一个像素的Activity,降低adj值,提高应用进程的优先级

    具体实现:注册一个广播,监听系统发出的关屏和开屏的广播,当关屏时开启一个像素的Activity,开屏时销毁这个Activity

    首先定义一个像素的Activity

    public class KeepActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.act_keep);
            Window window = getWindow();
            //设置Activity处于左上角
            window.setGravity(Gravity.START | Gravity.TOP);
            WindowManager.LayoutParams attr = window.getAttributes();
            //设置宽高都是1px
            attr.width = 1;
            attr.height = 1;
            //设置坐标
            attr.x = 0;
            attr.y = 0;
            window.setAttributes(attr);
            KeepManager.getManager().setKeepActivity(this);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            Log.e("KeepActivity","onResume");
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            Log.e("KeepActivity","onDestroy");
        }
    }
    

    在Manifest中注册

            <activity android:name=".onepx.activity.KeepActivity"
                      android:excludeFromRecents="true"
                	  android:exported="false"
                	  android:finishOnTaskLaunch="false"
                	  android:launchMode="singleInstance"
                      android:theme="@style/KeepTheme">
            </activity>
    

    主题设置

        <style name="KeepTheme">
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:windowBackground">@android:color/transparent</item>
            <item name="android:windowAnimationStyle">@null</item>
            <item name="android:windowNoTitle">true</item>
        </style>
    

    接下来就要定义广播

    public class KeyReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
    
            String action = intent.getAction();
            Log.e("KeyReceiver","action="+action);
            if (TextUtils.equals(action,Intent.ACTION_SCREEN_OFF)) {
                //当屏幕关闭时
                KeepManager.getManager().startKeepActivity(context);
            } else if (TextUtils.equals(action,Intent.ACTION_SCREEN_ON)) {
                //当屏幕开启
                KeepManager.getManager().finishActivity();
            }
        }
    }
    

    动态注册或者静态注册都行

    <receiver android:name=".onepx.receiver.KeyReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.SCREEN_OFF"></action>
            <action android:name="android.intent.action.SCREEN_ON"></action>
        </intent-filter>
    </receiver>
    

    这里就是要当关屏时开启一个像素的Activity,让应用处于前台进程;开屏时销毁这个Activity;这些事情通过一个工具类管理

    public class KeepManager {
    
        private static class Instance {
            private static final KeepManager INSTANCE = new KeepManager();
        }
        private KeepManager(){}
        public static KeepManager getManager(){
            return Instance.INSTANCE;
        }
    
        private KeyReceiver mKeyReceiver;
        private WeakReference<Activity> mKeepAct;
    
        /**
         * 注册开关屏广播
         * @param context
         */
        public void registerReceiver(Context context){
            if (mKeyReceiver != null) return;
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_ON);
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            mKeyReceiver = new KeyReceiver();
            context.registerReceiver(mKeyReceiver,filter);
        }
    
        /**
         * 注销广播
         * @param context
         */
        public void unRegisterReceiver(Context context){
            if (mKeyReceiver != null) {
                context.unregisterReceiver(mKeyReceiver);
            }
        }
    
        public void setKeepActivity(KeepActivity activity){
            mKeepAct = new WeakReference<Activity>(activity);
        }
    
        /**
         * 关屏时打开1px的Activity
         * @param context
         */
        public void startKeepActivity(Context context){
            Intent intent = new Intent(context,KeepActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
    
        /**
         * 开屏时把Activity销毁
         */
        public void finishActivity(){
            if (mKeepAct != null) {
                Activity activity = mKeepAct.get();
                if (activity != null) {
                    activity.finish();
                }
                mKeepAct = null;
            }
        }
    }
    

    代码比较简单,就不做过多叙述了,最终的效果就是你退出了应用,变成了后台进程;然后按下电源键关屏屏幕,此时应用内的广播接收器接收到关屏广播,然后打开一个像素的Activity,让应用成为前台进程,大大降低被系统杀死的几率

    这种方案的局限性就是必须要用户关屏后你的方案才能发挥作用,要是用户一直在使用的过程中你的进程在后台被回收了,那这个方法也就无效了

    同时要注意在Android8.0开始,静态注册是收不到这些系统广播了,需要改为动态注册;但是动态注册退出APP后又要注销广播,那这方案就失效了,除非用户按home键退出,然后锁屏

    前台Service

    第一种情况:

    在API18以下,直接调用startForeground(ID, new Notification()),发送空的Notification ,将后台服务推到前台成为前台Service,这样即使退出应用,或者在Recent Task中删除该应用,前台Service依然存在,应用的优先级依然很高

    这样能实现的原理其实是Android的一个漏洞,发送一个空的Notification,通知栏并不会显示我们发送的Notification,这样用户就不知道你的应用还是在运行了,真正的做到了用户无感知下的进程保活

    public class ForegroundService extends Service {
    
    
        private class ProcessBinder extends Binder{}
    
        @Override
        public IBinder onBind(Intent intent) {
            return new ProcessBinder();
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
            @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(NOTIFICATION_ID, new Notification());
            return super.onStartCommand(intent, flags, startId);
        }
    }
    

    如图:
    在这里插入图片描述

    第二种情况:

    从api 18(4.3)开始,Google已经意识到了这个漏洞,因为非常多的流氓应用都这样处理,即使你发送一个空的Notification,在通知栏也会有一个通知框提示你的应用正常运行,如图:

    在这里插入图片描述
    这样用户大概率会主动将你的应用杀掉,那我们怎么继续做到用户无感知呢?看下面

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            //API 18[4.3]以下,直接发送Notification并将其置为前台
            if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
                startForeground(NOTIFICATION_ID, new Notification());
            } else {
                //API 18开始,发送Notification并将其置为前台后,启动InnerService
                startForeground(NOTIFICATION_ID, new Notification());
                startService(new Intent(this, InnerService.class));
            } 
            return super.onStartCommand(intent, flags, startId);
        }
    
        public  static class  InnerService extends Service{
            @Override
            public IBinder onBind(Intent intent) {
                return null;
            }
    
            @Override
            public int onStartCommand(Intent intent, int flags, int startId) {
                //发送与FrontService中ID相同的Notification,覆盖上面的,然后将其停止并结束自己
                //因为两个通知ID相同,所以结束掉一个相当于两个通知都结束了
                startForeground(NOTIFICATION_ID, new Notification());
                stopForeground(true);
                stopSelf();
                return super.onStartCommand(intent, flags, startId);
            }
        }
    

    我们知道唯一确定一个通知是根据id,如果我们先发送通知,然后再开启一个服务,在里面发送另一个id一样的通知,最后将第二个前台服务停止并销毁自己,这样的结果就是第一次发送的通知也会被取消掉,也就是在下拉栏看不到通知框了,用户自然也看不到任何你的应用还在运行的信息了;但是这在某些设备上会有一些问题,发送前台通知会唤醒设备并点亮屏幕,这样会很耗电

    第三种情况:

    到了Android 26,Notification 需要设置channel,也就是NotificationChannel 对象,可以理解为渠道,就是做一下通知的兼容处理

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            //API 18[4.3]以下,直接发送空的Notification并将其置为前台,可以不显示通知栏消息
            if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
                startForeground(NOTIFICATION_ID, new Notification());
            } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                //API 18-26,发送Notification并将其置为前台后,启动InnerService
                startForeground(NOTIFICATION_ID, new Notification());
                startService(new Intent(this, InnerService.class));
            } else {
                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                if (manager != null) {
                    //进行渠道设置
                    //第三个参数值越小,该通知的重要性越低,因为我们只需要开启前台服务,不需要让用户知道
                    NotificationChannel channel = new NotificationChannel("channel","keep",NotificationManager.IMPORTANCE_MIN);
                    manager.createNotificationChannel(channel);
                    Notification notification = new NotificationCompat.Builder(this,"channel").build();
                    startForeground(NOTIFICATION_ID,notification);
                }
            }
            return super.onStartCommand(intent, flags, startId);
        }
    

    这样操作以后,应用在退出后,进程adj值基本上还是在1-3之间,这种方案可以说是使用的比较广的一种了,使用简单,通过前台服务将自己的进程优先级大大的提高

    8.0兼容处理:如果是在后台启动ForegroundService ,那么一定要注意,从Android api26开始(即8.0),Google对后台启动Service做了限制,即不允许后台应用启动Service,什么叫后台应用呢?先看看什么是前台应用:

    • 具有可见 Activity(不管该 Activity 已启动还是已暂停)
    • 具有前台服务
    • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)

    如果以上条件均不满足,应用将被视为处于后台,这时创建Service将会报错

    解决办法:Android 8.0 引入了一种全新的方法,即 Context.startForegroundService()

    if (Build.VERSION.SDK_INT >= 26) {
        Context.startForegroundService(service);
    } else {
        Context.startService(service);
    }
    

    启动完前台service, 一定记得在5s以内要执行如下类似代码, 否则程序会报ANR问题

    if (Build.VERSION.SDK_INT >= 26) {
          startForeground(1, new Notification());
      }
    

    9.0兼容处理:需要在AndroidManifest中添加权限:

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    

    进程拉活

    上面说的两种方式是【保活】,重点是保,也就是说应用其实没死,我们只是通过这些方法提高它的生存几率【降低adj值】,如果应用被用户在设置界面强行停止或者被360等软件强行杀死,那上面两种方案也行不通了

    这样就要讲到另一种说法了,也就是【拉活】,意思就是进程死了再将其拉起来,死而复生

    粘性Service

    这种是系统提供的合法方案,根据onStartCommand方法返回值操作;系统内存是有限的,当系统内存资源不足,Service是会被销毁,如果你在Service里做了什么重要事情,那被销毁显然是你不愿意看到的,所以要有一种方法让系统帮我们重启该服务,那要不要重启就由这个返回值决定了,看下方:

    • START_STICKY:如果service进程被kill掉,系统会尝试重新创建Service,如果在此期间没有任何启动命令被传递到Service,那么参数intent将为null。
    • START_NOT_STICKY:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统不会自动重启该服务。
    • START_REDELIVER_INTENT:使用这个返回值时,如果在执行完onStartCommand()后,服务被异常kill掉,系统会自动重启该服务,并将intent的值传入。
    • START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

    作为一个后台常驻Service,不建议在使用时通过Intent传递数据,所以我们返回START_STICKY和START_STICKY_COMPATIBILITY即可;不过这只是在原生系统上是这样操作,至于国内各个厂商深度定制后就不一定能保证重启了;同时Service第一次被异常杀死后会在5S内重启,第二次被杀后10S内重启,第三次就是20S,如果短时间内被杀5次,系统就不会再将其拉起,所以单单靠这个还是不保险的

    JobScheduler

    相对于隐式广播和Service,Google推荐使用JobScheduler,它是在Android 5.0推出来的API,允许开发者创建在后台执行的Job,当到达未来某个时间点或者未来满足某种条件(比如插入电源或者连接WiFi)的情况下,这些Job将会在后台被执行。和AlarmManager类似,但有点不一样,执行这些操作的时间并不是严格准确的。 JobScheduler会把一系列的job收集起来一起执行,这样既允许我们的job被执行,又能兼顾到手机电量的使用情况,达到节电的目的。

    JobScheduler是一个系统提供的框架,旨于在应用进程、而非系统进程内执行各种作业调度,其原理是启动通过bindservice的方式启动应用进程的service,并在Service中进行作业。在执行一个Job时,将会使得系统持有一个WakeLock锁,以防止系统休眠进入Suspend。
    在创建一个作业时,会设置多个约束条件,比如可以指定特定的网络、是否只在充电时执行作业等,JobScheduler框架会根据这些约束条件,智能地执行作业,并尽可能对作业进行批操作和推迟,以防止频繁唤醒系统而影响功耗,还可以指定该Job的执行的截至期限。如果不指定一个作业的截至期限,那么该作业可能会在任意一个时刻运行,这取决于JobScheduler的内部队列。

    要想使用JobScheduler,必须使项目最小API达到21,目前还没有support library提供低版本的兼容;JobScheduler根据其字面意思理解为工作日程表,也就是将所有应用添加的工作进行统一调度执行;它的使用比较简单,JobScheduler框架为应用提供了如下四个组件,通过这四个类的API可以让用户在应用中创建一个作业,并让系统对他进行调度

    • JobScheduler:JobScheduler类负责将应用需要执行的作业发送给框架,以准备对该应用Job的调度。JobScheduler是一个系统服务,可通过如下方式获取:

      JobScheduler jobScheduler = (JobScheduler) Context.getSystemService(Context.JOB_SCHEDULER_SERVICE). 
      
    • JobInfo:JobInfo是传递给JobScheduler类的数据容器,它封装了针对调用应用程序调度作业所需的各种约束,也可以认为一个JobInfo对象对应一个作业,JobInfo对象通过JobInfo.Builder创建。它将作为参数传递给JobScheduler

    • JobInfo.Builder:JobInfo.Builder是JobInfo的一个内部类,顾名思义,它就是用来创建JobInfo的Builder类

    • JobService:JobService是一个继承于Service的抽象类,他作为系统回调执行作业内容的终端,JobScheduler框架将通过bindService()方式来启动该服务.因此,用户必须在应用程序中创建一个JobService的子类,并实现其onStartJob()等回调方法,以及在清单文件中对它授予如下权限:

      <service android:name=".JobSchedulerService"
          android:permission="android.permission.BIND_JOB_SERVICE"/>
      

    使用方法如下:

    第一步:创建JobService的子类

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public class JobSchedulerService extends JobService {
    
        
        @Override
        public boolean onStartJob(JobParameters params) {
            return false;
        }
    
        @Override
        public boolean onStopJob(JobParameters params) {
            return false;
        }
    }
    

    我们需要重写这两个回调方法,它们都是系统回调的

    • onStartJob:当系统要触发执行我们的Job的时候,会调用onStartJob方法。这个方法会返回一个布尔型的值:

      • 返回false:表明该方法内的任务已经完成,同时系统会认为当前已经没有任务在运行, 就不会调用对应的onStopJob方法了
      • 返回true:也就是告诉系统,我这里在做一个耗时任务(要注意,这个方法在主线程回调,所以耗时任务要放到子线程执行),即使该方法已经返回,我们的工作还在异步执行,这时候系统就会把任务的结束调用交给用户去做,所以当任务结束时,我们需要手动调用jobFinished方法;如果不调用jobFinished,系统会一直认为我们在执行当前Job,那么系统就不会再入队本应用其它的Job去执行,也就是说JobScheduler的执行队列就会被阻塞了
    • onStopJob:当系统收到取消请求且onStartJob返回true的时候才会调用onStopJob,否则不调用

    实际操作中这个类就类似于下面这样了

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public class JobSchedulerService extends JobService {
    
        private final String TAG = JobSchedulerService.class.getSimpleName();
    
        @Override
        public boolean onStartJob(JobParameters params) {
            Log.e(TAG,"onStartJob");
            //将耗时任务放到子线程执行
            new Thread(new Task(params)).start();
            //返回true,意味着我们需要做耗时操作,需要手动调用jobFinished方法
            return true;
        }
    
        @Override
        public boolean onStopJob(JobParameters params) {
            Log.e(TAG,"onStopJob");
            return false;
        }
    
        class Task implements Runnable {
            JobParameters params;
    
            public Task(JobParameters params) {
                this.params = params;
            }
    
            @Override
            public void run() {
                try {
                    //做一些耗时操作
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    jobFinished(params,false);
                }
    
            }
        }
    
    }
    

    注意jobFinished方法有两个参数,第一个是onStartJob方法的参数,第二参数表示是否在条件满足时重复执行该任务

    第二步:创建JobScheduler,开启任务

    mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
    ComponentName componentName = new ComponentName(this,JobSchedulerService.class);
    JobInfo.Builder builder = new JobInfo.Builder(++mJobId,componentName);
    builder.setPeriodic(10*1000);
    int result = mJobScheduler.schedule(builder.build());
    Log.e(TAG,"result="+result);
    

    setPeriodic方法是设置任务执行周期,这里是每10秒钟执行一次,但是实际过程中并不一定会严格按照这个周期执行
    schedule方法有一个返回值,当schedule失败的时候会返回一个负数,成功的时候会返回我们在创建JobInfo.Builder时传入的JobId

    第三步:取消任务

    如果需要在某个时间段取消任务,进行如下操作:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //根据JobId取消某个任务
        mJobScheduler.cancel(mJobId);
        //也可以取消全部任务
        mJobScheduler.cancelAll();
    }
    

    所以要想使用JobScheduler实现进程保活或者拉活有两种方法:

    第一种:每隔一段时间执行周期性作业

     mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
     ComponentName componentName = new ComponentName(this,JobSchedulerService.class);
     JobInfo.Builder builder = new JobInfo.Builder(++mJobId,componentName);
     mJobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
     if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
         mJobBuilder.setMinimumLatency(10 * 1000);
     } else {
         mJobBuilder.setPeriodic( 10 * 1000);
     }
     mJobScheduler.schedule(builder.build());
    

    这就是每隔10秒钟执行一次JobSchedulerService,它里面可以什么逻辑都不写,但是onStartJob要返回true,这种能实现保活是因为JobSchedulerService实际上就是一个Service,当启动该Service时,如果其所在进程不存在,系统会创建相应的进程

    但是要做一下版本兼容处理,在Android 7.0以下,setPeriodic的值可以随意设置,但是从7.0开始Google对于新设备功耗要求越来越严格,对于APP的限制也越来约多,导致setPeriodic的值,也就说定期作业的间隔时间>=15分钟时才能运行。

    第二种:每隔一段时间检查保活服务是否存在,不存在就将其启动;然后再创建一个新的JobScheduler任务,并结束当前JobScheduler任务。

    public class JobSchedulerService extends JobService {
    
        private final String TAG = JobSchedulerService.class.getSimpleName();
        private int mJobId;
    
        @Override
        public boolean onStartJob(JobParameters params) {
            Log.e(TAG,"onStartJob");
            /**
             * 做版本兼容
             */
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    String servicename = params.getExtras().getString("servicename");
                    Class service = getClassLoader().loadClass(servicename);
                    if (service != null) {
                        //判断保活的service是否被杀死
                        if (!isServiceRunning(service)) {
                            //重启service
                            startService(new Intent(getApplicationContext(), service));
                        }
                    }
                    //创建一个新的JobScheduler任务
                    scheduleRefresh(servicename);
                    jobFinished(params, false);
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
    
        @Override
        public boolean onStopJob(JobParameters params) {
            Log.e(TAG,"onStopJob");
            return false;
        }
    
        private void scheduleRefresh(String serviceName) {
    
            if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
    
            JobScheduler mJobScheduler = (JobScheduler)getApplicationContext().getSystemService(JOB_SCHEDULER_SERVICE);
    
            JobInfo.Builder mJobBuilder =
                    new JobInfo.Builder(++mJobId,new ComponentName(getPackageName(),JobSchedulerService.class.getName()));
            mJobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
            if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                mJobBuilder.setMinimumLatency(6 * 1000);
            } else {
                mJobBuilder.setPeriodic( 6 * 1000);
            }
    
            PersistableBundle persiBundle = new PersistableBundle();
            persiBundle.putString("servicename", serviceName);
            mJobBuilder.setExtras(persiBundle);
    
            if (mJobScheduler != null && mJobScheduler.schedule(mJobBuilder.build()) <= JobScheduler.RESULT_FAILURE) {
                Log.e(TAG, "schedule the service FAILURE!");
            }else{
                Log.e(TAG, "7.0 schedule the service SUCCESS!");
            }
        }
    
        /**
         * 判断保活Service是否启动
         * @param serviceClass
         * @return
         */
        public boolean isServiceRunning(Class<?> serviceClass) {
            ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            for (ActivityManager.RunningServiceInfo runningService : manager.getRunningServices(Integer.MAX_VALUE)) {
                if (serviceClass.getName().equals(runningService.service.getClassName())) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    Doze模式:在Android 6.0之后Google推出了一个Doze模式,即休眠、打盹之意。是为了延长电池使用寿命的一种节能方式,它的核心思想是在手机处于屏幕熄灭、不插电或静止不动一段时间后,手机会自动进入Doze模式。处于Doze模式的手机将停止所有非系统应用的WalkLocks、网络访问、闹钟、GPS/WIFI扫描、JobSheduler活动。当进入Doze模式的手机屏幕被点亮、移动或充电时,会立即从Doze模式恢复到正常,系统继续执行被Doze模式"冷冻"的各项活动。

    换而言之,Doze模式不会杀死进程,只是停止了进程相关的耗电活动,使其进入"休眠"状态。至Android N(即Android 7.0)后,谷歌进一步对Doze休眠机制进行了优化,休眠机制的应用场景和使用规则进行了扩展。Doze在Android 6.0中需要将手机平行放置一段时间才能开启,在7.0中则可随时开启。

    因此:

    • 对于Android 5.0:JobSheduler的唤醒是非常有效的;
    • 对于Android 6.0:虽然谷歌引入了Doze模式,但通常很难真正进入Doze模式,所以JobSheduler的唤醒依然有效;
    • 对于Android 7.0:JobSheduler的唤醒会有一定的影响,我们可以在电池管理中给APP开绿色通道,防止手机Doze模式后阻止APP使用JobSheduler功能。

    如果遇到深度定制机型,这就要看运气了

    双进程守护

    所谓双进程(是Java层的双进程),就是一个主进程和一个子进程,一个前台Service放在主进程,一个前台Service放在子进程,只要有一个进程挂了,另外一个进程就将其拉起来

    这个实现也是很简单的,主要就是创建两个Service

    public class LocalService extends ForegroundService {
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            //启动远程服务
            bindService(new Intent(LocalService.this,RemoteService.class),connection,Service.BIND_IMPORTANT);
            return super.onStartCommand(intent, flags, startId);
        }
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //当与远程服务断开连接时,即守护进程挂了,那从新将远程服务启动,也就将守护进程拉起来了
                startService(new Intent(LocalService.this,RemoteService.class));
                bindService(new Intent(LocalService.this,RemoteService.class),connection,Service.BIND_IMPORTANT);
            }
        };
    }
    
    public class RemoteService extends ForegroundService {
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            //启动本地服务
            bindService(new Intent(RemoteService.this,LocalService.class),connection,Service.BIND_IMPORTANT);
            return super.onStartCommand(intent, flags, startId);
        }
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //当与本地服务断开连接时,即主进程挂了,那从新将本地服务启动,也就将主进程拉起来了
                startService(new Intent(RemoteService.this,LocalService.class));
                bindService(new Intent(RemoteService.this,LocalService.class),connection,Service.BIND_IMPORTANT);
            }
        };
    }
    

    配置文件

            <service android:name=".doubleprocess.LocalService"></service>
            <service android:name=".doubleprocess.RemoteService"
                android:process=":remote"></service>
    
    • 两个Service都继承前面讲的前台服务
    • 重点是将RemoteService置于一个子进程中
    • 当onServiceDisconnected方法被调用时,说明对方断开了连接,基本上是对方进程挂了,然后就将其启动起来就行了

    Android5.0以后:

    注意:在Android5.0以前,AMS在回收应用内存时的做法是

    在这里插入图片描述
    即回收内存时将主进程杀死,但是归属这个应用的其它子进程并没有杀死,这就给很多应用提供了双进程守护的机会

    然而Google也发现了这个漏洞,于是在Android5.0以后就变成了这样

    在这里插入图片描述
    不仅把主进程给杀死,还把主进程所属的进程组一并杀死,所以说如果是系统将其进程组里的进程都杀死,那双进程守护也失效了(如果是用户在设置界面强行停止你的应用,双进程守护同样失效),当然了如果系统杀死进程所花的时间比你拉活逻辑的时间要久,那你的拉活还是能成功的

    全家桶唤醒

    这里指的是不同应用的进程之间相互唤醒,这种方式也是最无耻的了,比如你的手机装了百度全家桶,阿里全家桶,腾讯全家桶等归属一家公司的应用,那么每个公司的一堆应用就开始拉帮结派了,只要一个应用被打开,结果可能是全家桶中的所有应用都被唤醒了,不同APP之间相互唤醒主要通过广播

    不光APP之间能唤醒,有些SDK也能唤醒APP,比如你的应用接入微信SDK,那么你的应用打开后,也会唤醒微信

    像微信这样的应用在普通手机里可能有二三十条唤醒路径,其他的APP也都不是神马善类,同样几十条唤醒路径。所以你就可以知道为什么Android机子会慢慢卡成一坨翔

    如果你的手机里安装了支付宝,天猫,淘宝,闲鱼等阿里系应用,可以试下打开一个应用,其它应用是不是也被拉起来了

    系统广播唤醒

    我们知道系统的很多事件产生后会发出一条广播,比如开机后会发一条开机广播,网络切换、拍照、拍视频等都会发出广播,于是很多应用就注册了非常多的广播,当接收到这些广播后就提高应用进程优先级,这可以说是相当恶心的做法了

    Google可能也意识到这个问题了,在Android 7.0中做了后台优化,如图

    在这里插入图片描述
    大概意思是删除了三项隐式广播CONNECTIVITY_CHANGE,ACTION_NEW_PICTURE ,ACTION_NEW_VIDEO,以帮助优化内存使用和电量消耗

    • 在Android N平台下即使在Manifest.xml清单文件中注册了 CONNECTIVITY_ACTION广播,在网络发生变化时也不会接收到任何的信息。但是正在前台运行的应用程序如果在主线程中通过Context.registerReceiver()动态注册了CONNECTIVITY_ACTION广播,该应用程序仍然可以接收到该广播。(注:这样开发者就可以根据不同的网络状态加载相应的页面信息了,从而提高用户体验)。

    • 应用程序无法发送或接收 ACTION_NEW_PICTURE(拍照) 或 ACTION_NEW_VIDEO(录像) 广播。此项优化会影响所有应用,而不仅仅是面向 Android N 的应用。

    这无疑是给了很多应用开发商一个沉重的打击

    重点:而在Android 8.0中,Google做的更狠,除了官方文档列出的,其余所有的隐式广播都被移除了;Google 认为应用程序在其 manifest 中注册了太多没必要的 BraodcastReceiver,导致了不必要的耗电。比如,很多的应用和第三方 SDK 都会监听 CONNECTIVITY_ACTION 广播。当你离开家,断开了家里的 wifi。Android 发送 CONNECTIVITY_ACTION 广播,结果几乎所有的应用都会被唤醒并对此作出反应。于是Google移除了大量的隐式广播

    Android 8.0行为变更
    国内镜像

    所以使用静态注册广播的朋友们要注意版本变化了

    总结

    站在一个Android开发者的立场来说,极力告诫大家不要这么干,为了维护好Android应用环境,给用户一个良好的用户体验,当用户退出应用后,就应该让这个应用彻底销毁掉,释放出系统资源;有的人可能说了我的应用需要实时上报数据,但是用户都退出了,你还上报个鸡毛啊,明显是站着茅坑不拉屎的干活;这种需求完全可以放在当手机处于充电且应用存活的状态下去实现,所以说你的应用是不是真的需要常驻后台,需要认真考虑下其实现方式;当然了,如果你碰到了一个做过一阵子开发就以为了解全世界的上司,那只能放弃抵抗了

    展开全文
  • 麒麟810+屏幕指纹跌至冰点,还有6+128G大内存!众所周知华为今年的处境是十分艰难的,所以比起以往来说,它发布的手机要更多一些,其中就包括了不少性价比颇高的千元机,而荣耀Play4T Pro就是之前它们推出的一款4G...
  • 而且我们是否总会觉得我的手机内存非常,但可用内存很少。这到底是怎么回事?小编告诉你如何解决这些问题。有一个词叫做对症,这意味着你需要先找到疾病的根源,然后对它做出回应。那么我们内存被占用的原因是...
  • 手机经常无空间的解决方案

    千次阅读 2013-08-09 11:18:48
    一些手机经常会没有空间了,安装软件和照相一直提示没有空间,非常人烦恼。 首先手机连接电脑,查找哪个文件夹占用空间最大,如果无用建议删掉。判断有用还是无用的标准,有好多人可能不知道怎么判断。建议使用...
  • 腾讯视频播放器是一款支持多种音视频格式的主流播放器,内置丰富的视频资源,涵盖了电视、电影、综艺、动漫等节目,启动速度快,运行稳定,对内存、CPU等系统资源占用极少,无广告无捆绑,你安心追剧!腾讯视频视频...
  • 这使得它相当的简单,但是我不确定我怎么能够调整大小(IE下有一点小,但是不是像素的关心)图片作为图片按钮的源文件在起作用。所以我只是调整了来自于手机、相机的照片的大小。 这个问题就是当它试图返回重新加载...
  • bochsWIN7IMG镜像文件包含了bochswin7系统的完整版文件,通过bochsWIN7IMG镜像文件能让手机完美运行WIN7系统并且支持上网,让很多用户能用手机玩一些电脑游戏,尤其是在这个手机内存越来越高的时代,有需要的可以...
  • 云计算就是将处理数据这个步骤放在网络的远程端进行,因为手机、平板和个人电脑等个人设备的数据处理性能(CPU、内存、硬盘和GPU等)是非常有限的,而用户购买十几万甚至上百万的高性能服务器是非常不现实的;...
  • 普通的手机可能一下子内存就爆了,还有页面渲染变得拖拖拉拉</li><li>使用的是vue3,原本用JSX渲染,发现对性能会有影响,或者说vue3对于模板渲染本身有做优化,然后全部改写成vue单...
  • 普通用户的手机是配置低配的 CPU 和 GPU, 可能由于手机内存的限制, 也没有 L2/L3 级缓存设置. 在 <a href="https://medium.com/reloading/javascript-start-up-performance-69200f43b201">JavaScript 性能</a> 一文...
  • 大话数据结构

    2018-12-14 16:02:18
    求100个人的高考成绩平均分与求全省所有考生的成绩平均分在占用时间和内存存储上有非常的差异,我们自然追求高效率和低存储的算法来解决问题。 2.6.1正确性 22 2.6.2可读性 23 2.6.3健壮性 23 2.6.4时间效率高...
  • C#微软培训教材(高清PDF)

    千次下载 热门讨论 2009-07-30 08:51:17
    14.4 继承中关于属性的一些问题.169 14.5 小 结 .172 第四部分 深入了解 C#.174 第十五章 接 口 .174 15.1 组件编程技术 .174 15.2 接 口 定 义 .177 15.3 接口的成员 .178 15.4 接口的实现 .182 ...
  • 注:①测试使用小米9手机,单表数据量从最小100条到最大200W条,字段为30个String+一个自增ID,每个字符串长度都在20到30长度的随机字符,测试过程没有严格做到控制变量法,所以测试并不是很严谨,仅供参考;...
  • 我不是什么牛,我其实想做的就是一个传播者。内容可能过于基础,但对于刚入门的人来说或许是一个窗口,一个解惑之窗。我要先坚持分享20年,大家来一起见证吧。 每年至少会分享不少于200篇的优质文章,如果想第一...
  • C#微软培训资料

    2014-01-22 14:10:17
    14.4 继承中关于属性的一些问题.169 14.5 小 结 .172 第四部分 深入了解 C#.174 第十五章 接 口 .174 15.1 组件编程技术 .174 15.2 接 口 定 义 .177 15.3 接口的成员 .178 15.4 接口的实现 .182 ...
  • 新版Android开发教程.rar

    千次下载 热门讨论 2010-12-14 15:49:11
    � 采用了对有限内存、电池和 CPU 优化过的虚拟机 Dalvik , Android 的运行速度比想象的要快很多。 � 运营商(中国移动等)的大力支持,产业链条的热捧。 � 良好的盈利模式( 3/7 开),产业链条的各方:运营商、...
  • vc++ 应用源码包_1

    热门讨论 2012-09-15 14:22:12
    不同的是,暴风影音将Media Player Classic改成了自己的名字并加入了许多的解码器,打包成自己的产品,其实这也无可厚非,关键就在于其作者老爱把里面捆绑一些我们用不到的软件. TT--仿qq+p2p通讯(nat穿透) VC++遍历...
  • 11、FLYWEIGHT —每天跟 MM 发短信,手指都累死了,最近买了个新手机,可以把一些常 、 用的句子存在手机里,要用的时候,直接拿出来,在前面加上 MM 的名字就可以发送了, 再 不用一个字一个字敲了。共享的句子就是...
  • vc++ 应用源码包_2

    热门讨论 2012-09-15 14:27:40
    不同的是,暴风影音将Media Player Classic改成了自己的名字并加入了许多的解码器,打包成自己的产品,其实这也无可厚非,关键就在于其作者老爱把里面捆绑一些我们用不到的软件. TT--仿qq+p2p通讯(nat穿透) VC++遍历...
  • vc++ 应用源码包_6

    热门讨论 2012-09-15 14:59:46
    不同的是,暴风影音将Media Player Classic改成了自己的名字并加入了许多的解码器,打包成自己的产品,其实这也无可厚非,关键就在于其作者老爱把里面捆绑一些我们用不到的软件. TT--仿qq+p2p通讯(nat穿透) VC++遍历...
  • C#23种设计模式

    2013-06-02 16:49:43
    每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是...

空空如也

空空如也

1 2 3
收藏数 42
精华内容 16
关键字:

怎么让手机内存大一些